162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010 LaCie 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Simon Guinot <sguinot@lacie.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Based on leds-gpio.c by Raphael Assenat <raph@8d.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1662306a36Sopenharmony_ci#include <linux/leds.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/of.h> 1962306a36Sopenharmony_ci#include "leds.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cienum ns2_led_modes { 2262306a36Sopenharmony_ci NS_V2_LED_OFF, 2362306a36Sopenharmony_ci NS_V2_LED_ON, 2462306a36Sopenharmony_ci NS_V2_LED_SATA, 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* 2862306a36Sopenharmony_ci * If the size of this structure or types of its members is changed, 2962306a36Sopenharmony_ci * the filling of array modval in function ns2_led_register must be changed 3062306a36Sopenharmony_ci * accordingly. 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistruct ns2_led_modval { 3362306a36Sopenharmony_ci u32 mode; 3462306a36Sopenharmony_ci u32 cmd_level; 3562306a36Sopenharmony_ci u32 slow_level; 3662306a36Sopenharmony_ci} __packed; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* 3962306a36Sopenharmony_ci * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED 4062306a36Sopenharmony_ci * modes are available: off, on and SATA activity blinking. The LED modes are 4162306a36Sopenharmony_ci * controlled through two GPIOs (command and slow): each combination of values 4262306a36Sopenharmony_ci * for the command/slow GPIOs corresponds to a LED mode. 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct ns2_led { 4662306a36Sopenharmony_ci struct led_classdev cdev; 4762306a36Sopenharmony_ci struct gpio_desc *cmd; 4862306a36Sopenharmony_ci struct gpio_desc *slow; 4962306a36Sopenharmony_ci bool can_sleep; 5062306a36Sopenharmony_ci unsigned char sata; /* True when SATA mode active. */ 5162306a36Sopenharmony_ci rwlock_t rw_lock; /* Lock GPIOs. */ 5262306a36Sopenharmony_ci int num_modes; 5362306a36Sopenharmony_ci struct ns2_led_modval *modval; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci int i; 5962306a36Sopenharmony_ci int cmd_level; 6062306a36Sopenharmony_ci int slow_level; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci cmd_level = gpiod_get_value_cansleep(led->cmd); 6362306a36Sopenharmony_ci slow_level = gpiod_get_value_cansleep(led->slow); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci for (i = 0; i < led->num_modes; i++) { 6662306a36Sopenharmony_ci if (cmd_level == led->modval[i].cmd_level && 6762306a36Sopenharmony_ci slow_level == led->modval[i].slow_level) { 6862306a36Sopenharmony_ci *mode = led->modval[i].mode; 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return -EINVAL; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci int i; 7962306a36Sopenharmony_ci unsigned long flags; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci for (i = 0; i < led->num_modes; i++) 8262306a36Sopenharmony_ci if (mode == led->modval[i].mode) 8362306a36Sopenharmony_ci break; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (i == led->num_modes) 8662306a36Sopenharmony_ci return; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci write_lock_irqsave(&led->rw_lock, flags); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if (!led->can_sleep) { 9162306a36Sopenharmony_ci gpiod_set_value(led->cmd, led->modval[i].cmd_level); 9262306a36Sopenharmony_ci gpiod_set_value(led->slow, led->modval[i].slow_level); 9362306a36Sopenharmony_ci goto exit_unlock; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level); 9762306a36Sopenharmony_ci gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ciexit_unlock: 10062306a36Sopenharmony_ci write_unlock_irqrestore(&led->rw_lock, flags); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic void ns2_led_set(struct led_classdev *led_cdev, 10462306a36Sopenharmony_ci enum led_brightness value) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 10762306a36Sopenharmony_ci enum ns2_led_modes mode; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (value == LED_OFF) 11062306a36Sopenharmony_ci mode = NS_V2_LED_OFF; 11162306a36Sopenharmony_ci else if (led->sata) 11262306a36Sopenharmony_ci mode = NS_V2_LED_SATA; 11362306a36Sopenharmony_ci else 11462306a36Sopenharmony_ci mode = NS_V2_LED_ON; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci ns2_led_set_mode(led, mode); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int ns2_led_set_blocking(struct led_classdev *led_cdev, 12062306a36Sopenharmony_ci enum led_brightness value) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci ns2_led_set(led_cdev, value); 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic ssize_t ns2_led_sata_store(struct device *dev, 12762306a36Sopenharmony_ci struct device_attribute *attr, 12862306a36Sopenharmony_ci const char *buff, size_t count) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 13162306a36Sopenharmony_ci struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 13262306a36Sopenharmony_ci int ret; 13362306a36Sopenharmony_ci unsigned long enable; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci ret = kstrtoul(buff, 10, &enable); 13662306a36Sopenharmony_ci if (ret < 0) 13762306a36Sopenharmony_ci return ret; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci enable = !!enable; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (led->sata == enable) 14262306a36Sopenharmony_ci goto exit; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci led->sata = enable; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci if (!led_get_brightness(led_cdev)) 14762306a36Sopenharmony_ci goto exit; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if (enable) 15062306a36Sopenharmony_ci ns2_led_set_mode(led, NS_V2_LED_SATA); 15162306a36Sopenharmony_ci else 15262306a36Sopenharmony_ci ns2_led_set_mode(led, NS_V2_LED_ON); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ciexit: 15562306a36Sopenharmony_ci return count; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic ssize_t ns2_led_sata_show(struct device *dev, 15962306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 16262306a36Sopenharmony_ci struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci return sprintf(buf, "%d\n", led->sata); 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic struct attribute *ns2_led_attrs[] = { 17062306a36Sopenharmony_ci &dev_attr_sata.attr, 17162306a36Sopenharmony_ci NULL 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ciATTRIBUTE_GROUPS(ns2_led); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic int ns2_led_register(struct device *dev, struct fwnode_handle *node, 17662306a36Sopenharmony_ci struct ns2_led *led) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct led_init_data init_data = {}; 17962306a36Sopenharmony_ci struct ns2_led_modval *modval; 18062306a36Sopenharmony_ci enum ns2_led_modes mode; 18162306a36Sopenharmony_ci int nmodes, ret; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci led->cmd = devm_fwnode_gpiod_get_index(dev, node, "cmd", 0, GPIOD_ASIS, 18462306a36Sopenharmony_ci fwnode_get_name(node)); 18562306a36Sopenharmony_ci if (IS_ERR(led->cmd)) 18662306a36Sopenharmony_ci return PTR_ERR(led->cmd); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci led->slow = devm_fwnode_gpiod_get_index(dev, node, "slow", 0, 18962306a36Sopenharmony_ci GPIOD_ASIS, 19062306a36Sopenharmony_ci fwnode_get_name(node)); 19162306a36Sopenharmony_ci if (IS_ERR(led->slow)) 19262306a36Sopenharmony_ci return PTR_ERR(led->slow); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci ret = fwnode_property_count_u32(node, "modes-map"); 19562306a36Sopenharmony_ci if (ret < 0 || ret % 3) { 19662306a36Sopenharmony_ci dev_err(dev, "Missing or malformed modes-map for %pfw\n", node); 19762306a36Sopenharmony_ci return -EINVAL; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci nmodes = ret / 3; 20162306a36Sopenharmony_ci modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL); 20262306a36Sopenharmony_ci if (!modval) 20362306a36Sopenharmony_ci return -ENOMEM; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci fwnode_property_read_u32_array(node, "modes-map", (void *)modval, 20662306a36Sopenharmony_ci nmodes * 3); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci rwlock_init(&led->rw_lock); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci led->cdev.blink_set = NULL; 21162306a36Sopenharmony_ci led->cdev.flags |= LED_CORE_SUSPENDRESUME; 21262306a36Sopenharmony_ci led->cdev.groups = ns2_led_groups; 21362306a36Sopenharmony_ci led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow); 21462306a36Sopenharmony_ci if (led->can_sleep) 21562306a36Sopenharmony_ci led->cdev.brightness_set_blocking = ns2_led_set_blocking; 21662306a36Sopenharmony_ci else 21762306a36Sopenharmony_ci led->cdev.brightness_set = ns2_led_set; 21862306a36Sopenharmony_ci led->num_modes = nmodes; 21962306a36Sopenharmony_ci led->modval = modval; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ret = ns2_led_get_mode(led, &mode); 22262306a36Sopenharmony_ci if (ret < 0) 22362306a36Sopenharmony_ci return ret; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* Set LED initial state. */ 22662306a36Sopenharmony_ci led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 22762306a36Sopenharmony_ci led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci init_data.fwnode = node; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); 23262306a36Sopenharmony_ci if (ret) 23362306a36Sopenharmony_ci dev_err(dev, "Failed to register LED for node %pfw\n", node); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return ret; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic int ns2_led_probe(struct platform_device *pdev) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci struct device *dev = &pdev->dev; 24162306a36Sopenharmony_ci struct fwnode_handle *child; 24262306a36Sopenharmony_ci struct ns2_led *leds; 24362306a36Sopenharmony_ci int count; 24462306a36Sopenharmony_ci int ret; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci count = device_get_child_node_count(dev); 24762306a36Sopenharmony_ci if (!count) 24862306a36Sopenharmony_ci return -ENODEV; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci leds = devm_kcalloc(dev, count, sizeof(*leds), GFP_KERNEL); 25162306a36Sopenharmony_ci if (!leds) 25262306a36Sopenharmony_ci return -ENOMEM; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci device_for_each_child_node(dev, child) { 25562306a36Sopenharmony_ci ret = ns2_led_register(dev, child, leds++); 25662306a36Sopenharmony_ci if (ret) { 25762306a36Sopenharmony_ci fwnode_handle_put(child); 25862306a36Sopenharmony_ci return ret; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci return 0; 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic const struct of_device_id of_ns2_leds_match[] = { 26662306a36Sopenharmony_ci { .compatible = "lacie,ns2-leds", }, 26762306a36Sopenharmony_ci {}, 26862306a36Sopenharmony_ci}; 26962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_ns2_leds_match); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic struct platform_driver ns2_led_driver = { 27262306a36Sopenharmony_ci .probe = ns2_led_probe, 27362306a36Sopenharmony_ci .driver = { 27462306a36Sopenharmony_ci .name = "leds-ns2", 27562306a36Sopenharmony_ci .of_match_table = of_ns2_leds_match, 27662306a36Sopenharmony_ci }, 27762306a36Sopenharmony_ci}; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cimodule_platform_driver(ns2_led_driver); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ciMODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 28262306a36Sopenharmony_ciMODULE_DESCRIPTION("Network Space v2 LED driver"); 28362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 28462306a36Sopenharmony_ciMODULE_ALIAS("platform:leds-ns2"); 285