Commit 11efe71f6564e59ba9acfc636ac6f423e059dc69

Authored by Simon Guinot
Committed by Nicolas Pitre
1 parent b6a044ff57

leds: add LED driver for Network Space v2 LEDs

This patch add a LED class driver for the dual-GPIO LEDs found on the
Network Space v2 board (and parents). This include Internet Space v2,
Network Space (Max) v2 and d2 Network v2 boards.

This dual-GPIO LED is wired to a CPLD and can blink in relation with the
SATA activity. The driver expose this capability through a "sata" sysfs
attribute.

Signed-off-by: Simon Guinot <sguinot@lacie.com>
Signed-off-by: Nicolas Pitre <nico@fluxnic.net>

Showing 4 changed files with 374 additions and 0 deletions Side-by-side Diff

arch/arm/mach-kirkwood/include/mach/leds-ns2.h
  1 +/*
  2 + * arch/arm/mach-kirkwood/include/mach/leds-ns2.h
  3 + *
  4 + * Platform data structure for Network Space v2 LED driver
  5 + *
  6 + * This file is licensed under the terms of the GNU General Public
  7 + * License version 2. This program is licensed "as is" without any
  8 + * warranty of any kind, whether express or implied.
  9 + */
  10 +
  11 +#ifndef __MACH_LEDS_NS2_H
  12 +#define __MACH_LEDS_NS2_H
  13 +
  14 +struct ns2_led {
  15 + const char *name;
  16 + const char *default_trigger;
  17 + unsigned cmd;
  18 + unsigned slow;
  19 +};
  20 +
  21 +struct ns2_led_platform_data {
  22 + int num_leds;
  23 + struct ns2_led *leds;
  24 +};
  25 +
  26 +#endif /* __MACH_LEDS_NS2_H */
drivers/leds/Kconfig
... ... @@ -302,6 +302,15 @@
302 302 This option enable support for on-chip LED drivers found
303 303 on Freescale Semiconductor MC13783 PMIC.
304 304  
  305 +config LEDS_NS2
  306 + tristate "LED support for Network Space v2 GPIO LEDs"
  307 + depends on MACH_NETSPACE_V2 || MACH_INETSPACE_V2 || MACH_NETSPACE_MAX_V2
  308 + default y
  309 + help
  310 + This option enable support for the dual-GPIO LED found on the
  311 + Network Space v2 board (and parents). This include Internet Space v2,
  312 + Network Space (Max) v2 and d2 Network v2 boards.
  313 +
305 314 config LEDS_TRIGGERS
306 315 bool "LED Trigger support"
307 316 help
drivers/leds/Makefile
... ... @@ -37,6 +37,7 @@
37 37 obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
38 38 obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o
39 39 obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
  40 +obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
