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++) 77*48b77cdcSMarek Behún if (mode == led->modval[i].mode) 784b90432dSSimon Guinot break; 794b90432dSSimon Guinot 80*48b77cdcSMarek 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 { 173f72deb71SMarek Behún struct ns2_led_modval *modval; 174a4a469b4SMarek Behún enum ns2_led_modes mode; 175f72deb71SMarek Behún int nmodes, ret, i; 176f72deb71SMarek Behún 177a4a469b4SMarek Behún ret = of_property_read_string(np, "label", &led->cdev.name); 178f72deb71SMarek Behún if (ret) 179a4a469b4SMarek Behún led->cdev.name = np->name; 180f72deb71SMarek Behún 181528c9515SMarek Behún led->cmd = devm_gpiod_get_from_of_node(dev, np, "cmd-gpio", 0, 182a4a469b4SMarek Behún GPIOD_ASIS, np->name); 183f72deb71SMarek Behún if (IS_ERR(led->cmd)) 184f72deb71SMarek Behún return PTR_ERR(led->cmd); 185f72deb71SMarek Behún 186528c9515SMarek Behún led->slow = devm_gpiod_get_from_of_node(dev, np, "slow-gpio", 0, 187a4a469b4SMarek Behún GPIOD_ASIS, np->name); 188f72deb71SMarek Behún if (IS_ERR(led->slow)) 189f72deb71SMarek Behún return PTR_ERR(led->slow); 190f72deb71SMarek Behún 191f72deb71SMarek Behún of_property_read_string(np, "linux,default-trigger", 192a4a469b4SMarek Behún &led->cdev.default_trigger); 193f72deb71SMarek Behún 194f72deb71SMarek Behún ret = of_property_count_u32_elems(np, "modes-map"); 195f72deb71SMarek Behún if (ret < 0 || ret % 3) { 196f72deb71SMarek Behún dev_err(dev, "Missing or malformed modes-map for %pOF\n", np); 197f72deb71SMarek Behún return -EINVAL; 198f72deb71SMarek Behún } 199f72deb71SMarek Behún 200f72deb71SMarek Behún nmodes = ret / 3; 201f72deb71SMarek Behún modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL); 202f72deb71SMarek Behún if (!modval) 203f72deb71SMarek Behún return -ENOMEM; 204f72deb71SMarek Behún 205f72deb71SMarek Behún for (i = 0; i < nmodes; i++) { 206f72deb71SMarek Behún u32 val; 207f72deb71SMarek Behún 208f72deb71SMarek Behún of_property_read_u32_index(np, "modes-map", 3 * i, &val); 209f72deb71SMarek Behún modval[i].mode = val; 210f72deb71SMarek Behún of_property_read_u32_index(np, "modes-map", 3 * i + 1, &val); 211f72deb71SMarek Behún modval[i].cmd_level = val; 212f72deb71SMarek Behún of_property_read_u32_index(np, "modes-map", 3 * i + 2, &val); 213f72deb71SMarek Behún modval[i].slow_level = val; 214f72deb71SMarek Behún } 215f72deb71SMarek Behún 216a4a469b4SMarek Behún rwlock_init(&led->rw_lock); 217a4a469b4SMarek Behún 218a4a469b4SMarek Behún led->cdev.blink_set = NULL; 219a4a469b4SMarek Behún led->cdev.flags |= LED_CORE_SUSPENDRESUME; 220a4a469b4SMarek Behún led->cdev.groups = ns2_led_groups; 221a4a469b4SMarek Behún led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow); 222a4a469b4SMarek Behún if (led->can_sleep) 223a4a469b4SMarek Behún led->cdev.brightness_set_blocking = ns2_led_set_blocking; 224a4a469b4SMarek Behún else 225a4a469b4SMarek Behún led->cdev.brightness_set = ns2_led_set; 226f72deb71SMarek Behún led->num_modes = nmodes; 227f72deb71SMarek Behún led->modval = modval; 228f72deb71SMarek Behún 229a4a469b4SMarek Behún ret = ns2_led_get_mode(led, &mode); 230a4a469b4SMarek Behún if (ret < 0) 231f72deb71SMarek Behún return ret; 23272052fccSSimon Guinot 233a4a469b4SMarek Behún /* Set LED initial state. */ 234a4a469b4SMarek Behún led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 235a4a469b4SMarek Behún led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 23672052fccSSimon Guinot 237a4a469b4SMarek Behún ret = devm_led_classdev_register(dev, &led->cdev); 238a4a469b4SMarek Behún if (ret) 239a4a469b4SMarek Behún dev_err(dev, "Failed to register LED for node %pOF\n", np); 240a4a469b4SMarek Behún 241a4a469b4SMarek Behún return ret; 24272052fccSSimon Guinot } 24372052fccSSimon Guinot 24472052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = { 24572052fccSSimon Guinot { .compatible = "lacie,ns2-leds", }, 24672052fccSSimon Guinot {}, 24772052fccSSimon Guinot }; 24898f9cc7fSLuis de Bethencourt MODULE_DEVICE_TABLE(of, of_ns2_leds_match); 24972052fccSSimon Guinot 25098ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev) 25111efe71fSSimon Guinot { 252b3f96922SMarek Behún struct device *dev = &pdev->dev; 253a4a469b4SMarek Behún struct device_node *np, *child; 25401026cecSMarek Behún struct ns2_led *leds; 255a4a469b4SMarek Behún int count; 25611efe71fSSimon Guinot int ret; 25711efe71fSSimon Guinot 258a4a469b4SMarek Behún np = dev_of_node(dev); 259a4a469b4SMarek Behún count = of_get_available_child_count(np); 260a4a469b4SMarek Behún if (!count) 261a4a469b4SMarek Behún return -ENODEV; 26272052fccSSimon Guinot 263a4a469b4SMarek Behún leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL); 26419d4deb7SMarek Behún if (!leds) 26511efe71fSSimon Guinot return -ENOMEM; 26611efe71fSSimon Guinot 267a4a469b4SMarek Behún for_each_available_child_of_node(np, child) { 268a4a469b4SMarek Behún ret = ns2_led_register(dev, child, leds++); 269a4a469b4SMarek Behún if (ret) { 270a4a469b4SMarek Behún of_node_put(child); 271a209f766SBryan Wu return ret; 272a209f766SBryan Wu } 273a4a469b4SMarek Behún } 27411efe71fSSimon Guinot 27511efe71fSSimon Guinot return 0; 27611efe71fSSimon Guinot } 27711efe71fSSimon Guinot 27811efe71fSSimon Guinot static struct platform_driver ns2_led_driver = { 27911efe71fSSimon Guinot .probe = ns2_led_probe, 28011efe71fSSimon Guinot .driver = { 28111efe71fSSimon Guinot .name = "leds-ns2", 28272052fccSSimon Guinot .of_match_table = of_match_ptr(of_ns2_leds_match), 28311efe71fSSimon Guinot }, 28411efe71fSSimon Guinot }; 28511efe71fSSimon Guinot 286892a8843SAxel Lin module_platform_driver(ns2_led_driver); 28711efe71fSSimon Guinot 28811efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 28911efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver"); 29011efe71fSSimon Guinot MODULE_LICENSE("GPL"); 291892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2"); 292