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 3311efe71fSSimon Guinot /* 34f7fafd08SVincent Donnefort * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED 35f7fafd08SVincent Donnefort * modes are available: off, on and SATA activity blinking. The LED modes are 36f7fafd08SVincent Donnefort * controlled through two GPIOs (command and slow): each combination of values 37f7fafd08SVincent Donnefort * for the command/slow GPIOs corresponds to a LED mode. 3811efe71fSSimon Guinot */ 3911efe71fSSimon Guinot 4001026cecSMarek Behún struct ns2_led { 4111efe71fSSimon Guinot struct led_classdev cdev; 42ccbbb117SLinus Walleij struct gpio_desc *cmd; 43ccbbb117SLinus Walleij struct gpio_desc *slow; 444b90432dSSimon Guinot bool can_sleep; 4511efe71fSSimon Guinot unsigned char sata; /* True when SATA mode active. */ 4611efe71fSSimon Guinot rwlock_t rw_lock; /* Lock GPIOs. */ 47f7fafd08SVincent Donnefort int num_modes; 48f7fafd08SVincent Donnefort struct ns2_led_modval *modval; 4911efe71fSSimon Guinot }; 5011efe71fSSimon Guinot 51a78bd8f3SMarek Behún static int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode) 5211efe71fSSimon Guinot { 5311efe71fSSimon Guinot int i; 5411efe71fSSimon Guinot int cmd_level; 5511efe71fSSimon Guinot int slow_level; 5611efe71fSSimon Guinot 57a78bd8f3SMarek Behún cmd_level = gpiod_get_value_cansleep(led->cmd); 58a78bd8f3SMarek Behún slow_level = gpiod_get_value_cansleep(led->slow); 5911efe71fSSimon Guinot 60a78bd8f3SMarek Behún for (i = 0; i < led->num_modes; i++) { 61a78bd8f3SMarek Behún if (cmd_level == led->modval[i].cmd_level && 62a78bd8f3SMarek Behún slow_level == led->modval[i].slow_level) { 63a78bd8f3SMarek Behún *mode = led->modval[i].mode; 64a2fc703cSMarek Behún return 0; 6511efe71fSSimon Guinot } 6611efe71fSSimon Guinot } 6711efe71fSSimon Guinot 68a2fc703cSMarek Behún return -EINVAL; 6911efe71fSSimon Guinot } 7011efe71fSSimon Guinot 71a78bd8f3SMarek Behún static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode) 7211efe71fSSimon Guinot { 7311efe71fSSimon Guinot int i; 74f539dfedSSimon Guinot unsigned long flags; 7511efe71fSSimon Guinot 76a78bd8f3SMarek Behún for (i = 0; i < led->num_modes; i++) 7748b77cdcSMarek Behún if (mode == led->modval[i].mode) 784b90432dSSimon Guinot break; 794b90432dSSimon Guinot 8048b77cdcSMarek Behún if (i == led->num_modes) 814b90432dSSimon Guinot return; 824b90432dSSimon Guinot 83a78bd8f3SMarek Behún write_lock_irqsave(&led->rw_lock, flags); 8411efe71fSSimon Guinot 85a78bd8f3SMarek Behún if (!led->can_sleep) { 86a78bd8f3SMarek Behún gpiod_set_value(led->cmd, led->modval[i].cmd_level); 87a78bd8f3SMarek Behún gpiod_set_value(led->slow, led->modval[i].slow_level); 884b90432dSSimon Guinot goto exit_unlock; 8911efe71fSSimon Guinot } 9011efe71fSSimon Guinot 91a78bd8f3SMarek Behún gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level); 92a78bd8f3SMarek Behún gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level); 934b90432dSSimon Guinot 944b90432dSSimon Guinot exit_unlock: 95a78bd8f3SMarek Behún write_unlock_irqrestore(&led->rw_lock, flags); 9611efe71fSSimon Guinot } 9711efe71fSSimon Guinot 9811efe71fSSimon Guinot static void ns2_led_set(struct led_classdev *led_cdev, 9911efe71fSSimon Guinot enum led_brightness value) 10011efe71fSSimon Guinot { 101a78bd8f3SMarek Behún struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 10211efe71fSSimon Guinot enum ns2_led_modes mode; 10311efe71fSSimon Guinot 10411efe71fSSimon Guinot if (value == LED_OFF) 10511efe71fSSimon Guinot mode = NS_V2_LED_OFF; 106a78bd8f3SMarek Behún else if (led->sata) 10711efe71fSSimon Guinot mode = NS_V2_LED_SATA; 10811efe71fSSimon Guinot else 10911efe71fSSimon Guinot mode = NS_V2_LED_ON; 11011efe71fSSimon Guinot 111a78bd8f3SMarek Behún ns2_led_set_mode(led, mode); 11211efe71fSSimon Guinot } 11311efe71fSSimon Guinot 114c29e650bSJacek Anaszewski static int ns2_led_set_blocking(struct led_classdev *led_cdev, 115c29e650bSJacek Anaszewski enum led_brightness value) 116c29e650bSJacek Anaszewski { 117c29e650bSJacek Anaszewski ns2_led_set(led_cdev, value); 118c29e650bSJacek Anaszewski return 0; 119c29e650bSJacek Anaszewski } 120c29e650bSJacek Anaszewski 12111efe71fSSimon Guinot static ssize_t ns2_led_sata_store(struct device *dev, 12211efe71fSSimon Guinot struct device_attribute *attr, 12311efe71fSSimon Guinot const char *buff, size_t count) 12411efe71fSSimon Guinot { 125e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 126a78bd8f3SMarek Behún struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 12711efe71fSSimon Guinot int ret; 12811efe71fSSimon Guinot unsigned long enable; 12911efe71fSSimon Guinot 1303874350cSJingoo Han ret = kstrtoul(buff, 10, &enable); 13111efe71fSSimon Guinot if (ret < 0) 13211efe71fSSimon Guinot return ret; 13311efe71fSSimon Guinot 13411efe71fSSimon Guinot enable = !!enable; 13511efe71fSSimon Guinot 136a78bd8f3SMarek Behún if (led->sata == enable) 1374b90432dSSimon Guinot goto exit; 13811efe71fSSimon Guinot 139a78bd8f3SMarek Behún led->sata = enable; 14011efe71fSSimon Guinot 1414b90432dSSimon Guinot if (!led_get_brightness(led_cdev)) 1424b90432dSSimon Guinot goto exit; 1434b90432dSSimon Guinot 1444b90432dSSimon Guinot if (enable) 145a78bd8f3SMarek Behún ns2_led_set_mode(led, NS_V2_LED_SATA); 1464b90432dSSimon Guinot else 147a78bd8f3SMarek Behún ns2_led_set_mode(led, NS_V2_LED_ON); 1484b90432dSSimon Guinot 1494b90432dSSimon Guinot exit: 15011efe71fSSimon Guinot return count; 15111efe71fSSimon Guinot } 15211efe71fSSimon Guinot 15311efe71fSSimon Guinot static ssize_t ns2_led_sata_show(struct device *dev, 15411efe71fSSimon Guinot struct device_attribute *attr, char *buf) 15511efe71fSSimon Guinot { 156e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 157a78bd8f3SMarek Behún struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 15811efe71fSSimon Guinot 159a78bd8f3SMarek Behún return sprintf(buf, "%d\n", led->sata); 16011efe71fSSimon Guinot } 16111efe71fSSimon Guinot 16211efe71fSSimon Guinot static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 16311efe71fSSimon Guinot 164475f8548SJohan Hovold static struct attribute *ns2_led_attrs[] = { 165475f8548SJohan Hovold &dev_attr_sata.attr, 166475f8548SJohan Hovold NULL 167475f8548SJohan Hovold }; 168475f8548SJohan Hovold ATTRIBUTE_GROUPS(ns2_led); 169475f8548SJohan Hovold 170a4a469b4SMarek Behún static int ns2_led_register(struct device *dev, struct device_node *np, 171a4a469b4SMarek Behún struct ns2_led *led) 172f72deb71SMarek Behún { 173f847ef54SMarek Behún struct led_init_data init_data = {}; 174f72deb71SMarek Behún struct ns2_led_modval *modval; 175a4a469b4SMarek Behún enum ns2_led_modes mode; 176f72deb71SMarek Behún int nmodes, ret, i; 177f72deb71SMarek Behún 178528c9515SMarek Behún led->cmd = devm_gpiod_get_from_of_node(dev, np, "cmd-gpio", 0, 179a4a469b4SMarek Behún GPIOD_ASIS, np->name); 180f72deb71SMarek Behún if (IS_ERR(led->cmd)) 181f72deb71SMarek Behún return PTR_ERR(led->cmd); 182f72deb71SMarek Behún 183528c9515SMarek Behún led->slow = devm_gpiod_get_from_of_node(dev, np, "slow-gpio", 0, 184a4a469b4SMarek Behún GPIOD_ASIS, np->name); 185f72deb71SMarek Behún if (IS_ERR(led->slow)) 186f72deb71SMarek Behún return PTR_ERR(led->slow); 187f72deb71SMarek Behún 188f72deb71SMarek Behún of_property_read_string(np, "linux,default-trigger", 189a4a469b4SMarek Behún &led->cdev.default_trigger); 190f72deb71SMarek Behún 191f72deb71SMarek Behún ret = of_property_count_u32_elems(np, "modes-map"); 192f72deb71SMarek Behún if (ret < 0 || ret % 3) { 193f72deb71SMarek Behún dev_err(dev, "Missing or malformed modes-map for %pOF\n", np); 194f72deb71SMarek Behún return -EINVAL; 195f72deb71SMarek Behún } 196f72deb71SMarek Behún 197f72deb71SMarek Behún nmodes = ret / 3; 198f72deb71SMarek Behún modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL); 199f72deb71SMarek Behún if (!modval) 200f72deb71SMarek Behún return -ENOMEM; 201f72deb71SMarek Behún 202f72deb71SMarek Behún for (i = 0; i < nmodes; i++) { 203f72deb71SMarek Behún u32 val; 204f72deb71SMarek Behún 205f72deb71SMarek Behún of_property_read_u32_index(np, "modes-map", 3 * i, &val); 206f72deb71SMarek Behún modval[i].mode = val; 207f72deb71SMarek Behún of_property_read_u32_index(np, "modes-map", 3 * i + 1, &val); 208f72deb71SMarek Behún modval[i].cmd_level = val; 209f72deb71SMarek Behún of_property_read_u32_index(np, "modes-map", 3 * i + 2, &val); 210f72deb71SMarek Behún modval[i].slow_level = val; 211f72deb71SMarek Behún } 212f72deb71SMarek Behún 213a4a469b4SMarek Behún rwlock_init(&led->rw_lock); 214a4a469b4SMarek Behún 215a4a469b4SMarek Behún led->cdev.blink_set = NULL; 216a4a469b4SMarek Behún led->cdev.flags |= LED_CORE_SUSPENDRESUME; 217a4a469b4SMarek Behún led->cdev.groups = ns2_led_groups; 218a4a469b4SMarek Behún led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow); 219a4a469b4SMarek Behún if (led->can_sleep) 220a4a469b4SMarek Behún led->cdev.brightness_set_blocking = ns2_led_set_blocking; 221a4a469b4SMarek Behún else 222a4a469b4SMarek Behún led->cdev.brightness_set = ns2_led_set; 223f72deb71SMarek Behún led->num_modes = nmodes; 224f72deb71SMarek Behún led->modval = modval; 225f72deb71SMarek Behún 226a4a469b4SMarek Behún ret = ns2_led_get_mode(led, &mode); 227a4a469b4SMarek Behún if (ret < 0) 228f72deb71SMarek Behún return ret; 22972052fccSSimon Guinot 230a4a469b4SMarek Behún /* Set LED initial state. */ 231a4a469b4SMarek Behún led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 232a4a469b4SMarek Behún led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 23372052fccSSimon Guinot 234f847ef54SMarek Behún init_data.fwnode = of_fwnode_handle(np); 235f847ef54SMarek Behún 236f847ef54SMarek Behún ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); 237a4a469b4SMarek Behún if (ret) 238a4a469b4SMarek Behún dev_err(dev, "Failed to register LED for node %pOF\n", np); 239a4a469b4SMarek Behún 240a4a469b4SMarek Behún return ret; 24172052fccSSimon Guinot } 24272052fccSSimon Guinot 24372052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = { 24472052fccSSimon Guinot { .compatible = "lacie,ns2-leds", }, 24572052fccSSimon Guinot {}, 24672052fccSSimon Guinot }; 24798f9cc7fSLuis de Bethencourt MODULE_DEVICE_TABLE(of, of_ns2_leds_match); 24872052fccSSimon Guinot 24998ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev) 25011efe71fSSimon Guinot { 251b3f96922SMarek Behún struct device *dev = &pdev->dev; 252a4a469b4SMarek Behún struct device_node *np, *child; 25301026cecSMarek Behún struct ns2_led *leds; 254a4a469b4SMarek Behún int count; 25511efe71fSSimon Guinot int ret; 25611efe71fSSimon Guinot 257a4a469b4SMarek Behún np = dev_of_node(dev); 258a4a469b4SMarek Behún count = of_get_available_child_count(np); 259a4a469b4SMarek Behún if (!count) 260a4a469b4SMarek Behún return -ENODEV; 26172052fccSSimon Guinot 262a4a469b4SMarek Behún leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL); 26319d4deb7SMarek Behún if (!leds) 26411efe71fSSimon Guinot return -ENOMEM; 26511efe71fSSimon Guinot 266a4a469b4SMarek Behún for_each_available_child_of_node(np, child) { 267a4a469b4SMarek Behún ret = ns2_led_register(dev, child, leds++); 268a4a469b4SMarek Behún if (ret) { 269a4a469b4SMarek Behún of_node_put(child); 270a209f766SBryan Wu return ret; 271a209f766SBryan Wu } 272a4a469b4SMarek Behún } 27311efe71fSSimon Guinot 27411efe71fSSimon Guinot return 0; 27511efe71fSSimon Guinot } 27611efe71fSSimon Guinot 27711efe71fSSimon Guinot static struct platform_driver ns2_led_driver = { 27811efe71fSSimon Guinot .probe = ns2_led_probe, 27911efe71fSSimon Guinot .driver = { 28011efe71fSSimon Guinot .name = "leds-ns2", 28172052fccSSimon Guinot .of_match_table = of_match_ptr(of_ns2_leds_match), 28211efe71fSSimon Guinot }, 28311efe71fSSimon Guinot }; 28411efe71fSSimon Guinot 285892a8843SAxel Lin module_platform_driver(ns2_led_driver); 28611efe71fSSimon Guinot 28711efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 28811efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver"); 28911efe71fSSimon Guinot MODULE_LICENSE("GPL"); 290892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2"); 291