blob: bd806e7c8017bc9844e654a060ba4c5b66436014 [file] [log] [blame]
Thomas Gleixner1a59d1b82019-05-27 08:55:05 +02001// SPDX-License-Identifier: GPL-2.0-or-later
Simon Guinot11efe712010-07-06 16:08:46 +02002/*
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>
Simon Guinot11efe712010-07-06 16:08:46 +020010 */
11
12#include <linux/kernel.h>
Simon Guinot11efe712010-07-06 16:08:46 +020013#include <linux/platform_device.h>
14#include <linux/slab.h>
Linus Walleijc7896492020-01-07 15:10:28 +010015#include <linux/gpio/consumer.h>
Simon Guinot11efe712010-07-06 16:08:46 +020016#include <linux/leds.h>
Paul Gortmaker54f4ded2011-07-03 13:56:03 -040017#include <linux/module.h>
Sachin Kamatc68f46d2013-09-28 04:38:30 -070018#include <linux/of.h>
Simon Guinot4b904322015-07-02 19:56:42 +020019#include "leds.h"
Simon Guinot11efe712010-07-06 16:08:46 +020020
Linus Walleijc7896492020-01-07 15:10:28 +010021enum 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;
Linus Walleijccbbb112020-01-07 15:10:29 +010036 struct gpio_desc *cmd;
37 struct gpio_desc *slow;
Linus Walleijc7896492020-01-07 15:10:28 +010038 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
Simon Guinot11efe712010-07-06 16:08:46 +020047/*
Vincent Donnefortf7fafd02015-07-02 19:56:40 +020048 * 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.
Simon Guinot11efe712010-07-06 16:08:46 +020052 */
53
Simon Guinot11efe712010-07-06 16:08:46 +020054struct ns2_led_data {
55 struct led_classdev cdev;
Linus Walleijccbbb112020-01-07 15:10:29 +010056 struct gpio_desc *cmd;
57 struct gpio_desc *slow;
Simon Guinot4b904322015-07-02 19:56:42 +020058 bool can_sleep;
Simon Guinot11efe712010-07-06 16:08:46 +020059 unsigned char sata; /* True when SATA mode active. */
60 rwlock_t rw_lock; /* Lock GPIOs. */
Vincent Donnefortf7fafd02015-07-02 19:56:40 +020061 int num_modes;
62 struct ns2_led_modval *modval;
Simon Guinot11efe712010-07-06 16:08:46 +020063};
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
Linus Walleijccbbb112020-01-07 15:10:29 +010073 cmd_level = gpiod_get_value_cansleep(led_dat->cmd);
74 slow_level = gpiod_get_value_cansleep(led_dat->slow);
Simon Guinot11efe712010-07-06 16:08:46 +020075
Vincent Donnefortf7fafd02015-07-02 19:56:40 +020076 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;
Simon Guinot11efe712010-07-06 16:08:46 +020080 ret = 0;
81 break;
82 }
83 }
84
Simon Guinot11efe712010-07-06 16:08:46 +020085 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;
Simon Guinot4b904322015-07-02 19:56:42 +020092 bool found = false;
Simon Guinotf539dfe2010-09-19 15:30:59 +020093 unsigned long flags;
Simon Guinot11efe712010-07-06 16:08:46 +020094
Simon Guinot4b904322015-07-02 19:56:42 +020095 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
Simon Guinotf539dfe2010-09-19 15:30:59 +0200104 write_lock_irqsave(&led_dat->rw_lock, flags);
Simon Guinot11efe712010-07-06 16:08:46 +0200105
Simon Guinot4b904322015-07-02 19:56:42 +0200106 if (!led_dat->can_sleep) {
Linus Walleijccbbb112020-01-07 15:10:29 +0100107 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);
Simon Guinot4b904322015-07-02 19:56:42 +0200111 goto exit_unlock;
Simon Guinot11efe712010-07-06 16:08:46 +0200112 }
113
Linus Walleijccbbb112020-01-07 15:10:29 +0100114 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);
Simon Guinot4b904322015-07-02 19:56:42 +0200116
117exit_unlock:
Simon Guinotf539dfe2010-09-19 15:30:59 +0200118 write_unlock_irqrestore(&led_dat->rw_lock, flags);
Simon Guinot11efe712010-07-06 16:08:46 +0200119}
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
Jacek Anaszewskic29e6502015-11-20 11:39:41 +0100138static 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
Simon Guinot11efe712010-07-06 16:08:46 +0200145static ssize_t ns2_led_sata_store(struct device *dev,
146 struct device_attribute *attr,
147 const char *buff, size_t count)
148{
Simon Guinote5971bb2010-10-07 16:35:40 +0200149 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);
Simon Guinot11efe712010-07-06 16:08:46 +0200152 int ret;
153 unsigned long enable;
Simon Guinot11efe712010-07-06 16:08:46 +0200154
Jingoo Han38743502012-10-23 05:25:35 -0700155 ret = kstrtoul(buff, 10, &enable);
Simon Guinot11efe712010-07-06 16:08:46 +0200156 if (ret < 0)
157 return ret;
158
159 enable = !!enable;
160
161 if (led_dat->sata == enable)
Simon Guinot4b904322015-07-02 19:56:42 +0200162 goto exit;
Simon Guinot11efe712010-07-06 16:08:46 +0200163
164 led_dat->sata = enable;
165
Simon Guinot4b904322015-07-02 19:56:42 +0200166 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:
Simon Guinot11efe712010-07-06 16:08:46 +0200175 return count;
176}
177
178static ssize_t ns2_led_sata_show(struct device *dev,
179 struct device_attribute *attr, char *buf)
180{
Simon Guinote5971bb2010-10-07 16:35:40 +0200181 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);
Simon Guinot11efe712010-07-06 16:08:46 +0200184
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
Johan Hovold475f8542014-06-25 10:08:51 -0700190static struct attribute *ns2_led_attrs[] = {
191 &dev_attr_sata.attr,
192 NULL
193};
194ATTRIBUTE_GROUPS(ns2_led);
195
Bill Pemberton98ea1ea2012-11-19 13:23:02 -0500196static int
Simon Guinot11efe712010-07-06 16:08:46 +0200197create_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
Simon Guinot11efe712010-07-06 16:08:46 +0200203 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;
Simon Guinot11efe712010-07-06 16:08:46 +0200208 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
Johan Hovold475f8542014-06-25 10:08:51 -0700209 led_dat->cdev.groups = ns2_led_groups;
Simon Guinot11efe712010-07-06 16:08:46 +0200210 led_dat->cmd = template->cmd;
211 led_dat->slow = template->slow;
Linus Walleijccbbb112020-01-07 15:10:29 +0100212 led_dat->can_sleep = gpiod_cansleep(led_dat->cmd) |
213 gpiod_cansleep(led_dat->slow);
Jacek Anaszewskic29e6502015-11-20 11:39:41 +0100214 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;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200218 led_dat->modval = template->modval;
219 led_dat->num_modes = template->num_modes;
Simon Guinot11efe712010-07-06 16:08:46 +0200220
221 ret = ns2_led_get_mode(led_dat, &mode);
222 if (ret < 0)
Sachin Kamat04195822012-11-25 10:28:10 +0530223 return ret;
Simon Guinot11efe712010-07-06 16:08:46 +0200224
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)
Sachin Kamat04195822012-11-25 10:28:10 +0530232 return ret;
Simon Guinot11efe712010-07-06 16:08:46 +0200233
Simon Guinot11efe712010-07-06 16:08:46 +0200234 return 0;
Simon Guinot11efe712010-07-06 16:08:46 +0200235}
236
Arnd Bergmannb8cd7422012-05-10 13:01:46 -0700237static void delete_ns2_led(struct ns2_led_data *led_dat)
Simon Guinot11efe712010-07-06 16:08:46 +0200238{
Simon Guinot11efe712010-07-06 16:08:46 +0200239 led_classdev_unregister(&led_dat->cdev);
Simon Guinot11efe712010-07-06 16:08:46 +0200240}
241
Simon Guinot72052fc2012-10-17 12:09:03 +0200242#ifdef CONFIG_OF_GPIO
243/*
244 * Translate OpenFirmware node properties into platform_data.
245 */
Linus Torvaldscf4af012012-12-12 12:14:06 -0800246static int
Simon Guinot72052fc2012-10-17 12:09:03 +0200247ns2_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;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200251 struct ns2_led *led, *leds;
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530252 int ret, num_leds = 0;
Simon Guinot72052fc2012-10-17 12:09:03 +0200253
254 num_leds = of_get_child_count(np);
255 if (!num_leds)
256 return -ENODEV;
257
Kees Cooka86854d2018-06-12 14:07:58 -0700258 leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led),
Simon Guinot72052fc2012-10-17 12:09:03 +0200259 GFP_KERNEL);
260 if (!leds)
261 return -ENOMEM;
262
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200263 led = leds;
Simon Guinot72052fc2012-10-17 12:09:03 +0200264 for_each_child_of_node(np, child) {
265 const char *string;
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530266 int i, num_modes;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200267 struct ns2_led_modval *modval;
Linus Walleijccbbb112020-01-07 15:10:29 +0100268 struct gpio_desc *gd;
Simon Guinot72052fc2012-10-17 12:09:03 +0200269
Simon Guinot72052fc2012-10-17 12:09:03 +0200270 ret = of_property_read_string(child, "label", &string);
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200271 led->name = (ret == 0) ? string : child->name;
Linus Walleijccbbb112020-01-07 15:10:29 +0100272
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
Simon Guinot72052fc2012-10-17 12:09:03 +0200288 ret = of_property_read_string(child, "linux,default-trigger",
289 &string);
290 if (ret == 0)
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200291 led->default_trigger = string;
Simon Guinot72052fc2012-10-17 12:09:03 +0200292
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200293 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");
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530297 ret = -EINVAL;
298 goto err_node_put;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200299 }
300
301 num_modes = ret / 3;
Kees Cooka86854d2018-06-12 14:07:58 -0700302 modval = devm_kcalloc(dev,
303 num_modes,
304 sizeof(struct ns2_led_modval),
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200305 GFP_KERNEL);
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530306 if (!modval) {
307 ret = -ENOMEM;
308 goto err_node_put;
309 }
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200310
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++;
Simon Guinot72052fc2012-10-17 12:09:03 +0200327 }
328
329 pdata->leds = leds;
330 pdata->num_leds = num_leds;
331
332 return 0;
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530333
334err_node_put:
335 of_node_put(child);
336 return ret;
Simon Guinot72052fc2012-10-17 12:09:03 +0200337}
338
339static const struct of_device_id of_ns2_leds_match[] = {
340 { .compatible = "lacie,ns2-leds", },
341 {},
342};
Luis de Bethencourt98f9cc72015-09-01 23:36:59 +0200343MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
Simon Guinot72052fc2012-10-17 12:09:03 +0200344#endif /* CONFIG_OF_GPIO */
345
Simon Guinot3de19292013-03-19 11:07:29 -0700346struct ns2_led_priv {
347 int num_leds;
348 struct ns2_led_data leds_data[];
349};
350
Bill Pemberton98ea1ea2012-11-19 13:23:02 -0500351static int ns2_led_probe(struct platform_device *pdev)
Simon Guinot11efe712010-07-06 16:08:46 +0200352{
Jingoo Han87aae1e2013-07-30 01:07:35 -0700353 struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
Simon Guinot3de19292013-03-19 11:07:29 -0700354 struct ns2_led_priv *priv;
Simon Guinot11efe712010-07-06 16:08:46 +0200355 int i;
356 int ret;
357
Simon Guinot72052fc2012-10-17 12:09:03 +0200358#ifdef CONFIG_OF_GPIO
359 if (!pdata) {
360 pdata = devm_kzalloc(&pdev->dev,
361 sizeof(struct ns2_led_platform_data),
362 GFP_KERNEL);
363 if (!pdata)
364 return -ENOMEM;
365
366 ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
367 if (ret)
368 return ret;
369 }
370#else
Simon Guinot11efe712010-07-06 16:08:46 +0200371 if (!pdata)
372 return -EINVAL;
Simon Guinot72052fc2012-10-17 12:09:03 +0200373#endif /* CONFIG_OF_GPIO */
Simon Guinot11efe712010-07-06 16:08:46 +0200374
Gustavo A. R. Silvaa7ad53c2020-06-17 18:07:57 -0500375 priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds_data, pdata->num_leds), GFP_KERNEL);
Simon Guinot3de19292013-03-19 11:07:29 -0700376 if (!priv)
Simon Guinot11efe712010-07-06 16:08:46 +0200377 return -ENOMEM;
Simon Guinot3de19292013-03-19 11:07:29 -0700378 priv->num_leds = pdata->num_leds;
Simon Guinot11efe712010-07-06 16:08:46 +0200379
Simon Guinot3de19292013-03-19 11:07:29 -0700380 for (i = 0; i < priv->num_leds; i++) {
381 ret = create_ns2_led(pdev, &priv->leds_data[i],
382 &pdata->leds[i]);
Bryan Wua209f762012-07-04 12:30:50 +0800383 if (ret < 0) {
384 for (i = i - 1; i >= 0; i--)
Simon Guinot3de19292013-03-19 11:07:29 -0700385 delete_ns2_led(&priv->leds_data[i]);
Bryan Wua209f762012-07-04 12:30:50 +0800386 return ret;
387 }
Simon Guinot11efe712010-07-06 16:08:46 +0200388 }
389
Simon Guinot3de19292013-03-19 11:07:29 -0700390 platform_set_drvdata(pdev, priv);
Simon Guinot11efe712010-07-06 16:08:46 +0200391
392 return 0;
Simon Guinot11efe712010-07-06 16:08:46 +0200393}
394
Bill Pemberton678e8a62012-11-19 13:26:00 -0500395static int ns2_led_remove(struct platform_device *pdev)
Simon Guinot11efe712010-07-06 16:08:46 +0200396{
397 int i;
Simon Guinot3de19292013-03-19 11:07:29 -0700398 struct ns2_led_priv *priv;
Simon Guinot11efe712010-07-06 16:08:46 +0200399
Simon Guinot3de19292013-03-19 11:07:29 -0700400 priv = platform_get_drvdata(pdev);
Simon Guinot11efe712010-07-06 16:08:46 +0200401
Simon Guinot3de19292013-03-19 11:07:29 -0700402 for (i = 0; i < priv->num_leds; i++)
403 delete_ns2_led(&priv->leds_data[i]);
Simon Guinot11efe712010-07-06 16:08:46 +0200404
Simon Guinot11efe712010-07-06 16:08:46 +0200405 return 0;
406}
407
408static struct platform_driver ns2_led_driver = {
409 .probe = ns2_led_probe,
Bill Pembertondf07cf82012-11-19 13:20:20 -0500410 .remove = ns2_led_remove,
Simon Guinot11efe712010-07-06 16:08:46 +0200411 .driver = {
Simon Guinot72052fc2012-10-17 12:09:03 +0200412 .name = "leds-ns2",
Simon Guinot72052fc2012-10-17 12:09:03 +0200413 .of_match_table = of_match_ptr(of_ns2_leds_match),
Simon Guinot11efe712010-07-06 16:08:46 +0200414 },
415};
Simon Guinot11efe712010-07-06 16:08:46 +0200416
Axel Lin892a8842012-01-10 15:09:24 -0800417module_platform_driver(ns2_led_driver);
Simon Guinot11efe712010-07-06 16:08:46 +0200418
419MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
420MODULE_DESCRIPTION("Network Space v2 LED driver");
421MODULE_LICENSE("GPL");
Axel Lin892a8842012-01-10 15:09:24 -0800422MODULE_ALIAS("platform:leds-ns2");