xref: /openbmc/linux/drivers/leds/leds-ns2.c (revision 483eb062)
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 <linux/module.h>
32 #include <linux/platform_data/leds-kirkwood-ns2.h>
33 #include <linux/of.h>
34 #include <linux/of_gpio.h>
35 
36 /*
37  * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
38  * relation with the SATA activity. This capability is exposed through the
39  * "sata" sysfs attribute.
40  *
41  * The following array detail the different LED registers and the combination
42  * of their possible values:
43  *
44  *  cmd_led   |  slow_led  | /SATA active | LED state
45  *            |            |              |
46  *     1      |     0      |      x       |  off
47  *     -      |     1      |      x       |  on
48  *     0      |     0      |      1       |  on
49  *     0      |     0      |      0       |  blink (rate 300ms)
50  */
51 
52 enum ns2_led_modes {
53 	NS_V2_LED_OFF,
54 	NS_V2_LED_ON,
55 	NS_V2_LED_SATA,
56 };
57 
58 struct ns2_led_mode_value {
59 	enum ns2_led_modes	mode;
60 	int			cmd_level;
61 	int			slow_level;
62 };
63 
64 static struct ns2_led_mode_value ns2_led_modval[] = {
65 	{ NS_V2_LED_OFF	, 1, 0 },
66 	{ NS_V2_LED_ON	, 0, 1 },
67 	{ NS_V2_LED_ON	, 1, 1 },
68 	{ NS_V2_LED_SATA, 0, 0 },
69 };
70 
71 struct ns2_led_data {
72 	struct led_classdev	cdev;
73 	unsigned		cmd;
74 	unsigned		slow;
75 	unsigned char		sata; /* True when SATA mode active. */
76 	rwlock_t		rw_lock; /* Lock GPIOs. */
77 };
78 
79 static int ns2_led_get_mode(struct ns2_led_data *led_dat,
80 			    enum ns2_led_modes *mode)
81 {
82 	int i;
83 	int ret = -EINVAL;
84 	int cmd_level;
85 	int slow_level;
86 
87 	read_lock_irq(&led_dat->rw_lock);
88 
89 	cmd_level = gpio_get_value(led_dat->cmd);
90 	slow_level = gpio_get_value(led_dat->slow);
91 
92 	for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
93 		if (cmd_level == ns2_led_modval[i].cmd_level &&
94 		    slow_level == ns2_led_modval[i].slow_level) {
95 			*mode = ns2_led_modval[i].mode;
96 			ret = 0;
97 			break;
98 		}
99 	}
100 
101 	read_unlock_irq(&led_dat->rw_lock);
102 
103 	return ret;
104 }
105 
106 static void ns2_led_set_mode(struct ns2_led_data *led_dat,
107 			     enum ns2_led_modes mode)
108 {
109 	int i;
110 	unsigned long flags;
111 
112 	write_lock_irqsave(&led_dat->rw_lock, flags);
113 
114 	for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
115 		if (mode == ns2_led_modval[i].mode) {
116 			gpio_set_value(led_dat->cmd,
117 				       ns2_led_modval[i].cmd_level);
118 			gpio_set_value(led_dat->slow,
119 				       ns2_led_modval[i].slow_level);
120 		}
121 	}
122 
123 	write_unlock_irqrestore(&led_dat->rw_lock, flags);
124 }
125 
126 static void ns2_led_set(struct led_classdev *led_cdev,
127 			enum led_brightness value)
128 {
129 	struct ns2_led_data *led_dat =
130 		container_of(led_cdev, struct ns2_led_data, cdev);
131 	enum ns2_led_modes mode;
132 
133 	if (value == LED_OFF)
134 		mode = NS_V2_LED_OFF;
135 	else if (led_dat->sata)
136 		mode = NS_V2_LED_SATA;
137 	else
138 		mode = NS_V2_LED_ON;
139 
140 	ns2_led_set_mode(led_dat, mode);
141 }
142 
143 static ssize_t ns2_led_sata_store(struct device *dev,
144 				  struct device_attribute *attr,
145 				  const char *buff, size_t count)
146 {
147 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
148 	struct ns2_led_data *led_dat =
149 		container_of(led_cdev, struct ns2_led_data, cdev);
150 	int ret;
151 	unsigned long enable;
152 	enum ns2_led_modes mode;
153 
154 	ret = kstrtoul(buff, 10, &enable);
155 	if (ret < 0)
156 		return ret;
157 
158 	enable = !!enable;
159 
160 	if (led_dat->sata == enable)
161 		return count;
162 
163 	ret = ns2_led_get_mode(led_dat, &mode);
164 	if (ret < 0)
165 		return ret;
166 
167 	if (enable && mode == NS_V2_LED_ON)
168 		ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
169 	if (!enable && mode == NS_V2_LED_SATA)
170 		ns2_led_set_mode(led_dat, NS_V2_LED_ON);
171 
172 	led_dat->sata = enable;
173 
174 	return count;
175 }
176 
177 static ssize_t ns2_led_sata_show(struct device *dev,
178 				 struct device_attribute *attr, char *buf)
179 {
180 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
181 	struct ns2_led_data *led_dat =
182 		container_of(led_cdev, struct ns2_led_data, cdev);
183 
184 	return sprintf(buf, "%d\n", led_dat->sata);
185 }
186 
187 static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
188 
189 static int
190 create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
191 	       const struct ns2_led *template)
192 {
193 	int ret;
194 	enum ns2_led_modes mode;
195 
196 	ret = devm_gpio_request_one(&pdev->dev, template->cmd,
197 			gpio_get_value(template->cmd) ?
198 			GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
199 			template->name);
200 	if (ret) {
201 		dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
202 			template->name);
203 		return ret;
204 	}
205 
206 	ret = devm_gpio_request_one(&pdev->dev, template->slow,
207 			gpio_get_value(template->slow) ?
208 			GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
209 			template->name);
210 	if (ret) {
211 		dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
212 			template->name);
213 		return ret;
214 	}
215 
216 	rwlock_init(&led_dat->rw_lock);
217 
218 	led_dat->cdev.name = template->name;
219 	led_dat->cdev.default_trigger = template->default_trigger;
220 	led_dat->cdev.blink_set = NULL;
221 	led_dat->cdev.brightness_set = ns2_led_set;
222 	led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
223 	led_dat->cmd = template->cmd;
224 	led_dat->slow = template->slow;
225 
226 	ret = ns2_led_get_mode(led_dat, &mode);
227 	if (ret < 0)
228 		return ret;
229 
230 	/* Set LED initial state. */
231 	led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
232 	led_dat->cdev.brightness =
233 		(mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
234 
235 	ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
236 	if (ret < 0)
237 		return ret;
238 
239 	ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
240 	if (ret < 0)
241 		goto err_free_cdev;
242 
243 	return 0;
244 
245 err_free_cdev:
246 	led_classdev_unregister(&led_dat->cdev);
247 	return ret;
248 }
249 
250 static void delete_ns2_led(struct ns2_led_data *led_dat)
251 {
252 	device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
253 	led_classdev_unregister(&led_dat->cdev);
254 }
255 
256 #ifdef CONFIG_OF_GPIO
257 /*
258  * Translate OpenFirmware node properties into platform_data.
259  */
260 static int
261 ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
262 {
263 	struct device_node *np = dev->of_node;
264 	struct device_node *child;
265 	struct ns2_led *leds;
266 	int num_leds = 0;
267 	int i = 0;
268 
269 	num_leds = of_get_child_count(np);
270 	if (!num_leds)
271 		return -ENODEV;
272 
273 	leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led),
274 			    GFP_KERNEL);
275 	if (!leds)
276 		return -ENOMEM;
277 
278 	for_each_child_of_node(np, child) {
279 		const char *string;
280 		int ret;
281 
282 		ret = of_get_named_gpio(child, "cmd-gpio", 0);
283 		if (ret < 0)
284 			return ret;
285 		leds[i].cmd = ret;
286 		ret = of_get_named_gpio(child, "slow-gpio", 0);
287 		if (ret < 0)
288 			return ret;
289 		leds[i].slow = ret;
290 		ret = of_property_read_string(child, "label", &string);
291 		leds[i].name = (ret == 0) ? string : child->name;
292 		ret = of_property_read_string(child, "linux,default-trigger",
293 					      &string);
294 		if (ret == 0)
295 			leds[i].default_trigger = string;
296 
297 		i++;
298 	}
299 
300 	pdata->leds = leds;
301 	pdata->num_leds = num_leds;
302 
303 	return 0;
304 }
305 
306 static const struct of_device_id of_ns2_leds_match[] = {
307 	{ .compatible = "lacie,ns2-leds", },
308 	{},
309 };
310 #endif /* CONFIG_OF_GPIO */
311 
312 struct ns2_led_priv {
313 	int num_leds;
314 	struct ns2_led_data leds_data[];
315 };
316 
317 static inline int sizeof_ns2_led_priv(int num_leds)
318 {
319 	return sizeof(struct ns2_led_priv) +
320 		      (sizeof(struct ns2_led_data) * num_leds);
321 }
322 
323 static int ns2_led_probe(struct platform_device *pdev)
324 {
325 	struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
326 	struct ns2_led_priv *priv;
327 	int i;
328 	int ret;
329 
330 #ifdef CONFIG_OF_GPIO
331 	if (!pdata) {
332 		pdata = devm_kzalloc(&pdev->dev,
333 				     sizeof(struct ns2_led_platform_data),
334 				     GFP_KERNEL);
335 		if (!pdata)
336 			return -ENOMEM;
337 
338 		ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
339 		if (ret)
340 			return ret;
341 	}
342 #else
343 	if (!pdata)
344 		return -EINVAL;
345 #endif /* CONFIG_OF_GPIO */
346 
347 	priv = devm_kzalloc(&pdev->dev,
348 			    sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
349 	if (!priv)
350 		return -ENOMEM;
351 	priv->num_leds = pdata->num_leds;
352 
353 	for (i = 0; i < priv->num_leds; i++) {
354 		ret = create_ns2_led(pdev, &priv->leds_data[i],
355 				     &pdata->leds[i]);
356 		if (ret < 0) {
357 			for (i = i - 1; i >= 0; i--)
358 				delete_ns2_led(&priv->leds_data[i]);
359 			return ret;
360 		}
361 	}
362 
363 	platform_set_drvdata(pdev, priv);
364 
365 	return 0;
366 }
367 
368 static int ns2_led_remove(struct platform_device *pdev)
369 {
370 	int i;
371 	struct ns2_led_priv *priv;
372 
373 	priv = platform_get_drvdata(pdev);
374 
375 	for (i = 0; i < priv->num_leds; i++)
376 		delete_ns2_led(&priv->leds_data[i]);
377 
378 	return 0;
379 }
380 
381 static struct platform_driver ns2_led_driver = {
382 	.probe		= ns2_led_probe,
383 	.remove		= ns2_led_remove,
384 	.driver		= {
385 		.name		= "leds-ns2",
386 		.owner		= THIS_MODULE,
387 		.of_match_table	= of_match_ptr(of_ns2_leds_match),
388 	},
389 };
390 
391 module_platform_driver(ns2_led_driver);
392 
393 MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
394 MODULE_DESCRIPTION("Network Space v2 LED driver");
395 MODULE_LICENSE("GPL");
396 MODULE_ALIAS("platform:leds-ns2");
397