diff options
author | Nate Case <ncase@xes-inc.com> | 2008-07-16 17:49:55 -0400 |
---|---|---|
committer | Richard Purdie <rpurdie@rpsys.net> | 2008-07-23 04:49:56 -0400 |
commit | f46e9203d9a100bae216cc06e17f2e77351aa8d8 (patch) | |
tree | 169cb323535845591afcef0298c29ba91b9bc749 /drivers/leds/leds-pca955x.c | |
parent | dd1160dc1842ae172495a6da274a77e35c593ed8 (diff) |
leds: Add support for Philips PCA955x I2C LED drivers
This driver supports the PCA9550, PCA9551, PCA9552, and PCA9553
LED driver chips.
Signed-off-by: Nate Case <ncase@xes-inc.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Richard Purdie <rpurdie@rpsys.net>
Diffstat (limited to 'drivers/leds/leds-pca955x.c')
-rw-r--r-- | drivers/leds/leds-pca955x.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c new file mode 100644 index 000000000000..146c06972863 --- /dev/null +++ b/drivers/leds/leds-pca955x.c | |||
@@ -0,0 +1,384 @@ | |||
1 | /* | ||
2 | * Copyright 2007-2008 Extreme Engineering Solutions, Inc. | ||
3 | * | ||
4 | * Author: Nate Case <ncase@xes-inc.com> | ||
5 | * | ||
6 | * This file is subject to the terms and conditions of version 2 of | ||
7 | * the GNU General Public License. See the file COPYING in the main | ||
8 | * directory of this archive for more details. | ||
9 | * | ||
10 | * LED driver for various PCA955x I2C LED drivers | ||
11 | * | ||
12 | * Supported devices: | ||
13 | * | ||
14 | * Device Description 7-bit slave address | ||
15 | * ------ ----------- ------------------- | ||
16 | * PCA9550 2-bit driver 0x60 .. 0x61 | ||
17 | * PCA9551 8-bit driver 0x60 .. 0x67 | ||
18 | * PCA9552 16-bit driver 0x60 .. 0x67 | ||
19 | * PCA9553/01 4-bit driver 0x62 | ||
20 | * PCA9553/02 4-bit driver 0x63 | ||
21 | * | ||
22 | * Philips PCA955x LED driver chips follow a register map as shown below: | ||
23 | * | ||
24 | * Control Register Description | ||
25 | * ---------------- ----------- | ||
26 | * 0x0 Input register 0 | ||
27 | * .. | ||
28 | * NUM_INPUT_REGS - 1 Last Input register X | ||
29 | * | ||
30 | * NUM_INPUT_REGS Frequency prescaler 0 | ||
31 | * NUM_INPUT_REGS + 1 PWM register 0 | ||
32 | * NUM_INPUT_REGS + 2 Frequency prescaler 1 | ||
33 | * NUM_INPUT_REGS + 3 PWM register 1 | ||
34 | * | ||
35 | * NUM_INPUT_REGS + 4 LED selector 0 | ||
36 | * NUM_INPUT_REGS + 4 | ||
37 | * + NUM_LED_REGS - 1 Last LED selector | ||
38 | * | ||
39 | * where NUM_INPUT_REGS and NUM_LED_REGS vary depending on how many | ||
40 | * bits the chip supports. | ||
41 | */ | ||
42 | |||
43 | #include <linux/module.h> | ||
44 | #include <linux/delay.h> | ||
45 | #include <linux/string.h> | ||
46 | #include <linux/ctype.h> | ||
47 | #include <linux/leds.h> | ||
48 | #include <linux/err.h> | ||
49 | #include <linux/i2c.h> | ||
50 | #include <linux/workqueue.h> | ||
51 | |||
52 | /* LED select registers determine the source that drives LED outputs */ | ||
53 | #define PCA955X_LS_LED_ON 0x0 /* Output LOW */ | ||
54 | #define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */ | ||
55 | #define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */ | ||
56 | #define PCA955X_LS_BLINK1 0x3 /* Blink at PWM1 rate */ | ||
57 | |||
58 | enum pca955x_type { | ||
59 | pca9550, | ||
60 | pca9551, | ||
61 | pca9552, | ||
62 | pca9553, | ||
63 | }; | ||
64 | |||
65 | struct pca955x_chipdef { | ||
66 | int bits; | ||
67 | u8 slv_addr; /* 7-bit slave address mask */ | ||
68 | int slv_addr_shift; /* Number of bits to ignore */ | ||
69 | }; | ||
70 | |||
71 | static struct pca955x_chipdef pca955x_chipdefs[] = { | ||
72 | [pca9550] = { | ||
73 | .bits = 2, | ||
74 | .slv_addr = /* 110000x */ 0x60, | ||
75 | .slv_addr_shift = 1, | ||
76 | }, | ||
77 | [pca9551] = { | ||
78 | .bits = 8, | ||
79 | .slv_addr = /* 1100xxx */ 0x60, | ||
80 | .slv_addr_shift = 3, | ||
81 | }, | ||
82 | [pca9552] = { | ||
83 | .bits = 16, | ||
84 | .slv_addr = /* 1100xxx */ 0x60, | ||
85 | .slv_addr_shift = 3, | ||
86 | }, | ||
87 | [pca9553] = { | ||
88 | .bits = 4, | ||
89 | .slv_addr = /* 110001x */ 0x62, | ||
90 | .slv_addr_shift = 1, | ||
91 | }, | ||
92 | }; | ||
93 | |||
94 | static const struct i2c_device_id pca955x_id[] = { | ||
95 | { "pca9550", pca9550 }, | ||
96 | { "pca9551", pca9551 }, | ||
97 | { "pca9552", pca9552 }, | ||
98 | { "pca9553", pca9553 }, | ||
99 | { } | ||
100 | }; | ||
101 | MODULE_DEVICE_TABLE(i2c, pca955x_id); | ||
102 | |||
103 | struct pca955x_led { | ||
104 | struct pca955x_chipdef *chipdef; | ||
105 | struct i2c_client *client; | ||
106 | struct work_struct work; | ||
107 | spinlock_t lock; | ||
108 | enum led_brightness brightness; | ||
109 | struct led_classdev led_cdev; | ||
110 | int led_num; /* 0 .. 15 potentially */ | ||
111 | char name[32]; | ||
112 | }; | ||
113 | |||
114 | /* 8 bits per input register */ | ||
115 | static inline int pca95xx_num_input_regs(int bits) | ||
116 | { | ||
117 | return (bits + 7) / 8; | ||
118 | } | ||
119 | |||
120 | /* 4 bits per LED selector register */ | ||
121 | static inline int pca95xx_num_led_regs(int bits) | ||
122 | { | ||
123 | return (bits + 3) / 4; | ||
124 | } | ||
125 | |||
126 | /* | ||
127 | * Return an LED selector register value based on an existing one, with | ||
128 | * the appropriate 2-bit state value set for the given LED number (0-3). | ||
129 | */ | ||
130 | static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state) | ||
131 | { | ||
132 | return (oldval & (~(0x3 << (led_num << 1)))) | | ||
133 | ((state & 0x3) << (led_num << 1)); | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | * Write to frequency prescaler register, used to program the | ||
138 | * period of the PWM output. period = (PSCx + 1) / 38 | ||
139 | */ | ||
140 | static void pca955x_write_psc(struct i2c_client *client, int n, u8 val) | ||
141 | { | ||
142 | struct pca955x_led *pca955x = i2c_get_clientdata(client); | ||
143 | |||
144 | i2c_smbus_write_byte_data(client, | ||
145 | pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n, | ||
146 | val); | ||
147 | } | ||
148 | |||
149 | /* | ||
150 | * Write to PWM register, which determines the duty cycle of the | ||
151 | * output. LED is OFF when the count is less than the value of this | ||
152 | * register, and ON when it is greater. If PWMx == 0, LED is always OFF. | ||
153 | * | ||
154 | * Duty cycle is (256 - PWMx) / 256 | ||
155 | */ | ||
156 | static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val) | ||
157 | { | ||
158 | struct pca955x_led *pca955x = i2c_get_clientdata(client); | ||
159 | |||
160 | i2c_smbus_write_byte_data(client, | ||
161 | pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n, | ||
162 | val); | ||
163 | } | ||
164 | |||
165 | /* | ||
166 | * Write to LED selector register, which determines the source that | ||
167 | * drives the LED output. | ||
168 | */ | ||
169 | static void pca955x_write_ls(struct i2c_client *client, int n, u8 val) | ||
170 | { | ||
171 | struct pca955x_led *pca955x = i2c_get_clientdata(client); | ||
172 | |||
173 | i2c_smbus_write_byte_data(client, | ||
174 | pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n, | ||
175 | val); | ||
176 | } | ||
177 | |||
178 | /* | ||
179 | * Read the LED selector register, which determines the source that | ||
180 | * drives the LED output. | ||
181 | */ | ||
182 | static u8 pca955x_read_ls(struct i2c_client *client, int n) | ||
183 | { | ||
184 | struct pca955x_led *pca955x = i2c_get_clientdata(client); | ||
185 | |||
186 | return (u8) i2c_smbus_read_byte_data(client, | ||
187 | pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n); | ||
188 | } | ||
189 | |||
190 | static void pca955x_led_work(struct work_struct *work) | ||
191 | { | ||
192 | struct pca955x_led *pca955x; | ||
193 | u8 ls; | ||
194 | int chip_ls; /* which LSx to use (0-3 potentially) */ | ||
195 | int ls_led; /* which set of bits within LSx to use (0-3) */ | ||
196 | |||
197 | pca955x = container_of(work, struct pca955x_led, work); | ||
198 | chip_ls = pca955x->led_num / 4; | ||
199 | ls_led = pca955x->led_num % 4; | ||
200 | |||
201 | ls = pca955x_read_ls(pca955x->client, chip_ls); | ||
202 | |||
203 | switch (pca955x->brightness) { | ||
204 | case LED_FULL: | ||
205 | ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); | ||
206 | break; | ||
207 | case LED_OFF: | ||
208 | ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF); | ||
209 | break; | ||
210 | case LED_HALF: | ||
211 | ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0); | ||
212 | break; | ||
213 | default: | ||
214 | /* | ||
215 | * Use PWM1 for all other values. This has the unwanted | ||
216 | * side effect of making all LEDs on the chip share the | ||
217 | * same brightness level if set to a value other than | ||
218 | * OFF, HALF, or FULL. But, this is probably better than | ||
219 | * just turning off for all other values. | ||
220 | */ | ||
221 | pca955x_write_pwm(pca955x->client, 1, 255-pca955x->brightness); | ||
222 | ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1); | ||
223 | break; | ||
224 | } | ||
225 | |||
226 | pca955x_write_ls(pca955x->client, chip_ls, ls); | ||
227 | } | ||
228 | |||
229 | void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value) | ||
230 | { | ||
231 | struct pca955x_led *pca955x; | ||
232 | |||
233 | pca955x = container_of(led_cdev, struct pca955x_led, led_cdev); | ||
234 | |||
235 | spin_lock(&pca955x->lock); | ||
236 | pca955x->brightness = value; | ||
237 | |||
238 | /* | ||
239 | * Must use workqueue for the actual I/O since I2C operations | ||
240 | * can sleep. | ||
241 | */ | ||
242 | schedule_work(&pca955x->work); | ||
243 | |||
244 | spin_unlock(&pca955x->lock); | ||
245 | } | ||
246 | |||
247 | static int __devinit pca955x_probe(struct i2c_client *client, | ||
248 | const struct i2c_device_id *id) | ||
249 | { | ||
250 | struct pca955x_led *pca955x; | ||
251 | int i; | ||
252 | int err = -ENODEV; | ||
253 | struct pca955x_chipdef *chip; | ||
254 | struct i2c_adapter *adapter; | ||
255 | struct led_platform_data *pdata; | ||
256 | |||
257 | chip = &pca955x_chipdefs[id->driver_data]; | ||
258 | adapter = to_i2c_adapter(client->dev.parent); | ||
259 | pdata = client->dev.platform_data; | ||
260 | |||
261 | /* Make sure the slave address / chip type combo given is possible */ | ||
262 | if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) != | ||
263 | chip->slv_addr) { | ||
264 | dev_err(&client->dev, "invalid slave address %02x\n", | ||
265 | client->addr); | ||
266 | return -ENODEV; | ||
267 | } | ||
268 | |||
269 | printk(KERN_INFO "leds-pca955x: Using %s %d-bit LED driver at " | ||
270 | "slave address 0x%02x\n", | ||
271 | id->name, chip->bits, client->addr); | ||
272 | |||
273 | if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) | ||
274 | return -EIO; | ||
275 | |||
276 | if (pdata) { | ||
277 | if (pdata->num_leds != chip->bits) { | ||
278 | dev_err(&client->dev, "board info claims %d LEDs" | ||
279 | " on a %d-bit chip\n", | ||
280 | pdata->num_leds, chip->bits); | ||
281 | return -ENODEV; | ||
282 | } | ||
283 | } | ||
284 | |||
285 | for (i = 0; i < chip->bits; i++) { | ||
286 | pca955x = kzalloc(sizeof(struct pca955x_led), GFP_KERNEL); | ||
287 | if (!pca955x) { | ||
288 | err = -ENOMEM; | ||
289 | goto exit; | ||
290 | } | ||
291 | |||
292 | pca955x->chipdef = chip; | ||
293 | pca955x->client = client; | ||
294 | pca955x->led_num = i; | ||
295 | /* Platform data can specify LED names and default triggers */ | ||
296 | if (pdata) { | ||
297 | if (pdata->leds[i].name) | ||
298 | snprintf(pca955x->name, 32, "pca955x:%s", | ||
299 | pdata->leds[i].name); | ||
300 | if (pdata->leds[i].default_trigger) | ||
301 | pca955x->led_cdev.default_trigger = | ||
302 | pdata->leds[i].default_trigger; | ||
303 | } else { | ||
304 | snprintf(pca955x->name, 32, "pca955x:%d", i); | ||
305 | } | ||
306 | spin_lock_init(&pca955x->lock); | ||
307 | |||
308 | pca955x->led_cdev.name = pca955x->name; | ||
309 | pca955x->led_cdev.brightness_set = | ||
310 | pca955x_led_set; | ||
311 | |||
312 | /* | ||
313 | * Client data is a pointer to the _first_ pca955x_led | ||
314 | * struct | ||
315 | */ | ||
316 | if (i == 0) | ||
317 | i2c_set_clientdata(client, pca955x); | ||
318 | |||
319 | INIT_WORK(&(pca955x->work), pca955x_led_work); | ||
320 | |||
321 | led_classdev_register(&client->dev, &(pca955x->led_cdev)); | ||
322 | } | ||
323 | |||
324 | /* Turn off LEDs */ | ||
325 | for (i = 0; i < pca95xx_num_led_regs(chip->bits); i++) | ||
326 | pca955x_write_ls(client, i, 0x55); | ||
327 | |||
328 | /* PWM0 is used for half brightness or 50% duty cycle */ | ||
329 | pca955x_write_pwm(client, 0, 255-LED_HALF); | ||
330 | |||
331 | /* PWM1 is used for variable brightness, default to OFF */ | ||
332 | pca955x_write_pwm(client, 1, 0); | ||
333 | |||
334 | /* Set to fast frequency so we do not see flashing */ | ||
335 | pca955x_write_psc(client, 0, 0); | ||
336 | pca955x_write_psc(client, 1, 0); | ||
337 | |||
338 | return 0; | ||
339 | exit: | ||
340 | return err; | ||
341 | } | ||
342 | |||
343 | static int __devexit pca955x_remove(struct i2c_client *client) | ||
344 | { | ||
345 | struct pca955x_led *pca955x = i2c_get_clientdata(client); | ||
346 | int leds = pca955x->chipdef->bits; | ||
347 | int i; | ||
348 | |||
349 | for (i = 0; i < leds; i++) { | ||
350 | led_classdev_unregister(&(pca955x->led_cdev)); | ||
351 | cancel_work_sync(&(pca955x->work)); | ||
352 | kfree(pca955x); | ||
353 | pca955x = pca955x + 1; | ||
354 | } | ||
355 | |||
356 | return 0; | ||
357 | } | ||
358 | |||
359 | static struct i2c_driver pca955x_driver = { | ||
360 | .driver = { | ||
361 | .name = "leds-pca955x", | ||
362 | .owner = THIS_MODULE, | ||
363 | }, | ||
364 | .probe = pca955x_probe, | ||
365 | .remove = __devexit_p(pca955x_remove), | ||
366 | .id_table = pca955x_id, | ||
367 | }; | ||
368 | |||
369 | static int __init pca955x_leds_init(void) | ||
370 | { | ||
371 | return i2c_add_driver(&pca955x_driver); | ||
372 | } | ||
373 | |||
374 | static void __exit pca955x_leds_exit(void) | ||
375 | { | ||
376 | i2c_del_driver(&pca955x_driver); | ||
377 | } | ||
378 | |||
379 | module_init(pca955x_leds_init); | ||
380 | module_exit(pca955x_leds_exit); | ||
381 | |||
382 | MODULE_AUTHOR("Nate Case <ncase@xes-inc.com>"); | ||
383 | MODULE_DESCRIPTION("PCA955x LED driver"); | ||
384 | MODULE_LICENSE("GPL v2"); | ||