diff options
Diffstat (limited to 'drivers/leds/leds-wm831x-status.c')
-rw-r--r-- | drivers/leds/leds-wm831x-status.c | 341 |
1 files changed, 341 insertions, 0 deletions
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 | |||
22 | struct 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 | |||
42 | static 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 | |||
72 | static 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 | |||
86 | static 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 | |||
158 | static const char *led_src_texts[] = { | ||
159 | "otp", | ||
160 | "power", | ||
161 | "charger", | ||
162 | "soft", | ||
163 | }; | ||
164 | |||
165 | static 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 | |||
188 | static 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 | |||
219 | static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store); | ||
220 | |||
221 | static 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 | |||
299 | err_led: | ||
300 | led_classdev_unregister(&drvdata->cdev); | ||
301 | kfree(drvdata); | ||
302 | err: | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | static 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 | |||
317 | static 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 | |||
326 | static int __devinit wm831x_status_init(void) | ||
327 | { | ||
328 | return platform_driver_register(&wm831x_status_driver); | ||
329 | } | ||
330 | module_init(wm831x_status_init); | ||
331 | |||
332 | static void wm831x_status_exit(void) | ||
333 | { | ||
334 | platform_driver_unregister(&wm831x_status_driver); | ||
335 | } | ||
336 | module_exit(wm831x_status_exit); | ||
337 | |||
338 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | ||
339 | MODULE_DESCRIPTION("WM831x status LED driver"); | ||
340 | MODULE_LICENSE("GPL"); | ||
341 | MODULE_ALIAS("platform:wm831x-status"); | ||