~ubuntu-branches/ubuntu/precise/linux-lowlatency/precise

« back to all changes in this revision

Viewing changes to drivers/leds/leds-lp3944.c

  • Committer: Package Import Robot
  • Author(s): Alessio Igor Bogani
  • Date: 2011-10-26 11:13:05 UTC
  • Revision ID: package-import@ubuntu.com-20111026111305-tz023xykf0i6eosh
Tags: upstream-3.2.0
ImportĀ upstreamĀ versionĀ 3.2.0

Show diffs side-by-side

added added

removed removed

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