aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/leds/leds-wm8350.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-wm8350.c')
-rw-r--r--drivers/leds/leds-wm8350.c333
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 */
21static 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
91static 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
114static 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
137static 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
171out:
172 mutex_unlock(&led->mutex);
173}
174
175static 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
188static 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
196static 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
208static 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
218static 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
293static 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
306static 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
318static int __devinit wm8350_led_init(void)
319{
320 return platform_driver_register(&wm8350_led_driver);
321}
322module_init(wm8350_led_init);
323
324static void wm8350_led_exit(void)
325{
326 platform_driver_unregister(&wm8350_led_driver);
327}
328module_exit(wm8350_led_exit);
329
330MODULE_AUTHOR("Mark Brown");
331MODULE_DESCRIPTION("WM8350 LED driver");
332MODULE_LICENSE("GPL");
333MODULE_ALIAS("platform:wm8350-led");