111efe71fSSimon Guinot /* 211efe71fSSimon Guinot * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED 311efe71fSSimon Guinot * 411efe71fSSimon Guinot * Copyright (C) 2010 LaCie 511efe71fSSimon Guinot * 611efe71fSSimon Guinot * Author: Simon Guinot <sguinot@lacie.com> 711efe71fSSimon Guinot * 811efe71fSSimon Guinot * Based on leds-gpio.c by Raphael Assenat <raph@8d.com> 911efe71fSSimon Guinot * 1011efe71fSSimon Guinot * This program is free software; you can redistribute it and/or modify 1111efe71fSSimon Guinot * it under the terms of the GNU General Public License as published by 1211efe71fSSimon Guinot * the Free Software Foundation; either version 2 of the License, or 1311efe71fSSimon Guinot * (at your option) any later version. 1411efe71fSSimon Guinot * 1511efe71fSSimon Guinot * This program is distributed in the hope that it will be useful, 1611efe71fSSimon Guinot * but WITHOUT ANY WARRANTY; without even the implied warranty of 1711efe71fSSimon Guinot * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1811efe71fSSimon Guinot * GNU General Public License for more details. 1911efe71fSSimon Guinot * 2011efe71fSSimon Guinot * You should have received a copy of the GNU General Public License 2111efe71fSSimon Guinot * along with this program; if not, write to the Free Software 2211efe71fSSimon Guinot * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 2311efe71fSSimon Guinot */ 2411efe71fSSimon Guinot 2511efe71fSSimon Guinot #include <linux/kernel.h> 2611efe71fSSimon Guinot #include <linux/init.h> 2711efe71fSSimon Guinot #include <linux/platform_device.h> 2811efe71fSSimon Guinot #include <linux/slab.h> 2911efe71fSSimon Guinot #include <linux/gpio.h> 3011efe71fSSimon Guinot #include <linux/leds.h> 3154f4dedbSPaul Gortmaker #include <linux/module.h> 32c02cecb9SArnd Bergmann #include <linux/platform_data/leds-kirkwood-ns2.h> 3372052fccSSimon Guinot #include <linux/of_gpio.h> 3411efe71fSSimon Guinot 3511efe71fSSimon Guinot /* 3611efe71fSSimon Guinot * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in 3711efe71fSSimon Guinot * relation with the SATA activity. This capability is exposed through the 3811efe71fSSimon Guinot * "sata" sysfs attribute. 3911efe71fSSimon Guinot * 4011efe71fSSimon Guinot * The following array detail the different LED registers and the combination 4111efe71fSSimon Guinot * of their possible values: 4211efe71fSSimon Guinot * 4311efe71fSSimon Guinot * cmd_led | slow_led | /SATA active | LED state 4411efe71fSSimon Guinot * | | | 4511efe71fSSimon Guinot * 1 | 0 | x | off 4611efe71fSSimon Guinot * - | 1 | x | on 4711efe71fSSimon Guinot * 0 | 0 | 1 | on 4811efe71fSSimon Guinot * 0 | 0 | 0 | blink (rate 300ms) 4911efe71fSSimon Guinot */ 5011efe71fSSimon Guinot 5111efe71fSSimon Guinot enum ns2_led_modes { 5211efe71fSSimon Guinot NS_V2_LED_OFF, 5311efe71fSSimon Guinot NS_V2_LED_ON, 5411efe71fSSimon Guinot NS_V2_LED_SATA, 5511efe71fSSimon Guinot }; 5611efe71fSSimon Guinot 5711efe71fSSimon Guinot struct ns2_led_mode_value { 5811efe71fSSimon Guinot enum ns2_led_modes mode; 5911efe71fSSimon Guinot int cmd_level; 6011efe71fSSimon Guinot int slow_level; 6111efe71fSSimon Guinot }; 6211efe71fSSimon Guinot 6311efe71fSSimon Guinot static struct ns2_led_mode_value ns2_led_modval[] = { 6411efe71fSSimon Guinot { NS_V2_LED_OFF , 1, 0 }, 6511efe71fSSimon Guinot { NS_V2_LED_ON , 0, 1 }, 6611efe71fSSimon Guinot { NS_V2_LED_ON , 1, 1 }, 6711efe71fSSimon Guinot { NS_V2_LED_SATA, 0, 0 }, 6811efe71fSSimon Guinot }; 6911efe71fSSimon Guinot 7011efe71fSSimon Guinot struct ns2_led_data { 7111efe71fSSimon Guinot struct led_classdev cdev; 7211efe71fSSimon Guinot unsigned cmd; 7311efe71fSSimon Guinot unsigned slow; 7411efe71fSSimon Guinot unsigned char sata; /* True when SATA mode active. */ 7511efe71fSSimon Guinot rwlock_t rw_lock; /* Lock GPIOs. */ 7611efe71fSSimon Guinot }; 7711efe71fSSimon Guinot 7811efe71fSSimon Guinot static int ns2_led_get_mode(struct ns2_led_data *led_dat, 7911efe71fSSimon Guinot enum ns2_led_modes *mode) 8011efe71fSSimon Guinot { 8111efe71fSSimon Guinot int i; 8211efe71fSSimon Guinot int ret = -EINVAL; 8311efe71fSSimon Guinot int cmd_level; 8411efe71fSSimon Guinot int slow_level; 8511efe71fSSimon Guinot 86f539dfedSSimon Guinot read_lock_irq(&led_dat->rw_lock); 8711efe71fSSimon Guinot 8811efe71fSSimon Guinot cmd_level = gpio_get_value(led_dat->cmd); 8911efe71fSSimon Guinot slow_level = gpio_get_value(led_dat->slow); 9011efe71fSSimon Guinot 9111efe71fSSimon Guinot for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 9211efe71fSSimon Guinot if (cmd_level == ns2_led_modval[i].cmd_level && 9311efe71fSSimon Guinot slow_level == ns2_led_modval[i].slow_level) { 9411efe71fSSimon Guinot *mode = ns2_led_modval[i].mode; 9511efe71fSSimon Guinot ret = 0; 9611efe71fSSimon Guinot break; 9711efe71fSSimon Guinot } 9811efe71fSSimon Guinot } 9911efe71fSSimon Guinot 100f539dfedSSimon Guinot read_unlock_irq(&led_dat->rw_lock); 10111efe71fSSimon Guinot 10211efe71fSSimon Guinot return ret; 10311efe71fSSimon Guinot } 10411efe71fSSimon Guinot 10511efe71fSSimon Guinot static void ns2_led_set_mode(struct ns2_led_data *led_dat, 10611efe71fSSimon Guinot enum ns2_led_modes mode) 10711efe71fSSimon Guinot { 10811efe71fSSimon Guinot int i; 109f539dfedSSimon Guinot unsigned long flags; 11011efe71fSSimon Guinot 111f539dfedSSimon Guinot write_lock_irqsave(&led_dat->rw_lock, flags); 11211efe71fSSimon Guinot 11311efe71fSSimon Guinot for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 11411efe71fSSimon Guinot if (mode == ns2_led_modval[i].mode) { 11511efe71fSSimon Guinot gpio_set_value(led_dat->cmd, 11611efe71fSSimon Guinot ns2_led_modval[i].cmd_level); 11711efe71fSSimon Guinot gpio_set_value(led_dat->slow, 11811efe71fSSimon Guinot ns2_led_modval[i].slow_level); 11911efe71fSSimon Guinot } 12011efe71fSSimon Guinot } 12111efe71fSSimon Guinot 122f539dfedSSimon Guinot write_unlock_irqrestore(&led_dat->rw_lock, flags); 12311efe71fSSimon Guinot } 12411efe71fSSimon Guinot 12511efe71fSSimon Guinot static void ns2_led_set(struct led_classdev *led_cdev, 12611efe71fSSimon Guinot enum led_brightness value) 12711efe71fSSimon Guinot { 12811efe71fSSimon Guinot struct ns2_led_data *led_dat = 12911efe71fSSimon Guinot container_of(led_cdev, struct ns2_led_data, cdev); 13011efe71fSSimon Guinot enum ns2_led_modes mode; 13111efe71fSSimon Guinot 13211efe71fSSimon Guinot if (value == LED_OFF) 13311efe71fSSimon Guinot mode = NS_V2_LED_OFF; 13411efe71fSSimon Guinot else if (led_dat->sata) 13511efe71fSSimon Guinot mode = NS_V2_LED_SATA; 13611efe71fSSimon Guinot else 13711efe71fSSimon Guinot mode = NS_V2_LED_ON; 13811efe71fSSimon Guinot 13911efe71fSSimon Guinot ns2_led_set_mode(led_dat, mode); 14011efe71fSSimon Guinot } 14111efe71fSSimon Guinot 14211efe71fSSimon Guinot static ssize_t ns2_led_sata_store(struct device *dev, 14311efe71fSSimon Guinot struct device_attribute *attr, 14411efe71fSSimon Guinot const char *buff, size_t count) 14511efe71fSSimon Guinot { 146e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 147e5971bbcSSimon Guinot struct ns2_led_data *led_dat = 148e5971bbcSSimon Guinot container_of(led_cdev, struct ns2_led_data, cdev); 14911efe71fSSimon Guinot int ret; 15011efe71fSSimon Guinot unsigned long enable; 15111efe71fSSimon Guinot enum ns2_led_modes mode; 15211efe71fSSimon Guinot 1533874350cSJingoo Han ret = kstrtoul(buff, 10, &enable); 15411efe71fSSimon Guinot if (ret < 0) 15511efe71fSSimon Guinot return ret; 15611efe71fSSimon Guinot 15711efe71fSSimon Guinot enable = !!enable; 15811efe71fSSimon Guinot 15911efe71fSSimon Guinot if (led_dat->sata == enable) 16011efe71fSSimon Guinot return count; 16111efe71fSSimon Guinot 16211efe71fSSimon Guinot ret = ns2_led_get_mode(led_dat, &mode); 16311efe71fSSimon Guinot if (ret < 0) 16411efe71fSSimon Guinot return ret; 16511efe71fSSimon Guinot 16611efe71fSSimon Guinot if (enable && mode == NS_V2_LED_ON) 16711efe71fSSimon Guinot ns2_led_set_mode(led_dat, NS_V2_LED_SATA); 16811efe71fSSimon Guinot if (!enable && mode == NS_V2_LED_SATA) 16911efe71fSSimon Guinot ns2_led_set_mode(led_dat, NS_V2_LED_ON); 17011efe71fSSimon Guinot 17111efe71fSSimon Guinot led_dat->sata = enable; 17211efe71fSSimon Guinot 17311efe71fSSimon Guinot return count; 17411efe71fSSimon Guinot } 17511efe71fSSimon Guinot 17611efe71fSSimon Guinot static ssize_t ns2_led_sata_show(struct device *dev, 17711efe71fSSimon Guinot struct device_attribute *attr, char *buf) 17811efe71fSSimon Guinot { 179e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 180e5971bbcSSimon Guinot struct ns2_led_data *led_dat = 181e5971bbcSSimon Guinot container_of(led_cdev, struct ns2_led_data, cdev); 18211efe71fSSimon Guinot 18311efe71fSSimon Guinot return sprintf(buf, "%d\n", led_dat->sata); 18411efe71fSSimon Guinot } 18511efe71fSSimon Guinot 18611efe71fSSimon Guinot static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 18711efe71fSSimon Guinot 18898ea1ea2SBill Pemberton static int 18911efe71fSSimon Guinot create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, 19011efe71fSSimon Guinot const struct ns2_led *template) 19111efe71fSSimon Guinot { 19211efe71fSSimon Guinot int ret; 19311efe71fSSimon Guinot enum ns2_led_modes mode; 19411efe71fSSimon Guinot 19504195823SSachin Kamat ret = devm_gpio_request_one(&pdev->dev, template->cmd, 1969d04cbaaSJingoo Han gpio_get_value(template->cmd) ? 1979d04cbaaSJingoo Han GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, 19831c3dc74SJingoo Han template->name); 19911efe71fSSimon Guinot if (ret) { 20011efe71fSSimon Guinot dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", 20111efe71fSSimon Guinot template->name); 20231c3dc74SJingoo Han return ret; 20311efe71fSSimon Guinot } 20411efe71fSSimon Guinot 20504195823SSachin Kamat ret = devm_gpio_request_one(&pdev->dev, template->slow, 2069d04cbaaSJingoo Han gpio_get_value(template->slow) ? 2079d04cbaaSJingoo Han GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, 20831c3dc74SJingoo Han template->name); 20911efe71fSSimon Guinot if (ret) { 21011efe71fSSimon Guinot dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", 21111efe71fSSimon Guinot template->name); 21204195823SSachin Kamat return ret; 21311efe71fSSimon Guinot } 21411efe71fSSimon Guinot 21511efe71fSSimon Guinot rwlock_init(&led_dat->rw_lock); 21611efe71fSSimon Guinot 21711efe71fSSimon Guinot led_dat->cdev.name = template->name; 21811efe71fSSimon Guinot led_dat->cdev.default_trigger = template->default_trigger; 21911efe71fSSimon Guinot led_dat->cdev.blink_set = NULL; 22011efe71fSSimon Guinot led_dat->cdev.brightness_set = ns2_led_set; 22111efe71fSSimon Guinot led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 22211efe71fSSimon Guinot led_dat->cmd = template->cmd; 22311efe71fSSimon Guinot led_dat->slow = template->slow; 22411efe71fSSimon Guinot 22511efe71fSSimon Guinot ret = ns2_led_get_mode(led_dat, &mode); 22611efe71fSSimon Guinot if (ret < 0) 22704195823SSachin Kamat return ret; 22811efe71fSSimon Guinot 22911efe71fSSimon Guinot /* Set LED initial state. */ 23011efe71fSSimon Guinot led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 23111efe71fSSimon Guinot led_dat->cdev.brightness = 23211efe71fSSimon Guinot (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 23311efe71fSSimon Guinot 23411efe71fSSimon Guinot ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 23511efe71fSSimon Guinot if (ret < 0) 23604195823SSachin Kamat return ret; 23711efe71fSSimon Guinot 23811efe71fSSimon Guinot ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); 23911efe71fSSimon Guinot if (ret < 0) 24011efe71fSSimon Guinot goto err_free_cdev; 24111efe71fSSimon Guinot 24211efe71fSSimon Guinot return 0; 24311efe71fSSimon Guinot 24411efe71fSSimon Guinot err_free_cdev: 24511efe71fSSimon Guinot led_classdev_unregister(&led_dat->cdev); 24611efe71fSSimon Guinot return ret; 24711efe71fSSimon Guinot } 24811efe71fSSimon Guinot 249b8cd742aSArnd Bergmann static void delete_ns2_led(struct ns2_led_data *led_dat) 25011efe71fSSimon Guinot { 25111efe71fSSimon Guinot device_remove_file(led_dat->cdev.dev, &dev_attr_sata); 25211efe71fSSimon Guinot led_classdev_unregister(&led_dat->cdev); 25311efe71fSSimon Guinot } 25411efe71fSSimon Guinot 25572052fccSSimon Guinot #ifdef CONFIG_OF_GPIO 25672052fccSSimon Guinot /* 25772052fccSSimon Guinot * Translate OpenFirmware node properties into platform_data. 25872052fccSSimon Guinot */ 259cf4af012SLinus Torvalds static int 26072052fccSSimon Guinot ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata) 26172052fccSSimon Guinot { 26272052fccSSimon Guinot struct device_node *np = dev->of_node; 26372052fccSSimon Guinot struct device_node *child; 26472052fccSSimon Guinot struct ns2_led *leds; 26572052fccSSimon Guinot int num_leds = 0; 26672052fccSSimon Guinot int i = 0; 26772052fccSSimon Guinot 26872052fccSSimon Guinot num_leds = of_get_child_count(np); 26972052fccSSimon Guinot if (!num_leds) 27072052fccSSimon Guinot return -ENODEV; 27172052fccSSimon Guinot 27272052fccSSimon Guinot leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led), 27372052fccSSimon Guinot GFP_KERNEL); 27472052fccSSimon Guinot if (!leds) 27572052fccSSimon Guinot return -ENOMEM; 27672052fccSSimon Guinot 27772052fccSSimon Guinot for_each_child_of_node(np, child) { 27872052fccSSimon Guinot const char *string; 27972052fccSSimon Guinot int ret; 28072052fccSSimon Guinot 28172052fccSSimon Guinot ret = of_get_named_gpio(child, "cmd-gpio", 0); 28272052fccSSimon Guinot if (ret < 0) 28372052fccSSimon Guinot return ret; 28472052fccSSimon Guinot leds[i].cmd = ret; 28572052fccSSimon Guinot ret = of_get_named_gpio(child, "slow-gpio", 0); 28672052fccSSimon Guinot if (ret < 0) 28772052fccSSimon Guinot return ret; 28872052fccSSimon Guinot leds[i].slow = ret; 28972052fccSSimon Guinot ret = of_property_read_string(child, "label", &string); 29072052fccSSimon Guinot leds[i].name = (ret == 0) ? string : child->name; 29172052fccSSimon Guinot ret = of_property_read_string(child, "linux,default-trigger", 29272052fccSSimon Guinot &string); 29372052fccSSimon Guinot if (ret == 0) 29472052fccSSimon Guinot leds[i].default_trigger = string; 29572052fccSSimon Guinot 29672052fccSSimon Guinot i++; 29772052fccSSimon Guinot } 29872052fccSSimon Guinot 29972052fccSSimon Guinot pdata->leds = leds; 30072052fccSSimon Guinot pdata->num_leds = num_leds; 30172052fccSSimon Guinot 30272052fccSSimon Guinot return 0; 30372052fccSSimon Guinot } 30472052fccSSimon Guinot 30572052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = { 30672052fccSSimon Guinot { .compatible = "lacie,ns2-leds", }, 30772052fccSSimon Guinot {}, 30872052fccSSimon Guinot }; 30972052fccSSimon Guinot #endif /* CONFIG_OF_GPIO */ 31072052fccSSimon Guinot 3113de1929bSSimon Guinot struct ns2_led_priv { 3123de1929bSSimon Guinot int num_leds; 3133de1929bSSimon Guinot struct ns2_led_data leds_data[]; 3143de1929bSSimon Guinot }; 3153de1929bSSimon Guinot 3163de1929bSSimon Guinot static inline int sizeof_ns2_led_priv(int num_leds) 3173de1929bSSimon Guinot { 3183de1929bSSimon Guinot return sizeof(struct ns2_led_priv) + 3193de1929bSSimon Guinot (sizeof(struct ns2_led_data) * num_leds); 3203de1929bSSimon Guinot } 3213de1929bSSimon Guinot 32298ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev) 32311efe71fSSimon Guinot { 32487aae1eaSJingoo Han struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev); 3253de1929bSSimon Guinot struct ns2_led_priv *priv; 32611efe71fSSimon Guinot int i; 32711efe71fSSimon Guinot int ret; 32811efe71fSSimon Guinot 32972052fccSSimon Guinot #ifdef CONFIG_OF_GPIO 33072052fccSSimon Guinot if (!pdata) { 33172052fccSSimon Guinot pdata = devm_kzalloc(&pdev->dev, 33272052fccSSimon Guinot sizeof(struct ns2_led_platform_data), 33372052fccSSimon Guinot GFP_KERNEL); 33472052fccSSimon Guinot if (!pdata) 33572052fccSSimon Guinot return -ENOMEM; 33672052fccSSimon Guinot 33772052fccSSimon Guinot ret = ns2_leds_get_of_pdata(&pdev->dev, pdata); 33872052fccSSimon Guinot if (ret) 33972052fccSSimon Guinot return ret; 34072052fccSSimon Guinot } 34172052fccSSimon Guinot #else 34211efe71fSSimon Guinot if (!pdata) 34311efe71fSSimon Guinot return -EINVAL; 34472052fccSSimon Guinot #endif /* CONFIG_OF_GPIO */ 34511efe71fSSimon Guinot 3463de1929bSSimon Guinot priv = devm_kzalloc(&pdev->dev, 3473de1929bSSimon Guinot sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL); 3483de1929bSSimon Guinot if (!priv) 34911efe71fSSimon Guinot return -ENOMEM; 3503de1929bSSimon Guinot priv->num_leds = pdata->num_leds; 35111efe71fSSimon Guinot 3523de1929bSSimon Guinot for (i = 0; i < priv->num_leds; i++) { 3533de1929bSSimon Guinot ret = create_ns2_led(pdev, &priv->leds_data[i], 3543de1929bSSimon Guinot &pdata->leds[i]); 355a209f766SBryan Wu if (ret < 0) { 356a209f766SBryan Wu for (i = i - 1; i >= 0; i--) 3573de1929bSSimon Guinot delete_ns2_led(&priv->leds_data[i]); 358a209f766SBryan Wu return ret; 359a209f766SBryan Wu } 36011efe71fSSimon Guinot } 36111efe71fSSimon Guinot 3623de1929bSSimon Guinot platform_set_drvdata(pdev, priv); 36311efe71fSSimon Guinot 36411efe71fSSimon Guinot return 0; 36511efe71fSSimon Guinot } 36611efe71fSSimon Guinot 367678e8a6bSBill Pemberton static int ns2_led_remove(struct platform_device *pdev) 36811efe71fSSimon Guinot { 36911efe71fSSimon Guinot int i; 3703de1929bSSimon Guinot struct ns2_led_priv *priv; 37111efe71fSSimon Guinot 3723de1929bSSimon Guinot priv = platform_get_drvdata(pdev); 37311efe71fSSimon Guinot 3743de1929bSSimon Guinot for (i = 0; i < priv->num_leds; i++) 3753de1929bSSimon Guinot delete_ns2_led(&priv->leds_data[i]); 37611efe71fSSimon Guinot 37711efe71fSSimon Guinot return 0; 37811efe71fSSimon Guinot } 37911efe71fSSimon Guinot 38011efe71fSSimon Guinot static struct platform_driver ns2_led_driver = { 38111efe71fSSimon Guinot .probe = ns2_led_probe, 382df07cf81SBill Pemberton .remove = ns2_led_remove, 38311efe71fSSimon Guinot .driver = { 38411efe71fSSimon Guinot .name = "leds-ns2", 38511efe71fSSimon Guinot .owner = THIS_MODULE, 38672052fccSSimon Guinot .of_match_table = of_match_ptr(of_ns2_leds_match), 38711efe71fSSimon Guinot }, 38811efe71fSSimon Guinot }; 38911efe71fSSimon Guinot 390892a8843SAxel Lin module_platform_driver(ns2_led_driver); 39111efe71fSSimon Guinot 39211efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 39311efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver"); 39411efe71fSSimon Guinot MODULE_LICENSE("GPL"); 395892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2"); 396