aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/leds/leds-wm8350.c
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2008-12-04 11:52:33 -0500
committerRichard Purdie <rpurdie@linux.intel.com>2009-01-08 07:38:58 -0500
commit0081e8020ebd814a99e45720a10e869a54ee08a6 (patch)
tree0036c31844ae55ff00b370a4a476768398c29844 /drivers/leds/leds-wm8350.c
parent934cd3f979a1daacbd403398f2c7a8f6720c33aa (diff)
leds: Add WM8350 LED driver
The voltage and current regulators on the WM8350 AudioPlus PMIC can be used in concert to provide a power efficient LED driver. This driver implements support for this within the standard LED class. Platform initialisation code should configure the LED hardware in the init callback provided by the WM8350 core driver. The callback should use wm8350_isink_set_flash(), wm8350_dcdc25_set_mode() and wm8350_dcdc_set_slot() to configure the operating parameters of the regulators for their hardware and then then use wm8350_register_led() to instantiate the LED driver. This driver was originally written by Liam Girdwood, though it has been extensively modified since then. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
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 00000000000..846ed978103
--- /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");