diff options
Diffstat (limited to 'drivers/leds/leds-wm8350.c')
-rw-r--r-- | drivers/leds/leds-wm8350.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c new file mode 100644 index 000000000000..846ed978103f --- /dev/null +++ b/drivers/leds/leds-wm8350.c | |||
@@ -0,0 +1,333 @@ | |||
1 | /* | ||
2 | * LED driver for WM8350 driven LEDS. | ||
3 | * | ||
4 | * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC. | ||
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 | #include <linux/kernel.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/leds.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/mfd/wm8350/pmic.h> | ||
18 | #include <linux/regulator/consumer.h> | ||
19 | |||
20 | /* Microamps */ | ||
21 | static const int isink_cur[] = { | ||
22 | 4, | ||
23 | 5, | ||
24 | 6, | ||
25 | 7, | ||
26 | 8, | ||
27 | 10, | ||
28 | 11, | ||
29 | 14, | ||
30 | 16, | ||
31 | 19, | ||
32 | 23, | ||
33 | 27, | ||
34 | 32, | ||
35 | 39, | ||
36 | 46, | ||
37 | 54, | ||
38 | 65, | ||
39 | 77, | ||
40 | 92, | ||
41 | 109, | ||
42 | 130, | ||
43 | 154, | ||
44 | 183, | ||
45 | 218, | ||
46 | 259, | ||
47 | 308, | ||
48 | 367, | ||
49 | 436, | ||
50 | 518, | ||
51 | 616, | ||
52 | 733, | ||
53 | 872, | ||
54 | 1037, | ||
55 | 1233, | ||
56 | 1466, | ||
57 | 1744, | ||
58 | 2073, | ||
59 | 2466, | ||
60 | 2933, | ||
61 | 3487, | ||
62 | 4147, | ||
63 | 4932, | ||
64 | 5865, | ||
65 | 6975, | ||
66 | 8294, | ||
67 | 9864, | ||
68 | 11730, | ||
69 | 13949, | ||
70 | 16589, | ||
71 | 19728, | ||
72 | 23460, | ||
73 | 27899, | ||
74 | 33178, | ||
75 | 39455, | ||
76 | 46920, | ||
77 | 55798, | ||
78 | 66355, | ||
79 | 78910, | ||
80 | 93840, | ||
81 | 111596, | ||
82 | 132710, | ||
83 | 157820, | ||
84 | 187681, | ||
85 | 223191 | ||
86 | }; | ||
87 | |||
88 | #define to_wm8350_led(led_cdev) \ | ||
89 | container_of(led_cdev, struct wm8350_led, cdev) | ||
90 | |||
91 | static void wm8350_led_enable(struct wm8350_led *led) | ||
92 | { | ||
93 | int ret; | ||
94 | |||
95 | if (led->enabled) | ||
96 | return; | ||
97 | |||
98 | ret = regulator_enable(led->isink); | ||
99 | if (ret != 0) { | ||
100 | dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret); | ||
101 | return; | ||
102 | } | ||
103 | |||
104 | ret = regulator_enable(led->dcdc); | ||
105 | if (ret != 0) { | ||
106 | dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret); | ||
107 | regulator_disable(led->isink); | ||
108 | return; | ||
109 | } | ||
110 | |||
111 | led->enabled = 1; | ||
112 | } | ||
113 | |||
114 | static void wm8350_led_disable(struct wm8350_led *led) | ||
115 | { | ||
116 | int ret; | ||
117 | |||
118 | if (!led->enabled) | ||
119 | return; | ||
120 | |||
121 | ret = regulator_disable(led->dcdc); | ||
122 | if (ret != 0) { | ||
123 | dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret); | ||
124 | return; | ||
125 | } | ||
126 | |||
127 | ret = regulator_disable(led->isink); | ||
128 | if (ret != 0) { | ||
129 | dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); | ||
130 | regulator_enable(led->dcdc); | ||
131 | return; | ||
132 | } | ||
133 | |||
134 | led->enabled = 0; | ||
135 | } | ||
136 | |||
137 | static void led_work(struct work_struct *work) | ||
138 | { | ||
139 | struct wm8350_led *led = container_of(work, struct wm8350_led, work); | ||
140 | int ret; | ||
141 | int uA; | ||
142 | unsigned long flags; | ||
143 | |||
144 | mutex_lock(&led->mutex); | ||
145 | |||
146 | spin_lock_irqsave(&led->value_lock, flags); | ||
147 | |||
148 | if (led->value == LED_OFF) { | ||
149 | spin_unlock_irqrestore(&led->value_lock, flags); | ||
150 | wm8350_led_disable(led); | ||
151 | goto out; | ||
152 | } | ||
153 | |||
154 | /* This scales linearly into the index of valid current | ||
155 | * settings which results in a linear scaling of perceived | ||
156 | * brightness due to the non-linear current settings provided | ||
157 | * by the hardware. | ||
158 | */ | ||
159 | uA = (led->max_uA_index * led->value) / LED_FULL; | ||
160 | spin_unlock_irqrestore(&led->value_lock, flags); | ||
161 | BUG_ON(uA >= ARRAY_SIZE(isink_cur)); | ||
162 | |||
163 | ret = regulator_set_current_limit(led->isink, isink_cur[uA], | ||
164 | isink_cur[uA]); | ||
165 | if (ret != 0) | ||
166 | dev_err(led->cdev.dev, "Failed to set %duA: %d\n", | ||
167 | isink_cur[uA], ret); | ||
168 | |||
169 | wm8350_led_enable(led); | ||
170 | |||
171 | out: | ||
172 | mutex_unlock(&led->mutex); | ||
173 | } | ||
174 | |||
175 | static void wm8350_led_set(struct led_classdev *led_cdev, | ||
176 | enum led_brightness value) | ||
177 | { | ||
178 | struct wm8350_led *led = to_wm8350_led(led_cdev); | ||
179 | unsigned long flags; | ||
180 | |||
181 | spin_lock_irqsave(&led->value_lock, flags); | ||
182 | led->value = value; | ||
183 | schedule_work(&led->work); | ||
184 | spin_unlock_irqrestore(&led->value_lock, flags); | ||
185 | } | ||
186 | |||
187 | #ifdef CONFIG_PM | ||
188 | static int wm8350_led_suspend(struct platform_device *pdev, pm_message_t state) | ||
189 | { | ||
190 | struct wm8350_led *led = platform_get_drvdata(pdev); | ||
191 | |||
192 | led_classdev_suspend(&led->cdev); | ||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static int wm8350_led_resume(struct platform_device *pdev) | ||
197 | { | ||
198 | struct wm8350_led *led = platform_get_drvdata(pdev); | ||
199 | |||
200 | led_classdev_resume(&led->cdev); | ||
201 | return 0; | ||
202 | } | ||
203 | #else | ||
204 | #define wm8350_led_suspend NULL | ||
205 | #define wm8350_led_resume NULL | ||
206 | #endif | ||
207 | |||
208 | static void wm8350_led_shutdown(struct platform_device *pdev) | ||
209 | { | ||
210 | struct wm8350_led *led = platform_get_drvdata(pdev); | ||
211 | |||
212 | mutex_lock(&led->mutex); | ||
213 | led->value = LED_OFF; | ||
214 | wm8350_led_disable(led); | ||
215 | mutex_unlock(&led->mutex); | ||
216 | } | ||
217 | |||
218 | static int wm8350_led_probe(struct platform_device *pdev) | ||
219 | { | ||
220 | struct regulator *isink, *dcdc; | ||
221 | struct wm8350_led *led; | ||
222 | struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; | ||
223 | int ret, i; | ||
224 | |||
225 | if (pdata == NULL) { | ||
226 | dev_err(&pdev->dev, "no platform data\n"); | ||
227 | return -ENODEV; | ||
228 | } | ||
229 | |||
230 | if (pdata->max_uA < isink_cur[0]) { | ||
231 | dev_err(&pdev->dev, "Invalid maximum current %duA\n", | ||
232 | pdata->max_uA); | ||
233 | return -EINVAL; | ||
234 | } | ||
235 | |||
236 | isink = regulator_get(&pdev->dev, "led_isink"); | ||
237 | if (IS_ERR(isink)) { | ||
238 | printk(KERN_ERR "%s: cant get ISINK\n", __func__); | ||
239 | return PTR_ERR(isink); | ||
240 | } | ||
241 | |||
242 | dcdc = regulator_get(&pdev->dev, "led_vcc"); | ||
243 | if (IS_ERR(dcdc)) { | ||
244 | printk(KERN_ERR "%s: cant get DCDC\n", __func__); | ||
245 | ret = PTR_ERR(dcdc); | ||
246 | goto err_isink; | ||
247 | } | ||
248 | |||
249 | led = kzalloc(sizeof(*led), GFP_KERNEL); | ||
250 | if (led == NULL) { | ||
251 | ret = -ENOMEM; | ||
252 | goto err_dcdc; | ||
253 | } | ||
254 | |||
255 | led->cdev.brightness_set = wm8350_led_set; | ||
256 | led->cdev.default_trigger = pdata->default_trigger; | ||
257 | led->cdev.name = pdata->name; | ||
258 | led->enabled = regulator_is_enabled(isink); | ||
259 | led->isink = isink; | ||
260 | led->dcdc = dcdc; | ||
261 | |||
262 | for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) | ||
263 | if (isink_cur[i] >= pdata->max_uA) | ||
264 | break; | ||
265 | led->max_uA_index = i; | ||
266 | if (pdata->max_uA != isink_cur[i]) | ||
267 | dev_warn(&pdev->dev, | ||
268 | "Maximum current %duA is not directly supported," | ||
269 | " check platform data\n", | ||
270 | pdata->max_uA); | ||
271 | |||
272 | spin_lock_init(&led->value_lock); | ||
273 | mutex_init(&led->mutex); | ||
274 | INIT_WORK(&led->work, led_work); | ||
275 | led->value = LED_OFF; | ||
276 | platform_set_drvdata(pdev, led); | ||
277 | |||
278 | ret = led_classdev_register(&pdev->dev, &led->cdev); | ||
279 | if (ret < 0) | ||
280 | goto err_led; | ||
281 | |||
282 | return 0; | ||
283 | |||
284 | err_led: | ||
285 | kfree(led); | ||
286 | err_dcdc: | ||
287 | regulator_put(dcdc); | ||
288 | err_isink: | ||
289 | regulator_put(isink); | ||
290 | return ret; | ||
291 | } | ||
292 | |||
293 | static int wm8350_led_remove(struct platform_device *pdev) | ||
294 | { | ||
295 | struct wm8350_led *led = platform_get_drvdata(pdev); | ||
296 | |||
297 | led_classdev_unregister(&led->cdev); | ||
298 | flush_scheduled_work(); | ||
299 | wm8350_led_disable(led); | ||
300 | regulator_put(led->dcdc); | ||
301 | regulator_put(led->isink); | ||
302 | kfree(led); | ||
303 | return 0; | ||
304 | } | ||
305 | |||
306 | static struct platform_driver wm8350_led_driver = { | ||
307 | .driver = { | ||
308 | .name = "wm8350-led", | ||
309 | .owner = THIS_MODULE, | ||
310 | }, | ||
311 | .probe = wm8350_led_probe, | ||
312 | .remove = wm8350_led_remove, | ||
313 | .shutdown = wm8350_led_shutdown, | ||
314 | .suspend = wm8350_led_suspend, | ||
315 | .resume = wm8350_led_resume, | ||
316 | }; | ||
317 | |||
318 | static int __devinit wm8350_led_init(void) | ||
319 | { | ||
320 | return platform_driver_register(&wm8350_led_driver); | ||
321 | } | ||
322 | module_init(wm8350_led_init); | ||
323 | |||
324 | static void wm8350_led_exit(void) | ||
325 | { | ||
326 | platform_driver_unregister(&wm8350_led_driver); | ||
327 | } | ||
328 | module_exit(wm8350_led_exit); | ||
329 | |||
330 | MODULE_AUTHOR("Mark Brown"); | ||
331 | MODULE_DESCRIPTION("WM8350 LED driver"); | ||
332 | MODULE_LICENSE("GPL"); | ||
333 | MODULE_ALIAS("platform:wm8350-led"); | ||