leds-ns2.c (6694B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED 4 * 5 * Copyright (C) 2010 LaCie 6 * 7 * Author: Simon Guinot <sguinot@lacie.com> 8 * 9 * Based on leds-gpio.c by Raphael Assenat <raph@8d.com> 10 */ 11 12#include <linux/kernel.h> 13#include <linux/platform_device.h> 14#include <linux/slab.h> 15#include <linux/gpio/consumer.h> 16#include <linux/leds.h> 17#include <linux/module.h> 18#include <linux/of.h> 19#include "leds.h" 20 21enum ns2_led_modes { 22 NS_V2_LED_OFF, 23 NS_V2_LED_ON, 24 NS_V2_LED_SATA, 25}; 26 27/* 28 * If the size of this structure or types of its members is changed, 29 * the filling of array modval in function ns2_led_register must be changed 30 * accordingly. 31 */ 32struct ns2_led_modval { 33 u32 mode; 34 u32 cmd_level; 35 u32 slow_level; 36} __packed; 37 38/* 39 * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED 40 * modes are available: off, on and SATA activity blinking. The LED modes are 41 * controlled through two GPIOs (command and slow): each combination of values 42 * for the command/slow GPIOs corresponds to a LED mode. 43 */ 44 45struct ns2_led { 46 struct led_classdev cdev; 47 struct gpio_desc *cmd; 48 struct gpio_desc *slow; 49 bool can_sleep; 50 unsigned char sata; /* True when SATA mode active. */ 51 rwlock_t rw_lock; /* Lock GPIOs. */ 52 int num_modes; 53 struct ns2_led_modval *modval; 54}; 55 56static int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode) 57{ 58 int i; 59 int cmd_level; 60 int slow_level; 61 62 cmd_level = gpiod_get_value_cansleep(led->cmd); 63 slow_level = gpiod_get_value_cansleep(led->slow); 64 65 for (i = 0; i < led->num_modes; i++) { 66 if (cmd_level == led->modval[i].cmd_level && 67 slow_level == led->modval[i].slow_level) { 68 *mode = led->modval[i].mode; 69 return 0; 70 } 71 } 72 73 return -EINVAL; 74} 75 76static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode) 77{ 78 int i; 79 unsigned long flags; 80 81 for (i = 0; i < led->num_modes; i++) 82 if (mode == led->modval[i].mode) 83 break; 84 85 if (i == led->num_modes) 86 return; 87 88 write_lock_irqsave(&led->rw_lock, flags); 89 90 if (!led->can_sleep) { 91 gpiod_set_value(led->cmd, led->modval[i].cmd_level); 92 gpiod_set_value(led->slow, led->modval[i].slow_level); 93 goto exit_unlock; 94 } 95 96 gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level); 97 gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level); 98 99exit_unlock: 100 write_unlock_irqrestore(&led->rw_lock, flags); 101} 102 103static void ns2_led_set(struct led_classdev *led_cdev, 104 enum led_brightness value) 105{ 106 struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 107 enum ns2_led_modes mode; 108 109 if (value == LED_OFF) 110 mode = NS_V2_LED_OFF; 111 else if (led->sata) 112 mode = NS_V2_LED_SATA; 113 else 114 mode = NS_V2_LED_ON; 115 116 ns2_led_set_mode(led, mode); 117} 118 119static int ns2_led_set_blocking(struct led_classdev *led_cdev, 120 enum led_brightness value) 121{ 122 ns2_led_set(led_cdev, value); 123 return 0; 124} 125 126static ssize_t ns2_led_sata_store(struct device *dev, 127 struct device_attribute *attr, 128 const char *buff, size_t count) 129{ 130 struct led_classdev *led_cdev = dev_get_drvdata(dev); 131 struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 132 int ret; 133 unsigned long enable; 134 135 ret = kstrtoul(buff, 10, &enable); 136 if (ret < 0) 137 return ret; 138 139 enable = !!enable; 140 141 if (led->sata == enable) 142 goto exit; 143 144 led->sata = enable; 145 146 if (!led_get_brightness(led_cdev)) 147 goto exit; 148 149 if (enable) 150 ns2_led_set_mode(led, NS_V2_LED_SATA); 151 else 152 ns2_led_set_mode(led, NS_V2_LED_ON); 153 154exit: 155 return count; 156} 157 158static ssize_t ns2_led_sata_show(struct device *dev, 159 struct device_attribute *attr, char *buf) 160{ 161 struct led_classdev *led_cdev = dev_get_drvdata(dev); 162 struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 163 164 return sprintf(buf, "%d\n", led->sata); 165} 166 167static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 168 169static struct attribute *ns2_led_attrs[] = { 170 &dev_attr_sata.attr, 171 NULL 172}; 173ATTRIBUTE_GROUPS(ns2_led); 174 175static int ns2_led_register(struct device *dev, struct fwnode_handle *node, 176 struct ns2_led *led) 177{ 178 struct led_init_data init_data = {}; 179 struct ns2_led_modval *modval; 180 enum ns2_led_modes mode; 181 int nmodes, ret; 182 183 led->cmd = devm_fwnode_gpiod_get_index(dev, node, "cmd", 0, GPIOD_ASIS, 184 fwnode_get_name(node)); 185 if (IS_ERR(led->cmd)) 186 return PTR_ERR(led->cmd); 187 188 led->slow = devm_fwnode_gpiod_get_index(dev, node, "slow", 0, 189 GPIOD_ASIS, 190 fwnode_get_name(node)); 191 if (IS_ERR(led->slow)) 192 return PTR_ERR(led->slow); 193 194 ret = fwnode_property_count_u32(node, "modes-map"); 195 if (ret < 0 || ret % 3) { 196 dev_err(dev, "Missing or malformed modes-map for %pfw\n", node); 197 return -EINVAL; 198 } 199 200 nmodes = ret / 3; 201 modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL); 202 if (!modval) 203 return -ENOMEM; 204 205 fwnode_property_read_u32_array(node, "modes-map", (void *)modval, 206 nmodes * 3); 207 208 rwlock_init(&led->rw_lock); 209 210 led->cdev.blink_set = NULL; 211 led->cdev.flags |= LED_CORE_SUSPENDRESUME; 212 led->cdev.groups = ns2_led_groups; 213 led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow); 214 if (led->can_sleep) 215 led->cdev.brightness_set_blocking = ns2_led_set_blocking; 216 else 217 led->cdev.brightness_set = ns2_led_set; 218 led->num_modes = nmodes; 219 led->modval = modval; 220 221 ret = ns2_led_get_mode(led, &mode); 222 if (ret < 0) 223 return ret; 224 225 /* Set LED initial state. */ 226 led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 227 led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 228 229 init_data.fwnode = node; 230 231 ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); 232 if (ret) 233 dev_err(dev, "Failed to register LED for node %pfw\n", node); 234 235 return ret; 236} 237 238static int ns2_led_probe(struct platform_device *pdev) 239{ 240 struct device *dev = &pdev->dev; 241 struct fwnode_handle *child; 242 struct ns2_led *leds; 243 int count; 244 int ret; 245 246 count = device_get_child_node_count(dev); 247 if (!count) 248 return -ENODEV; 249 250 leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL); 251 if (!leds) 252 return -ENOMEM; 253 254 device_for_each_child_node(dev, child) { 255 ret = ns2_led_register(dev, child, leds++); 256 if (ret) { 257 fwnode_handle_put(child); 258 return ret; 259 } 260 } 261 262 return 0; 263} 264 265static const struct of_device_id of_ns2_leds_match[] = { 266 { .compatible = "lacie,ns2-leds", }, 267 {}, 268}; 269MODULE_DEVICE_TABLE(of, of_ns2_leds_match); 270 271static struct platform_driver ns2_led_driver = { 272 .probe = ns2_led_probe, 273 .driver = { 274 .name = "leds-ns2", 275 .of_match_table = of_ns2_leds_match, 276 }, 277}; 278 279module_platform_driver(ns2_led_driver); 280 281MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 282MODULE_DESCRIPTION("Network Space v2 LED driver"); 283MODULE_LICENSE("GPL"); 284MODULE_ALIAS("platform:leds-ns2");