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 15311efe71fSSimon Guinot ret = strict_strtoul(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 19511efe71fSSimon Guinot ret = gpio_request(template->cmd, template->name); 19611efe71fSSimon Guinot if (ret == 0) { 19711efe71fSSimon Guinot ret = gpio_direction_output(template->cmd, 19811efe71fSSimon Guinot gpio_get_value(template->cmd)); 19911efe71fSSimon Guinot if (ret) 20011efe71fSSimon Guinot gpio_free(template->cmd); 20111efe71fSSimon Guinot } 20211efe71fSSimon Guinot if (ret) { 20311efe71fSSimon Guinot dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", 20411efe71fSSimon Guinot template->name); 20511efe71fSSimon Guinot } 20611efe71fSSimon Guinot 20711efe71fSSimon Guinot ret = gpio_request(template->slow, template->name); 20811efe71fSSimon Guinot if (ret == 0) { 20911efe71fSSimon Guinot ret = gpio_direction_output(template->slow, 21011efe71fSSimon Guinot gpio_get_value(template->slow)); 21111efe71fSSimon Guinot if (ret) 21211efe71fSSimon Guinot gpio_free(template->slow); 21311efe71fSSimon Guinot } 21411efe71fSSimon Guinot if (ret) { 21511efe71fSSimon Guinot dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", 21611efe71fSSimon Guinot template->name); 21711efe71fSSimon Guinot goto err_free_cmd; 21811efe71fSSimon Guinot } 21911efe71fSSimon Guinot 22011efe71fSSimon Guinot rwlock_init(&led_dat->rw_lock); 22111efe71fSSimon Guinot 22211efe71fSSimon Guinot led_dat->cdev.name = template->name; 22311efe71fSSimon Guinot led_dat->cdev.default_trigger = template->default_trigger; 22411efe71fSSimon Guinot led_dat->cdev.blink_set = NULL; 22511efe71fSSimon Guinot led_dat->cdev.brightness_set = ns2_led_set; 22611efe71fSSimon Guinot led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 22711efe71fSSimon Guinot led_dat->cmd = template->cmd; 22811efe71fSSimon Guinot led_dat->slow = template->slow; 22911efe71fSSimon Guinot 23011efe71fSSimon Guinot ret = ns2_led_get_mode(led_dat, &mode); 23111efe71fSSimon Guinot if (ret < 0) 23211efe71fSSimon Guinot goto err_free_slow; 23311efe71fSSimon Guinot 23411efe71fSSimon Guinot /* Set LED initial state. */ 23511efe71fSSimon Guinot led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 23611efe71fSSimon Guinot led_dat->cdev.brightness = 23711efe71fSSimon Guinot (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 23811efe71fSSimon Guinot 23911efe71fSSimon Guinot ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 24011efe71fSSimon Guinot if (ret < 0) 24111efe71fSSimon Guinot goto err_free_slow; 24211efe71fSSimon Guinot 24311efe71fSSimon Guinot ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); 24411efe71fSSimon Guinot if (ret < 0) 24511efe71fSSimon Guinot goto err_free_cdev; 24611efe71fSSimon Guinot 24711efe71fSSimon Guinot return 0; 24811efe71fSSimon Guinot 24911efe71fSSimon Guinot err_free_cdev: 25011efe71fSSimon Guinot led_classdev_unregister(&led_dat->cdev); 25111efe71fSSimon Guinot err_free_slow: 25211efe71fSSimon Guinot gpio_free(led_dat->slow); 25311efe71fSSimon Guinot err_free_cmd: 25411efe71fSSimon Guinot gpio_free(led_dat->cmd); 25511efe71fSSimon Guinot 25611efe71fSSimon Guinot return ret; 25711efe71fSSimon Guinot } 25811efe71fSSimon Guinot 259b8cd742aSArnd Bergmann static void delete_ns2_led(struct ns2_led_data *led_dat) 26011efe71fSSimon Guinot { 26111efe71fSSimon Guinot device_remove_file(led_dat->cdev.dev, &dev_attr_sata); 26211efe71fSSimon Guinot led_classdev_unregister(&led_dat->cdev); 26311efe71fSSimon Guinot gpio_free(led_dat->cmd); 26411efe71fSSimon Guinot gpio_free(led_dat->slow); 26511efe71fSSimon Guinot } 26611efe71fSSimon Guinot 26772052fccSSimon Guinot #ifdef CONFIG_OF_GPIO 26872052fccSSimon Guinot /* 26972052fccSSimon Guinot * Translate OpenFirmware node properties into platform_data. 27072052fccSSimon Guinot */ 271cf4af012SLinus Torvalds static int 27272052fccSSimon Guinot ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata) 27372052fccSSimon Guinot { 27472052fccSSimon Guinot struct device_node *np = dev->of_node; 27572052fccSSimon Guinot struct device_node *child; 27672052fccSSimon Guinot struct ns2_led *leds; 27772052fccSSimon Guinot int num_leds = 0; 27872052fccSSimon Guinot int i = 0; 27972052fccSSimon Guinot 28072052fccSSimon Guinot num_leds = of_get_child_count(np); 28172052fccSSimon Guinot if (!num_leds) 28272052fccSSimon Guinot return -ENODEV; 28372052fccSSimon Guinot 28472052fccSSimon Guinot leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led), 28572052fccSSimon Guinot GFP_KERNEL); 28672052fccSSimon Guinot if (!leds) 28772052fccSSimon Guinot return -ENOMEM; 28872052fccSSimon Guinot 28972052fccSSimon Guinot for_each_child_of_node(np, child) { 29072052fccSSimon Guinot const char *string; 29172052fccSSimon Guinot int ret; 29272052fccSSimon Guinot 29372052fccSSimon Guinot ret = of_get_named_gpio(child, "cmd-gpio", 0); 29472052fccSSimon Guinot if (ret < 0) 29572052fccSSimon Guinot return ret; 29672052fccSSimon Guinot leds[i].cmd = ret; 29772052fccSSimon Guinot ret = of_get_named_gpio(child, "slow-gpio", 0); 29872052fccSSimon Guinot if (ret < 0) 29972052fccSSimon Guinot return ret; 30072052fccSSimon Guinot leds[i].slow = ret; 30172052fccSSimon Guinot ret = of_property_read_string(child, "label", &string); 30272052fccSSimon Guinot leds[i].name = (ret == 0) ? string : child->name; 30372052fccSSimon Guinot ret = of_property_read_string(child, "linux,default-trigger", 30472052fccSSimon Guinot &string); 30572052fccSSimon Guinot if (ret == 0) 30672052fccSSimon Guinot leds[i].default_trigger = string; 30772052fccSSimon Guinot 30872052fccSSimon Guinot i++; 30972052fccSSimon Guinot } 31072052fccSSimon Guinot 31172052fccSSimon Guinot pdata->leds = leds; 31272052fccSSimon Guinot pdata->num_leds = num_leds; 31372052fccSSimon Guinot 31472052fccSSimon Guinot return 0; 31572052fccSSimon Guinot } 31672052fccSSimon Guinot 31772052fccSSimon Guinot static const struct of_device_id of_ns2_leds_match[] = { 31872052fccSSimon Guinot { .compatible = "lacie,ns2-leds", }, 31972052fccSSimon Guinot {}, 32072052fccSSimon Guinot }; 32172052fccSSimon Guinot #endif /* CONFIG_OF_GPIO */ 32272052fccSSimon Guinot 32398ea1ea2SBill Pemberton static int ns2_led_probe(struct platform_device *pdev) 32411efe71fSSimon Guinot { 32511efe71fSSimon Guinot struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 32611efe71fSSimon Guinot struct ns2_led_data *leds_data; 32711efe71fSSimon Guinot int i; 32811efe71fSSimon Guinot int ret; 32911efe71fSSimon Guinot 33072052fccSSimon Guinot #ifdef CONFIG_OF_GPIO 33172052fccSSimon Guinot if (!pdata) { 33272052fccSSimon Guinot pdata = devm_kzalloc(&pdev->dev, 33372052fccSSimon Guinot sizeof(struct ns2_led_platform_data), 33472052fccSSimon Guinot GFP_KERNEL); 33572052fccSSimon Guinot if (!pdata) 33672052fccSSimon Guinot return -ENOMEM; 33772052fccSSimon Guinot 33872052fccSSimon Guinot ret = ns2_leds_get_of_pdata(&pdev->dev, pdata); 33972052fccSSimon Guinot if (ret) 34072052fccSSimon Guinot return ret; 34172052fccSSimon Guinot } 34272052fccSSimon Guinot #else 34311efe71fSSimon Guinot if (!pdata) 34411efe71fSSimon Guinot return -EINVAL; 34572052fccSSimon Guinot #endif /* CONFIG_OF_GPIO */ 34611efe71fSSimon Guinot 347a209f766SBryan Wu leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) * 34811efe71fSSimon Guinot pdata->num_leds, GFP_KERNEL); 34911efe71fSSimon Guinot if (!leds_data) 35011efe71fSSimon Guinot return -ENOMEM; 35111efe71fSSimon Guinot 35211efe71fSSimon Guinot for (i = 0; i < pdata->num_leds; i++) { 35311efe71fSSimon Guinot ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); 354a209f766SBryan Wu if (ret < 0) { 355a209f766SBryan Wu for (i = i - 1; i >= 0; i--) 356a209f766SBryan Wu delete_ns2_led(&leds_data[i]); 357a209f766SBryan Wu return ret; 358a209f766SBryan Wu } 35911efe71fSSimon Guinot } 36011efe71fSSimon Guinot 36111efe71fSSimon Guinot platform_set_drvdata(pdev, leds_data); 36211efe71fSSimon Guinot 36311efe71fSSimon Guinot return 0; 36411efe71fSSimon Guinot } 36511efe71fSSimon Guinot 366678e8a6bSBill Pemberton static int ns2_led_remove(struct platform_device *pdev) 36711efe71fSSimon Guinot { 36811efe71fSSimon Guinot int i; 36911efe71fSSimon Guinot struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 37011efe71fSSimon Guinot struct ns2_led_data *leds_data; 37111efe71fSSimon Guinot 37211efe71fSSimon Guinot leds_data = platform_get_drvdata(pdev); 37311efe71fSSimon Guinot 37411efe71fSSimon Guinot for (i = 0; i < pdata->num_leds; i++) 37511efe71fSSimon Guinot delete_ns2_led(&leds_data[i]); 37611efe71fSSimon Guinot 37711efe71fSSimon Guinot platform_set_drvdata(pdev, NULL); 37811efe71fSSimon Guinot 37911efe71fSSimon Guinot return 0; 38011efe71fSSimon Guinot } 38111efe71fSSimon Guinot 38211efe71fSSimon Guinot static struct platform_driver ns2_led_driver = { 38311efe71fSSimon Guinot .probe = ns2_led_probe, 384df07cf81SBill Pemberton .remove = ns2_led_remove, 38511efe71fSSimon Guinot .driver = { 38611efe71fSSimon Guinot .name = "leds-ns2", 38711efe71fSSimon Guinot .owner = THIS_MODULE, 38872052fccSSimon Guinot .of_match_table = of_match_ptr(of_ns2_leds_match), 38911efe71fSSimon Guinot }, 39011efe71fSSimon Guinot }; 39111efe71fSSimon Guinot 392892a8843SAxel Lin module_platform_driver(ns2_led_driver); 39311efe71fSSimon Guinot 39411efe71fSSimon Guinot MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 39511efe71fSSimon Guinot MODULE_DESCRIPTION("Network Space v2 LED driver"); 39611efe71fSSimon Guinot MODULE_LICENSE("GPL"); 397892a8843SAxel Lin MODULE_ALIAS("platform:leds-ns2"); 398