xref: /openbmc/linux/drivers/leds/leds-ns2.c (revision a2fc703c)
11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
211efe71fSSimon Guinot /*
311efe71fSSimon Guinot  * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
411efe71fSSimon Guinot  *
511efe71fSSimon Guinot  * Copyright (C) 2010 LaCie
611efe71fSSimon Guinot  *
711efe71fSSimon Guinot  * Author: Simon Guinot <sguinot@lacie.com>
811efe71fSSimon Guinot  *
911efe71fSSimon Guinot  * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
1011efe71fSSimon Guinot  */
1111efe71fSSimon Guinot 
1211efe71fSSimon Guinot #include <linux/kernel.h>
1311efe71fSSimon Guinot #include <linux/platform_device.h>
1411efe71fSSimon Guinot #include <linux/slab.h>
15c7896490SLinus Walleij #include <linux/gpio/consumer.h>
1611efe71fSSimon Guinot #include <linux/leds.h>
1754f4dedbSPaul Gortmaker #include <linux/module.h>
18c68f46ddSSachin Kamat #include <linux/of.h>
194b90432dSSimon Guinot #include "leds.h"
2011efe71fSSimon Guinot 
21c7896490SLinus Walleij enum ns2_led_modes {
22c7896490SLinus Walleij 	NS_V2_LED_OFF,
23c7896490SLinus Walleij 	NS_V2_LED_ON,
24c7896490SLinus Walleij 	NS_V2_LED_SATA,
25c7896490SLinus Walleij };
26c7896490SLinus Walleij 
27c7896490SLinus Walleij struct ns2_led_modval {
28c7896490SLinus Walleij 	enum ns2_led_modes	mode;
29c7896490SLinus Walleij 	int			cmd_level;
30c7896490SLinus Walleij 	int			slow_level;
31c7896490SLinus Walleij };
32c7896490SLinus Walleij 
3301026cecSMarek Behún struct ns2_led_of_one {
34c7896490SLinus Walleij 	const char	*name;
35c7896490SLinus Walleij 	const char	*default_trigger;
36ccbbb117SLinus Walleij 	struct gpio_desc *cmd;
37ccbbb117SLinus Walleij 	struct gpio_desc *slow;
38c7896490SLinus Walleij 	int		num_modes;
39c7896490SLinus Walleij 	struct ns2_led_modval *modval;
40c7896490SLinus Walleij };
41c7896490SLinus Walleij 
4201d0b14dSMarek Behún struct ns2_led_of {
43c7896490SLinus Walleij 	int			num_leds;
4401026cecSMarek Behún 	struct ns2_led_of_one	*leds;
45c7896490SLinus Walleij };
46c7896490SLinus Walleij 
4711efe71fSSimon Guinot /*
48f7fafd08SVincent Donnefort  * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
49f7fafd08SVincent Donnefort  * modes are available: off, on and SATA activity blinking. The LED modes are
50f7fafd08SVincent Donnefort  * controlled through two GPIOs (command and slow): each combination of values
51f7fafd08SVincent Donnefort  * for the command/slow GPIOs corresponds to a LED mode.
5211efe71fSSimon Guinot  */
5311efe71fSSimon Guinot 
5401026cecSMarek Behún struct ns2_led {
5511efe71fSSimon Guinot 	struct led_classdev	cdev;
56ccbbb117SLinus Walleij 	struct gpio_desc	*cmd;
57ccbbb117SLinus Walleij 	struct gpio_desc	*slow;
584b90432dSSimon Guinot 	bool			can_sleep;
5911efe71fSSimon Guinot 	unsigned char		sata; /* True when SATA mode active. */
6011efe71fSSimon Guinot 	rwlock_t		rw_lock; /* Lock GPIOs. */
61f7fafd08SVincent Donnefort 	int			num_modes;
62f7fafd08SVincent Donnefort 	struct ns2_led_modval	*modval;
6311efe71fSSimon Guinot };
6411efe71fSSimon Guinot 
65a78bd8f3SMarek Behún static int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode)
6611efe71fSSimon Guinot {
6711efe71fSSimon Guinot 	int i;
6811efe71fSSimon Guinot 	int cmd_level;
6911efe71fSSimon Guinot 	int slow_level;
7011efe71fSSimon Guinot 
71a78bd8f3SMarek Behún 	cmd_level = gpiod_get_value_cansleep(led->cmd);
72a78bd8f3SMarek Behún 	slow_level = gpiod_get_value_cansleep(led->slow);
7311efe71fSSimon Guinot 
74a78bd8f3SMarek Behún 	for (i = 0; i < led->num_modes; i++) {
75a78bd8f3SMarek Behún 		if (cmd_level == led->modval[i].cmd_level &&
76a78bd8f3SMarek Behún 		    slow_level == led->modval[i].slow_level) {
77a78bd8f3SMarek Behún 			*mode = led->modval[i].mode;
78a2fc703cSMarek Behún 			return 0;
7911efe71fSSimon Guinot 		}
8011efe71fSSimon Guinot 	}
8111efe71fSSimon Guinot 
82a2fc703cSMarek Behún 	return -EINVAL;
8311efe71fSSimon Guinot }
8411efe71fSSimon Guinot 
85a78bd8f3SMarek Behún static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode)
8611efe71fSSimon Guinot {
8711efe71fSSimon Guinot 	int i;
884b90432dSSimon Guinot 	bool found = false;
89f539dfedSSimon Guinot 	unsigned long flags;
9011efe71fSSimon Guinot 
91a78bd8f3SMarek Behún 	for (i = 0; i < led->num_modes; i++)
92a78bd8f3SMarek Behún 		if (mode == led->modval[i].mode) {
934b90432dSSimon Guinot 			found = true;
944b90432dSSimon Guinot 			break;
954b90432dSSimon Guinot 		}
964b90432dSSimon Guinot 
974b90432dSSimon Guinot 	if (!found)
984b90432dSSimon Guinot 		return;
994b90432dSSimon Guinot 
100a78bd8f3SMarek Behún 	write_lock_irqsave(&led->rw_lock, flags);
10111efe71fSSimon Guinot 
102a78bd8f3SMarek Behún 	if (!led->can_sleep) {
103a78bd8f3SMarek Behún 		gpiod_set_value(led->cmd, led->modval[i].cmd_level);
104a78bd8f3SMarek Behún 		gpiod_set_value(led->slow, led->modval[i].slow_level);
1054b90432dSSimon Guinot 		goto exit_unlock;
10611efe71fSSimon Guinot 	}
10711efe71fSSimon Guinot 
108a78bd8f3SMarek Behún 	gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level);
109a78bd8f3SMarek Behún 	gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level);
1104b90432dSSimon Guinot 
1114b90432dSSimon Guinot exit_unlock:
112a78bd8f3SMarek Behún 	write_unlock_irqrestore(&led->rw_lock, flags);
11311efe71fSSimon Guinot }
11411efe71fSSimon Guinot 
11511efe71fSSimon Guinot static void ns2_led_set(struct led_classdev *led_cdev,
11611efe71fSSimon Guinot 			enum led_brightness value)
11711efe71fSSimon Guinot {
118a78bd8f3SMarek Behún 	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
11911efe71fSSimon Guinot 	enum ns2_led_modes mode;
12011efe71fSSimon Guinot 
12111efe71fSSimon Guinot 	if (value == LED_OFF)
12211efe71fSSimon Guinot 		mode = NS_V2_LED_OFF;
123a78bd8f3SMarek Behún 	else if (led->sata)
12411efe71fSSimon Guinot 		mode = NS_V2_LED_SATA;
12511efe71fSSimon Guinot 	else
12611efe71fSSimon Guinot 		mode = NS_V2_LED_ON;
12711efe71fSSimon Guinot 
128a78bd8f3SMarek Behún 	ns2_led_set_mode(led, mode);
12911efe71fSSimon Guinot }
13011efe71fSSimon Guinot 
131c29e650bSJacek Anaszewski static int ns2_led_set_blocking(struct led_classdev *led_cdev,
132c29e650bSJacek Anaszewski 			enum led_brightness value)
133c29e650bSJacek Anaszewski {
134c29e650bSJacek Anaszewski 	ns2_led_set(led_cdev, value);
135c29e650bSJacek Anaszewski 	return 0;
136c29e650bSJacek Anaszewski }
137c29e650bSJacek Anaszewski 
13811efe71fSSimon Guinot static ssize_t ns2_led_sata_store(struct device *dev,
13911efe71fSSimon Guinot 				  struct device_attribute *attr,
14011efe71fSSimon Guinot 				  const char *buff, size_t count)
14111efe71fSSimon Guinot {
142e5971bbcSSimon Guinot 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
143a78bd8f3SMarek Behún 	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
14411efe71fSSimon Guinot 	int ret;
14511efe71fSSimon Guinot 	unsigned long enable;
14611efe71fSSimon Guinot 
1473874350cSJingoo Han 	ret = kstrtoul(buff, 10, &enable);
14811efe71fSSimon Guinot 	if (ret < 0)
14911efe71fSSimon Guinot 		return ret;
15011efe71fSSimon Guinot 
15111efe71fSSimon Guinot 	enable = !!enable;
15211efe71fSSimon Guinot 
153a78bd8f3SMarek Behún 	if (led->sata == enable)
1544b90432dSSimon Guinot 		goto exit;
15511efe71fSSimon Guinot 
156a78bd8f3SMarek Behún 	led->sata = enable;
15711efe71fSSimon Guinot 
1584b90432dSSimon Guinot 	if (!led_get_brightness(led_cdev))
1594b90432dSSimon Guinot 		goto exit;
1604b90432dSSimon Guinot 
1614b90432dSSimon Guinot 	if (enable)
162a78bd8f3SMarek Behún 		ns2_led_set_mode(led, NS_V2_LED_SATA);
1634b90432dSSimon Guinot 	else
164a78bd8f3SMarek Behún 		ns2_led_set_mode(led, NS_V2_LED_ON);
1654b90432dSSimon Guinot 
1664b90432dSSimon Guinot exit:
16711efe71fSSimon Guinot 	return count;
16811efe71fSSimon Guinot }
16911efe71fSSimon Guinot 
17011efe71fSSimon Guinot static ssize_t ns2_led_sata_show(struct device *dev,
17111efe71fSSimon Guinot 				 struct device_attribute *attr, char *buf)
17211efe71fSSimon Guinot {
173e5971bbcSSimon Guinot 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
174a78bd8f3SMarek Behún 	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
17511efe71fSSimon Guinot 
176a78bd8f3SMarek Behún 	return sprintf(buf, "%d\n", led->sata);
17711efe71fSSimon Guinot }
17811efe71fSSimon Guinot 
17911efe71fSSimon Guinot static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
18011efe71fSSimon Guinot 
181475f8548SJohan Hovold static struct attribute *ns2_led_attrs[] = {
182475f8548SJohan Hovold 	&dev_attr_sata.attr,
183475f8548SJohan Hovold 	NULL
184475f8548SJohan Hovold };
185475f8548SJohan Hovold ATTRIBUTE_GROUPS(ns2_led);
186475f8548SJohan Hovold 
18798ea1ea2SBill Pemberton static int
188a78bd8f3SMarek Behún create_ns2_led(struct platform_device *pdev, struct ns2_led *led,
18901026cecSMarek Behún 	       const struct ns2_led_of_one *template)
19011efe71fSSimon Guinot {
19111efe71fSSimon Guinot 	int ret;
19211efe71fSSimon Guinot 	enum ns2_led_modes mode;
19311efe71fSSimon Guinot 
194a78bd8f3SMarek Behún 	rwlock_init(&led->rw_lock);
19511efe71fSSimon Guinot 
196a78bd8f3SMarek Behún 	led->cdev.name = template->name;
197a78bd8f3SMarek Behún 	led->cdev.default_trigger = template->default_trigger;
198a78bd8f3SMarek Behún 	led->cdev.blink_set = NULL;
199a78bd8f3SMarek Behún 	led->cdev.flags |= LED_CORE_SUSPENDRESUME;
200a78bd8f3SMarek Behún 	led->cdev.groups = ns2_led_groups;
201a78bd8f3SMarek Behún 	led->cmd = template->cmd;
202a78bd8f3SMarek Behún 	led->slow = template->slow;
203a78bd8f3SMarek Behún 	led->can_sleep = gpiod_cansleep(led->cmd) | gpiod_cansleep(led->slow);
204a78bd8f3SMarek Behún 	if (led->can_sleep)
205a78bd8f3SMarek Behún 		led->cdev.brightness_set_blocking = ns2_led_set_blocking;
206c29e650bSJacek Anaszewski 	else
207a78bd8f3SMarek Behún 		led->cdev.brightness_set = ns2_led_set;
208a78bd8f3SMarek Behún 	led->modval = template->modval;
209a78bd8f3SMarek Behún 	led->num_modes = template->num_modes;
21011efe71fSSimon Guinot 
211a78bd8f3SMarek Behún 	ret = ns2_led_get_mode(led, &mode);
21211efe71fSSimon Guinot 	if (ret < 0)
21304195823SSachin Kamat 		return ret;
21411efe71fSSimon Guinot 
21511efe71fSSimon Guinot 	/* Set LED initial state. */
216a78bd8f3SMarek Behún 	led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
217a78bd8f3SMarek Behún 	led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
21811efe71fSSimon Guinot 
219a78bd8f3SMarek Behún 	return devm_led_classdev_register(&pdev->dev, &led->cdev);
22011efe71fSSimon Guinot }
22111efe71fSSimon Guinot 
222f72deb71SMarek Behún static int ns2_leds_parse_one(struct device *dev, struct device_node *np,
22301026cecSMarek Behún 			      struct ns2_led_of_one *led)
224f72deb71SMarek Behún {
225f72deb71SMarek Behún 	struct ns2_led_modval *modval;
226f72deb71SMarek Behún 	int nmodes, ret, i;
227f72deb71SMarek Behún 
228f72deb71SMarek Behún 	ret = of_property_read_string(np, "label", &led->name);
229f72deb71SMarek Behún 	if (ret)
230f72deb71SMarek Behún 		led->name = np->name;
231f72deb71SMarek Behún 
232528c9515SMarek Behún 	led->cmd = devm_gpiod_get_from_of_node(dev, np, "cmd-gpio", 0,
233528c9515SMarek Behún 					       GPIOD_ASIS, led->name);
234f72deb71SMarek Behún 	if (IS_ERR(led->cmd))
235f72deb71SMarek Behún 		return PTR_ERR(led->cmd);
236f72deb71SMarek Behún 
237528c9515SMarek Behún 	led->slow = devm_gpiod_get_from_of_node(dev, np, "slow-gpio", 0,
238528c9515SMarek Behún 						GPIOD_ASIS, led->name);
239f72deb71SMarek Behún 	if (IS_ERR(led->slow))
240f72deb71SMarek Behún 		return PTR_ERR(led->slow);
241f72deb71SMarek Behún 
242f72deb71SMarek Behún 	of_property_read_string(np, "linux,default-trigger",
243f72deb71SMarek Behún 				&led->default_trigger);
244f72deb71SMarek Behún 
245f72deb71SMarek Behún 	ret = of_property_count_u32_elems(np, "modes-map");
246f72deb71SMarek Behún 	if (ret < 0 || ret % 3) {
247f72deb71SMarek Behún 		dev_err(dev, "Missing or malformed modes-map for %pOF\n", np);
248f72deb71SMarek Behún 		return -EINVAL;
249f72deb71SMarek Behún 	}
250f72deb71SMarek Behún 
251f72deb71SMarek Behún 	nmodes = ret / 3;
252f72deb71SMarek Behún 	modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL);
253f72deb71SMarek Behún 	if (!modval)
254f72deb71SMarek Behún 		return -ENOMEM;
255f72deb71SMarek Behún 
256f72deb71SMarek Behún 	for (i = 0; i < nmodes; i++) {
257f72deb71SMarek Behún 		u32 val;
258f72deb71SMarek Behún 
259f72deb71SMarek Behún 		of_property_read_u32_index(np, "modes-map", 3 * i, &val);
260f72deb71SMarek Behún 		modval[i].mode = val;
261f72deb71SMarek Behún 		of_property_read_u32_index(np, "modes-map", 3 * i + 1, &val);
262f72deb71SMarek Behún 		modval[i].cmd_level = val;
263f72deb71SMarek Behún 		of_property_read_u32_index(np, "modes-map", 3 * i + 2, &val);
264f72deb71SMarek Behún 		modval[i].slow_level = val;
265f72deb71SMarek Behún 	}
266f72deb71SMarek Behún 
267f72deb71SMarek Behún 	led->num_modes = nmodes;
268f72deb71SMarek Behún 	led->modval = modval;
269f72deb71SMarek Behún 
270f72deb71SMarek Behún 	return 0;
271f72deb71SMarek Behún }
272f72deb71SMarek Behún 
27372052fccSSimon Guinot /*
27472052fccSSimon Guinot  * Translate OpenFirmware node properties into platform_data.
27572052fccSSimon Guinot  */
276cf4af012SLinus Torvalds static int
27701d0b14dSMarek Behún ns2_leds_parse_of(struct device *dev, struct ns2_led_of *ofdata)
27872052fccSSimon Guinot {
2798853c95eSMarek Behún 	struct device_node *np = dev_of_node(dev);
28072052fccSSimon Guinot 	struct device_node *child;
28101026cecSMarek Behún 	struct ns2_led_of_one *led, *leds;
28279937a4bSNishka Dasgupta 	int ret, num_leds = 0;
28372052fccSSimon Guinot 
28499a013c8SMarek Behún 	num_leds = of_get_available_child_count(np);
28572052fccSSimon Guinot 	if (!num_leds)
28672052fccSSimon Guinot 		return -ENODEV;
28772052fccSSimon Guinot 
288a86854d0SKees Cook 	leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led),
28972052fccSSimon Guinot 			    GFP_KERNEL);
29072052fccSSimon Guinot 	if (!leds)
29172052fccSSimon Guinot 		return -ENOMEM;
29272052fccSSimon Guinot 
293f7fafd08SVincent Donnefort 	led = leds;
29499a013c8SMarek Behún 	for_each_available_child_of_node(np, child) {
295f72deb71SMarek Behún 		ret = ns2_leds_parse_one(dev, child, led++);
296f72deb71SMarek Behún 		if (ret < 0) {
297f72deb71SMarek Behún 			of_node_put(child);
298f72deb71SMarek Behún 			return ret;
299ccbbb117SLinus Walleij 		}
30072052fccSSimon Guinot 	}
30172052fccSSimon Guinot 
30201d0b14dSMarek Behún 	ofdata->leds = leds;
30301d0b14dSMarek Behún 	ofdata->num_leds = num_leds;
30472052fccSSimon Guinot 
30572052fccSSimon Guinot 	return 0;
30672052fccSSimon Guinot }
30772052fccSSimon Guinot 
30872052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = {
30972052fccSSimon Guinot 	{ .compatible = "lacie,ns2-leds", },
31072052fccSSimon Guinot 	{},
31172052fccSSimon Guinot };
31298f9cc7fSLuis de Bethencourt MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
31372052fccSSimon Guinot 
31498ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev)
31511efe71fSSimon Guinot {
31601d0b14dSMarek Behún 	struct ns2_led_of *ofdata;
31701026cecSMarek Behún 	struct ns2_led *leds;
31811efe71fSSimon Guinot 	int i;
31911efe71fSSimon Guinot 	int ret;
32011efe71fSSimon Guinot 
32101d0b14dSMarek Behún 	ofdata = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_of),
32272052fccSSimon Guinot 			      GFP_KERNEL);
32301d0b14dSMarek Behún 	if (!ofdata)
32472052fccSSimon Guinot 		return -ENOMEM;
32572052fccSSimon Guinot 
32601d0b14dSMarek Behún 	ret = ns2_leds_parse_of(&pdev->dev, ofdata);
32772052fccSSimon Guinot 	if (ret)
32872052fccSSimon Guinot 		return ret;
32911efe71fSSimon Guinot 
33019d4deb7SMarek Behún 	leds = devm_kzalloc(&pdev->dev, array_size(sizeof(*leds),
33101d0b14dSMarek Behún 						   ofdata->num_leds),
33219d4deb7SMarek Behún 			    GFP_KERNEL);
33319d4deb7SMarek Behún 	if (!leds)
33411efe71fSSimon Guinot 		return -ENOMEM;
33511efe71fSSimon Guinot 
33601d0b14dSMarek Behún 	for (i = 0; i < ofdata->num_leds; i++) {
33701d0b14dSMarek Behún 		ret = create_ns2_led(pdev, &leds[i], &ofdata->leds[i]);
33840f97281SMarek Behún 		if (ret < 0)
339a209f766SBryan Wu 			return ret;
340a209f766SBryan Wu 	}
34111efe71fSSimon Guinot 
34211efe71fSSimon Guinot 	return 0;
34311efe71fSSimon Guinot }
34411efe71fSSimon Guinot 
34511efe71fSSimon Guinot static struct platform_driver ns2_led_driver = {
34611efe71fSSimon Guinot 	.probe		= ns2_led_probe,
34711efe71fSSimon Guinot 	.driver		= {
34811efe71fSSimon Guinot 		.name		= "leds-ns2",
34972052fccSSimon Guinot 		.of_match_table	= of_match_ptr(of_ns2_leds_match),
35011efe71fSSimon Guinot 	},
35111efe71fSSimon Guinot };
35211efe71fSSimon Guinot 
353892a8843SAxel Lin module_platform_driver(ns2_led_driver);
35411efe71fSSimon Guinot 
35511efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
35611efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver");
35711efe71fSSimon Guinot MODULE_LICENSE("GPL");
358892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2");
359