Commit 11efe71f6564e59ba9acfc636ac6f423e059dc69
Committed by
Nicolas Pitre
1 parent
b6a044ff57
Exists in
master
and in
7 other branches
leds: add LED driver for Network Space v2 LEDs
This patch add a LED class driver for the dual-GPIO LEDs found on the Network Space v2 board (and parents). This include Internet Space v2, Network Space (Max) v2 and d2 Network v2 boards. This dual-GPIO LED is wired to a CPLD and can blink in relation with the SATA activity. The driver expose this capability through a "sata" sysfs attribute. Signed-off-by: Simon Guinot <sguinot@lacie.com> Signed-off-by: Nicolas Pitre <nico@fluxnic.net>
Showing 4 changed files with 374 additions and 0 deletions Side-by-side Diff
arch/arm/mach-kirkwood/include/mach/leds-ns2.h
1 | +/* | |
2 | + * arch/arm/mach-kirkwood/include/mach/leds-ns2.h | |
3 | + * | |
4 | + * Platform data structure for Network Space v2 LED driver | |
5 | + * | |
6 | + * This file is licensed under the terms of the GNU General Public | |
7 | + * License version 2. This program is licensed "as is" without any | |
8 | + * warranty of any kind, whether express or implied. | |
9 | + */ | |
10 | + | |
11 | +#ifndef __MACH_LEDS_NS2_H | |
12 | +#define __MACH_LEDS_NS2_H | |
13 | + | |
14 | +struct ns2_led { | |
15 | + const char *name; | |
16 | + const char *default_trigger; | |
17 | + unsigned cmd; | |
18 | + unsigned slow; | |
19 | +}; | |
20 | + | |
21 | +struct ns2_led_platform_data { | |
22 | + int num_leds; | |
23 | + struct ns2_led *leds; | |
24 | +}; | |
25 | + | |
26 | +#endif /* __MACH_LEDS_NS2_H */ |
drivers/leds/Kconfig
... | ... | @@ -302,6 +302,15 @@ |
302 | 302 | This option enable support for on-chip LED drivers found |
303 | 303 | on Freescale Semiconductor MC13783 PMIC. |
304 | 304 | |
305 | +config LEDS_NS2 | |
306 | + tristate "LED support for Network Space v2 GPIO LEDs" | |
307 | + depends on MACH_NETSPACE_V2 || MACH_INETSPACE_V2 || MACH_NETSPACE_MAX_V2 | |
308 | + default y | |
309 | + help | |
310 | + This option enable support for the dual-GPIO LED found on the | |
311 | + Network Space v2 board (and parents). This include Internet Space v2, | |
312 | + Network Space (Max) v2 and d2 Network v2 boards. | |
313 | + | |
305 | 314 | config LEDS_TRIGGERS |
306 | 315 | bool "LED Trigger support" |
307 | 316 | help |
drivers/leds/Makefile
... | ... | @@ -37,6 +37,7 @@ |
37 | 37 | obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o |
38 | 38 | obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o |
39 | 39 | obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o |
40 | +obj-$(CONFIG_LEDS_NS2) += leds-ns2.o | |
40 | 41 | |
41 | 42 | # LED SPI Drivers |
42 | 43 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o |
drivers/leds/leds-ns2.c
1 | +/* | |
2 | + * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED | |
3 | + * | |
4 | + * Copyright (C) 2010 LaCie | |
5 | + * | |
6 | + * Author: Simon Guinot <sguinot@lacie.com> | |
7 | + * | |
8 | + * Based on leds-gpio.c by Raphael Assenat <raph@8d.com> | |
9 | + * | |
10 | + * This program is free software; you can redistribute it and/or modify | |
11 | + * it under the terms of the GNU General Public License as published by | |
12 | + * the Free Software Foundation; either version 2 of the License, or | |
13 | + * (at your option) any later version. | |
14 | + * | |
15 | + * This program is distributed in the hope that it will be useful, | |
16 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | + * GNU General Public License for more details. | |
19 | + * | |
20 | + * You should have received a copy of the GNU General Public License | |
21 | + * along with this program; if not, write to the Free Software | |
22 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
23 | + */ | |
24 | + | |
25 | +#include <linux/kernel.h> | |
26 | +#include <linux/init.h> | |
27 | +#include <linux/platform_device.h> | |
28 | +#include <linux/slab.h> | |
29 | +#include <linux/gpio.h> | |
30 | +#include <linux/leds.h> | |
31 | +#include <mach/leds-ns2.h> | |
32 | + | |
33 | +/* | |
34 | + * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in | |
35 | + * relation with the SATA activity. This capability is exposed through the | |
36 | + * "sata" sysfs attribute. | |
37 | + * | |
38 | + * The following array detail the different LED registers and the combination | |
39 | + * of their possible values: | |
40 | + * | |
41 | + * cmd_led | slow_led | /SATA active | LED state | |
42 | + * | | | | |
43 | + * 1 | 0 | x | off | |
44 | + * - | 1 | x | on | |
45 | + * 0 | 0 | 1 | on | |
46 | + * 0 | 0 | 0 | blink (rate 300ms) | |
47 | + */ | |
48 | + | |
49 | +enum ns2_led_modes { | |
50 | + NS_V2_LED_OFF, | |
51 | + NS_V2_LED_ON, | |
52 | + NS_V2_LED_SATA, | |
53 | +}; | |
54 | + | |
55 | +struct ns2_led_mode_value { | |
56 | + enum ns2_led_modes mode; | |
57 | + int cmd_level; | |
58 | + int slow_level; | |
59 | +}; | |
60 | + | |
61 | +static struct ns2_led_mode_value ns2_led_modval[] = { | |
62 | + { NS_V2_LED_OFF , 1, 0 }, | |
63 | + { NS_V2_LED_ON , 0, 1 }, | |
64 | + { NS_V2_LED_ON , 1, 1 }, | |
65 | + { NS_V2_LED_SATA, 0, 0 }, | |
66 | +}; | |
67 | + | |
68 | +struct ns2_led_data { | |
69 | + struct led_classdev cdev; | |
70 | + unsigned cmd; | |
71 | + unsigned slow; | |
72 | + unsigned char sata; /* True when SATA mode active. */ | |
73 | + rwlock_t rw_lock; /* Lock GPIOs. */ | |
74 | +}; | |
75 | + | |
76 | +static int ns2_led_get_mode(struct ns2_led_data *led_dat, | |
77 | + enum ns2_led_modes *mode) | |
78 | +{ | |
79 | + int i; | |
80 | + int ret = -EINVAL; | |
81 | + int cmd_level; | |
82 | + int slow_level; | |
83 | + | |
84 | + read_lock(&led_dat->rw_lock); | |
85 | + | |
86 | + cmd_level = gpio_get_value(led_dat->cmd); | |
87 | + slow_level = gpio_get_value(led_dat->slow); | |
88 | + | |
89 | + for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { | |
90 | + if (cmd_level == ns2_led_modval[i].cmd_level && | |
91 | + slow_level == ns2_led_modval[i].slow_level) { | |
92 | + *mode = ns2_led_modval[i].mode; | |
93 | + ret = 0; | |
94 | + break; | |
95 | + } | |
96 | + } | |
97 | + | |
98 | + read_unlock(&led_dat->rw_lock); | |
99 | + | |
100 | + return ret; | |
101 | +} | |
102 | + | |
103 | +static void ns2_led_set_mode(struct ns2_led_data *led_dat, | |
104 | + enum ns2_led_modes mode) | |
105 | +{ | |
106 | + int i; | |
107 | + | |
108 | + write_lock(&led_dat->rw_lock); | |
109 | + | |
110 | + for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { | |
111 | + if (mode == ns2_led_modval[i].mode) { | |
112 | + gpio_set_value(led_dat->cmd, | |
113 | + ns2_led_modval[i].cmd_level); | |
114 | + gpio_set_value(led_dat->slow, | |
115 | + ns2_led_modval[i].slow_level); | |
116 | + } | |
117 | + } | |
118 | + | |
119 | + write_unlock(&led_dat->rw_lock); | |
120 | +} | |
121 | + | |
122 | +static void ns2_led_set(struct led_classdev *led_cdev, | |
123 | + enum led_brightness value) | |
124 | +{ | |
125 | + struct ns2_led_data *led_dat = | |
126 | + container_of(led_cdev, struct ns2_led_data, cdev); | |
127 | + enum ns2_led_modes mode; | |
128 | + | |
129 | + if (value == LED_OFF) | |
130 | + mode = NS_V2_LED_OFF; | |
131 | + else if (led_dat->sata) | |
132 | + mode = NS_V2_LED_SATA; | |
133 | + else | |
134 | + mode = NS_V2_LED_ON; | |
135 | + | |
136 | + ns2_led_set_mode(led_dat, mode); | |
137 | +} | |
138 | + | |
139 | +static ssize_t ns2_led_sata_store(struct device *dev, | |
140 | + struct device_attribute *attr, | |
141 | + const char *buff, size_t count) | |
142 | +{ | |
143 | + int ret; | |
144 | + unsigned long enable; | |
145 | + enum ns2_led_modes mode; | |
146 | + struct ns2_led_data *led_dat = dev_get_drvdata(dev); | |
147 | + | |
148 | + ret = strict_strtoul(buff, 10, &enable); | |
149 | + if (ret < 0) | |
150 | + return ret; | |
151 | + | |
152 | + enable = !!enable; | |
153 | + | |
154 | + if (led_dat->sata == enable) | |
155 | + return count; | |
156 | + | |
157 | + ret = ns2_led_get_mode(led_dat, &mode); | |
158 | + if (ret < 0) | |
159 | + return ret; | |
160 | + | |
161 | + if (enable && mode == NS_V2_LED_ON) | |
162 | + ns2_led_set_mode(led_dat, NS_V2_LED_SATA); | |
163 | + if (!enable && mode == NS_V2_LED_SATA) | |
164 | + ns2_led_set_mode(led_dat, NS_V2_LED_ON); | |
165 | + | |
166 | + led_dat->sata = enable; | |
167 | + | |
168 | + return count; | |
169 | +} | |
170 | + | |
171 | +static ssize_t ns2_led_sata_show(struct device *dev, | |
172 | + struct device_attribute *attr, char *buf) | |
173 | +{ | |
174 | + struct ns2_led_data *led_dat = dev_get_drvdata(dev); | |
175 | + | |
176 | + return sprintf(buf, "%d\n", led_dat->sata); | |
177 | +} | |
178 | + | |
179 | +static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); | |
180 | + | |
181 | +static int __devinit | |
182 | +create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, | |
183 | + const struct ns2_led *template) | |
184 | +{ | |
185 | + int ret; | |
186 | + enum ns2_led_modes mode; | |
187 | + | |
188 | + ret = gpio_request(template->cmd, template->name); | |
189 | + if (ret == 0) { | |
190 | + ret = gpio_direction_output(template->cmd, | |
191 | + gpio_get_value(template->cmd)); | |
192 | + if (ret) | |
193 | + gpio_free(template->cmd); | |
194 | + } | |
195 | + if (ret) { | |
196 | + dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", | |
197 | + template->name); | |
198 | + } | |
199 | + | |
200 | + ret = gpio_request(template->slow, template->name); | |
201 | + if (ret == 0) { | |
202 | + ret = gpio_direction_output(template->slow, | |
203 | + gpio_get_value(template->slow)); | |
204 | + if (ret) | |
205 | + gpio_free(template->slow); | |
206 | + } | |
207 | + if (ret) { | |
208 | + dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", | |
209 | + template->name); | |
210 | + goto err_free_cmd; | |
211 | + } | |
212 | + | |
213 | + rwlock_init(&led_dat->rw_lock); | |
214 | + | |
215 | + led_dat->cdev.name = template->name; | |
216 | + led_dat->cdev.default_trigger = template->default_trigger; | |
217 | + led_dat->cdev.blink_set = NULL; | |
218 | + led_dat->cdev.brightness_set = ns2_led_set; | |
219 | + led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; | |
220 | + led_dat->cmd = template->cmd; | |
221 | + led_dat->slow = template->slow; | |
222 | + | |
223 | + ret = ns2_led_get_mode(led_dat, &mode); | |
224 | + if (ret < 0) | |
225 | + goto err_free_slow; | |
226 | + | |
227 | + /* Set LED initial state. */ | |
228 | + led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; | |
229 | + led_dat->cdev.brightness = | |
230 | + (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; | |
231 | + | |
232 | + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); | |
233 | + if (ret < 0) | |
234 | + goto err_free_slow; | |
235 | + | |
236 | + dev_set_drvdata(led_dat->cdev.dev, led_dat); | |
237 | + ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); | |
238 | + if (ret < 0) | |
239 | + goto err_free_cdev; | |
240 | + | |
241 | + return 0; | |
242 | + | |
243 | +err_free_cdev: | |
244 | + led_classdev_unregister(&led_dat->cdev); | |
245 | +err_free_slow: | |
246 | + gpio_free(led_dat->slow); | |
247 | +err_free_cmd: | |
248 | + gpio_free(led_dat->cmd); | |
249 | + | |
250 | + return ret; | |
251 | +} | |
252 | + | |
253 | +static void __devexit delete_ns2_led(struct ns2_led_data *led_dat) | |
254 | +{ | |
255 | + device_remove_file(led_dat->cdev.dev, &dev_attr_sata); | |
256 | + led_classdev_unregister(&led_dat->cdev); | |
257 | + gpio_free(led_dat->cmd); | |
258 | + gpio_free(led_dat->slow); | |
259 | +} | |
260 | + | |
261 | +static int __devinit ns2_led_probe(struct platform_device *pdev) | |
262 | +{ | |
263 | + struct ns2_led_platform_data *pdata = pdev->dev.platform_data; | |
264 | + struct ns2_led_data *leds_data; | |
265 | + int i; | |
266 | + int ret; | |
267 | + | |
268 | + if (!pdata) | |
269 | + return -EINVAL; | |
270 | + | |
271 | + leds_data = kzalloc(sizeof(struct ns2_led_data) * | |
272 | + pdata->num_leds, GFP_KERNEL); | |
273 | + if (!leds_data) | |
274 | + return -ENOMEM; | |
275 | + | |
276 | + for (i = 0; i < pdata->num_leds; i++) { | |
277 | + ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); | |
278 | + if (ret < 0) | |
279 | + goto err; | |
280 | + | |
281 | + } | |
282 | + | |
283 | + platform_set_drvdata(pdev, leds_data); | |
284 | + | |
285 | + return 0; | |
286 | + | |
287 | +err: | |
288 | + for (i = i - 1; i >= 0; i--) | |
289 | + delete_ns2_led(&leds_data[i]); | |
290 | + | |
291 | + kfree(leds_data); | |
292 | + | |
293 | + return ret; | |
294 | +} | |
295 | + | |
296 | +static int __devexit ns2_led_remove(struct platform_device *pdev) | |
297 | +{ | |
298 | + int i; | |
299 | + struct ns2_led_platform_data *pdata = pdev->dev.platform_data; | |
300 | + struct ns2_led_data *leds_data; | |
301 | + | |
302 | + leds_data = platform_get_drvdata(pdev); | |
303 | + | |
304 | + for (i = 0; i < pdata->num_leds; i++) | |
305 | + delete_ns2_led(&leds_data[i]); | |
306 | + | |
307 | + kfree(leds_data); | |
308 | + platform_set_drvdata(pdev, NULL); | |
309 | + | |
310 | + return 0; | |
311 | +} | |
312 | + | |
313 | +static struct platform_driver ns2_led_driver = { | |
314 | + .probe = ns2_led_probe, | |
315 | + .remove = __devexit_p(ns2_led_remove), | |
316 | + .driver = { | |
317 | + .name = "leds-ns2", | |
318 | + .owner = THIS_MODULE, | |
319 | + }, | |
320 | +}; | |
321 | +MODULE_ALIAS("platform:leds-ns2"); | |
322 | + | |
323 | +static int __init ns2_led_init(void) | |
324 | +{ | |
325 | + return platform_driver_register(&ns2_led_driver); | |
326 | +} | |
327 | + | |
328 | +static void __exit ns2_led_exit(void) | |
329 | +{ | |
330 | + platform_driver_unregister(&ns2_led_driver); | |
331 | +} | |
332 | + | |
333 | +module_init(ns2_led_init); | |
334 | +module_exit(ns2_led_exit); | |
335 | + | |
336 | +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); | |
337 | +MODULE_DESCRIPTION("Network Space v2 LED driver"); | |
338 | +MODULE_LICENSE("GPL"); |