leds-twl4030-pwm, pandora_bl: fix a race between the two
[pandora-kernel.git] / drivers / leds / leds-twl4030-pwm.c
1 /*
2  * TWL4030 PWM controlled LED driver (LEDA, LEDB, PWM0, PWM1)
3  *
4  * Author: Gražvydas Ignotas <notasas@gmail.com>
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 as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/workqueue.h>
15 #include <linux/platform_device.h>
16 #include <linux/leds.h>
17 #include <linux/leds_pwm.h>
18 #include <linux/i2c/twl.h>
19 #include <linux/slab.h>
20
21 #define TWL_INTBR_GPBR1         0x0c
22 #define TWL_INTBR_PMBR1         0x0d
23
24 #define TWL4030_PWMx_PWMxON     0x00
25 #define TWL4030_PWMx_PWMxOFF    0x01
26 #define TWL4030_LED_LEDEN       0x00
27
28 #define GPBR1_PWM0_CLK_ENABLE   BIT(0)
29 #define GPBR1_PWM1_CLK_ENABLE   BIT(1)
30 #define GPBR1_PWM0_ENABLE       BIT(2)
31 #define GPBR1_PWM1_ENABLE       BIT(3)
32
33 /* LEDEN bits */
34 #define LEDEN_LEDAON            BIT(0)
35 #define LEDEN_LEDBON            BIT(1)
36 #define LEDEN_LEDAPWM           BIT(4)
37 #define LEDEN_LEDBPWM           BIT(5)
38
39 #define LED_UNKNOWN             -1
40
41 enum twl4030_led {
42         TWL4030_LEDA = 0,
43         TWL4030_LEDB,
44         TWL4030_PWM0,
45         TWL4030_PWM1,
46 };
47
48 struct twl4030_pwmled {
49         struct led_classdev cdev;
50         void (*enable)(enum twl4030_led led, bool enable);
51         enum led_brightness new_brightness;
52         enum led_brightness old_brightness;
53         enum twl4030_led id;
54         int module;
55         struct work_struct work;
56 };
57
58 static void twl4030_enable_ledab(enum twl4030_led led, bool enable)
59 {
60         u8 bits;
61
62         if (led == TWL4030_LEDA)
63                 bits = LEDEN_LEDAON | LEDEN_LEDAPWM;
64         else
65                 bits = LEDEN_LEDBON | LEDEN_LEDBPWM;
66
67         if (enable)
68                 twl_i2c_rmw_u8(TWL4030_MODULE_LED, 0, bits, TWL4030_LED_LEDEN);
69         else
70                 twl_i2c_rmw_u8(TWL4030_MODULE_LED, bits, 0, TWL4030_LED_LEDEN);
71 }
72
73 static void twl4030_enable_pwm01(enum twl4030_led led, bool enable)
74 {
75         u8 enbit, clkbit;
76
77         if (led == TWL4030_PWM0) {
78                 enbit = GPBR1_PWM0_ENABLE;
79                 clkbit = GPBR1_PWM0_CLK_ENABLE;
80         } else {
81                 enbit = GPBR1_PWM1_ENABLE;
82                 clkbit = GPBR1_PWM1_CLK_ENABLE;
83         }
84
85         if (enable) {
86                 /* first enable clock, then PWM out */
87                 twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
88                         enbit, clkbit, TWL_INTBR_GPBR1);
89                 twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
90                         0, enbit, TWL_INTBR_GPBR1);
91         } else {
92                 /* first disable PWM output, then clock */
93                 twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
94                         enbit, 0, TWL_INTBR_GPBR1);
95                 twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
96                         clkbit, 0, TWL_INTBR_GPBR1);
97         }
98 }
99
100 static void twl4030_pwmled_work(struct work_struct *work)
101 {
102         struct twl4030_pwmled *led;
103         int val;
104
105         led = container_of(work, struct twl4030_pwmled, work);
106
107         if (led->new_brightness == LED_OFF) {
108                 if (led->old_brightness != LED_OFF)
109                         led->enable(led->id, 0);
110                 goto out;
111         }
112
113         val = led->new_brightness * 0x7f / LED_FULL;
114         /* avoid 0: on = off = 0 means full brightness */
115         if (val == 0)
116                 val = 1;
117
118         twl_i2c_write_u8(led->module, val, TWL4030_PWMx_PWMxOFF);
119
120         if (led->old_brightness == LED_OFF || led->old_brightness == LED_UNKNOWN)
121                 led->enable(led->id, 1);
122
123 out:
124         led->old_brightness = led->new_brightness;
125 }
126
127 static void twl4030_pwmled_brightness(struct led_classdev *cdev,
128                 enum led_brightness b)
129 {
130         struct twl4030_pwmled *led;
131
132         led = container_of(cdev, struct twl4030_pwmled, cdev);
133         led->new_brightness = b;
134         schedule_work(&led->work);
135 }
136
137 static int __devinit twl4030_pwmled_probe(struct platform_device *pdev)
138 {
139         const struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
140         struct twl4030_pwmled *led, *leds;
141         int ret;
142         int i;
143
144         pdata = pdev->dev.platform_data;
145         if (!pdata || pdata->num_leds < 1 || pdata->num_leds > 4)
146                 return -ENODEV;
147
148         leds = kcalloc(pdata->num_leds, sizeof(*leds), GFP_KERNEL);
149         if (!leds)
150                 return -ENOMEM;
151
152         for (i = 0; i < pdata->num_leds; i++) {
153                 led = &leds[i];
154                 led->cdev.name = pdata->leds[i].name;
155                 led->cdev.brightness = LED_OFF;
156                 led->cdev.brightness_set = twl4030_pwmled_brightness;
157                 led->cdev.default_trigger = pdata->leds[i].default_trigger;
158                 led->id = pdata->leds[i].pwm_id;
159                 led->old_brightness = LED_UNKNOWN;
160
161                 switch (pdata->leds[i].pwm_id) {
162                 case TWL4030_LEDA:
163                         led->module = TWL4030_MODULE_PWMA;
164                         led->enable = twl4030_enable_ledab;
165                         break;
166                 case TWL4030_LEDB:
167                         led->module = TWL4030_MODULE_PWMB;
168                         led->enable = twl4030_enable_ledab;
169                         break;
170                 case TWL4030_PWM0:
171                         led->module = TWL4030_MODULE_PWM0;
172                         led->enable = twl4030_enable_pwm01;
173                         /* enable PWM0 in pin mux */
174                         twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
175                                 0x0c, 0x04, TWL_INTBR_PMBR1);
176                         /* enable PWM clock for initial write */
177                         twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
178                                 0, GPBR1_PWM0_CLK_ENABLE, TWL_INTBR_GPBR1);
179                         break;
180                 case TWL4030_PWM1:
181                         led->module = TWL4030_MODULE_PWM1;
182                         led->enable = twl4030_enable_pwm01;
183                         twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
184                                 0, 0x30, TWL_INTBR_PMBR1);
185                         twl_i2c_rmw_u8(TWL4030_MODULE_INTBR,
186                                 0, GPBR1_PWM1_CLK_ENABLE, TWL_INTBR_GPBR1);
187                         break;
188                 default:
189                         dev_err(&pdev->dev, "invalid pwm_id: %d\n",
190                                 pdata->leds->pwm_id);
191                         ret = -ENODEV;
192                         goto err;
193                 }
194                 INIT_WORK(&led->work, twl4030_pwmled_work);
195
196                 twl_i2c_write_u8(led->module, 0, TWL4030_PWMx_PWMxON);
197                 led->new_brightness = LED_OFF;
198                 twl4030_pwmled_work(&led->work);
199
200                 /* Hand it over to the LED framework */
201                 ret = led_classdev_register(&pdev->dev, &led->cdev);
202                 if (ret < 0)
203                         goto err;
204         }
205
206         platform_set_drvdata(pdev, leds);
207         return 0;
208
209 err:
210         for (--i; i >= 0; i--)
211                 led_classdev_unregister(&leds[i].cdev);
212         kfree(leds);
213
214         return ret;
215 }
216
217 static int __devexit twl4030_pwmled_remove(struct platform_device *pdev)
218 {
219         const struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
220         struct twl4030_pwmled *leds;
221         int i;
222
223         leds = platform_get_drvdata(pdev);
224
225         for (i = 0; i < pdata->num_leds; i++) {
226                 led_classdev_unregister(&leds[i].cdev);
227                 cancel_work_sync(&leds[i].work);
228         }
229
230         kfree(leds);
231         platform_set_drvdata(pdev, NULL);
232         return 0;
233 }
234
235 static struct platform_driver twl4030_pwmled_driver = {
236         .driver = {
237                 .name   = "leds-twl4030-pwm",
238                 .owner  = THIS_MODULE,
239         },
240         .probe  = twl4030_pwmled_probe,
241         .remove = __devexit_p(twl4030_pwmled_remove),
242 };
243
244 static int __init twl4030_pwm_init(void)
245 {
246         return platform_driver_register(&twl4030_pwmled_driver);
247 }
248 module_init(twl4030_pwm_init);
249
250 static void __exit twl4030_pwm_exit(void)
251 {
252         platform_driver_unregister(&twl4030_pwmled_driver);
253 }
254 module_exit(twl4030_pwm_exit);
255
256 MODULE_AUTHOR("Gražvydas Ignotas");
257 MODULE_DESCRIPTION("Driver for TWL4030 PWM controlled LEDs");
258 MODULE_LICENSE("GPL");
259 MODULE_ALIAS("platform:leds-twl4030-pwm");