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