blob: 6d37dda12c39e160dfaa1928bfdd9300861946d3 [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;
36 unsigned cmd;
37 unsigned 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
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;
Kitone Elvis Peter2224f2f2018-08-06 20:27:59 +030056 unsigned int cmd;
57 unsigned int 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
Simon Guinot4b904322015-07-02 19:56:42 +020073 cmd_level = gpio_get_value_cansleep(led_dat->cmd);
74 slow_level = gpio_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) {
107 gpio_set_value(led_dat->cmd,
108 led_dat->modval[i].cmd_level);
109 gpio_set_value(led_dat->slow,
110 led_dat->modval[i].slow_level);
111 goto exit_unlock;
Simon Guinot11efe712010-07-06 16:08:46 +0200112 }
113
Jacek Anaszewskic29e6502015-11-20 11:39:41 +0100114 gpio_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level);
115 gpio_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
Sachin Kamat04195822012-11-25 10:28:10 +0530203 ret = devm_gpio_request_one(&pdev->dev, template->cmd,
Simon Guinot4b904322015-07-02 19:56:42 +0200204 gpio_get_value_cansleep(template->cmd) ?
Jingoo Han9d04cba2013-03-07 18:38:26 -0800205 GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
Jingoo Han31c3dc72012-10-23 05:18:21 -0700206 template->name);
Simon Guinot11efe712010-07-06 16:08:46 +0200207 if (ret) {
208 dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
209 template->name);
Jingoo Han31c3dc72012-10-23 05:18:21 -0700210 return ret;
Simon Guinot11efe712010-07-06 16:08:46 +0200211 }
212
Sachin Kamat04195822012-11-25 10:28:10 +0530213 ret = devm_gpio_request_one(&pdev->dev, template->slow,
Simon Guinot4b904322015-07-02 19:56:42 +0200214 gpio_get_value_cansleep(template->slow) ?
Jingoo Han9d04cba2013-03-07 18:38:26 -0800215 GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
Jingoo Han31c3dc72012-10-23 05:18:21 -0700216 template->name);
Simon Guinot11efe712010-07-06 16:08:46 +0200217 if (ret) {
218 dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
219 template->name);
Sachin Kamat04195822012-11-25 10:28:10 +0530220 return ret;
Simon Guinot11efe712010-07-06 16:08:46 +0200221 }
222
223 rwlock_init(&led_dat->rw_lock);
224
225 led_dat->cdev.name = template->name;
226 led_dat->cdev.default_trigger = template->default_trigger;
227 led_dat->cdev.blink_set = NULL;
Simon Guinot11efe712010-07-06 16:08:46 +0200228 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
Johan Hovold475f8542014-06-25 10:08:51 -0700229 led_dat->cdev.groups = ns2_led_groups;
Simon Guinot11efe712010-07-06 16:08:46 +0200230 led_dat->cmd = template->cmd;
231 led_dat->slow = template->slow;
Simon Guinot4b904322015-07-02 19:56:42 +0200232 led_dat->can_sleep = gpio_cansleep(led_dat->cmd) |
233 gpio_cansleep(led_dat->slow);
Jacek Anaszewskic29e6502015-11-20 11:39:41 +0100234 if (led_dat->can_sleep)
235 led_dat->cdev.brightness_set_blocking = ns2_led_set_blocking;
236 else
237 led_dat->cdev.brightness_set = ns2_led_set;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200238 led_dat->modval = template->modval;
239 led_dat->num_modes = template->num_modes;
Simon Guinot11efe712010-07-06 16:08:46 +0200240
241 ret = ns2_led_get_mode(led_dat, &mode);
242 if (ret < 0)
Sachin Kamat04195822012-11-25 10:28:10 +0530243 return ret;
Simon Guinot11efe712010-07-06 16:08:46 +0200244
245 /* Set LED initial state. */
246 led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
247 led_dat->cdev.brightness =
248 (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
249
250 ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
251 if (ret < 0)
Sachin Kamat04195822012-11-25 10:28:10 +0530252 return ret;
Simon Guinot11efe712010-07-06 16:08:46 +0200253
Simon Guinot11efe712010-07-06 16:08:46 +0200254 return 0;
Simon Guinot11efe712010-07-06 16:08:46 +0200255}
256
Arnd Bergmannb8cd7422012-05-10 13:01:46 -0700257static void delete_ns2_led(struct ns2_led_data *led_dat)
Simon Guinot11efe712010-07-06 16:08:46 +0200258{
Simon Guinot11efe712010-07-06 16:08:46 +0200259 led_classdev_unregister(&led_dat->cdev);
Simon Guinot11efe712010-07-06 16:08:46 +0200260}
261
Simon Guinot72052fc2012-10-17 12:09:03 +0200262#ifdef CONFIG_OF_GPIO
263/*
264 * Translate OpenFirmware node properties into platform_data.
265 */
Linus Torvaldscf4af012012-12-12 12:14:06 -0800266static int
Simon Guinot72052fc2012-10-17 12:09:03 +0200267ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
268{
269 struct device_node *np = dev->of_node;
270 struct device_node *child;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200271 struct ns2_led *led, *leds;
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530272 int ret, num_leds = 0;
Simon Guinot72052fc2012-10-17 12:09:03 +0200273
274 num_leds = of_get_child_count(np);
275 if (!num_leds)
276 return -ENODEV;
277
Kees Cooka86854d2018-06-12 14:07:58 -0700278 leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led),
Simon Guinot72052fc2012-10-17 12:09:03 +0200279 GFP_KERNEL);
280 if (!leds)
281 return -ENOMEM;
282
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200283 led = leds;
Simon Guinot72052fc2012-10-17 12:09:03 +0200284 for_each_child_of_node(np, child) {
285 const char *string;
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530286 int i, num_modes;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200287 struct ns2_led_modval *modval;
Simon Guinot72052fc2012-10-17 12:09:03 +0200288
289 ret = of_get_named_gpio(child, "cmd-gpio", 0);
290 if (ret < 0)
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530291 goto err_node_put;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200292 led->cmd = ret;
Simon Guinot72052fc2012-10-17 12:09:03 +0200293 ret = of_get_named_gpio(child, "slow-gpio", 0);
294 if (ret < 0)
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530295 goto err_node_put;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200296 led->slow = ret;
Simon Guinot72052fc2012-10-17 12:09:03 +0200297 ret = of_property_read_string(child, "label", &string);
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200298 led->name = (ret == 0) ? string : child->name;
Simon Guinot72052fc2012-10-17 12:09:03 +0200299 ret = of_property_read_string(child, "linux,default-trigger",
300 &string);
301 if (ret == 0)
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200302 led->default_trigger = string;
Simon Guinot72052fc2012-10-17 12:09:03 +0200303
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200304 ret = of_property_count_u32_elems(child, "modes-map");
305 if (ret < 0 || ret % 3) {
306 dev_err(dev,
307 "Missing or malformed modes-map property\n");
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530308 ret = -EINVAL;
309 goto err_node_put;
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200310 }
311
312 num_modes = ret / 3;
Kees Cooka86854d2018-06-12 14:07:58 -0700313 modval = devm_kcalloc(dev,
314 num_modes,
315 sizeof(struct ns2_led_modval),
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200316 GFP_KERNEL);
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530317 if (!modval) {
318 ret = -ENOMEM;
319 goto err_node_put;
320 }
Vincent Donnefortf7fafd02015-07-02 19:56:40 +0200321
322 for (i = 0; i < num_modes; i++) {
323 of_property_read_u32_index(child,
324 "modes-map", 3 * i,
325 (u32 *) &modval[i].mode);
326 of_property_read_u32_index(child,
327 "modes-map", 3 * i + 1,
328 (u32 *) &modval[i].cmd_level);
329 of_property_read_u32_index(child,
330 "modes-map", 3 * i + 2,
331 (u32 *) &modval[i].slow_level);
332 }
333
334 led->num_modes = num_modes;
335 led->modval = modval;
336
337 led++;
Simon Guinot72052fc2012-10-17 12:09:03 +0200338 }
339
340 pdata->leds = leds;
341 pdata->num_leds = num_leds;
342
343 return 0;
Nishka Dasgupta79937a42019-07-16 12:54:24 +0530344
345err_node_put:
346 of_node_put(child);
347 return ret;
Simon Guinot72052fc2012-10-17 12:09:03 +0200348}
349
350static const struct of_device_id of_ns2_leds_match[] = {
351 { .compatible = "lacie,ns2-leds", },
352 {},
353};
Luis de Bethencourt98f9cc72015-09-01 23:36:59 +0200354MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
Simon Guinot72052fc2012-10-17 12:09:03 +0200355#endif /* CONFIG_OF_GPIO */
356
Simon Guinot3de19292013-03-19 11:07:29 -0700357struct ns2_led_priv {
358 int num_leds;
359 struct ns2_led_data leds_data[];
360};
361
362static inline int sizeof_ns2_led_priv(int num_leds)
363{
364 return sizeof(struct ns2_led_priv) +
365 (sizeof(struct ns2_led_data) * num_leds);
366}
367
Bill Pemberton98ea1ea2012-11-19 13:23:02 -0500368static int ns2_led_probe(struct platform_device *pdev)
Simon Guinot11efe712010-07-06 16:08:46 +0200369{
Jingoo Han87aae1e2013-07-30 01:07:35 -0700370 struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
Simon Guinot3de19292013-03-19 11:07:29 -0700371 struct ns2_led_priv *priv;
Simon Guinot11efe712010-07-06 16:08:46 +0200372 int i;
373 int ret;
374
Simon Guinot72052fc2012-10-17 12:09:03 +0200375#ifdef CONFIG_OF_GPIO
376 if (!pdata) {
377 pdata = devm_kzalloc(&pdev->dev,
378 sizeof(struct ns2_led_platform_data),
379 GFP_KERNEL);
380 if (!pdata)
381 return -ENOMEM;
382
383 ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
384 if (ret)
385 return ret;
386 }
387#else
Simon Guinot11efe712010-07-06 16:08:46 +0200388 if (!pdata)
389 return -EINVAL;
Simon Guinot72052fc2012-10-17 12:09:03 +0200390#endif /* CONFIG_OF_GPIO */
Simon Guinot11efe712010-07-06 16:08:46 +0200391
Simon Guinot3de19292013-03-19 11:07:29 -0700392 priv = devm_kzalloc(&pdev->dev,
393 sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
394 if (!priv)
Simon Guinot11efe712010-07-06 16:08:46 +0200395 return -ENOMEM;
Simon Guinot3de19292013-03-19 11:07:29 -0700396 priv->num_leds = pdata->num_leds;
Simon Guinot11efe712010-07-06 16:08:46 +0200397
Simon Guinot3de19292013-03-19 11:07:29 -0700398 for (i = 0; i < priv->num_leds; i++) {
399 ret = create_ns2_led(pdev, &priv->leds_data[i],
400 &pdata->leds[i]);
Bryan Wua209f762012-07-04 12:30:50 +0800401 if (ret < 0) {
402 for (i = i - 1; i >= 0; i--)
Simon Guinot3de19292013-03-19 11:07:29 -0700403 delete_ns2_led(&priv->leds_data[i]);
Bryan Wua209f762012-07-04 12:30:50 +0800404 return ret;
405 }
Simon Guinot11efe712010-07-06 16:08:46 +0200406 }
407
Simon Guinot3de19292013-03-19 11:07:29 -0700408 platform_set_drvdata(pdev, priv);
Simon Guinot11efe712010-07-06 16:08:46 +0200409
410 return 0;
Simon Guinot11efe712010-07-06 16:08:46 +0200411}
412
Bill Pemberton678e8a62012-11-19 13:26:00 -0500413static int ns2_led_remove(struct platform_device *pdev)
Simon Guinot11efe712010-07-06 16:08:46 +0200414{
415 int i;
Simon Guinot3de19292013-03-19 11:07:29 -0700416 struct ns2_led_priv *priv;
Simon Guinot11efe712010-07-06 16:08:46 +0200417
Simon Guinot3de19292013-03-19 11:07:29 -0700418 priv = platform_get_drvdata(pdev);
Simon Guinot11efe712010-07-06 16:08:46 +0200419
Simon Guinot3de19292013-03-19 11:07:29 -0700420 for (i = 0; i < priv->num_leds; i++)
421 delete_ns2_led(&priv->leds_data[i]);
Simon Guinot11efe712010-07-06 16:08:46 +0200422
Simon Guinot11efe712010-07-06 16:08:46 +0200423 return 0;
424}
425
426static struct platform_driver ns2_led_driver = {
427 .probe = ns2_led_probe,
Bill Pembertondf07cf82012-11-19 13:20:20 -0500428 .remove = ns2_led_remove,
Simon Guinot11efe712010-07-06 16:08:46 +0200429 .driver = {
Simon Guinot72052fc2012-10-17 12:09:03 +0200430 .name = "leds-ns2",
Simon Guinot72052fc2012-10-17 12:09:03 +0200431 .of_match_table = of_match_ptr(of_ns2_leds_match),
Simon Guinot11efe712010-07-06 16:08:46 +0200432 },
433};
Simon Guinot11efe712010-07-06 16:08:46 +0200434
Axel Lin892a8842012-01-10 15:09:24 -0800435module_platform_driver(ns2_led_driver);
Simon Guinot11efe712010-07-06 16:08:46 +0200436
437MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
438MODULE_DESCRIPTION("Network Space v2 LED driver");
439MODULE_LICENSE("GPL");
Axel Lin892a8842012-01-10 15:09:24 -0800440MODULE_ALIAS("platform:leds-ns2");