linux/drivers/leds/leds-ns2.c
<<
>>
Prefs
   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 <linux/module.h>
  32#include <linux/platform_data/leds-kirkwood-ns2.h>
  33
  34/*
  35 * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
  36 * relation with the SATA activity. This capability is exposed through the
  37 * "sata" sysfs attribute.
  38 *
  39 * The following array detail the different LED registers and the combination
  40 * of their possible values:
  41 *
  42 *  cmd_led   |  slow_led  | /SATA active | LED state
  43 *            |            |              |
  44 *     1      |     0      |      x       |  off
  45 *     -      |     1      |      x       |  on
  46 *     0      |     0      |      1       |  on
  47 *     0      |     0      |      0       |  blink (rate 300ms)
  48 */
  49
  50enum ns2_led_modes {
  51        NS_V2_LED_OFF,
  52        NS_V2_LED_ON,
  53        NS_V2_LED_SATA,
  54};
  55
  56struct ns2_led_mode_value {
  57        enum ns2_led_modes      mode;
  58        int                     cmd_level;
  59        int                     slow_level;
  60};
  61
  62static struct ns2_led_mode_value ns2_led_modval[] = {
  63        { NS_V2_LED_OFF , 1, 0 },
  64        { NS_V2_LED_ON  , 0, 1 },
  65        { NS_V2_LED_ON  , 1, 1 },
  66        { NS_V2_LED_SATA, 0, 0 },
  67};
  68
  69struct ns2_led_data {
  70        struct led_classdev     cdev;
  71        unsigned                cmd;
  72        unsigned                slow;
  73        unsigned char           sata; /* True when SATA mode active. */
  74        rwlock_t                rw_lock; /* Lock GPIOs. */
  75};
  76
  77static int ns2_led_get_mode(struct ns2_led_data *led_dat,
  78                            enum ns2_led_modes *mode)
  79{
  80        int i;
  81        int ret = -EINVAL;
  82        int cmd_level;
  83        int slow_level;
  84
  85        read_lock_irq(&led_dat->rw_lock);
  86
  87        cmd_level = gpio_get_value(led_dat->cmd);
  88        slow_level = gpio_get_value(led_dat->slow);
  89
  90        for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
  91                if (cmd_level == ns2_led_modval[i].cmd_level &&
  92                    slow_level == ns2_led_modval[i].slow_level) {
  93                        *mode = ns2_led_modval[i].mode;
  94                        ret = 0;
  95                        break;
  96                }
  97        }
  98
  99        read_unlock_irq(&led_dat->rw_lock);
 100
 101        return ret;
 102}
 103
 104static void ns2_led_set_mode(struct ns2_led_data *led_dat,
 105                             enum ns2_led_modes mode)
 106{
 107        int i;
 108        unsigned long flags;
 109
 110        write_lock_irqsave(&led_dat->rw_lock, flags);
 111
 112        for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
 113                if (mode == ns2_led_modval[i].mode) {
 114                        gpio_set_value(led_dat->cmd,
 115                                       ns2_led_modval[i].cmd_level);
 116                        gpio_set_value(led_dat->slow,
 117                                       ns2_led_modval[i].slow_level);
 118                }
 119        }
 120
 121        write_unlock_irqrestore(&led_dat->rw_lock, flags);
 122}
 123
 124static void ns2_led_set(struct led_classdev *led_cdev,
 125                        enum led_brightness value)
 126{
 127        struct ns2_led_data *led_dat =
 128                container_of(led_cdev, struct ns2_led_data, cdev);
 129        enum ns2_led_modes mode;
 130
 131        if (value == LED_OFF)
 132                mode = NS_V2_LED_OFF;
 133        else if (led_dat->sata)
 134                mode = NS_V2_LED_SATA;
 135        else
 136                mode = NS_V2_LED_ON;
 137
 138        ns2_led_set_mode(led_dat, mode);
 139}
 140
 141static ssize_t ns2_led_sata_store(struct device *dev,
 142                                  struct device_attribute *attr,
 143                                  const char *buff, size_t count)
 144{
 145        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 146        struct ns2_led_data *led_dat =
 147                container_of(led_cdev, struct ns2_led_data, cdev);
 148        int ret;
 149        unsigned long enable;
 150        enum ns2_led_modes mode;
 151
 152        ret = strict_strtoul(buff, 10, &enable);
 153        if (ret < 0)
 154                return ret;
 155
 156        enable = !!enable;
 157
 158        if (led_dat->sata == enable)
 159                return count;
 160
 161        ret = ns2_led_get_mode(led_dat, &mode);
 162        if (ret < 0)
 163                return ret;
 164
 165        if (enable && mode == NS_V2_LED_ON)
 166                ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
 167        if (!enable && mode == NS_V2_LED_SATA)
 168                ns2_led_set_mode(led_dat, NS_V2_LED_ON);
 169
 170        led_dat->sata = enable;
 171
 172        return count;
 173}
 174
 175static ssize_t ns2_led_sata_show(struct device *dev,
 176                                 struct device_attribute *attr, char *buf)
 177{
 178        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 179        struct ns2_led_data *led_dat =
 180                container_of(led_cdev, struct ns2_led_data, cdev);
 181
 182        return sprintf(buf, "%d\n", led_dat->sata);
 183}
 184
 185static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
 186
 187static int __devinit
 188create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
 189               const struct ns2_led *template)
 190{
 191        int ret;
 192        enum ns2_led_modes mode;
 193
 194        ret = gpio_request(template->cmd, template->name);
 195        if (ret == 0) {
 196                ret = gpio_direction_output(template->cmd,
 197                                            gpio_get_value(template->cmd));
 198                if (ret)
 199                        gpio_free(template->cmd);
 200        }
 201        if (ret) {
 202                dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
 203                        template->name);
 204        }
 205
 206        ret = gpio_request(template->slow, template->name);
 207        if (ret == 0) {
 208                ret = gpio_direction_output(template->slow,
 209                                            gpio_get_value(template->slow));
 210                if (ret)
 211                        gpio_free(template->slow);
 212        }
 213        if (ret) {
 214                dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
 215                        template->name);
 216                goto err_free_cmd;
 217        }
 218
 219        rwlock_init(&led_dat->rw_lock);
 220
 221        led_dat->cdev.name = template->name;
 222        led_dat->cdev.default_trigger = template->default_trigger;
 223        led_dat->cdev.blink_set = NULL;
 224        led_dat->cdev.brightness_set = ns2_led_set;
 225        led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
 226        led_dat->cmd = template->cmd;
 227        led_dat->slow = template->slow;
 228
 229        ret = ns2_led_get_mode(led_dat, &mode);
 230        if (ret < 0)
 231                goto err_free_slow;
 232
 233        /* Set LED initial state. */
 234        led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
 235        led_dat->cdev.brightness =
 236                (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
 237
 238        ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
 239        if (ret < 0)
 240                goto err_free_slow;
 241
 242        ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
 243        if (ret < 0)
 244                goto err_free_cdev;
 245
 246        return 0;
 247
 248err_free_cdev:
 249        led_classdev_unregister(&led_dat->cdev);
 250err_free_slow:
 251        gpio_free(led_dat->slow);
 252err_free_cmd:
 253        gpio_free(led_dat->cmd);
 254
 255        return ret;
 256}
 257
 258static void delete_ns2_led(struct ns2_led_data *led_dat)
 259{
 260        device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
 261        led_classdev_unregister(&led_dat->cdev);
 262        gpio_free(led_dat->cmd);
 263        gpio_free(led_dat->slow);
 264}
 265
 266static int __devinit ns2_led_probe(struct platform_device *pdev)
 267{
 268        struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
 269        struct ns2_led_data *leds_data;
 270        int i;
 271        int ret;
 272
 273        if (!pdata)
 274                return -EINVAL;
 275
 276        leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) *
 277                            pdata->num_leds, GFP_KERNEL);
 278        if (!leds_data)
 279                return -ENOMEM;
 280
 281        for (i = 0; i < pdata->num_leds; i++) {
 282                ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]);
 283                if (ret < 0) {
 284                        for (i = i - 1; i >= 0; i--)
 285                                delete_ns2_led(&leds_data[i]);
 286                        return ret;
 287                }
 288        }
 289
 290        platform_set_drvdata(pdev, leds_data);
 291
 292        return 0;
 293}
 294
 295static int __devexit ns2_led_remove(struct platform_device *pdev)
 296{
 297        int i;
 298        struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
 299        struct ns2_led_data *leds_data;
 300
 301        leds_data = platform_get_drvdata(pdev);
 302
 303        for (i = 0; i < pdata->num_leds; i++)
 304                delete_ns2_led(&leds_data[i]);
 305
 306        platform_set_drvdata(pdev, NULL);
 307
 308        return 0;
 309}
 310
 311static struct platform_driver ns2_led_driver = {
 312        .probe          = ns2_led_probe,
 313        .remove         = __devexit_p(ns2_led_remove),
 314        .driver         = {
 315                .name   = "leds-ns2",
 316                .owner  = THIS_MODULE,
 317        },
 318};
 319
 320module_platform_driver(ns2_led_driver);
 321
 322MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
 323MODULE_DESCRIPTION("Network Space v2 LED driver");
 324MODULE_LICENSE("GPL");
 325MODULE_ALIAS("platform:leds-ns2");
 326
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.