xref: /openbmc/linux/drivers/leds/leds-ns2.c (revision a78bd8f33fd7f76aded73a4f371270f70e1f5cb8)
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 
65*a78bd8f3SMarek 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 ret = -EINVAL;
6911efe71fSSimon Guinot 	int cmd_level;
7011efe71fSSimon Guinot 	int slow_level;
7111efe71fSSimon Guinot 
72*a78bd8f3SMarek Behún 	cmd_level = gpiod_get_value_cansleep(led->cmd);
73*a78bd8f3SMarek Behún 	slow_level = gpiod_get_value_cansleep(led->slow);
7411efe71fSSimon Guinot 
75*a78bd8f3SMarek Behún 	for (i = 0; i < led->num_modes; i++) {
76*a78bd8f3SMarek Behún 		if (cmd_level == led->modval[i].cmd_level &&
77*a78bd8f3SMarek Behún 		    slow_level == led->modval[i].slow_level) {
78*a78bd8f3SMarek Behún 			*mode = led->modval[i].mode;
7911efe71fSSimon Guinot 			ret = 0;
8011efe71fSSimon Guinot 			break;
8111efe71fSSimon Guinot 		}
8211efe71fSSimon Guinot 	}
8311efe71fSSimon Guinot 
8411efe71fSSimon Guinot 	return ret;
8511efe71fSSimon Guinot }
8611efe71fSSimon Guinot 
87*a78bd8f3SMarek Behún static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode)
8811efe71fSSimon Guinot {
8911efe71fSSimon Guinot 	int i;
904b90432dSSimon Guinot 	bool found = false;
91f539dfedSSimon Guinot 	unsigned long flags;
9211efe71fSSimon Guinot 
93*a78bd8f3SMarek Behún 	for (i = 0; i < led->num_modes; i++)
94*a78bd8f3SMarek Behún 		if (mode == led->modval[i].mode) {
954b90432dSSimon Guinot 			found = true;
964b90432dSSimon Guinot 			break;
974b90432dSSimon Guinot 		}
984b90432dSSimon Guinot 
994b90432dSSimon Guinot 	if (!found)
1004b90432dSSimon Guinot 		return;
1014b90432dSSimon Guinot 
102*a78bd8f3SMarek Behún 	write_lock_irqsave(&led->rw_lock, flags);
10311efe71fSSimon Guinot 
104*a78bd8f3SMarek Behún 	if (!led->can_sleep) {
105*a78bd8f3SMarek Behún 		gpiod_set_value(led->cmd, led->modval[i].cmd_level);
106*a78bd8f3SMarek Behún 		gpiod_set_value(led->slow, led->modval[i].slow_level);
1074b90432dSSimon Guinot 		goto exit_unlock;
10811efe71fSSimon Guinot 	}
10911efe71fSSimon Guinot 
110*a78bd8f3SMarek Behún 	gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level);
111*a78bd8f3SMarek Behún 	gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level);
1124b90432dSSimon Guinot 
1134b90432dSSimon Guinot exit_unlock:
114*a78bd8f3SMarek Behún 	write_unlock_irqrestore(&led->rw_lock, flags);
11511efe71fSSimon Guinot }
11611efe71fSSimon Guinot 
11711efe71fSSimon Guinot static void ns2_led_set(struct led_classdev *led_cdev,
11811efe71fSSimon Guinot 			enum led_brightness value)
11911efe71fSSimon Guinot {
120*a78bd8f3SMarek Behún 	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
12111efe71fSSimon Guinot 	enum ns2_led_modes mode;
12211efe71fSSimon Guinot 
12311efe71fSSimon Guinot 	if (value == LED_OFF)
12411efe71fSSimon Guinot 		mode = NS_V2_LED_OFF;
125*a78bd8f3SMarek Behún 	else if (led->sata)
12611efe71fSSimon Guinot 		mode = NS_V2_LED_SATA;
12711efe71fSSimon Guinot 	else
12811efe71fSSimon Guinot 		mode = NS_V2_LED_ON;
12911efe71fSSimon Guinot 
130*a78bd8f3SMarek Behún 	ns2_led_set_mode(led, mode);
13111efe71fSSimon Guinot }
13211efe71fSSimon Guinot 
133c29e650bSJacek Anaszewski static int ns2_led_set_blocking(struct led_classdev *led_cdev,
134c29e650bSJacek Anaszewski 			enum led_brightness value)
135c29e650bSJacek Anaszewski {
136c29e650bSJacek Anaszewski 	ns2_led_set(led_cdev, value);
137c29e650bSJacek Anaszewski 	return 0;
138c29e650bSJacek Anaszewski }
139c29e650bSJacek Anaszewski 
14011efe71fSSimon Guinot static ssize_t ns2_led_sata_store(struct device *dev,
14111efe71fSSimon Guinot 				  struct device_attribute *attr,
14211efe71fSSimon Guinot 				  const char *buff, size_t count)
14311efe71fSSimon Guinot {
144e5971bbcSSimon Guinot 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
145*a78bd8f3SMarek Behún 	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
14611efe71fSSimon Guinot 	int ret;
14711efe71fSSimon Guinot 	unsigned long enable;
14811efe71fSSimon Guinot 
1493874350cSJingoo Han 	ret = kstrtoul(buff, 10, &enable);
15011efe71fSSimon Guinot 	if (ret < 0)
15111efe71fSSimon Guinot 		return ret;
15211efe71fSSimon Guinot 
15311efe71fSSimon Guinot 	enable = !!enable;
15411efe71fSSimon Guinot 
155*a78bd8f3SMarek Behún 	if (led->sata == enable)
1564b90432dSSimon Guinot 		goto exit;
15711efe71fSSimon Guinot 
158*a78bd8f3SMarek Behún 	led->sata = enable;
15911efe71fSSimon Guinot 
1604b90432dSSimon Guinot 	if (!led_get_brightness(led_cdev))
1614b90432dSSimon Guinot 		goto exit;
1624b90432dSSimon Guinot 
1634b90432dSSimon Guinot 	if (enable)
164*a78bd8f3SMarek Behún 		ns2_led_set_mode(led, NS_V2_LED_SATA);
1654b90432dSSimon Guinot 	else
166*a78bd8f3SMarek Behún 		ns2_led_set_mode(led, NS_V2_LED_ON);
1674b90432dSSimon Guinot 
1684b90432dSSimon Guinot exit:
16911efe71fSSimon Guinot 	return count;
17011efe71fSSimon Guinot }
17111efe71fSSimon Guinot 
17211efe71fSSimon Guinot static ssize_t ns2_led_sata_show(struct device *dev,
17311efe71fSSimon Guinot 				 struct device_attribute *attr, char *buf)
17411efe71fSSimon Guinot {
175e5971bbcSSimon Guinot 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
176*a78bd8f3SMarek Behún 	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
17711efe71fSSimon Guinot 
178*a78bd8f3SMarek Behún 	return sprintf(buf, "%d\n", led->sata);
17911efe71fSSimon Guinot }
18011efe71fSSimon Guinot 
18111efe71fSSimon Guinot static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
18211efe71fSSimon Guinot 
183475f8548SJohan Hovold static struct attribute *ns2_led_attrs[] = {
184475f8548SJohan Hovold 	&dev_attr_sata.attr,
185475f8548SJohan Hovold 	NULL
186475f8548SJohan Hovold };
187475f8548SJohan Hovold ATTRIBUTE_GROUPS(ns2_led);
188475f8548SJohan Hovold 
18998ea1ea2SBill Pemberton static int
190*a78bd8f3SMarek Behún create_ns2_led(struct platform_device *pdev, struct ns2_led *led,
19101026cecSMarek Behún 	       const struct ns2_led_of_one *template)
19211efe71fSSimon Guinot {
19311efe71fSSimon Guinot 	int ret;
19411efe71fSSimon Guinot 	enum ns2_led_modes mode;
19511efe71fSSimon Guinot 
196*a78bd8f3SMarek Behún 	rwlock_init(&led->rw_lock);
19711efe71fSSimon Guinot 
198*a78bd8f3SMarek Behún 	led->cdev.name = template->name;
199*a78bd8f3SMarek Behún 	led->cdev.default_trigger = template->default_trigger;
200*a78bd8f3SMarek Behún 	led->cdev.blink_set = NULL;
201*a78bd8f3SMarek Behún 	led->cdev.flags |= LED_CORE_SUSPENDRESUME;
202*a78bd8f3SMarek Behún 	led->cdev.groups = ns2_led_groups;
203*a78bd8f3SMarek Behún 	led->cmd = template->cmd;
204*a78bd8f3SMarek Behún 	led->slow = template->slow;
205*a78bd8f3SMarek Behún 	led->can_sleep = gpiod_cansleep(led->cmd) | gpiod_cansleep(led->slow);
206*a78bd8f3SMarek Behún 	if (led->can_sleep)
207*a78bd8f3SMarek Behún 		led->cdev.brightness_set_blocking = ns2_led_set_blocking;
208c29e650bSJacek Anaszewski 	else
209*a78bd8f3SMarek Behún 		led->cdev.brightness_set = ns2_led_set;
210*a78bd8f3SMarek Behún 	led->modval = template->modval;
211*a78bd8f3SMarek Behún 	led->num_modes = template->num_modes;
21211efe71fSSimon Guinot 
213*a78bd8f3SMarek Behún 	ret = ns2_led_get_mode(led, &mode);
21411efe71fSSimon Guinot 	if (ret < 0)
21504195823SSachin Kamat 		return ret;
21611efe71fSSimon Guinot 
21711efe71fSSimon Guinot 	/* Set LED initial state. */
218*a78bd8f3SMarek Behún 	led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
219*a78bd8f3SMarek Behún 	led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
22011efe71fSSimon Guinot 
221*a78bd8f3SMarek Behún 	return devm_led_classdev_register(&pdev->dev, &led->cdev);
22211efe71fSSimon Guinot }
22311efe71fSSimon Guinot 
224f72deb71SMarek Behún static int ns2_leds_parse_one(struct device *dev, struct device_node *np,
22501026cecSMarek Behún 			      struct ns2_led_of_one *led)
226f72deb71SMarek Behún {
227f72deb71SMarek Behún 	struct ns2_led_modval *modval;
228f72deb71SMarek Behún 	int nmodes, ret, i;
229f72deb71SMarek Behún 
230f72deb71SMarek Behún 	ret = of_property_read_string(np, "label", &led->name);
231f72deb71SMarek Behún 	if (ret)
232f72deb71SMarek Behún 		led->name = np->name;
233f72deb71SMarek Behún 
234528c9515SMarek Behún 	led->cmd = devm_gpiod_get_from_of_node(dev, np, "cmd-gpio", 0,
235528c9515SMarek Behún 					       GPIOD_ASIS, led->name);
236f72deb71SMarek Behún 	if (IS_ERR(led->cmd))
237f72deb71SMarek Behún 		return PTR_ERR(led->cmd);
238f72deb71SMarek Behún 
239528c9515SMarek Behún 	led->slow = devm_gpiod_get_from_of_node(dev, np, "slow-gpio", 0,
240528c9515SMarek Behún 						GPIOD_ASIS, led->name);
241f72deb71SMarek Behún 	if (IS_ERR(led->slow))
242f72deb71SMarek Behún 		return PTR_ERR(led->slow);
243f72deb71SMarek Behún 
244f72deb71SMarek Behún 	of_property_read_string(np, "linux,default-trigger",
245f72deb71SMarek Behún 				&led->default_trigger);
246f72deb71SMarek Behún 
247f72deb71SMarek Behún 	ret = of_property_count_u32_elems(np, "modes-map");
248f72deb71SMarek Behún 	if (ret < 0 || ret % 3) {
249f72deb71SMarek Behún 		dev_err(dev, "Missing or malformed modes-map for %pOF\n", np);
250f72deb71SMarek Behún 		return -EINVAL;
251f72deb71SMarek Behún 	}
252f72deb71SMarek Behún 
253f72deb71SMarek Behún 	nmodes = ret / 3;
254f72deb71SMarek Behún 	modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL);
255f72deb71SMarek Behún 	if (!modval)
256f72deb71SMarek Behún 		return -ENOMEM;
257f72deb71SMarek Behún 
258f72deb71SMarek Behún 	for (i = 0; i < nmodes; i++) {
259f72deb71SMarek Behún 		u32 val;
260f72deb71SMarek Behún 
261f72deb71SMarek Behún 		of_property_read_u32_index(np, "modes-map", 3 * i, &val);
262f72deb71SMarek Behún 		modval[i].mode = val;
263f72deb71SMarek Behún 		of_property_read_u32_index(np, "modes-map", 3 * i + 1, &val);
264f72deb71SMarek Behún 		modval[i].cmd_level = val;
265f72deb71SMarek Behún 		of_property_read_u32_index(np, "modes-map", 3 * i + 2, &val);
266f72deb71SMarek Behún 		modval[i].slow_level = val;
267f72deb71SMarek Behún 	}
268f72deb71SMarek Behún 
269f72deb71SMarek Behún 	led->num_modes = nmodes;
270f72deb71SMarek Behún 	led->modval = modval;
271f72deb71SMarek Behún 
272f72deb71SMarek Behún 	return 0;
273f72deb71SMarek Behún }
274f72deb71SMarek Behún 
27572052fccSSimon Guinot /*
27672052fccSSimon Guinot  * Translate OpenFirmware node properties into platform_data.
27772052fccSSimon Guinot  */
278cf4af012SLinus Torvalds static int
27901d0b14dSMarek Behún ns2_leds_parse_of(struct device *dev, struct ns2_led_of *ofdata)
28072052fccSSimon Guinot {
2818853c95eSMarek Behún 	struct device_node *np = dev_of_node(dev);
28272052fccSSimon Guinot 	struct device_node *child;
28301026cecSMarek Behún 	struct ns2_led_of_one *led, *leds;
28479937a4bSNishka Dasgupta 	int ret, num_leds = 0;
28572052fccSSimon Guinot 
28699a013c8SMarek Behún 	num_leds = of_get_available_child_count(np);
28772052fccSSimon Guinot 	if (!num_leds)
28872052fccSSimon Guinot 		return -ENODEV;
28972052fccSSimon Guinot 
290a86854d0SKees Cook 	leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led),
29172052fccSSimon Guinot 			    GFP_KERNEL);
29272052fccSSimon Guinot 	if (!leds)
29372052fccSSimon Guinot 		return -ENOMEM;
29472052fccSSimon Guinot 
295f7fafd08SVincent Donnefort 	led = leds;
29699a013c8SMarek Behún 	for_each_available_child_of_node(np, child) {
297f72deb71SMarek Behún 		ret = ns2_leds_parse_one(dev, child, led++);
298f72deb71SMarek Behún 		if (ret < 0) {
299f72deb71SMarek Behún 			of_node_put(child);
300f72deb71SMarek Behún 			return ret;
301ccbbb117SLinus Walleij 		}
30272052fccSSimon Guinot 	}
30372052fccSSimon Guinot 
30401d0b14dSMarek Behún 	ofdata->leds = leds;
30501d0b14dSMarek Behún 	ofdata->num_leds = num_leds;
30672052fccSSimon Guinot 
30772052fccSSimon Guinot 	return 0;
30872052fccSSimon Guinot }
30972052fccSSimon Guinot 
31072052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = {
31172052fccSSimon Guinot 	{ .compatible = "lacie,ns2-leds", },
31272052fccSSimon Guinot 	{},
31372052fccSSimon Guinot };
31498f9cc7fSLuis de Bethencourt MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
31572052fccSSimon Guinot 
31698ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev)
31711efe71fSSimon Guinot {
31801d0b14dSMarek Behún 	struct ns2_led_of *ofdata;
31901026cecSMarek Behún 	struct ns2_led *leds;
32011efe71fSSimon Guinot 	int i;
32111efe71fSSimon Guinot 	int ret;
32211efe71fSSimon Guinot 
32301d0b14dSMarek Behún 	ofdata = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_of),
32472052fccSSimon Guinot 			      GFP_KERNEL);
32501d0b14dSMarek Behún 	if (!ofdata)
32672052fccSSimon Guinot 		return -ENOMEM;
32772052fccSSimon Guinot 
32801d0b14dSMarek Behún 	ret = ns2_leds_parse_of(&pdev->dev, ofdata);
32972052fccSSimon Guinot 	if (ret)
33072052fccSSimon Guinot 		return ret;
33111efe71fSSimon Guinot 
33219d4deb7SMarek Behún 	leds = devm_kzalloc(&pdev->dev, array_size(sizeof(*leds),
33301d0b14dSMarek Behún 						   ofdata->num_leds),
33419d4deb7SMarek Behún 			    GFP_KERNEL);
33519d4deb7SMarek Behún 	if (!leds)
33611efe71fSSimon Guinot 		return -ENOMEM;
33711efe71fSSimon Guinot 
33801d0b14dSMarek Behún 	for (i = 0; i < ofdata->num_leds; i++) {
33901d0b14dSMarek Behún 		ret = create_ns2_led(pdev, &leds[i], &ofdata->leds[i]);
34040f97281SMarek Behún 		if (ret < 0)
341a209f766SBryan Wu 			return ret;
342a209f766SBryan Wu 	}
34311efe71fSSimon Guinot 
34411efe71fSSimon Guinot 	return 0;
34511efe71fSSimon Guinot }
34611efe71fSSimon Guinot 
34711efe71fSSimon Guinot static struct platform_driver ns2_led_driver = {
34811efe71fSSimon Guinot 	.probe		= ns2_led_probe,
34911efe71fSSimon Guinot 	.driver		= {
35011efe71fSSimon Guinot 		.name		= "leds-ns2",
35172052fccSSimon Guinot 		.of_match_table	= of_match_ptr(of_ns2_leds_match),
35211efe71fSSimon Guinot 	},
35311efe71fSSimon Guinot };
35411efe71fSSimon Guinot 
355892a8843SAxel Lin module_platform_driver(ns2_led_driver);
35611efe71fSSimon Guinot 
35711efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
35811efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver");
35911efe71fSSimon Guinot MODULE_LICENSE("GPL");
360892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2");
361