Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
4 *
5 * Copyright (C) 2010 LaCie
6 *
7 * Author: Simon Guinot <sguinot@lacie.com>
8 *
9 * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
10 */
11
12#include <linux/kernel.h>
13#include <linux/platform_device.h>
14#include <linux/slab.h>
15#include <linux/gpio/consumer.h>
16#include <linux/leds.h>
17#include <linux/module.h>
18#include <linux/of.h>
19#include "leds.h"
20
21enum ns2_led_modes {
22 NS_V2_LED_OFF,
23 NS_V2_LED_ON,
24 NS_V2_LED_SATA,
25};
26
27struct ns2_led_modval {
28 enum ns2_led_modes mode;
29 int cmd_level;
30 int slow_level;
31};
32
33struct ns2_led {
34 const char *name;
35 const char *default_trigger;
36 struct gpio_desc *cmd;
37 struct gpio_desc *slow;
38 int num_modes;
39 struct ns2_led_modval *modval;
40};
41
42struct ns2_led_platform_data {
43 int num_leds;
44 struct ns2_led *leds;
45};
46
47/*
48 * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
49 * modes are available: off, on and SATA activity blinking. The LED modes are
50 * controlled through two GPIOs (command and slow): each combination of values
51 * for the command/slow GPIOs corresponds to a LED mode.
52 */
53
54struct ns2_led_data {
55 struct led_classdev cdev;
56 struct gpio_desc *cmd;
57 struct gpio_desc *slow;
58 bool can_sleep;
59 unsigned char sata; /* True when SATA mode active. */
60 rwlock_t rw_lock; /* Lock GPIOs. */
61 int num_modes;
62 struct ns2_led_modval *modval;
63};
64
65static int ns2_led_get_mode(struct ns2_led_data *led_dat,
66 enum ns2_led_modes *mode)
67{
68 int i;
69 int ret = -EINVAL;
70 int cmd_level;
71 int slow_level;
72
73 cmd_level = gpiod_get_value_cansleep(led_dat->cmd);
74 slow_level = gpiod_get_value_cansleep(led_dat->slow);
75
76 for (i = 0; i < led_dat->num_modes; i++) {
77 if (cmd_level == led_dat->modval[i].cmd_level &&
78 slow_level == led_dat->modval[i].slow_level) {
79 *mode = led_dat->modval[i].mode;
80 ret = 0;
81 break;
82 }
83 }
84
85 return ret;
86}
87
88static void ns2_led_set_mode(struct ns2_led_data *led_dat,
89 enum ns2_led_modes mode)
90{
91 int i;
92 bool found = false;
93 unsigned long flags;
94
95 for (i = 0; i < led_dat->num_modes; i++)
96 if (mode == led_dat->modval[i].mode) {
97 found = true;
98 break;
99 }
100
101 if (!found)
102 return;
103
104 write_lock_irqsave(&led_dat->rw_lock, flags);
105
106 if (!led_dat->can_sleep) {
107 gpiod_set_value(led_dat->cmd,
108 led_dat->modval[i].cmd_level);
109 gpiod_set_value(led_dat->slow,
110 led_dat->modval[i].slow_level);
111 goto exit_unlock;
112 }
113
114 gpiod_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level);
115 gpiod_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level);
116
117exit_unlock:
118 write_unlock_irqrestore(&led_dat->rw_lock, flags);
119}
120
121static void ns2_led_set(struct led_classdev *led_cdev,
122 enum led_brightness value)
123{
124 struct ns2_led_data *led_dat =
125 container_of(led_cdev, struct ns2_led_data, cdev);
126 enum ns2_led_modes mode;
127
128 if (value == LED_OFF)
129 mode = NS_V2_LED_OFF;
130 else if (led_dat->sata)
131 mode = NS_V2_LED_SATA;
132 else
133 mode = NS_V2_LED_ON;
134
135 ns2_led_set_mode(led_dat, mode);
136}
137
138static int ns2_led_set_blocking(struct led_classdev *led_cdev,
139 enum led_brightness value)
140{
141 ns2_led_set(led_cdev, value);
142 return 0;
143}
144
145static ssize_t ns2_led_sata_store(struct device *dev,
146 struct device_attribute *attr,
147 const char *buff, size_t count)
148{
149 struct led_classdev *led_cdev = dev_get_drvdata(dev);
150 struct ns2_led_data *led_dat =
151 container_of(led_cdev, struct ns2_led_data, cdev);
152 int ret;
153 unsigned long enable;
154
155 ret = kstrtoul(buff, 10, &enable);
156 if (ret < 0)
157 return ret;
158
159 enable = !!enable;
160
161 if (led_dat->sata == enable)
162 goto exit;
163
164 led_dat->sata = enable;
165
166 if (!led_get_brightness(led_cdev))
167 goto exit;
168
169 if (enable)
170 ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
171 else
172 ns2_led_set_mode(led_dat, NS_V2_LED_ON);
173
174exit:
175 return count;
176}
177
178static ssize_t ns2_led_sata_show(struct device *dev,
179 struct device_attribute *attr, char *buf)
180{
181 struct led_classdev *led_cdev = dev_get_drvdata(dev);
182 struct ns2_led_data *led_dat =
183 container_of(led_cdev, struct ns2_led_data, cdev);
184
185 return sprintf(buf, "%d\n", led_dat->sata);
186}
187
188static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
189
190static struct attribute *ns2_led_attrs[] = {
191 &dev_attr_sata.attr,
192 NULL
193};
194ATTRIBUTE_GROUPS(ns2_led);
195
196static int
197create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
198 const struct ns2_led *template)
199{
200 int ret;
201 enum ns2_led_modes mode;
202
203 rwlock_init(&led_dat->rw_lock);
204
205 led_dat->cdev.name = template->name;
206 led_dat->cdev.default_trigger = template->default_trigger;
207 led_dat->cdev.blink_set = NULL;
208 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
209 led_dat->cdev.groups = ns2_led_groups;
210 led_dat->cmd = template->cmd;
211 led_dat->slow = template->slow;
212 led_dat->can_sleep = gpiod_cansleep(led_dat->cmd) |
213 gpiod_cansleep(led_dat->slow);
214 if (led_dat->can_sleep)
215 led_dat->cdev.brightness_set_blocking = ns2_led_set_blocking;
216 else
217 led_dat->cdev.brightness_set = ns2_led_set;
218 led_dat->modval = template->modval;
219 led_dat->num_modes = template->num_modes;
220
221 ret = ns2_led_get_mode(led_dat, &mode);
222 if (ret < 0)
223 return ret;
224
225 /* Set LED initial state. */
226 led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
227 led_dat->cdev.brightness =
228 (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
229
230 ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
231 if (ret < 0)
232 return ret;
233
234 return 0;
235}
236
237static void delete_ns2_led(struct ns2_led_data *led_dat)
238{
239 led_classdev_unregister(&led_dat->cdev);
240}
241
242#ifdef CONFIG_OF_GPIO
243/*
244 * Translate OpenFirmware node properties into platform_data.
245 */
246static int
247ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
248{
249 struct device_node *np = dev->of_node;
250 struct device_node *child;
251 struct ns2_led *led, *leds;
252 int ret, num_leds = 0;
253
254 num_leds = of_get_child_count(np);
255 if (!num_leds)
256 return -ENODEV;
257
258 leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led),
259 GFP_KERNEL);
260 if (!leds)
261 return -ENOMEM;
262
263 led = leds;
264 for_each_child_of_node(np, child) {
265 const char *string;
266 int i, num_modes;
267 struct ns2_led_modval *modval;
268 struct gpio_desc *gd;
269
270 ret = of_property_read_string(child, "label", &string);
271 led->name = (ret == 0) ? string : child->name;
272
273 gd = gpiod_get_from_of_node(child, "cmd-gpio", 0,
274 GPIOD_ASIS, led->name);
275 if (IS_ERR(gd)) {
276 ret = PTR_ERR(gd);
277 goto err_node_put;
278 }
279 led->cmd = gd;
280 gd = gpiod_get_from_of_node(child, "slow-gpio", 0,
281 GPIOD_ASIS, led->name);
282 if (IS_ERR(gd)) {
283 ret = PTR_ERR(gd);
284 goto err_node_put;
285 }
286 led->slow = gd;
287
288 ret = of_property_read_string(child, "linux,default-trigger",
289 &string);
290 if (ret == 0)
291 led->default_trigger = string;
292
293 ret = of_property_count_u32_elems(child, "modes-map");
294 if (ret < 0 || ret % 3) {
295 dev_err(dev,
296 "Missing or malformed modes-map property\n");
297 ret = -EINVAL;
298 goto err_node_put;
299 }
300
301 num_modes = ret / 3;
302 modval = devm_kcalloc(dev,
303 num_modes,
304 sizeof(struct ns2_led_modval),
305 GFP_KERNEL);
306 if (!modval) {
307 ret = -ENOMEM;
308 goto err_node_put;
309 }
310
311 for (i = 0; i < num_modes; i++) {
312 of_property_read_u32_index(child,
313 "modes-map", 3 * i,
314 (u32 *) &modval[i].mode);
315 of_property_read_u32_index(child,
316 "modes-map", 3 * i + 1,
317 (u32 *) &modval[i].cmd_level);
318 of_property_read_u32_index(child,
319 "modes-map", 3 * i + 2,
320 (u32 *) &modval[i].slow_level);
321 }
322
323 led->num_modes = num_modes;
324 led->modval = modval;
325
326 led++;
327 }
328
329 pdata->leds = leds;
330 pdata->num_leds = num_leds;
331
332 return 0;
333
334err_node_put:
335 of_node_put(child);
336 return ret;
337}
338
339static const struct of_device_id of_ns2_leds_match[] = {
340 { .compatible = "lacie,ns2-leds", },
341 {},
342};
343MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
344#endif /* CONFIG_OF_GPIO */
345
346struct ns2_led_priv {
347 int num_leds;
348 struct ns2_led_data leds_data[];
349};
350
351static inline int sizeof_ns2_led_priv(int num_leds)
352{
353 return sizeof(struct ns2_led_priv) +
354 (sizeof(struct ns2_led_data) * num_leds);
355}
356
357static int ns2_led_probe(struct platform_device *pdev)
358{
359 struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
360 struct ns2_led_priv *priv;
361 int i;
362 int ret;
363
364#ifdef CONFIG_OF_GPIO
365 if (!pdata) {
366 pdata = devm_kzalloc(&pdev->dev,
367 sizeof(struct ns2_led_platform_data),
368 GFP_KERNEL);
369 if (!pdata)
370 return -ENOMEM;
371
372 ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
373 if (ret)
374 return ret;
375 }
376#else
377 if (!pdata)
378 return -EINVAL;
379#endif /* CONFIG_OF_GPIO */
380
381 priv = devm_kzalloc(&pdev->dev,
382 sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
383 if (!priv)
384 return -ENOMEM;
385 priv->num_leds = pdata->num_leds;
386
387 for (i = 0; i < priv->num_leds; i++) {
388 ret = create_ns2_led(pdev, &priv->leds_data[i],
389 &pdata->leds[i]);
390 if (ret < 0) {
391 for (i = i - 1; i >= 0; i--)
392 delete_ns2_led(&priv->leds_data[i]);
393 return ret;
394 }
395 }
396
397 platform_set_drvdata(pdev, priv);
398
399 return 0;
400}
401
402static int ns2_led_remove(struct platform_device *pdev)
403{
404 int i;
405 struct ns2_led_priv *priv;
406
407 priv = platform_get_drvdata(pdev);
408
409 for (i = 0; i < priv->num_leds; i++)
410 delete_ns2_led(&priv->leds_data[i]);
411
412 return 0;
413}
414
415static struct platform_driver ns2_led_driver = {
416 .probe = ns2_led_probe,
417 .remove = ns2_led_remove,
418 .driver = {
419 .name = "leds-ns2",
420 .of_match_table = of_match_ptr(of_ns2_leds_match),
421 },
422};
423
424module_platform_driver(ns2_led_driver);
425
426MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
427MODULE_DESCRIPTION("Network Space v2 LED driver");
428MODULE_LICENSE("GPL");
429MODULE_ALIAS("platform:leds-ns2");