40 41  
41 42 # LED SPI Drivers
42 43 obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
drivers/leds/leds-ns2.c
  1 +/*
  2 + * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
  3 + *
  4 + * Copyright (C) 2010 LaCie
  5 + *
  6 + * Author: Simon Guinot <sguinot@lacie.com>
  7 + *
  8 + * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
  9 + *
  10 + * This program is free software; you can redistribute it and/or modify
  11 + * it under the terms of the GNU General Public License as published by
  12 + * the Free Software Foundation; either version 2 of the License, or
  13 + * (at your option) any later version.
  14 + *
  15 + * This program is distributed in the hope that it will be useful,
  16 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 + * GNU General Public License for more details.
  19 + *
  20 + * You should have received a copy of the GNU General Public License
  21 + * along with this program; if not, write to the Free Software
  22 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  23 + */
  24 +
  25 +#include <linux/kernel.h>
  26 +#include <linux/init.h>
  27 +#include <linux/platform_device.h>
  28 +#include <linux/slab.h>
  29 +#include <linux/gpio.h>
  30 +#include <linux/leds.h>
  31 +#include <mach/leds-ns2.h>
  32 +
  33 +/*
  34 + * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
  35 + * relation with the SATA activity. This capability is exposed through the
  36 + * "sata" sysfs attribute.
  37 + *
  38 + * The following array detail the different LED registers and the combination
  39 + * of their possible values:
  40 + *
  41 + * cmd_led | slow_led | /SATA active | LED state
  42 + * | | |
  43 + * 1 | 0 | x | off
  44 + * - | 1 | x | on
  45 + * 0 | 0 | 1 | on
  46 + * 0 | 0 | 0 | blink (rate 300ms)
  47 + */
  48 +
  49 +enum ns2_led_modes {
  50 + NS_V2_LED_OFF,
  51 + NS_V2_LED_ON,
  52 + NS_V2_LED_SATA,
  53 +};
  54 +
  55 +struct ns2_led_mode_value {
  56 + enum ns2_led_modes mode;
  57 + int cmd_level;
  58 + int slow_level;
  59 +};
  60 +
  61 +static struct ns2_led_mode_value ns2_led_modval[] = {
  62 + { NS_V2_LED_OFF , 1, 0 },
  63 + { NS_V2_LED_ON , 0, 1 },
  64 + { NS_V2_LED_ON , 1, 1 },
  65 + { NS_V2_LED_SATA, 0, 0 },
  66 +};
  67 +
  68 +struct ns2_led_data {
  69 + struct led_classdev cdev;
  70 + unsigned cmd;
  71 + unsigned slow;
  72 + unsigned char sata; /* True when SATA mode active. */
  73 + rwlock_t rw_lock; /* Lock GPIOs. */
  74 +};
  75 +
  76 +static int ns2_led_get_mode(struct ns2_led_data *led_dat,
  77 + enum ns2_led_modes *mode)
  78 +{
  79 + int i;
  80 + int ret = -EINVAL;
  81 + int cmd_level;
  82 + int slow_level;
  83 +
  84 + read_lock(&led_dat->rw_lock);
  85 +
  86 + cmd_level = gpio_get_value(led_dat->cmd);
  87 + slow_level = gpio_get_value(led_dat->slow);
  88 +
  89 + for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
  90 + if (cmd_level == ns2_led_modval[i].cmd_level &&
  91 + slow_level == ns2_led_modval[i].slow_level) {
  92 + *mode = ns2_led_modval[i].mode;
  93 + ret = 0;
  94 + break;
  95 + }
  96 + }
  97 +
  98 + read_unlock(&led_dat->rw_lock);
  99 +
  100 + return ret;
  101 +}
  102 +
  103 +static void ns2_led_set_mode(struct ns2_led_data *led_dat,
  104 + enum ns2_led_modes mode)
  105 +{
  106 + int i;
  107 +
  108 + write_lock(&led_dat->rw_lock);
  109 +
  110 + for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
  111 + if (mode == ns2_led_modval[i].mode) {
  112 + gpio_set_value(led_dat->cmd,
  113 + ns2_led_modval[i].cmd_level);
  114 + gpio_set_value(led_dat->slow,
  115 + ns2_led_modval[i].slow_level);
  116 + }
  117 + }
  118 +
  119 + write_unlock(&led_dat->rw_lock);
  120 +}
  121 +
  122 +static void ns2_led_set(struct led_classdev *led_cdev,
  123 + enum led_brightness value)
  124 +{
  125 + struct ns2_led_data *led_dat =
  126 + container_of(led_cdev, struct ns2_led_data, cdev);
  127 + enum ns2_led_modes mode;
  128 +
  129 + if (value == LED_OFF)
  130 + mode = NS_V2_LED_OFF;
  131 + else if (led_dat->sata)
  132 + mode = NS_V2_LED_SATA;
  133 + else
  134 + mode = NS_V2_LED_ON;
  135 +
  136 + ns2_led_set_mode(led_dat, mode);
  137 +}
  138 +
  139 +static ssize_t ns2_led_sata_store(struct device *dev,
  140 + struct device_attribute *attr,
  141 + const char *buff, size_t count)
  142 +{
  143 + int ret;
  144 + unsigned long enable;
  145 + enum ns2_led_modes mode;
  146 + struct ns2_led_data *led_dat = dev_get_drvdata(dev);
  147 +
  148 + ret = strict_strtoul(buff, 10, &enable);
  149 + if (ret < 0)
  150 + return ret;
  151 +
  152 + enable = !!enable;
  153 +
  154 + if (led_dat->sata == enable)
  155 + return count;
  156 +
  157 + ret = ns2_led_get_mode(led_dat, &mode);
  158 + if (ret < 0)
  159 + return ret;
  160 +
  161 + if (enable && mode == NS_V2_LED_ON)
  162 + ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
  163 + if (!enable && mode == NS_V2_LED_SATA)
  164 + ns2_led_set_mode(led_dat, NS_V2_LED_ON);
  165 +
  166 + led_dat->sata = enable;
  167 +
  168 + return count;
  169 +}
  170 +
  171 +static ssize_t ns2_led_sata_show(struct device *dev,
  172 + struct device_attribute *attr, char *buf)
  173 +{
  174 + struct ns2_led_data *led_dat = dev_get_drvdata(dev);
  175 +
  176 + return sprintf(buf, "%d\n", led_dat->sata);
  177 +}
  178 +
  179 +static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
  180 +
  181 +static int __devinit
  182 +create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
  183 + const struct ns2_led *template)
  184 +{
  185 + int ret;
  186 + enum ns2_led_modes mode;
  187 +
  188 + ret = gpio_request(template->cmd, template->name);
  189 + if (ret == 0) {
  190 + ret = gpio_direction_output(template->cmd,
  191 + gpio_get_value(template->cmd));
  192 + if (ret)
  193 + gpio_free(template->cmd);
  194 + }
  195 + if (ret) {
  196 + dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
  197 + template->name);
  198 + }
  199 +
  200 + ret = gpio_request(template->slow, template->name);
  201 + if (ret == 0) {
  202 + ret = gpio_direction_output(template->slow,
  203 + gpio_get_value(template->slow));
  204 + if (ret)
  205 + gpio_free(template->slow);
  206 + }
  207 + if (ret) {
  208 + dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
  209 + template->name);
  210 + goto err_free_cmd;
  211 + }
  212 +
  213 + rwlock_init(&led_dat->rw_lock);
  214 +
  215 + led_dat->cdev.name = template->name;
  216 + led_dat->cdev.default_trigger = template->default_trigger;
  217 + led_dat->cdev.blink_set = NULL;
  218 + led_dat->cdev.brightness_set = ns2_led_set;
  219 + led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
  220 + led_dat->cmd = template->cmd;
  221 + led_dat->slow = template->slow;
  222 +
  223 + ret = ns2_led_get_mode(led_dat, &mode);
  224 + if (ret < 0)
  225 + goto err_free_slow;
  226 +
  227 + /* Set LED initial state. */
  228 + led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
  229 + led_dat->cdev.brightness =
  230 + (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
  231 +
  232 + ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
  233 + if (ret < 0)
  234 + goto err_free_slow;
  235 +
  236 + dev_set_drvdata(led_dat->cdev.dev, led_dat);
  237 + ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
  238 + if (ret < 0)
  239 + goto err_free_cdev;
  240 +
  241 + return 0;
  242 +
  243 +err_free_cdev:
  244 + led_classdev_unregister(&led_dat->cdev);
  245 +err_free_slow:
  246 + gpio_free(led_dat->slow);
  247 +err_free_cmd:
  248 + gpio_free(led_dat->cmd);
  249 +
  250 + return ret;
  251 +}
  252 +
  253 +static void __devexit delete_ns2_led(struct ns2_led_data *led_dat)
  254 +{
  255 + device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
  256 + led_classdev_unregister(&led_dat->cdev);
  257 + gpio_free(led_dat->cmd);
  258 + gpio_free(led_dat->slow);
  259 +}
  260 +
  261 +static int __devinit ns2_led_probe(struct platform_device *pdev)
  262 +{
  263 + struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
  264 + struct ns2_led_data *leds_data;
  265 + int i;
  266 + int ret;
  267 +
  268 + if (!pdata)
  269 + return -EINVAL;
  270 +
  271 + leds_data = kzalloc(sizeof(struct ns2_led_data) *
  272 + pdata->num_leds, GFP_KERNEL);
  273 + if (!leds_data)
  274 + return -ENOMEM;
  275 +
  276 + for (i = 0; i < pdata->num_leds; i++) {
  277 + ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]);
  278 + if (ret < 0)
  279 + goto err;
  280 +
  281 + }
  282 +
  283 + platform_set_drvdata(pdev, leds_data);
  284 +
  285 + return 0;
  286 +
  287 +err:
  288 + for (i = i - 1; i >= 0; i--)
  289 + delete_ns2_led(&leds_data[i]);
  290 +
  291 + kfree(leds_data);
  292 +
  293 + return ret;
  294 +}
  295 +
  296 +static int __devexit ns2_led_remove(struct platform_device *pdev)
  297 +{
  298 + int i;
  299 + struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
  300 + struct ns2_led_data *leds_data;
  301 +
  302 + leds_data = platform_get_drvdata(pdev);
  303 +
  304 + for (i = 0; i < pdata->num_leds; i++)
  305 + delete_ns2_led(&leds_data[i]);
  306 +
  307 + kfree(leds_data);
  308 + platform_set_drvdata(pdev, NULL);
  309 +
  310 + return 0;
  311 +}
  312 +
  313 +static struct platform_driver ns2_led_driver = {
  314 + .probe = ns2_led_probe,
  315 + .remove = __devexit_p(ns2_led_remove),
  316 + .driver = {
  317 + .name = "leds-ns2",
  318 + .owner = THIS_MODULE,
  319 + },
  320 +};
  321 +MODULE_ALIAS("platform:leds-ns2");
  322 +
  323 +static int __init ns2_led_init(void)
  324 +{
  325 + return platform_driver_register(&ns2_led_driver);
  326 +}
  327 +
  328 +static void __exit ns2_led_exit(void)
  329 +{
  330 + platform_driver_unregister(&ns2_led_driver);
  331 +}
  332 +
  333 +module_init(ns2_led_init);
  334 +module_exit(ns2_led_exit);
  335 +
  336 +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
  337 +MODULE_DESCRIPTION("Network Space v2 LED driver");
  338 +MODULE_LICENSE("GPL");