aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2009-09-05 09:09:21 -0400
committerRichard Purdie <rpurdie@linux.intel.com>2009-09-07 09:33:33 -0400
commitc746b5519a88b8803d43946ad06326ece4829116 (patch)
tree886c979ce67fb26f550700261b67ef70ff36c539
parente07cccf4046978df10f2e13fe2b99b2f9b3a65db (diff)
leds: Add WM831x status LED driver
The WM831x devices feature two software controlled status LEDs with hardware assisted blinking. The device can also autonomously control the LEDs based on a selection of sources. This can be configured at boot time using either platform data or the chip OTP. A sysfs file in the style of that for triggers allowing the control source to be configured at run time. Triggers can't be used here since they can't depend on the implementation details of a specific LED type. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
-rw-r--r--drivers/leds/Kconfig7
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-wm831x-status.c341
-rw-r--r--include/linux/mfd/wm831x/status.h34
4 files changed, 383 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 7c8e7122aaa9..edfd4e302be7 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -195,6 +195,13 @@ config LEDS_PCA955X
195 LED driver chips accessed via the I2C bus. Supported 195 LED driver chips accessed via the I2C bus. Supported
196 devices include PCA9550, PCA9551, PCA9552, and PCA9553. 196 devices include PCA9550, PCA9551, PCA9552, and PCA9553.
197 197
198config LEDS_WM831X_STATUS
199 tristate "LED support for status LEDs on WM831x PMICs"
200 depends on LEDS_CLASS && MFD_WM831X
201 help
202 This option enables support for the status LEDs of the WM831x
203 series of PMICs.
204
198config LEDS_WM8350 205config LEDS_WM8350
199 tristate "LED Support for WM8350 AudioPlus PMIC" 206 tristate "LED Support for WM8350 AudioPlus PMIC"
200 depends on LEDS_CLASS && MFD_WM8350 207 depends on LEDS_CLASS && MFD_WM8350
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index e8cdcf77a4c3..46d72704d606 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
26obj-$(CONFIG_LEDS_FSG) += leds-fsg.o 26obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
27obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o 27obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
28obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o 28obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
29obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
29obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o 30obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
30obj-$(CONFIG_LEDS_PWM) += leds-pwm.o 31obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
31 32
diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c
new file mode 100644
index 000000000000..c586d05e336a
--- /dev/null
+++ b/drivers/leds/leds-wm831x-status.c
@@ -0,0 +1,341 @@
1/*
2 * LED driver for WM831x status LEDs
3 *
4 * Copyright(C) 2009 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/wm831x/core.h>
18#include <linux/mfd/wm831x/pdata.h>
19#include <linux/mfd/wm831x/status.h>
20
21
22struct wm831x_status {
23 struct led_classdev cdev;
24 struct wm831x *wm831x;
25 struct work_struct work;
26 struct mutex mutex;
27
28 spinlock_t value_lock;
29 int reg; /* Control register */
30 int reg_val; /* Control register value */
31
32 int blink;
33 int blink_time;
34 int blink_cyc;
35 int src;
36 enum led_brightness brightness;
37};
38
39#define to_wm831x_status(led_cdev) \
40 container_of(led_cdev, struct wm831x_status, cdev)
41
42static void wm831x_status_work(struct work_struct *work)
43{
44 struct wm831x_status *led = container_of(work, struct wm831x_status,
45 work);
46 unsigned long flags;
47
48 mutex_lock(&led->mutex);
49
50 led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK |
51 WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK);
52
53 spin_lock_irqsave(&led->value_lock, flags);
54
55 led->reg_val |= led->src << WM831X_LED_SRC_SHIFT;
56 if (led->blink) {
57 led->reg_val |= 2 << WM831X_LED_MODE_SHIFT;
58 led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT;
59 led->reg_val |= led->blink_cyc;
60 } else {
61 if (led->brightness != LED_OFF)
62 led->reg_val |= 1 << WM831X_LED_MODE_SHIFT;
63 }
64
65 spin_unlock_irqrestore(&led->value_lock, flags);
66
67 wm831x_reg_write(led->wm831x, led->reg, led->reg_val);
68
69 mutex_unlock(&led->mutex);
70}
71
72static void wm831x_status_set(struct led_classdev *led_cdev,
73 enum led_brightness value)
74{
75 struct wm831x_status *led = to_wm831x_status(led_cdev);
76 unsigned long flags;
77
78 spin_lock_irqsave(&led->value_lock, flags);
79 led->brightness = value;
80 if (value == LED_OFF)
81 led->blink = 0;
82 schedule_work(&led->work);
83 spin_unlock_irqrestore(&led->value_lock, flags);
84}
85
86static int wm831x_status_blink_set(struct led_classdev *led_cdev,
87 unsigned long *delay_on,
88 unsigned long *delay_off)
89{
90 struct wm831x_status *led = to_wm831x_status(led_cdev);
91 unsigned long flags;
92 int ret = 0;
93
94 /* Pick some defaults if we've not been given times */
95 if (*delay_on == 0 && *delay_off == 0) {
96 *delay_on = 250;
97 *delay_off = 250;
98 }
99
100 spin_lock_irqsave(&led->value_lock, flags);
101
102 /* We only have a limited selection of settings, see if we can
103 * support the configuration we're being given */
104 switch (*delay_on) {
105 case 1000:
106 led->blink_time = 0;
107 break;
108 case 250:
109 led->blink_time = 1;
110 break;
111 case 125:
112 led->blink_time = 2;
113 break;
114 case 62:
115 case 63:
116 /* Actually 62.5ms */
117 led->blink_time = 3;
118 break;
119 default:
120 ret = -EINVAL;
121 break;
122 }
123
124 if (ret == 0) {
125 switch (*delay_off / *delay_on) {
126 case 1:
127 led->blink_cyc = 0;
128 break;
129 case 3:
130 led->blink_cyc = 1;
131 break;
132 case 4:
133 led->blink_cyc = 2;
134 break;
135 case 8:
136 led->blink_cyc = 3;
137 break;
138 default:
139 ret = -EINVAL;
140 break;
141 }
142 }
143
144 if (ret == 0)
145 led->blink = 1;
146 else
147 led->blink = 0;
148
149 /* Always update; if we fail turn off blinking since we expect
150 * a software fallback. */
151 schedule_work(&led->work);
152
153 spin_unlock_irqrestore(&led->value_lock, flags);
154
155 return ret;
156}
157
158static const char *led_src_texts[] = {
159 "otp",
160 "power",
161 "charger",
162 "soft",
163};
164
165static ssize_t wm831x_status_src_show(struct device *dev,
166 struct device_attribute *attr, char *buf)
167{
168 struct led_classdev *led_cdev = dev_get_drvdata(dev);
169 struct wm831x_status *led = to_wm831x_status(led_cdev);
170 int i;
171 ssize_t ret = 0;
172
173 mutex_lock(&led->mutex);
174
175 for (i = 0; i < ARRAY_SIZE(led_src_texts); i++)
176 if (i == led->src)
177 ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]);
178 else
179 ret += sprintf(&buf[ret], "%s ", led_src_texts[i]);
180
181 mutex_unlock(&led->mutex);
182
183 ret += sprintf(&buf[ret], "\n");
184
185 return ret;
186}
187
188static ssize_t wm831x_status_src_store(struct device *dev,
189 struct device_attribute *attr,
190 const char *buf, size_t size)
191{
192 struct led_classdev *led_cdev = dev_get_drvdata(dev);
193 struct wm831x_status *led = to_wm831x_status(led_cdev);
194 char name[20];
195 int i;
196 size_t len;
197
198 name[sizeof(name) - 1] = '\0';
199 strncpy(name, buf, sizeof(name) - 1);
200 len = strlen(name);
201
202 if (len && name[len - 1] == '\n')
203 name[len - 1] = '\0';
204
205 for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) {
206 if (!strcmp(name, led_src_texts[i])) {
207 mutex_lock(&led->mutex);
208
209 led->src = i;
210 schedule_work(&led->work);
211
212 mutex_unlock(&led->mutex);
213 }
214 }
215
216 return size;
217}
218
219static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store);
220
221static int wm831x_status_probe(struct platform_device *pdev)
222{
223 struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
224 struct wm831x_pdata *chip_pdata;
225 struct wm831x_status_pdata pdata;
226 struct wm831x_status *drvdata;
227 struct resource *res;
228 int id = pdev->id % ARRAY_SIZE(chip_pdata->status);
229 int ret;
230
231 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
232 if (res == NULL) {
233 dev_err(&pdev->dev, "No I/O resource\n");
234 ret = -EINVAL;
235 goto err;
236 }
237
238 drvdata = kzalloc(sizeof(struct wm831x_status), GFP_KERNEL);
239 if (!drvdata)
240 return -ENOMEM;
241 dev_set_drvdata(&pdev->dev, drvdata);
242
243 drvdata->wm831x = wm831x;
244 drvdata->reg = res->start;
245
246 if (wm831x->dev->platform_data)
247 chip_pdata = wm831x->dev->platform_data;
248 else
249 chip_pdata = NULL;
250
251 memset(&pdata, 0, sizeof(pdata));
252 if (chip_pdata && chip_pdata->status[id])
253 memcpy(&pdata, chip_pdata->status[id], sizeof(pdata));
254 else
255 pdata.name = dev_name(&pdev->dev);
256
257 mutex_init(&drvdata->mutex);
258 INIT_WORK(&drvdata->work, wm831x_status_work);
259 spin_lock_init(&drvdata->value_lock);
260
261 /* We cache the configuration register and read startup values
262 * from it. */
263 drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg);
264
265 if (drvdata->reg_val & WM831X_LED_MODE_MASK)
266 drvdata->brightness = LED_FULL;
267 else
268 drvdata->brightness = LED_OFF;
269
270 /* Set a default source if configured, otherwise leave the
271 * current hardware setting.
272 */
273 if (pdata.default_src == WM831X_STATUS_PRESERVE) {
274 drvdata->src = drvdata->reg_val;
275 drvdata->src &= WM831X_LED_SRC_MASK;
276 drvdata->src >>= WM831X_LED_SRC_SHIFT;
277 } else {
278 drvdata->src = pdata.default_src - 1;
279 }
280
281 drvdata->cdev.name = pdata.name;
282 drvdata->cdev.default_trigger = pdata.default_trigger;
283 drvdata->cdev.brightness_set = wm831x_status_set;
284 drvdata->cdev.blink_set = wm831x_status_blink_set;
285
286 ret = led_classdev_register(wm831x->dev, &drvdata->cdev);
287 if (ret < 0) {
288 dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
289 goto err_led;
290 }
291
292 ret = device_create_file(drvdata->cdev.dev, &dev_attr_src);
293 if (ret != 0)
294 dev_err(&pdev->dev,
295 "No source control for LED: %d\n", ret);
296
297 return 0;
298
299err_led:
300 led_classdev_unregister(&drvdata->cdev);
301 kfree(drvdata);
302err:
303 return ret;
304}
305
306static int wm831x_status_remove(struct platform_device *pdev)
307{
308 struct wm831x_status *drvdata = platform_get_drvdata(pdev);
309
310 device_remove_file(drvdata->cdev.dev, &dev_attr_src);
311 led_classdev_unregister(&drvdata->cdev);
312 kfree(drvdata);
313
314 return 0;
315}
316
317static struct platform_driver wm831x_status_driver = {
318 .driver = {
319 .name = "wm831x-status",
320 .owner = THIS_MODULE,
321 },
322 .probe = wm831x_status_probe,
323 .remove = wm831x_status_remove,
324};
325
326static int __devinit wm831x_status_init(void)
327{
328 return platform_driver_register(&wm831x_status_driver);
329}
330module_init(wm831x_status_init);
331
332static void wm831x_status_exit(void)
333{
334 platform_driver_unregister(&wm831x_status_driver);
335}
336module_exit(wm831x_status_exit);
337
338MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
339MODULE_DESCRIPTION("WM831x status LED driver");
340MODULE_LICENSE("GPL");
341MODULE_ALIAS("platform:wm831x-status");
diff --git a/include/linux/mfd/wm831x/status.h b/include/linux/mfd/wm831x/status.h
new file mode 100644
index 000000000000..6bc090d0e3ac
--- /dev/null
+++ b/include/linux/mfd/wm831x/status.h
@@ -0,0 +1,34 @@
1/*
2 * include/linux/mfd/wm831x/status.h -- Status LEDs for WM831x
3 *
4 * Copyright 2009 Wolfson Microelectronics PLC.
5 *
6 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 *
13 */
14
15#ifndef __MFD_WM831X_STATUS_H__
16#define __MFD_WM831X_STATUS_H__
17
18#define WM831X_LED_SRC_MASK 0xC000 /* LED_SRC - [15:14] */
19#define WM831X_LED_SRC_SHIFT 14 /* LED_SRC - [15:14] */
20#define WM831X_LED_SRC_WIDTH 2 /* LED_SRC - [15:14] */
21#define WM831X_LED_MODE_MASK 0x0300 /* LED_MODE - [9:8] */
22#define WM831X_LED_MODE_SHIFT 8 /* LED_MODE - [9:8] */
23#define WM831X_LED_MODE_WIDTH 2 /* LED_MODE - [9:8] */
24#define WM831X_LED_SEQ_LEN_MASK 0x0030 /* LED_SEQ_LEN - [5:4] */
25#define WM831X_LED_SEQ_LEN_SHIFT 4 /* LED_SEQ_LEN - [5:4] */
26#define WM831X_LED_SEQ_LEN_WIDTH 2 /* LED_SEQ_LEN - [5:4] */
27#define WM831X_LED_DUR_MASK 0x000C /* LED_DUR - [3:2] */
28#define WM831X_LED_DUR_SHIFT 2 /* LED_DUR - [3:2] */
29#define WM831X_LED_DUR_WIDTH 2 /* LED_DUR - [3:2] */
30#define WM831X_LED_DUTY_CYC_MASK 0x0003 /* LED_DUTY_CYC - [1:0] */
31#define WM831X_LED_DUTY_CYC_SHIFT 0 /* LED_DUTY_CYC - [1:0] */
32#define WM831X_LED_DUTY_CYC_WIDTH 2 /* LED_DUTY_CYC - [1:0] */
33
34#endif