root/drivers/leds/leds-lp3944.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. lp3944_reg_read
  2. lp3944_reg_write
  3. lp3944_dim_set_period
  4. lp3944_dim_set_dutycycle
  5. lp3944_led_set
  6. lp3944_led_set_blink
  7. lp3944_led_set_brightness
  8. lp3944_configure
  9. lp3944_probe
  10. lp3944_remove

   1 // SPDX-License-Identifier: GPL-2.0-only
   2 /*
   3  * leds-lp3944.c - driver for National Semiconductor LP3944 Funlight Chip
   4  *
   5  * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
   6  */
   7 
   8 /*
   9  * I2C driver for National Semiconductor LP3944 Funlight Chip
  10  * http://www.national.com/pf/LP/LP3944.html
  11  *
  12  * This helper chip can drive up to 8 leds, with two programmable DIM modes;
  13  * it could even be used as a gpio expander but this driver assumes it is used
  14  * as a led controller.
  15  *
  16  * The DIM modes are used to set _blink_ patterns for leds, the pattern is
  17  * specified supplying two parameters:
  18  *   - period: from 0s to 1.6s
  19  *   - duty cycle: percentage of the period the led is on, from 0 to 100
  20  *
  21  * LP3944 can be found on Motorola A910 smartphone, where it drives the rgb
  22  * leds, the camera flash light and the displays backlights.
  23  */
  24 
  25 #include <linux/module.h>
  26 #include <linux/i2c.h>
  27 #include <linux/slab.h>
  28 #include <linux/leds.h>
  29 #include <linux/mutex.h>
  30 #include <linux/leds-lp3944.h>
  31 
  32 /* Read Only Registers */
  33 #define LP3944_REG_INPUT1     0x00 /* LEDs 0-7 InputRegister (Read Only) */
  34 #define LP3944_REG_REGISTER1  0x01 /* None (Read Only) */
  35 
  36 #define LP3944_REG_PSC0       0x02 /* Frequency Prescaler 0 (R/W) */
  37 #define LP3944_REG_PWM0       0x03 /* PWM Register 0 (R/W) */
  38 #define LP3944_REG_PSC1       0x04 /* Frequency Prescaler 1 (R/W) */
  39 #define LP3944_REG_PWM1       0x05 /* PWM Register 1 (R/W) */
  40 #define LP3944_REG_LS0        0x06 /* LEDs 0-3 Selector (R/W) */
  41 #define LP3944_REG_LS1        0x07 /* LEDs 4-7 Selector (R/W) */
  42 
  43 /* These registers are not used to control leds in LP3944, they can store
  44  * arbitrary values which the chip will ignore.
  45  */
  46 #define LP3944_REG_REGISTER8  0x08
  47 #define LP3944_REG_REGISTER9  0x09
  48 
  49 #define LP3944_DIM0 0
  50 #define LP3944_DIM1 1
  51 
  52 /* period in ms */
  53 #define LP3944_PERIOD_MIN 0
  54 #define LP3944_PERIOD_MAX 1600
  55 
  56 /* duty cycle is a percentage */
  57 #define LP3944_DUTY_CYCLE_MIN 0
  58 #define LP3944_DUTY_CYCLE_MAX 100
  59 
  60 #define ldev_to_led(c)       container_of(c, struct lp3944_led_data, ldev)
  61 
  62 /* Saved data */
  63 struct lp3944_led_data {
  64         u8 id;
  65         enum lp3944_type type;
  66         struct led_classdev ldev;
  67         struct i2c_client *client;
  68 };
  69 
  70 struct lp3944_data {
  71         struct mutex lock;
  72         struct i2c_client *client;
  73         struct lp3944_led_data leds[LP3944_LEDS_MAX];
  74 };
  75 
  76 static int lp3944_reg_read(struct i2c_client *client, u8 reg, u8 *value)
  77 {
  78         int tmp;
  79 
  80         tmp = i2c_smbus_read_byte_data(client, reg);
  81         if (tmp < 0)
  82                 return tmp;
  83 
  84         *value = tmp;
  85 
  86         return 0;
  87 }
  88 
  89 static int lp3944_reg_write(struct i2c_client *client, u8 reg, u8 value)
  90 {
  91         return i2c_smbus_write_byte_data(client, reg, value);
  92 }
  93 
  94 /**
  95  * Set the period for DIM status
  96  *
  97  * @client: the i2c client
  98  * @dim: either LP3944_DIM0 or LP3944_DIM1
  99  * @period: period of a blink, that is a on/off cycle, expressed in ms.
 100  */
 101 static int lp3944_dim_set_period(struct i2c_client *client, u8 dim, u16 period)
 102 {
 103         u8 psc_reg;
 104         u8 psc_value;
 105         int err;
 106 
 107         if (dim == LP3944_DIM0)
 108                 psc_reg = LP3944_REG_PSC0;
 109         else if (dim == LP3944_DIM1)
 110                 psc_reg = LP3944_REG_PSC1;
 111         else
 112                 return -EINVAL;
 113 
 114         /* Convert period to Prescaler value */
 115         if (period > LP3944_PERIOD_MAX)
 116                 return -EINVAL;
 117 
 118         psc_value = (period * 255) / LP3944_PERIOD_MAX;
 119 
 120         err = lp3944_reg_write(client, psc_reg, psc_value);
 121 
 122         return err;
 123 }
 124 
 125 /**
 126  * Set the duty cycle for DIM status
 127  *
 128  * @client: the i2c client
 129  * @dim: either LP3944_DIM0 or LP3944_DIM1
 130  * @duty_cycle: percentage of a period during which a led is ON
 131  */
 132 static int lp3944_dim_set_dutycycle(struct i2c_client *client, u8 dim,
 133                                     u8 duty_cycle)
 134 {
 135         u8 pwm_reg;
 136         u8 pwm_value;
 137         int err;
 138 
 139         if (dim == LP3944_DIM0)
 140                 pwm_reg = LP3944_REG_PWM0;
 141         else if (dim == LP3944_DIM1)
 142                 pwm_reg = LP3944_REG_PWM1;
 143         else
 144                 return -EINVAL;
 145 
 146         /* Convert duty cycle to PWM value */
 147         if (duty_cycle > LP3944_DUTY_CYCLE_MAX)
 148                 return -EINVAL;
 149 
 150         pwm_value = (duty_cycle * 255) / LP3944_DUTY_CYCLE_MAX;
 151 
 152         err = lp3944_reg_write(client, pwm_reg, pwm_value);
 153 
 154         return err;
 155 }
 156 
 157 /**
 158  * Set the led status
 159  *
 160  * @led: a lp3944_led_data structure
 161  * @status: one of LP3944_LED_STATUS_OFF
 162  *                 LP3944_LED_STATUS_ON
 163  *                 LP3944_LED_STATUS_DIM0
 164  *                 LP3944_LED_STATUS_DIM1
 165  */
 166 static int lp3944_led_set(struct lp3944_led_data *led, u8 status)
 167 {
 168         struct lp3944_data *data = i2c_get_clientdata(led->client);
 169         u8 id = led->id;
 170         u8 reg;
 171         u8 val = 0;
 172         int err;
 173 
 174         dev_dbg(&led->client->dev, "%s: %s, status before normalization:%d\n",
 175                 __func__, led->ldev.name, status);
 176 
 177         switch (id) {
 178         case LP3944_LED0:
 179         case LP3944_LED1:
 180         case LP3944_LED2:
 181         case LP3944_LED3:
 182                 reg = LP3944_REG_LS0;
 183                 break;
 184         case LP3944_LED4:
 185         case LP3944_LED5:
 186         case LP3944_LED6:
 187         case LP3944_LED7:
 188                 id -= LP3944_LED4;
 189                 reg = LP3944_REG_LS1;
 190                 break;
 191         default:
 192                 return -EINVAL;
 193         }
 194 
 195         if (status > LP3944_LED_STATUS_DIM1)
 196                 return -EINVAL;
 197 
 198         /*
 199          * Invert status only when it's < 2 (i.e. 0 or 1) which means it's
 200          * controlling the on/off state directly.
 201          * When, instead, status is >= 2 don't invert it because it would mean
 202          * to mess with the hardware blinking mode.
 203          */
 204         if (led->type == LP3944_LED_TYPE_LED_INVERTED && status < 2)
 205                 status = 1 - status;
 206 
 207         mutex_lock(&data->lock);
 208         lp3944_reg_read(led->client, reg, &val);
 209 
 210         val &= ~(LP3944_LED_STATUS_MASK << (id << 1));
 211         val |= (status << (id << 1));
 212 
 213         dev_dbg(&led->client->dev, "%s: %s, reg:%d id:%d status:%d val:%#x\n",
 214                 __func__, led->ldev.name, reg, id, status, val);
 215 
 216         /* set led status */
 217         err = lp3944_reg_write(led->client, reg, val);
 218         mutex_unlock(&data->lock);
 219 
 220         return err;
 221 }
 222 
 223 static int lp3944_led_set_blink(struct led_classdev *led_cdev,
 224                                 unsigned long *delay_on,
 225                                 unsigned long *delay_off)
 226 {
 227         struct lp3944_led_data *led = ldev_to_led(led_cdev);
 228         u16 period;
 229         u8 duty_cycle;
 230         int err;
 231 
 232         /* units are in ms */
 233         if (*delay_on + *delay_off > LP3944_PERIOD_MAX)
 234                 return -EINVAL;
 235 
 236         if (*delay_on == 0 && *delay_off == 0) {
 237                 /* Special case: the leds subsystem requires a default user
 238                  * friendly blink pattern for the LED.  Let's blink the led
 239                  * slowly (1Hz).
 240                  */
 241                 *delay_on = 500;
 242                 *delay_off = 500;
 243         }
 244 
 245         period = (*delay_on) + (*delay_off);
 246 
 247         /* duty_cycle is the percentage of period during which the led is ON */
 248         duty_cycle = 100 * (*delay_on) / period;
 249 
 250         /* invert duty cycle for inverted leds, this has the same effect of
 251          * swapping delay_on and delay_off
 252          */
 253         if (led->type == LP3944_LED_TYPE_LED_INVERTED)
 254                 duty_cycle = 100 - duty_cycle;
 255 
 256         /* NOTE: using always the first DIM mode, this means that all leds
 257          * will have the same blinking pattern.
 258          *
 259          * We could find a way later to have two leds blinking in hardware
 260          * with different patterns at the same time, falling back to software
 261          * control for the other ones.
 262          */
 263         err = lp3944_dim_set_period(led->client, LP3944_DIM0, period);
 264         if (err)
 265                 return err;
 266 
 267         err = lp3944_dim_set_dutycycle(led->client, LP3944_DIM0, duty_cycle);
 268         if (err)
 269                 return err;
 270 
 271         dev_dbg(&led->client->dev, "%s: OK hardware accelerated blink!\n",
 272                 __func__);
 273 
 274         lp3944_led_set(led, LP3944_LED_STATUS_DIM0);
 275 
 276         return 0;
 277 }
 278 
 279 static int lp3944_led_set_brightness(struct led_classdev *led_cdev,
 280                                       enum led_brightness brightness)
 281 {
 282         struct lp3944_led_data *led = ldev_to_led(led_cdev);
 283 
 284         dev_dbg(&led->client->dev, "%s: %s, %d\n",
 285                 __func__, led_cdev->name, brightness);
 286 
 287         return lp3944_led_set(led, !!brightness);
 288 }
 289 
 290 static int lp3944_configure(struct i2c_client *client,
 291                             struct lp3944_data *data,
 292                             struct lp3944_platform_data *pdata)
 293 {
 294         int i, err = 0;
 295 
 296         for (i = 0; i < pdata->leds_size; i++) {
 297                 struct lp3944_led *pled = &pdata->leds[i];
 298                 struct lp3944_led_data *led = &data->leds[i];
 299                 led->client = client;
 300                 led->id = i;
 301 
 302                 switch (pled->type) {
 303 
 304                 case LP3944_LED_TYPE_LED:
 305                 case LP3944_LED_TYPE_LED_INVERTED:
 306                         led->type = pled->type;
 307                         led->ldev.name = pled->name;
 308                         led->ldev.max_brightness = 1;
 309                         led->ldev.brightness_set_blocking =
 310                                                 lp3944_led_set_brightness;
 311                         led->ldev.blink_set = lp3944_led_set_blink;
 312                         led->ldev.flags = LED_CORE_SUSPENDRESUME;
 313 
 314                         err = led_classdev_register(&client->dev, &led->ldev);
 315                         if (err < 0) {
 316                                 dev_err(&client->dev,
 317                                         "couldn't register LED %s\n",
 318                                         led->ldev.name);
 319                                 goto exit;
 320                         }
 321 
 322                         /* to expose the default value to userspace */
 323                         led->ldev.brightness =
 324                                         (enum led_brightness) pled->status;
 325 
 326                         /* Set the default led status */
 327                         err = lp3944_led_set(led, pled->status);
 328                         if (err < 0) {
 329                                 dev_err(&client->dev,
 330                                         "%s couldn't set STATUS %d\n",
 331                                         led->ldev.name, pled->status);
 332                                 goto exit;
 333                         }
 334                         break;
 335 
 336                 case LP3944_LED_TYPE_NONE:
 337                 default:
 338                         break;
 339 
 340                 }
 341         }
 342         return 0;
 343 
 344 exit:
 345         if (i > 0)
 346                 for (i = i - 1; i >= 0; i--)
 347                         switch (pdata->leds[i].type) {
 348 
 349                         case LP3944_LED_TYPE_LED:
 350                         case LP3944_LED_TYPE_LED_INVERTED:
 351                                 led_classdev_unregister(&data->leds[i].ldev);
 352                                 break;
 353 
 354                         case LP3944_LED_TYPE_NONE:
 355                         default:
 356                                 break;
 357                         }
 358 
 359         return err;
 360 }
 361 
 362 static int lp3944_probe(struct i2c_client *client,
 363                                   const struct i2c_device_id *id)
 364 {
 365         struct lp3944_platform_data *lp3944_pdata =
 366                         dev_get_platdata(&client->dev);
 367         struct lp3944_data *data;
 368         int err;
 369 
 370         if (lp3944_pdata == NULL) {
 371                 dev_err(&client->dev, "no platform data\n");
 372                 return -EINVAL;
 373         }
 374 
 375         /* Let's see whether this adapter can support what we need. */
 376         if (!i2c_check_functionality(client->adapter,
 377                                 I2C_FUNC_SMBUS_BYTE_DATA)) {
 378                 dev_err(&client->dev, "insufficient functionality!\n");
 379                 return -ENODEV;
 380         }
 381 
 382         data = devm_kzalloc(&client->dev, sizeof(struct lp3944_data),
 383                         GFP_KERNEL);
 384         if (!data)
 385                 return -ENOMEM;
 386 
 387         data->client = client;
 388         i2c_set_clientdata(client, data);
 389 
 390         mutex_init(&data->lock);
 391 
 392         err = lp3944_configure(client, data, lp3944_pdata);
 393         if (err < 0)
 394                 return err;
 395 
 396         dev_info(&client->dev, "lp3944 enabled\n");
 397         return 0;
 398 }
 399 
 400 static int lp3944_remove(struct i2c_client *client)
 401 {
 402         struct lp3944_platform_data *pdata = dev_get_platdata(&client->dev);
 403         struct lp3944_data *data = i2c_get_clientdata(client);
 404         int i;
 405 
 406         for (i = 0; i < pdata->leds_size; i++)
 407                 switch (data->leds[i].type) {
 408                 case LP3944_LED_TYPE_LED:
 409                 case LP3944_LED_TYPE_LED_INVERTED:
 410                         led_classdev_unregister(&data->leds[i].ldev);
 411                         break;
 412 
 413                 case LP3944_LED_TYPE_NONE:
 414                 default:
 415                         break;
 416                 }
 417 
 418         return 0;
 419 }
 420 
 421 /* lp3944 i2c driver struct */
 422 static const struct i2c_device_id lp3944_id[] = {
 423         {"lp3944", 0},
 424         {}
 425 };
 426 
 427 MODULE_DEVICE_TABLE(i2c, lp3944_id);
 428 
 429 static struct i2c_driver lp3944_driver = {
 430         .driver   = {
 431                    .name = "lp3944",
 432         },
 433         .probe    = lp3944_probe,
 434         .remove   = lp3944_remove,
 435         .id_table = lp3944_id,
 436 };
 437 
 438 module_i2c_driver(lp3944_driver);
 439 
 440 MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
 441 MODULE_DESCRIPTION("LP3944 Fun Light Chip");
 442 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */