Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/sparc-2.6
[linux-2.6.git] / drivers / leds / leds-88pm860x.c
1 /*
2  * LED driver for Marvell 88PM860x
3  *
4  * Copyright (C) 2009 Marvell International Ltd.
5  *      Haojian Zhuang <haojian.zhuang@marvell.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  *
11  */
12
13 #include <linux/kernel.h>
14 #include <linux/init.h>
15 #include <linux/platform_device.h>
16 #include <linux/i2c.h>
17 #include <linux/leds.h>
18 #include <linux/workqueue.h>
19 #include <linux/mfd/88pm860x.h>
20
21 #define LED_PWM_SHIFT           (3)
22 #define LED_PWM_MASK            (0x1F)
23 #define LED_CURRENT_MASK        (0x07 << 5)
24
25 #define LED_BLINK_ON_MASK       (0x07)
26 #define LED_BLINK_PERIOD_MASK   (0x0F << 3)
27 #define LED_BLINK_MASK          (0x7F)
28
29 #define LED_BLINK_ON(x)         ((x & 0x7) * 66 + 66)
30 #define LED_BLINK_PERIOD(x)     ((x & 0xF) * 530 + 930)
31 #define LED_BLINK_ON_MIN        LED_BLINK_ON(0)
32 #define LED_BLINK_ON_MAX        LED_BLINK_ON(0x7)
33 #define LED_BLINK_PERIOD_MIN    LED_BLINK_PERIOD(0)
34 #define LED_BLINK_PERIOD_MAX    LED_BLINK_PERIOD(0xE)
35 #define LED_TO_ON(x)            ((x - 66) / 66)
36 #define LED_TO_PERIOD(x)        ((x - 930) / 530)
37
38 #define LED1_BLINK_EN           (1 << 1)
39 #define LED2_BLINK_EN           (1 << 2)
40
41 enum {
42         SET_BRIGHTNESS,
43         SET_BLINK,
44 };
45
46 struct pm860x_led {
47         struct led_classdev cdev;
48         struct i2c_client *i2c;
49         struct work_struct work;
50         struct pm860x_chip *chip;
51         struct mutex lock;
52         char name[MFD_NAME_SIZE];
53
54         int port;
55         int iset;
56         int command;
57         int offset;
58         unsigned char brightness;
59         unsigned char current_brightness;
60
61         int blink_data;
62         int blink_time;
63         int blink_on;
64         int blink_off;
65 };
66
67 /* return offset of color register */
68 static inline int __led_off(int port)
69 {
70         int ret = -EINVAL;
71
72         switch (port) {
73         case PM8606_LED1_RED:
74         case PM8606_LED1_GREEN:
75         case PM8606_LED1_BLUE:
76                 ret = port - PM8606_LED1_RED + PM8606_RGB1B;
77                 break;
78         case PM8606_LED2_RED:
79         case PM8606_LED2_GREEN:
80         case PM8606_LED2_BLUE:
81                 ret = port - PM8606_LED2_RED + PM8606_RGB2B;
82                 break;
83         }
84         return ret;
85 }
86
87 /* return offset of blink register */
88 static inline int __blink_off(int port)
89 {
90         int ret = -EINVAL;
91
92         switch (port) {
93         case PM8606_LED1_RED:
94         case PM8606_LED1_GREEN:
95         case PM8606_LED1_BLUE:
96                 ret = PM8606_RGB1A;
97         case PM8606_LED2_RED:
98         case PM8606_LED2_GREEN:
99         case PM8606_LED2_BLUE:
100                 ret = PM8606_RGB2A;
101         }
102         return ret;
103 }
104
105 static inline int __blink_ctl_mask(int port)
106 {
107         int ret = -EINVAL;
108
109         switch (port) {
110         case PM8606_LED1_RED:
111         case PM8606_LED1_GREEN:
112         case PM8606_LED1_BLUE:
113                 ret = LED1_BLINK_EN;
114                 break;
115         case PM8606_LED2_RED:
116         case PM8606_LED2_GREEN:
117         case PM8606_LED2_BLUE:
118                 ret = LED2_BLINK_EN;
119                 break;
120         }
121         return ret;
122 }
123
124 static int __led_set(struct pm860x_led *led, int command)
125 {
126         struct pm860x_chip *chip = led->chip;
127         int mask, ret;
128
129         mutex_lock(&led->lock);
130         switch (command) {
131         case SET_BRIGHTNESS:
132                 if ((led->current_brightness == 0) && led->brightness) {
133                         if (led->iset) {
134                                 ret = pm860x_set_bits(led->i2c, led->offset,
135                                                 LED_CURRENT_MASK, led->iset);
136                                 if (ret < 0)
137                                         goto out;
138                         }
139                 } else if (led->brightness == 0) {
140                         ret = pm860x_set_bits(led->i2c, led->offset,
141                                                 LED_CURRENT_MASK, 0);
142                         if (ret < 0)
143                                 goto out;
144                 }
145                 ret = pm860x_set_bits(led->i2c, led->offset, LED_PWM_MASK,
146                                         led->brightness);
147                 if (ret < 0)
148                         goto out;
149                 led->current_brightness = led->brightness;
150                 dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
151                         led->offset, led->brightness);
152                 break;
153         case SET_BLINK:
154                 ret = pm860x_set_bits(led->i2c, led->offset,
155                                 LED_BLINK_MASK, led->blink_data);
156                 if (ret < 0)
157                         goto out;
158
159                 mask = __blink_ctl_mask(led->port);
160                 ret = pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask);
161                 if (ret < 0)
162                         goto out;
163                 dev_dbg(chip->dev, "LED blink delay on:%dms, delay off:%dms\n",
164                         led->blink_on, led->blink_off);
165                 break;
166         }
167 out:
168         mutex_unlock(&led->lock);
169         return 0;
170 }
171
172 static void pm860x_led_work(struct work_struct *work)
173 {
174         struct pm860x_led *led;
175
176         led = container_of(work, struct pm860x_led, work);
177         __led_set(led, led->command);
178 }
179
180 static void pm860x_led_set(struct led_classdev *cdev,
181                            enum led_brightness value)
182 {
183         struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
184
185         data->offset = __led_off(data->port);
186         data->brightness = value >> 3;
187         data->command = SET_BRIGHTNESS;
188         schedule_work(&data->work);
189 }
190
191 static int pm860x_led_blink(struct led_classdev *cdev,
192                             unsigned long *delay_on,
193                             unsigned long *delay_off)
194 {
195         struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
196         int period, on;
197
198         on = *delay_on;
199         if ((on < LED_BLINK_ON_MIN) || (on > LED_BLINK_ON_MAX))
200                 return -EINVAL;
201
202         on = LED_TO_ON(on);
203         on = LED_BLINK_ON(on);
204
205         period = on + *delay_off;
206         if ((period < LED_BLINK_PERIOD_MIN) || (period > LED_BLINK_PERIOD_MAX))
207                 return -EINVAL;
208         period = LED_TO_PERIOD(period);
209         period = LED_BLINK_PERIOD(period);
210
211         data->offset = __blink_off(data->port);
212         data->blink_on = on;
213         data->blink_off = period - data->blink_on;
214         data->blink_data = (period << 3) | data->blink_on;
215         data->command = SET_BLINK;
216         schedule_work(&data->work);
217
218         return 0;
219 }
220
221 static int __check_device(struct pm860x_led_pdata *pdata, char *name)
222 {
223         struct pm860x_led_pdata *p = pdata;
224         int ret = -EINVAL;
225
226         while (p && p->id) {
227                 if ((p->id != PM8606_ID_LED) || (p->flags < 0))
228                         break;
229
230                 if (!strncmp(name, pm860x_led_name[p->flags],
231                         MFD_NAME_SIZE)) {
232                         ret = (int)p->flags;
233                         break;
234                 }
235                 p++;
236         }
237         return ret;
238 }
239
240 static int pm860x_led_probe(struct platform_device *pdev)
241 {
242         struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
243         struct pm860x_platform_data *pm860x_pdata;
244         struct pm860x_led_pdata *pdata;
245         struct pm860x_led *data;
246         struct resource *res;
247         int ret;
248
249         res = platform_get_resource(pdev, IORESOURCE_IO, 0);
250         if (res == NULL) {
251                 dev_err(&pdev->dev, "No I/O resource!\n");
252                 return -EINVAL;
253         }
254
255         if (pdev->dev.parent->platform_data) {
256                 pm860x_pdata = pdev->dev.parent->platform_data;
257                 pdata = pm860x_pdata->led;
258         } else
259                 pdata = NULL;
260
261         data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL);
262         if (data == NULL)
263                 return -ENOMEM;
264         strncpy(data->name, res->name, MFD_NAME_SIZE);
265         dev_set_drvdata(&pdev->dev, data);
266         data->chip = chip;
267         data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
268         data->iset = pdata->iset;
269         data->port = __check_device(pdata, data->name);
270         if (data->port < 0)
271                 return -EINVAL;
272
273         data->current_brightness = 0;
274         data->cdev.name = data->name;
275         data->cdev.brightness_set = pm860x_led_set;
276         data->cdev.blink_set = pm860x_led_blink;
277         mutex_init(&data->lock);
278         INIT_WORK(&data->work, pm860x_led_work);
279
280         ret = led_classdev_register(chip->dev, &data->cdev);
281         if (ret < 0) {
282                 dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
283                 goto out;
284         }
285         return 0;
286 out:
287         kfree(data);
288         return ret;
289 }
290
291 static int pm860x_led_remove(struct platform_device *pdev)
292 {
293         struct pm860x_led *data = platform_get_drvdata(pdev);
294
295         led_classdev_unregister(&data->cdev);
296         kfree(data);
297
298         return 0;
299 }
300
301 static struct platform_driver pm860x_led_driver = {
302         .driver = {
303                 .name   = "88pm860x-led",
304                 .owner  = THIS_MODULE,
305         },
306         .probe  = pm860x_led_probe,
307         .remove = pm860x_led_remove,
308 };
309
310 static int __devinit pm860x_led_init(void)
311 {
312         return platform_driver_register(&pm860x_led_driver);
313 }
314 module_init(pm860x_led_init);
315
316 static void __devexit pm860x_led_exit(void)
317 {
318         platform_driver_unregister(&pm860x_led_driver);
319 }
320 module_exit(pm860x_led_exit);
321
322 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
323 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
324 MODULE_LICENSE("GPL");
325 MODULE_ALIAS("platform:88pm860x-led");