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 27*940cca1aSMarek Behún /* 28*940cca1aSMarek Behún * If the size of this structure or types of its members is changed, 29*940cca1aSMarek Behún * the filling of array modval in function ns2_led_register must be changed 30*940cca1aSMarek Behún * accordingly. 31*940cca1aSMarek Behún */ 32c7896490SLinus Walleij struct ns2_led_modval { 33*940cca1aSMarek Behún u32 mode; 34*940cca1aSMarek Behún u32 cmd_level; 35*940cca1aSMarek Behún u32 slow_level; 36*940cca1aSMarek Behún } __packed; 37c7896490SLinus Walleij 3811efe71fSSimon Guinot /* 39f7fafd08SVincent Donnefort * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED 40f7fafd08SVincent Donnefort * modes are available: off, on and SATA activity blinking. The LED modes are 41f7fafd08SVincent Donnefort * controlled through two GPIOs (command and slow): each combination of values 42f7fafd08SVincent Donnefort * for the command/slow GPIOs corresponds to a LED mode. 4311efe71fSSimon Guinot */ 4411efe71fSSimon Guinot 4501026cecSMarek Behún struct ns2_led { 4611efe71fSSimon Guinot struct led_classdev cdev; 47ccbbb117SLinus Walleij struct gpio_desc *cmd; 48ccbbb117SLinus Walleij struct gpio_desc *slow; 494b90432dSSimon Guinot bool can_sleep; 5011efe71fSSimon Guinot unsigned char sata; /* True when SATA mode active. */ 5111efe71fSSimon Guinot rwlock_t rw_lock; /* Lock GPIOs. */ 52f7fafd08SVincent Donnefort int num_modes; 53f7fafd08SVincent Donnefort struct ns2_led_modval *modval; 5411efe71fSSimon Guinot }; 5511efe71fSSimon Guinot 56a78bd8f3SMarek Behún static int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode) 5711efe71fSSimon Guinot { 5811efe71fSSimon Guinot int i; 5911efe71fSSimon Guinot int cmd_level; 6011efe71fSSimon Guinot int slow_level; 6111efe71fSSimon Guinot 62a78bd8f3SMarek Behún cmd_level = gpiod_get_value_cansleep(led->cmd); 63a78bd8f3SMarek Behún slow_level = gpiod_get_value_cansleep(led->slow); 6411efe71fSSimon Guinot 65a78bd8f3SMarek Behún for (i = 0; i < led->num_modes; i++) { 66a78bd8f3SMarek Behún if (cmd_level == led->modval[i].cmd_level && 67a78bd8f3SMarek Behún slow_level == led->modval[i].slow_level) { 68a78bd8f3SMarek Behún *mode = led->modval[i].mode; 69a2fc703cSMarek Behún return 0; 7011efe71fSSimon Guinot } 7111efe71fSSimon Guinot } 7211efe71fSSimon Guinot 73a2fc703cSMarek Behún return -EINVAL; 7411efe71fSSimon Guinot } 7511efe71fSSimon Guinot 76a78bd8f3SMarek Behún static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode) 7711efe71fSSimon Guinot { 7811efe71fSSimon Guinot int i; 79f539dfedSSimon Guinot unsigned long flags; 8011efe71fSSimon Guinot 81a78bd8f3SMarek Behún for (i = 0; i < led->num_modes; i++) 8248b77cdcSMarek Behún if (mode == led->modval[i].mode) 834b90432dSSimon Guinot break; 844b90432dSSimon Guinot 8548b77cdcSMarek Behún if (i == led->num_modes) 864b90432dSSimon Guinot return; 874b90432dSSimon Guinot 88a78bd8f3SMarek Behún write_lock_irqsave(&led->rw_lock, flags); 8911efe71fSSimon Guinot 90a78bd8f3SMarek Behún if (!led->can_sleep) { 91a78bd8f3SMarek Behún gpiod_set_value(led->cmd, led->modval[i].cmd_level); 92a78bd8f3SMarek Behún gpiod_set_value(led->slow, led->modval[i].slow_level); 934b90432dSSimon Guinot goto exit_unlock; 9411efe71fSSimon Guinot } 9511efe71fSSimon Guinot 96a78bd8f3SMarek Behún gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level); 97a78bd8f3SMarek Behún gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level); 984b90432dSSimon Guinot 994b90432dSSimon Guinot exit_unlock: 100a78bd8f3SMarek Behún write_unlock_irqrestore(&led->rw_lock, flags); 10111efe71fSSimon Guinot } 10211efe71fSSimon Guinot 10311efe71fSSimon Guinot static void ns2_led_set(struct led_classdev *led_cdev, 10411efe71fSSimon Guinot enum led_brightness value) 10511efe71fSSimon Guinot { 106a78bd8f3SMarek Behún struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 10711efe71fSSimon Guinot enum ns2_led_modes mode; 10811efe71fSSimon Guinot 10911efe71fSSimon Guinot if (value == LED_OFF) 11011efe71fSSimon Guinot mode = NS_V2_LED_OFF; 111a78bd8f3SMarek Behún else if (led->sata) 11211efe71fSSimon Guinot mode = NS_V2_LED_SATA; 11311efe71fSSimon Guinot else 11411efe71fSSimon Guinot mode = NS_V2_LED_ON; 11511efe71fSSimon Guinot 116a78bd8f3SMarek Behún ns2_led_set_mode(led, mode); 11711efe71fSSimon Guinot } 11811efe71fSSimon Guinot 119c29e650bSJacek Anaszewski static int ns2_led_set_blocking(struct led_classdev *led_cdev, 120c29e650bSJacek Anaszewski enum led_brightness value) 121c29e650bSJacek Anaszewski { 122c29e650bSJacek Anaszewski ns2_led_set(led_cdev, value); 123c29e650bSJacek Anaszewski return 0; 124c29e650bSJacek Anaszewski } 125c29e650bSJacek Anaszewski 12611efe71fSSimon Guinot static ssize_t ns2_led_sata_store(struct device *dev, 12711efe71fSSimon Guinot struct device_attribute *attr, 12811efe71fSSimon Guinot const char *buff, size_t count) 12911efe71fSSimon Guinot { 130e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 131a78bd8f3SMarek Behún struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 13211efe71fSSimon Guinot int ret; 13311efe71fSSimon Guinot unsigned long enable; 13411efe71fSSimon Guinot 1353874350cSJingoo Han ret = kstrtoul(buff, 10, &enable); 13611efe71fSSimon Guinot if (ret < 0) 13711efe71fSSimon Guinot return ret; 13811efe71fSSimon Guinot 13911efe71fSSimon Guinot enable = !!enable; 14011efe71fSSimon Guinot 141a78bd8f3SMarek Behún if (led->sata == enable) 1424b90432dSSimon Guinot goto exit; 14311efe71fSSimon Guinot 144a78bd8f3SMarek Behún led->sata = enable; 14511efe71fSSimon Guinot 1464b90432dSSimon Guinot if (!led_get_brightness(led_cdev)) 1474b90432dSSimon Guinot goto exit; 1484b90432dSSimon Guinot 1494b90432dSSimon Guinot if (enable) 150a78bd8f3SMarek Behún ns2_led_set_mode(led, NS_V2_LED_SATA); 1514b90432dSSimon Guinot else 152a78bd8f3SMarek Behún ns2_led_set_mode(led, NS_V2_LED_ON); 1534b90432dSSimon Guinot 1544b90432dSSimon Guinot exit: 15511efe71fSSimon Guinot return count; 15611efe71fSSimon Guinot } 15711efe71fSSimon Guinot 15811efe71fSSimon Guinot static ssize_t ns2_led_sata_show(struct device *dev, 15911efe71fSSimon Guinot struct device_attribute *attr, char *buf) 16011efe71fSSimon Guinot { 161e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 162a78bd8f3SMarek Behún struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 16311efe71fSSimon Guinot 164a78bd8f3SMarek Behún return sprintf(buf, "%d\n", led->sata); 16511efe71fSSimon Guinot } 16611efe71fSSimon Guinot 16711efe71fSSimon Guinot static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 16811efe71fSSimon Guinot 169475f8548SJohan Hovold static struct attribute *ns2_led_attrs[] = { 170475f8548SJohan Hovold &dev_attr_sata.attr, 171475f8548SJohan Hovold NULL 172475f8548SJohan Hovold }; 173475f8548SJohan Hovold ATTRIBUTE_GROUPS(ns2_led); 174475f8548SJohan Hovold 175*940cca1aSMarek Behún static int ns2_led_register(struct device *dev, struct fwnode_handle *node, 176a4a469b4SMarek Behún struct ns2_led *led) 177f72deb71SMarek Behún { 178f847ef54SMarek Behún struct led_init_data init_data = {}; 179f72deb71SMarek Behún struct ns2_led_modval *modval; 180a4a469b4SMarek Behún enum ns2_led_modes mode; 181*940cca1aSMarek Behún int nmodes, ret; 182f72deb71SMarek Behún 183*940cca1aSMarek Behún led->cmd = devm_fwnode_gpiod_get_index(dev, node, "cmd", 0, GPIOD_ASIS, 184*940cca1aSMarek Behún fwnode_get_name(node)); 185f72deb71SMarek Behún if (IS_ERR(led->cmd)) 186f72deb71SMarek Behún return PTR_ERR(led->cmd); 187f72deb71SMarek Behún 188*940cca1aSMarek Behún led->slow = devm_fwnode_gpiod_get_index(dev, node, "slow", 0, 189*940cca1aSMarek Behún GPIOD_ASIS, 190*940cca1aSMarek Behún fwnode_get_name(node)); 191f72deb71SMarek Behún if (IS_ERR(led->slow)) 192f72deb71SMarek Behún return PTR_ERR(led->slow); 193f72deb71SMarek Behún 194*940cca1aSMarek Behún ret = fwnode_property_count_u32(node, "modes-map"); 195f72deb71SMarek Behún if (ret < 0 || ret % 3) { 196*940cca1aSMarek Behún dev_err(dev, "Missing or malformed modes-map for %pfw\n", node); 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 205*940cca1aSMarek Behún fwnode_property_read_u32_array(node, "modes-map", (void *)modval, 206*940cca1aSMarek Behún nmodes * 3); 207f72deb71SMarek Behún 208a4a469b4SMarek Behún rwlock_init(&led->rw_lock); 209a4a469b4SMarek Behún 210a4a469b4SMarek Behún led->cdev.blink_set = NULL; 211a4a469b4SMarek Behún led->cdev.flags |= LED_CORE_SUSPENDRESUME; 212a4a469b4SMarek Behún led->cdev.groups = ns2_led_groups; 213a4a469b4SMarek Behún led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow); 214a4a469b4SMarek Behún if (led->can_sleep) 215a4a469b4SMarek Behún led->cdev.brightness_set_blocking = ns2_led_set_blocking; 216a4a469b4SMarek Behún else 217a4a469b4SMarek Behún led->cdev.brightness_set = ns2_led_set; 218f72deb71SMarek Behún led->num_modes = nmodes; 219f72deb71SMarek Behún led->modval = modval; 220f72deb71SMarek Behún 221a4a469b4SMarek Behún ret = ns2_led_get_mode(led, &mode); 222a4a469b4SMarek Behún if (ret < 0) 223f72deb71SMarek Behún return ret; 22472052fccSSimon Guinot 225a4a469b4SMarek Behún /* Set LED initial state. */ 226a4a469b4SMarek Behún led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 227a4a469b4SMarek Behún led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 22872052fccSSimon Guinot 229*940cca1aSMarek Behún init_data.fwnode = node; 230f847ef54SMarek Behún 231f847ef54SMarek Behún ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); 232a4a469b4SMarek Behún if (ret) 233*940cca1aSMarek Behún dev_err(dev, "Failed to register LED for node %pfw\n", node); 234a4a469b4SMarek Behún 235a4a469b4SMarek Behún return ret; 23672052fccSSimon Guinot } 23772052fccSSimon Guinot 23872052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = { 23972052fccSSimon Guinot { .compatible = "lacie,ns2-leds", }, 24072052fccSSimon Guinot {}, 24172052fccSSimon Guinot }; 24298f9cc7fSLuis de Bethencourt MODULE_DEVICE_TABLE(of, of_ns2_leds_match); 24372052fccSSimon Guinot 24498ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev) 24511efe71fSSimon Guinot { 246b3f96922SMarek Behún struct device *dev = &pdev->dev; 247*940cca1aSMarek Behún struct fwnode_handle *child; 24801026cecSMarek Behún struct ns2_led *leds; 249a4a469b4SMarek Behún int count; 25011efe71fSSimon Guinot int ret; 25111efe71fSSimon Guinot 252*940cca1aSMarek Behún count = device_get_child_node_count(dev); 253a4a469b4SMarek Behún if (!count) 254a4a469b4SMarek Behún return -ENODEV; 25572052fccSSimon Guinot 256a4a469b4SMarek Behún leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL); 25719d4deb7SMarek Behún if (!leds) 25811efe71fSSimon Guinot return -ENOMEM; 25911efe71fSSimon Guinot 260*940cca1aSMarek Behún device_for_each_child_node(dev, child) { 261a4a469b4SMarek Behún ret = ns2_led_register(dev, child, leds++); 262a4a469b4SMarek Behún if (ret) { 263*940cca1aSMarek Behún fwnode_handle_put(child); 264a209f766SBryan Wu return ret; 265a209f766SBryan Wu } 266a4a469b4SMarek Behún } 26711efe71fSSimon Guinot 26811efe71fSSimon Guinot return 0; 26911efe71fSSimon Guinot } 27011efe71fSSimon Guinot 27111efe71fSSimon Guinot static struct platform_driver ns2_led_driver = { 27211efe71fSSimon Guinot .probe = ns2_led_probe, 27311efe71fSSimon Guinot .driver = { 27411efe71fSSimon Guinot .name = "leds-ns2", 27572052fccSSimon Guinot .of_match_table = of_match_ptr(of_ns2_leds_match), 27611efe71fSSimon Guinot }, 27711efe71fSSimon Guinot }; 27811efe71fSSimon Guinot 279892a8843SAxel Lin module_platform_driver(ns2_led_driver); 28011efe71fSSimon Guinot 28111efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 28211efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver"); 28311efe71fSSimon Guinot MODULE_LICENSE("GPL"); 284892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2"); 285