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> 3311efe71fSSimon Guinot 3411efe71fSSimon Guinot /* 3511efe71fSSimon Guinot * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in 3611efe71fSSimon Guinot * relation with the SATA activity. This capability is exposed through the 3711efe71fSSimon Guinot * "sata" sysfs attribute. 3811efe71fSSimon Guinot * 3911efe71fSSimon Guinot * The following array detail the different LED registers and the combination 4011efe71fSSimon Guinot * of their possible values: 4111efe71fSSimon Guinot * 4211efe71fSSimon Guinot * cmd_led | slow_led | /SATA active | LED state 4311efe71fSSimon Guinot * | | | 4411efe71fSSimon Guinot * 1 | 0 | x | off 4511efe71fSSimon Guinot * - | 1 | x | on 4611efe71fSSimon Guinot * 0 | 0 | 1 | on 4711efe71fSSimon Guinot * 0 | 0 | 0 | blink (rate 300ms) 4811efe71fSSimon Guinot */ 4911efe71fSSimon Guinot 5011efe71fSSimon Guinot enum ns2_led_modes { 5111efe71fSSimon Guinot NS_V2_LED_OFF, 5211efe71fSSimon Guinot NS_V2_LED_ON, 5311efe71fSSimon Guinot NS_V2_LED_SATA, 5411efe71fSSimon Guinot }; 5511efe71fSSimon Guinot 5611efe71fSSimon Guinot struct ns2_led_mode_value { 5711efe71fSSimon Guinot enum ns2_led_modes mode; 5811efe71fSSimon Guinot int cmd_level; 5911efe71fSSimon Guinot int slow_level; 6011efe71fSSimon Guinot }; 6111efe71fSSimon Guinot 6211efe71fSSimon Guinot static struct ns2_led_mode_value ns2_led_modval[] = { 6311efe71fSSimon Guinot { NS_V2_LED_OFF , 1, 0 }, 6411efe71fSSimon Guinot { NS_V2_LED_ON , 0, 1 }, 6511efe71fSSimon Guinot { NS_V2_LED_ON , 1, 1 }, 6611efe71fSSimon Guinot { NS_V2_LED_SATA, 0, 0 }, 6711efe71fSSimon Guinot }; 6811efe71fSSimon Guinot 6911efe71fSSimon Guinot struct ns2_led_data { 7011efe71fSSimon Guinot struct led_classdev cdev; 7111efe71fSSimon Guinot unsigned cmd; 7211efe71fSSimon Guinot unsigned slow; 7311efe71fSSimon Guinot unsigned char sata; /* True when SATA mode active. */ 7411efe71fSSimon Guinot rwlock_t rw_lock; /* Lock GPIOs. */ 7511efe71fSSimon Guinot }; 7611efe71fSSimon Guinot 7711efe71fSSimon Guinot static int ns2_led_get_mode(struct ns2_led_data *led_dat, 7811efe71fSSimon Guinot enum ns2_led_modes *mode) 7911efe71fSSimon Guinot { 8011efe71fSSimon Guinot int i; 8111efe71fSSimon Guinot int ret = -EINVAL; 8211efe71fSSimon Guinot int cmd_level; 8311efe71fSSimon Guinot int slow_level; 8411efe71fSSimon Guinot 85f539dfedSSimon Guinot read_lock_irq(&led_dat->rw_lock); 8611efe71fSSimon Guinot 8711efe71fSSimon Guinot cmd_level = gpio_get_value(led_dat->cmd); 8811efe71fSSimon Guinot slow_level = gpio_get_value(led_dat->slow); 8911efe71fSSimon Guinot 9011efe71fSSimon Guinot for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 9111efe71fSSimon Guinot if (cmd_level == ns2_led_modval[i].cmd_level && 9211efe71fSSimon Guinot slow_level == ns2_led_modval[i].slow_level) { 9311efe71fSSimon Guinot *mode = ns2_led_modval[i].mode; 9411efe71fSSimon Guinot ret = 0; 9511efe71fSSimon Guinot break; 9611efe71fSSimon Guinot } 9711efe71fSSimon Guinot } 9811efe71fSSimon Guinot 99f539dfedSSimon Guinot read_unlock_irq(&led_dat->rw_lock); 10011efe71fSSimon Guinot 10111efe71fSSimon Guinot return ret; 10211efe71fSSimon Guinot } 10311efe71fSSimon Guinot 10411efe71fSSimon Guinot static void ns2_led_set_mode(struct ns2_led_data *led_dat, 10511efe71fSSimon Guinot enum ns2_led_modes mode) 10611efe71fSSimon Guinot { 10711efe71fSSimon Guinot int i; 108f539dfedSSimon Guinot unsigned long flags; 10911efe71fSSimon Guinot 110f539dfedSSimon Guinot write_lock_irqsave(&led_dat->rw_lock, flags); 11111efe71fSSimon Guinot 11211efe71fSSimon Guinot for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 11311efe71fSSimon Guinot if (mode == ns2_led_modval[i].mode) { 11411efe71fSSimon Guinot gpio_set_value(led_dat->cmd, 11511efe71fSSimon Guinot ns2_led_modval[i].cmd_level); 11611efe71fSSimon Guinot gpio_set_value(led_dat->slow, 11711efe71fSSimon Guinot ns2_led_modval[i].slow_level); 11811efe71fSSimon Guinot } 11911efe71fSSimon Guinot } 12011efe71fSSimon Guinot 121f539dfedSSimon Guinot write_unlock_irqrestore(&led_dat->rw_lock, flags); 12211efe71fSSimon Guinot } 12311efe71fSSimon Guinot 12411efe71fSSimon Guinot static void ns2_led_set(struct led_classdev *led_cdev, 12511efe71fSSimon Guinot enum led_brightness value) 12611efe71fSSimon Guinot { 12711efe71fSSimon Guinot struct ns2_led_data *led_dat = 12811efe71fSSimon Guinot container_of(led_cdev, struct ns2_led_data, cdev); 12911efe71fSSimon Guinot enum ns2_led_modes mode; 13011efe71fSSimon Guinot 13111efe71fSSimon Guinot if (value == LED_OFF) 13211efe71fSSimon Guinot mode = NS_V2_LED_OFF; 13311efe71fSSimon Guinot else if (led_dat->sata) 13411efe71fSSimon Guinot mode = NS_V2_LED_SATA; 13511efe71fSSimon Guinot else 13611efe71fSSimon Guinot mode = NS_V2_LED_ON; 13711efe71fSSimon Guinot 13811efe71fSSimon Guinot ns2_led_set_mode(led_dat, mode); 13911efe71fSSimon Guinot } 14011efe71fSSimon Guinot 14111efe71fSSimon Guinot static ssize_t ns2_led_sata_store(struct device *dev, 14211efe71fSSimon Guinot struct device_attribute *attr, 14311efe71fSSimon Guinot const char *buff, size_t count) 14411efe71fSSimon Guinot { 145e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 146e5971bbcSSimon Guinot struct ns2_led_data *led_dat = 147e5971bbcSSimon Guinot container_of(led_cdev, struct ns2_led_data, cdev); 14811efe71fSSimon Guinot int ret; 14911efe71fSSimon Guinot unsigned long enable; 15011efe71fSSimon Guinot enum ns2_led_modes mode; 15111efe71fSSimon Guinot 15211efe71fSSimon Guinot ret = strict_strtoul(buff, 10, &enable); 15311efe71fSSimon Guinot if (ret < 0) 15411efe71fSSimon Guinot return ret; 15511efe71fSSimon Guinot 15611efe71fSSimon Guinot enable = !!enable; 15711efe71fSSimon Guinot 15811efe71fSSimon Guinot if (led_dat->sata == enable) 15911efe71fSSimon Guinot return count; 16011efe71fSSimon Guinot 16111efe71fSSimon Guinot ret = ns2_led_get_mode(led_dat, &mode); 16211efe71fSSimon Guinot if (ret < 0) 16311efe71fSSimon Guinot return ret; 16411efe71fSSimon Guinot 16511efe71fSSimon Guinot if (enable && mode == NS_V2_LED_ON) 16611efe71fSSimon Guinot ns2_led_set_mode(led_dat, NS_V2_LED_SATA); 16711efe71fSSimon Guinot if (!enable && mode == NS_V2_LED_SATA) 16811efe71fSSimon Guinot ns2_led_set_mode(led_dat, NS_V2_LED_ON); 16911efe71fSSimon Guinot 17011efe71fSSimon Guinot led_dat->sata = enable; 17111efe71fSSimon Guinot 17211efe71fSSimon Guinot return count; 17311efe71fSSimon Guinot } 17411efe71fSSimon Guinot 17511efe71fSSimon Guinot static ssize_t ns2_led_sata_show(struct device *dev, 17611efe71fSSimon Guinot struct device_attribute *attr, char *buf) 17711efe71fSSimon Guinot { 178e5971bbcSSimon Guinot struct led_classdev *led_cdev = dev_get_drvdata(dev); 179e5971bbcSSimon Guinot struct ns2_led_data *led_dat = 180e5971bbcSSimon Guinot container_of(led_cdev, struct ns2_led_data, cdev); 18111efe71fSSimon Guinot 18211efe71fSSimon Guinot return sprintf(buf, "%d\n", led_dat->sata); 18311efe71fSSimon Guinot } 18411efe71fSSimon Guinot 18511efe71fSSimon Guinot static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 18611efe71fSSimon Guinot 18711efe71fSSimon Guinot static int __devinit 18811efe71fSSimon Guinot create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, 18911efe71fSSimon Guinot const struct ns2_led *template) 19011efe71fSSimon Guinot { 19111efe71fSSimon Guinot int ret; 19211efe71fSSimon Guinot enum ns2_led_modes mode; 19311efe71fSSimon Guinot 19411efe71fSSimon Guinot ret = gpio_request(template->cmd, template->name); 19511efe71fSSimon Guinot if (ret == 0) { 19611efe71fSSimon Guinot ret = gpio_direction_output(template->cmd, 19711efe71fSSimon Guinot gpio_get_value(template->cmd)); 19811efe71fSSimon Guinot if (ret) 19911efe71fSSimon Guinot gpio_free(template->cmd); 20011efe71fSSimon Guinot } 20111efe71fSSimon Guinot if (ret) { 20211efe71fSSimon Guinot dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", 20311efe71fSSimon Guinot template->name); 20411efe71fSSimon Guinot } 20511efe71fSSimon Guinot 20611efe71fSSimon Guinot ret = gpio_request(template->slow, template->name); 20711efe71fSSimon Guinot if (ret == 0) { 20811efe71fSSimon Guinot ret = gpio_direction_output(template->slow, 20911efe71fSSimon Guinot gpio_get_value(template->slow)); 21011efe71fSSimon Guinot if (ret) 21111efe71fSSimon Guinot gpio_free(template->slow); 21211efe71fSSimon Guinot } 21311efe71fSSimon Guinot if (ret) { 21411efe71fSSimon Guinot dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", 21511efe71fSSimon Guinot template->name); 21611efe71fSSimon Guinot goto err_free_cmd; 21711efe71fSSimon Guinot } 21811efe71fSSimon Guinot 21911efe71fSSimon Guinot rwlock_init(&led_dat->rw_lock); 22011efe71fSSimon Guinot 22111efe71fSSimon Guinot led_dat->cdev.name = template->name; 22211efe71fSSimon Guinot led_dat->cdev.default_trigger = template->default_trigger; 22311efe71fSSimon Guinot led_dat->cdev.blink_set = NULL; 22411efe71fSSimon Guinot led_dat->cdev.brightness_set = ns2_led_set; 22511efe71fSSimon Guinot led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 22611efe71fSSimon Guinot led_dat->cmd = template->cmd; 22711efe71fSSimon Guinot led_dat->slow = template->slow; 22811efe71fSSimon Guinot 22911efe71fSSimon Guinot ret = ns2_led_get_mode(led_dat, &mode); 23011efe71fSSimon Guinot if (ret < 0) 23111efe71fSSimon Guinot goto err_free_slow; 23211efe71fSSimon Guinot 23311efe71fSSimon Guinot /* Set LED initial state. */ 23411efe71fSSimon Guinot led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 23511efe71fSSimon Guinot led_dat->cdev.brightness = 23611efe71fSSimon Guinot (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 23711efe71fSSimon Guinot 23811efe71fSSimon Guinot ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 23911efe71fSSimon Guinot if (ret < 0) 24011efe71fSSimon Guinot goto err_free_slow; 24111efe71fSSimon Guinot 24211efe71fSSimon Guinot ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); 24311efe71fSSimon Guinot if (ret < 0) 24411efe71fSSimon Guinot goto err_free_cdev; 24511efe71fSSimon Guinot 24611efe71fSSimon Guinot return 0; 24711efe71fSSimon Guinot 24811efe71fSSimon Guinot err_free_cdev: 24911efe71fSSimon Guinot led_classdev_unregister(&led_dat->cdev); 25011efe71fSSimon Guinot err_free_slow: 25111efe71fSSimon Guinot gpio_free(led_dat->slow); 25211efe71fSSimon Guinot err_free_cmd: 25311efe71fSSimon Guinot gpio_free(led_dat->cmd); 25411efe71fSSimon Guinot 25511efe71fSSimon Guinot return ret; 25611efe71fSSimon Guinot } 25711efe71fSSimon Guinot 258b8cd742aSArnd Bergmann static void delete_ns2_led(struct ns2_led_data *led_dat) 25911efe71fSSimon Guinot { 26011efe71fSSimon Guinot device_remove_file(led_dat->cdev.dev, &dev_attr_sata); 26111efe71fSSimon Guinot led_classdev_unregister(&led_dat->cdev); 26211efe71fSSimon Guinot gpio_free(led_dat->cmd); 26311efe71fSSimon Guinot gpio_free(led_dat->slow); 26411efe71fSSimon Guinot } 26511efe71fSSimon Guinot 26611efe71fSSimon Guinot static int __devinit ns2_led_probe(struct platform_device *pdev) 26711efe71fSSimon Guinot { 26811efe71fSSimon Guinot struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 26911efe71fSSimon Guinot struct ns2_led_data *leds_data; 27011efe71fSSimon Guinot int i; 27111efe71fSSimon Guinot int ret; 27211efe71fSSimon Guinot 27311efe71fSSimon Guinot if (!pdata) 27411efe71fSSimon Guinot return -EINVAL; 27511efe71fSSimon Guinot 276a209f766SBryan Wu leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) * 27711efe71fSSimon Guinot pdata->num_leds, GFP_KERNEL); 27811efe71fSSimon Guinot if (!leds_data) 27911efe71fSSimon Guinot return -ENOMEM; 28011efe71fSSimon Guinot 28111efe71fSSimon Guinot for (i = 0; i < pdata->num_leds; i++) { 28211efe71fSSimon Guinot ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); 283a209f766SBryan Wu if (ret < 0) { 284a209f766SBryan Wu for (i = i - 1; i >= 0; i--) 285a209f766SBryan Wu delete_ns2_led(&leds_data[i]); 286a209f766SBryan Wu return ret; 287a209f766SBryan Wu } 28811efe71fSSimon Guinot } 28911efe71fSSimon Guinot 29011efe71fSSimon Guinot platform_set_drvdata(pdev, leds_data); 29111efe71fSSimon Guinot 29211efe71fSSimon Guinot return 0; 29311efe71fSSimon Guinot } 29411efe71fSSimon Guinot 29511efe71fSSimon Guinot static int __devexit ns2_led_remove(struct platform_device *pdev) 29611efe71fSSimon Guinot { 29711efe71fSSimon Guinot int i; 29811efe71fSSimon Guinot struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 29911efe71fSSimon Guinot struct ns2_led_data *leds_data; 30011efe71fSSimon Guinot 30111efe71fSSimon Guinot leds_data = platform_get_drvdata(pdev); 30211efe71fSSimon Guinot 30311efe71fSSimon Guinot for (i = 0; i < pdata->num_leds; i++) 30411efe71fSSimon Guinot delete_ns2_led(&leds_data[i]); 30511efe71fSSimon Guinot 30611efe71fSSimon Guinot platform_set_drvdata(pdev, NULL); 30711efe71fSSimon Guinot 30811efe71fSSimon Guinot return 0; 30911efe71fSSimon Guinot } 31011efe71fSSimon Guinot 31111efe71fSSimon Guinot static struct platform_driver ns2_led_driver = { 31211efe71fSSimon Guinot .probe = ns2_led_probe, 313df07cf81SBill Pemberton .remove = ns2_led_remove, 31411efe71fSSimon Guinot .driver = { 31511efe71fSSimon Guinot .name = "leds-ns2", 31611efe71fSSimon Guinot .owner = THIS_MODULE, 31711efe71fSSimon Guinot }, 31811efe71fSSimon Guinot }; 31911efe71fSSimon Guinot 320892a8843SAxel Lin module_platform_driver(ns2_led_driver); 32111efe71fSSimon Guinot 32211efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 32311efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver"); 32411efe71fSSimon Guinot MODULE_LICENSE("GPL"); 325892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2"); 326