1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
5 * Copyright (C) 2010 LaCie
7 * Author: Simon Guinot <sguinot@lacie.com>
9 * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
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>
27 struct ns2_led_modval {
28 enum ns2_led_modes mode;
35 const char *default_trigger;
36 struct gpio_desc *cmd;
37 struct gpio_desc *slow;
39 struct ns2_led_modval *modval;
42 struct ns2_led_platform_data {
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.
55 struct led_classdev cdev;
56 struct gpio_desc *cmd;
57 struct gpio_desc *slow;
59 unsigned char sata; /* True when SATA mode active. */
60 rwlock_t rw_lock; /* Lock GPIOs. */
62 struct ns2_led_modval *modval;
65 static int ns2_led_get_mode(struct ns2_led_data *led_dat,
66 enum ns2_led_modes *mode)
73 cmd_level = gpiod_get_value_cansleep(led_dat->cmd);
74 slow_level = gpiod_get_value_cansleep(led_dat->slow);
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;
88 static void ns2_led_set_mode(struct ns2_led_data *led_dat,
89 enum ns2_led_modes mode)
95 for (i = 0; i < led_dat->num_modes; i++)
96 if (mode == led_dat->modval[i].mode) {
104 write_lock_irqsave(&led_dat->rw_lock, flags);
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);
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);
118 write_unlock_irqrestore(&led_dat->rw_lock, flags);
121 static void ns2_led_set(struct led_classdev *led_cdev,
122 enum led_brightness value)
124 struct ns2_led_data *led_dat =
125 container_of(led_cdev, struct ns2_led_data, cdev);
126 enum ns2_led_modes mode;
128 if (value == LED_OFF)
129 mode = NS_V2_LED_OFF;
130 else if (led_dat->sata)
131 mode = NS_V2_LED_SATA;
135 ns2_led_set_mode(led_dat, mode);
138 static int ns2_led_set_blocking(struct led_classdev *led_cdev,
139 enum led_brightness value)
141 ns2_led_set(led_cdev, value);
145 static ssize_t ns2_led_sata_store(struct device *dev,
146 struct device_attribute *attr,
147 const char *buff, size_t count)
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);
153 unsigned long enable;
155 ret = kstrtoul(buff, 10, &enable);
161 if (led_dat->sata == enable)
164 led_dat->sata = enable;
166 if (!led_get_brightness(led_cdev))
170 ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
172 ns2_led_set_mode(led_dat, NS_V2_LED_ON);
178 static ssize_t ns2_led_sata_show(struct device *dev,
179 struct device_attribute *attr, char *buf)
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);
185 return sprintf(buf, "%d\n", led_dat->sata);
188 static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
190 static struct attribute *ns2_led_attrs[] = {
194 ATTRIBUTE_GROUPS(ns2_led);
197 create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
198 const struct ns2_led *template)
201 enum ns2_led_modes mode;
203 rwlock_init(&led_dat->rw_lock);
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;
217 led_dat->cdev.brightness_set = ns2_led_set;
218 led_dat->modval = template->modval;
219 led_dat->num_modes = template->num_modes;
221 ret = ns2_led_get_mode(led_dat, &mode);
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;
230 ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
237 static void delete_ns2_led(struct ns2_led_data *led_dat)
239 led_classdev_unregister(&led_dat->cdev);
242 #ifdef CONFIG_OF_GPIO
244 * Translate OpenFirmware node properties into platform_data.
247 ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
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;
254 num_leds = of_get_child_count(np);
258 leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led),
264 for_each_child_of_node(np, child) {
267 struct ns2_led_modval *modval;
268 struct gpio_desc *gd;
270 ret = of_property_read_string(child, "label", &string);
271 led->name = (ret == 0) ? string : child->name;
273 gd = gpiod_get_from_of_node(child, "cmd-gpio", 0,
274 GPIOD_ASIS, led->name);
280 gd = gpiod_get_from_of_node(child, "slow-gpio", 0,
281 GPIOD_ASIS, led->name);
288 ret = of_property_read_string(child, "linux,default-trigger",
291 led->default_trigger = string;
293 ret = of_property_count_u32_elems(child, "modes-map");
294 if (ret < 0 || ret % 3) {
296 "Missing or malformed modes-map property\n");
302 modval = devm_kcalloc(dev,
304 sizeof(struct ns2_led_modval),
311 for (i = 0; i < num_modes; i++) {
312 of_property_read_u32_index(child,
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);
323 led->num_modes = num_modes;
324 led->modval = modval;
330 pdata->num_leds = num_leds;
339 static const struct of_device_id of_ns2_leds_match[] = {
340 { .compatible = "lacie,ns2-leds", },
343 MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
344 #endif /* CONFIG_OF_GPIO */
346 struct ns2_led_priv {
348 struct ns2_led_data leds_data[];
351 static inline int sizeof_ns2_led_priv(int num_leds)
353 return sizeof(struct ns2_led_priv) +
354 (sizeof(struct ns2_led_data) * num_leds);
357 static int ns2_led_probe(struct platform_device *pdev)
359 struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
360 struct ns2_led_priv *priv;
364 #ifdef CONFIG_OF_GPIO
366 pdata = devm_kzalloc(&pdev->dev,
367 sizeof(struct ns2_led_platform_data),
372 ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
379 #endif /* CONFIG_OF_GPIO */
381 priv = devm_kzalloc(&pdev->dev,
382 sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
385 priv->num_leds = pdata->num_leds;
387 for (i = 0; i < priv->num_leds; i++) {
388 ret = create_ns2_led(pdev, &priv->leds_data[i],
391 for (i = i - 1; i >= 0; i--)
392 delete_ns2_led(&priv->leds_data[i]);
397 platform_set_drvdata(pdev, priv);
402 static int ns2_led_remove(struct platform_device *pdev)
405 struct ns2_led_priv *priv;
407 priv = platform_get_drvdata(pdev);
409 for (i = 0; i < priv->num_leds; i++)
410 delete_ns2_led(&priv->leds_data[i]);
415 static struct platform_driver ns2_led_driver = {
416 .probe = ns2_led_probe,
417 .remove = ns2_led_remove,
420 .of_match_table = of_match_ptr(of_ns2_leds_match),
424 module_platform_driver(ns2_led_driver);
426 MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
427 MODULE_DESCRIPTION("Network Space v2 LED driver");
428 MODULE_LICENSE("GPL");
429 MODULE_ALIAS("platform:leds-ns2");