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 21 enum ns2_led_modes { 22 NS_V2_LED_OFF, 23 NS_V2_LED_ON, 24 NS_V2_LED_SATA, 25 }; 26 27 struct ns2_led_modval { 28 enum ns2_led_modes mode; 29 int cmd_level; 30 int slow_level; 31 }; 32 33 struct ns2_led { 34 const char *name; 35 const char *default_trigger; 36 struct gpio_desc *cmd; 37 struct gpio_desc *slow; 38 int num_modes; 39 struct ns2_led_modval *modval; 40 }; 41 42 struct ns2_led_platform_data { 43 int num_leds; 44 struct ns2_led *leds; 45 }; 46 47 /* 48 * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED 49 * modes are available: off, on and SATA activity blinking. The LED modes are 50 * controlled through two GPIOs (command and slow): each combination of values 51 * for the command/slow GPIOs corresponds to a LED mode. 52 */ 53 54 struct ns2_led_data { 55 struct led_classdev cdev; 56 struct gpio_desc *cmd; 57 struct gpio_desc *slow; 58 bool can_sleep; 59 unsigned char sata; /* True when SATA mode active. */ 60 rwlock_t rw_lock; /* Lock GPIOs. */ 61 int num_modes; 62 struct ns2_led_modval *modval; 63 }; 64 65 static int ns2_led_get_mode(struct ns2_led_data *led_dat, 66 enum ns2_led_modes *mode) 67 { 68 int i; 69 int ret = -EINVAL; 70 int cmd_level; 71 int slow_level; 72 73 cmd_level = gpiod_get_value_cansleep(led_dat->cmd); 74 slow_level = gpiod_get_value_cansleep(led_dat->slow); 75 76 for (i = 0; i < led_dat->num_modes; i++) { 77 if (cmd_level == led_dat->modval[i].cmd_level && 78 slow_level == led_dat->modval[i].slow_level) { 79 *mode = led_dat->modval[i].mode; 80 ret = 0; 81 break; 82 } 83 } 84 85 return ret; 86 } 87 88 static void ns2_led_set_mode(struct ns2_led_data *led_dat, 89 enum ns2_led_modes mode) 90 { 91 int i; 92 bool found = false; 93 unsigned long flags; 94 95 for (i = 0; i < led_dat->num_modes; i++) 96 if (mode == led_dat->modval[i].mode) { 97 found = true; 98 break; 99 } 100 101 if (!found) 102 return; 103 104 write_lock_irqsave(&led_dat->rw_lock, flags); 105 106 if (!led_dat->can_sleep) { 107 gpiod_set_value(led_dat->cmd, 108 led_dat->modval[i].cmd_level); 109 gpiod_set_value(led_dat->slow, 110 led_dat->modval[i].slow_level); 111 goto exit_unlock; 112 } 113 114 gpiod_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level); 115 gpiod_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level); 116 117 exit_unlock: 118 write_unlock_irqrestore(&led_dat->rw_lock, flags); 119 } 120 121 static void ns2_led_set(struct led_classdev *led_cdev, 122 enum led_brightness value) 123 { 124 struct ns2_led_data *led_dat = 125 container_of(led_cdev, struct ns2_led_data, cdev); 126 enum ns2_led_modes mode; 127 128 if (value == LED_OFF) 129 mode = NS_V2_LED_OFF; 130 else if (led_dat->sata) 131 mode = NS_V2_LED_SATA; 132 else 133 mode = NS_V2_LED_ON; 134 135 ns2_led_set_mode(led_dat, mode); 136 } 137 138 static int ns2_led_set_blocking(struct led_classdev *led_cdev, 139 enum led_brightness value) 140 { 141 ns2_led_set(led_cdev, value); 142 return 0; 143 } 144 145 static ssize_t ns2_led_sata_store(struct device *dev, 146 struct device_attribute *attr, 147 const char *buff, size_t count) 148 { 149 struct led_classdev *led_cdev = dev_get_drvdata(dev); 150 struct ns2_led_data *led_dat = 151 container_of(led_cdev, struct ns2_led_data, cdev); 152 int ret; 153 unsigned long enable; 154 155 ret = kstrtoul(buff, 10, &enable); 156 if (ret < 0) 157 return ret; 158 159 enable = !!enable; 160 161 if (led_dat->sata == enable) 162 goto exit; 163 164 led_dat->sata = enable; 165 166 if (!led_get_brightness(led_cdev)) 167 goto exit; 168 169 if (enable) 170 ns2_led_set_mode(led_dat, NS_V2_LED_SATA); 171 else 172 ns2_led_set_mode(led_dat, NS_V2_LED_ON); 173 174 exit: 175 return count; 176 } 177 178 static ssize_t ns2_led_sata_show(struct device *dev, 179 struct device_attribute *attr, char *buf) 180 { 181 struct led_classdev *led_cdev = dev_get_drvdata(dev); 182 struct ns2_led_data *led_dat = 183 container_of(led_cdev, struct ns2_led_data, cdev); 184 185 return sprintf(buf, "%d\n", led_dat->sata); 186 } 187 188 static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 189 190 static struct attribute *ns2_led_attrs[] = { 191 &dev_attr_sata.attr, 192 NULL 193 }; 194 ATTRIBUTE_GROUPS(ns2_led); 195 196 static int 197 create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, 198 const struct ns2_led *template) 199 { 200 int ret; 201 enum ns2_led_modes mode; 202 203 rwlock_init(&led_dat->rw_lock); 204 205 led_dat->cdev.name = template->name; 206 led_dat->cdev.default_trigger = template->default_trigger; 207 led_dat->cdev.blink_set = NULL; 208 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 209 led_dat->cdev.groups = ns2_led_groups; 210 led_dat->cmd = template->cmd; 211 led_dat->slow = template->slow; 212 led_dat->can_sleep = gpiod_cansleep(led_dat->cmd) | 213 gpiod_cansleep(led_dat->slow); 214 if (led_dat->can_sleep) 215 led_dat->cdev.brightness_set_blocking = ns2_led_set_blocking; 216 else 217 led_dat->cdev.brightness_set = ns2_led_set; 218 led_dat->modval = template->modval; 219 led_dat->num_modes = template->num_modes; 220 221 ret = ns2_led_get_mode(led_dat, &mode); 222 if (ret < 0) 223 return ret; 224 225 /* Set LED initial state. */ 226 led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 227 led_dat->cdev.brightness = 228 (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 229 230 ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 231 if (ret < 0) 232 return ret; 233 234 return 0; 235 } 236 237 static void delete_ns2_led(struct ns2_led_data *led_dat) 238 { 239 led_classdev_unregister(&led_dat->cdev); 240 } 241 242 #ifdef CONFIG_OF_GPIO 243 /* 244 * Translate OpenFirmware node properties into platform_data. 245 */ 246 static int 247 ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata) 248 { 249 struct device_node *np = dev->of_node; 250 struct device_node *child; 251 struct ns2_led *led, *leds; 252 int ret, num_leds = 0; 253 254 num_leds = of_get_child_count(np); 255 if (!num_leds) 256 return -ENODEV; 257 258 leds = devm_kcalloc(dev, num_leds, sizeof(struct ns2_led), 259 GFP_KERNEL); 260 if (!leds) 261 return -ENOMEM; 262 263 led = leds; 264 for_each_child_of_node(np, child) { 265 const char *string; 266 int i, num_modes; 267 struct ns2_led_modval *modval; 268 struct gpio_desc *gd; 269 270 ret = of_property_read_string(child, "label", &string); 271 led->name = (ret == 0) ? string : child->name; 272 273 gd = gpiod_get_from_of_node(child, "cmd-gpio", 0, 274 GPIOD_ASIS, led->name); 275 if (IS_ERR(gd)) { 276 ret = PTR_ERR(gd); 277 goto err_node_put; 278 } 279 led->cmd = gd; 280 gd = gpiod_get_from_of_node(child, "slow-gpio", 0, 281 GPIOD_ASIS, led->name); 282 if (IS_ERR(gd)) { 283 ret = PTR_ERR(gd); 284 goto err_node_put; 285 } 286 led->slow = gd; 287 288 ret = of_property_read_string(child, "linux,default-trigger", 289 &string); 290 if (ret == 0) 291 led->default_trigger = string; 292 293 ret = of_property_count_u32_elems(child, "modes-map"); 294 if (ret < 0 || ret % 3) { 295 dev_err(dev, 296 "Missing or malformed modes-map property\n"); 297 ret = -EINVAL; 298 goto err_node_put; 299 } 300 301 num_modes = ret / 3; 302 modval = devm_kcalloc(dev, 303 num_modes, 304 sizeof(struct ns2_led_modval), 305 GFP_KERNEL); 306 if (!modval) { 307 ret = -ENOMEM; 308 goto err_node_put; 309 } 310 311 for (i = 0; i < num_modes; i++) { 312 of_property_read_u32_index(child, 313 "modes-map", 3 * i, 314 (u32 *) &modval[i].mode); 315 of_property_read_u32_index(child, 316 "modes-map", 3 * i + 1, 317 (u32 *) &modval[i].cmd_level); 318 of_property_read_u32_index(child, 319 "modes-map", 3 * i + 2, 320 (u32 *) &modval[i].slow_level); 321 } 322 323 led->num_modes = num_modes; 324 led->modval = modval; 325 326 led++; 327 } 328 329 pdata->leds = leds; 330 pdata->num_leds = num_leds; 331 332 return 0; 333 334 err_node_put: 335 of_node_put(child); 336 return ret; 337 } 338 339 static const struct of_device_id of_ns2_leds_match[] = { 340 { .compatible = "lacie,ns2-leds", }, 341 {}, 342 }; 343 MODULE_DEVICE_TABLE(of, of_ns2_leds_match); 344 #endif /* CONFIG_OF_GPIO */ 345 346 struct ns2_led_priv { 347 int num_leds; 348 struct ns2_led_data leds_data[]; 349 }; 350 351 static int ns2_led_probe(struct platform_device *pdev) 352 { 353 struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev); 354 struct ns2_led_priv *priv; 355 int i; 356 int ret; 357 358 #ifdef CONFIG_OF_GPIO 359 if (!pdata) { 360 pdata = devm_kzalloc(&pdev->dev, 361 sizeof(struct ns2_led_platform_data), 362 GFP_KERNEL); 363 if (!pdata) 364 return -ENOMEM; 365 366 ret = ns2_leds_get_of_pdata(&pdev->dev, pdata); 367 if (ret) 368 return ret; 369 } 370 #else 371 if (!pdata) 372 return -EINVAL; 373 #endif /* CONFIG_OF_GPIO */ 374 375 priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds_data, pdata->num_leds), GFP_KERNEL); 376 if (!priv) 377 return -ENOMEM; 378 priv->num_leds = pdata->num_leds; 379 380 for (i = 0; i < priv->num_leds; i++) { 381 ret = create_ns2_led(pdev, &priv->leds_data[i], 382 &pdata->leds[i]); 383 if (ret < 0) { 384 for (i = i - 1; i >= 0; i--) 385 delete_ns2_led(&priv->leds_data[i]); 386 return ret; 387 } 388 } 389 390 platform_set_drvdata(pdev, priv); 391 392 return 0; 393 } 394 395 static int ns2_led_remove(struct platform_device *pdev) 396 { 397 int i; 398 struct ns2_led_priv *priv; 399 400 priv = platform_get_drvdata(pdev); 401 402 for (i = 0; i < priv->num_leds; i++) 403 delete_ns2_led(&priv->leds_data[i]); 404 405 return 0; 406 } 407 408 static struct platform_driver ns2_led_driver = { 409 .probe = ns2_led_probe, 410 .remove = ns2_led_remove, 411 .driver = { 412 .name = "leds-ns2", 413 .of_match_table = of_match_ptr(of_ns2_leds_match), 414 }, 415 }; 416 417 module_platform_driver(ns2_led_driver); 418 419 MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 420 MODULE_DESCRIPTION("Network Space v2 LED driver"); 421 MODULE_LICENSE("GPL"); 422 MODULE_ALIAS("platform:leds-ns2"); 423