aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorNate Case <ncase@xes-inc.com>2008-07-16 17:49:55 -0400
committerRichard Purdie <rpurdie@rpsys.net>2008-07-23 04:49:56 -0400
commitf46e9203d9a100bae216cc06e17f2e77351aa8d8 (patch)
tree169cb323535845591afcef0298c29ba91b9bc749 /drivers
parentdd1160dc1842ae172495a6da274a77e35c593ed8 (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')
-rw-r--r--drivers/leds/Kconfig8
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-pca955x.c384
3 files changed, 393 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 1c35dfaef721..9556262dda5a 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -155,6 +155,14 @@ config LEDS_CLEVO_MAIL
155 To compile this driver as a module, choose M here: the 155 To compile this driver as a module, choose M here: the
156 module will be called leds-clevo-mail. 156 module will be called leds-clevo-mail.
157 157
158config LEDS_PCA955X
159 tristate "LED Support for PCA955x I2C chips"
160 depends on LEDS_CLASS && I2C
161 help
162 This option enables support for LEDs connected to PCA955x
163 LED driver chips accessed via the I2C bus. Supported
164 devices include PCA9550, PCA9551, PCA9552, and PCA9553.
165
158comment "LED Triggers" 166comment "LED Triggers"
159 167
160config LEDS_TRIGGERS 168config LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7156f9970fa9..ff7982b44565 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_LEDS_CM_X270) += leds-cm-x270.o
22obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o 22obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
23obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o 23obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
24obj-$(CONFIG_LEDS_FSG) += leds-fsg.o 24obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
25obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
25 26
26# LED Triggers 27# LED Triggers
27obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o 28obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
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
58enum pca955x_type {
59 pca9550,
60 pca9551,
61 pca9552,
62 pca9553,
63};
64
65struct 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
71static 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
94static const struct i2c_device_id pca955x_id[] = {
95 { "pca9550", pca9550 },
96 { "pca9551", pca9551 },
97 { "pca9552", pca9552 },
98 { "pca9553", pca9553 },
99 { }
100};
101MODULE_DEVICE_TABLE(i2c, pca955x_id);
102
103struct 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 */
115static inline int pca95xx_num_input_regs(int bits)
116{
117 return (bits + 7) / 8;
118}
119
120/* 4 bits per LED selector register */
121static 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 */
130static 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 */
140static 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 */
156static 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 */
169static 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 */
182static 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
190static 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
229void 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
247static 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;
339exit:
340 return err;
341}
342
343static 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
359static 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
369static int __init pca955x_leds_init(void)
370{
371 return i2c_add_driver(&pca955x_driver);
372}
373
374static void __exit pca955x_leds_exit(void)
375{
376 i2c_del_driver(&pca955x_driver);
377}
378
379module_init(pca955x_leds_init);
380module_exit(pca955x_leds_exit);
381
382MODULE_AUTHOR("Nate Case <ncase@xes-inc.com>");
383MODULE_DESCRIPTION("PCA955x LED driver");
384MODULE_LICENSE("GPL v2");