Root/
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 tmp; |
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 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 = devm_kzalloc(&client->dev, sizeof(struct lp3944_data), |
397 | GFP_KERNEL); |
398 | if (!data) |
399 | return -ENOMEM; |
400 | |
401 | data->client = client; |
402 | i2c_set_clientdata(client, data); |
403 | |
404 | mutex_init(&data->lock); |
405 | |
406 | err = lp3944_configure(client, data, lp3944_pdata); |
407 | if (err < 0) |
408 | return err; |
409 | |
410 | dev_info(&client->dev, "lp3944 enabled\n"); |
411 | return 0; |
412 | } |
413 | |
414 | static int lp3944_remove(struct i2c_client *client) |
415 | { |
416 | struct lp3944_platform_data *pdata = client->dev.platform_data; |
417 | struct lp3944_data *data = i2c_get_clientdata(client); |
418 | int i; |
419 | |
420 | for (i = 0; i < pdata->leds_size; i++) |
421 | switch (data->leds[i].type) { |
422 | case LP3944_LED_TYPE_LED: |
423 | case LP3944_LED_TYPE_LED_INVERTED: |
424 | led_classdev_unregister(&data->leds[i].ldev); |
425 | cancel_work_sync(&data->leds[i].work); |
426 | break; |
427 | |
428 | case LP3944_LED_TYPE_NONE: |
429 | default: |
430 | break; |
431 | } |
432 | |
433 | return 0; |
434 | } |
435 | |
436 | /* lp3944 i2c driver struct */ |
437 | static const struct i2c_device_id lp3944_id[] = { |
438 | {"lp3944", 0}, |
439 | {} |
440 | }; |
441 | |
442 | MODULE_DEVICE_TABLE(i2c, lp3944_id); |
443 | |
444 | static struct i2c_driver lp3944_driver = { |
445 | .driver = { |
446 | .name = "lp3944", |
447 | }, |
448 | .probe = lp3944_probe, |
449 | .remove = lp3944_remove, |
450 | .id_table = lp3944_id, |
451 | }; |
452 | |
453 | module_i2c_driver(lp3944_driver); |
454 | |
455 | MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>"); |
456 | MODULE_DESCRIPTION("LP3944 Fun Light Chip"); |
457 | MODULE_LICENSE("GPL"); |
458 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9