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#include <linux/of_gpio.h>
  34
  35/*
  36 * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
  37 * relation with the SATA activity. This capability is exposed through the
  38 * "sata" sysfs attribute.
  39 *
  40 * The following array detail the different LED registers and the combination
  41 * of their possible values:
  42 *
  43 *  cmd_led   |  slow_led  | /SATA active | LED state
  44 *            |            |              |
  45 *     1      |     0      |      x       |  off
  46 *     -      |     1      |      x       |  on
  47 *     0      |     0      |      1       |  on
  48 *     0      |     0      |      0       |  blink (rate 300ms)
  49 */
  50
  51enum ns2_led_modes {
  52        NS_V2_LED_OFF,
  53        NS_V2_LED_ON,
  54        NS_V2_LED_SATA,
  55};
  56
  57struct ns2_led_mode_value {
  58        enum ns2_led_modes      mode;
  59        int                     cmd_level;
  60        int                     slow_level;
  61};
  62
  63static struct ns2_led_mode_value ns2_led_modval[] = {
  64        { NS_V2_LED_OFF , 1, 0 },
  65        { NS_V2_LED_ON  , 0, 1 },
  66        { NS_V2_LED_ON  , 1, 1 },
  67        { NS_V2_LED_SATA, 0, 0 },
  68};
  69
  70struct ns2_led_data {
  71        struct led_classdev     cdev;
  72        unsigned                cmd;
  73        unsigned                slow;
  74        unsigned char           sata; /* True when SATA mode active. */
  75        rwlock_t                rw_lock; /* Lock GPIOs. */
  76};
  77
  78static int ns2_led_get_mode(struct ns2_led_data *led_dat,
  79                            enum ns2_led_modes *mode)
  80{
  81        int i;
  82        int ret = -EINVAL;
  83        int cmd_level;
  84        int slow_level;
  85
  86        read_lock_irq(&led_dat->rw_lock);
  87
  88        cmd_level = gpio_get_value(led_dat->cmd);
  89        slow_level = gpio_get_value(led_dat->slow);
  90
  91        for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
  92                if (cmd_level == ns2_led_modval[i].cmd_level &&
  93                    slow_level == ns2_led_modval[i].slow_level) {
  94                        *mode = ns2_led_modval[i].mode;
  95                        ret = 0;
  96                        break;
  97                }
  98        }
  99
 100        read_unlock_irq(&led_dat->rw_lock);
 101
 102        return ret;
 103}
 104
 105static void ns2_led_set_mode(struct ns2_led_data *led_dat,
 106                             enum ns2_led_modes mode)
 107{
 108        int i;
 109        unsigned long flags;
 110
 111        write_lock_irqsave(&led_dat->rw_lock, flags);
 112
 113        for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
 114                if (mode == ns2_led_modval[i].mode) {
 115                        gpio_set_value(led_dat->cmd,
 116                                       ns2_led_modval[i].cmd_level);
 117                        gpio_set_value(led_dat->slow,
 118                                       ns2_led_modval[i].slow_level);
 119                }
 120        }
 121
 122        write_unlock_irqrestore(&led_dat->rw_lock, flags);
 123}
 124
 125static void ns2_led_set(struct led_classdev *led_cdev,
 126                        enum led_brightness value)
 127{
 128        struct ns2_led_data *led_dat =
 129                container_of(led_cdev, struct ns2_led_data, cdev);
 130        enum ns2_led_modes mode;
 131
 132        if (value == LED_OFF)
 133                mode = NS_V2_LED_OFF;
 134        else if (led_dat->sata)
 135                mode = NS_V2_LED_SATA;
 136        else
 137                mode = NS_V2_LED_ON;
 138
 139        ns2_led_set_mode(led_dat, mode);
 140}
 141
 142static ssize_t ns2_led_sata_store(struct device *dev,
 143                                  struct device_attribute *attr,
 144                                  const char *buff, size_t count)
 145{
 146        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 147        struct ns2_led_data *led_dat =
 148                container_of(led_cdev, struct ns2_led_data, cdev);
 149        int ret;
 150        unsigned long enable;
 151        enum ns2_led_modes mode;
 152
 153        ret = kstrtoul(buff, 10, &enable);
 154        if (ret < 0)
 155                return ret;
 156
 157        enable = !!enable;
 158
 159        if (led_dat->sata == enable)
 160                return count;
 161
 162        ret = ns2_led_get_mode(led_dat, &mode);
 163        if (ret < 0)
 164                return ret;
 165
 166        if (enable && mode == NS_V2_LED_ON)
 167                ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
 168        if (!enable && mode == NS_V2_LED_SATA)
 169                ns2_led_set_mode(led_dat, NS_V2_LED_ON);
 170
 171        led_dat->sata = enable;
 172
 173        return count;
 174}
 175
 176static ssize_t ns2_led_sata_show(struct device *dev,
 177                                 struct device_attribute *attr, char *buf)
 178{
 179        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 180        struct ns2_led_data *led_dat =
 181                container_of(led_cdev, struct ns2_led_data, cdev);
 182
 183        return sprintf(buf, "%d\n", led_dat->sata);
 184}
 185
 186static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
 187
 188static int
 189create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
 190               const struct ns2_led *template)
 191{
 192        int ret;
 193        enum ns2_led_modes mode;
 194
 195        ret = devm_gpio_request_one(&pdev->dev, template->cmd,
 196                        GPIOF_DIR_OUT | gpio_get_value(template->cmd),
 197                        template->name);
 198        if (ret) {
 199                dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
 200                        template->name);
 201                return ret;
 202        }
 203
 204        ret = devm_gpio_request_one(&pdev->dev, template->slow,
 205                        GPIOF_DIR_OUT | gpio_get_value(template->slow),
 206                        template->name);
 207        if (ret) {
 208                dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
 209                        template->name);
 210                return ret;
 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                return ret;
 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                return ret;
 235
 236        ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
 237        if (ret < 0)
 238                goto err_free_cdev;
 239
 240        return 0;
 241
 242err_free_cdev:
 243        led_classdev_unregister(&led_dat->cdev);
 244        return ret;
 245}
 246
 247static void delete_ns2_led(struct ns2_led_data *led_dat)
 248{
 249        device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
 250        led_classdev_unregister(&led_dat->cdev);
 251}
 252
 253#ifdef CONFIG_OF_GPIO
 254/*
 255 * Translate OpenFirmware node properties into platform_data.
 256 */
 257static int
 258ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
 259{
 260        struct device_node *np = dev->of_node;
 261        struct device_node *child;
 262        struct ns2_led *leds;
 263        int num_leds = 0;
 264        int i = 0;
 265
 266        num_leds = of_get_child_count(np);
 267        if (!num_leds)
 268                return -ENODEV;
 269
 270        leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led),
 271                            GFP_KERNEL);
 272        if (!leds)
 273                return -ENOMEM;
 274
 275        for_each_child_of_node(np, child) {
 276                const char *string;
 277                int ret;
 278
 279                ret = of_get_named_gpio(child, "cmd-gpio", 0);
 280                if (ret < 0)
 281                        return ret;
 282                leds[i].cmd = ret;
 283                ret = of_get_named_gpio(child, "slow-gpio", 0);
 284                if (ret < 0)
 285                        return ret;
 286                leds[i].slow = ret;
 287                ret = of_property_read_string(child, "label", &string);
 288                leds[i].name = (ret == 0) ? string : child->name;
 289                ret = of_property_read_string(child, "linux,default-trigger",
 290                                              &string);
 291                if (ret == 0)
 292                        leds[i].default_trigger = string;
 293
 294                i++;
 295        }
 296
 297        pdata->leds = leds;
 298        pdata->num_leds = num_leds;
 299
 300        return 0;
 301}
 302
 303static const struct of_device_id of_ns2_leds_match[] = {
 304        { .compatible = "lacie,ns2-leds", },
 305        {},
 306};
 307#endif /* CONFIG_OF_GPIO */
 308
 309static int ns2_led_probe(struct platform_device *pdev)
 310{
 311        struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
 312        struct ns2_led_data *leds_data;
 313        int i;
 314        int ret;
 315
 316#ifdef CONFIG_OF_GPIO
 317        if (!pdata) {
 318                pdata = devm_kzalloc(&pdev->dev,
 319                                     sizeof(struct ns2_led_platform_data),
 320                                     GFP_KERNEL);
 321                if (!pdata)
 322                        return -ENOMEM;
 323
 324                ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
 325                if (ret)
 326                        return ret;
 327        }
 328#else
 329        if (!pdata)
 330                return -EINVAL;
 331#endif /* CONFIG_OF_GPIO */
 332
 333        leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) *
 334                                 pdata->num_leds, GFP_KERNEL);
 335        if (!leds_data)
 336                return -ENOMEM;
 337
 338        for (i = 0; i < pdata->num_leds; i++) {
 339                ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]);
 340                if (ret < 0) {
 341                        for (i = i - 1; i >= 0; i--)
 342                                delete_ns2_led(&leds_data[i]);
 343                        return ret;
 344                }
 345        }
 346
 347        platform_set_drvdata(pdev, leds_data);
 348
 349        return 0;
 350}
 351
 352static int ns2_led_remove(struct platform_device *pdev)
 353{
 354        int i;
 355        struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
 356        struct ns2_led_data *leds_data;
 357
 358        leds_data = platform_get_drvdata(pdev);
 359
 360        for (i = 0; i < pdata->num_leds; i++)
 361                delete_ns2_led(&leds_data[i]);
 362
 363        platform_set_drvdata(pdev, NULL);
 364
 365        return 0;
 366}
 367
 368static struct platform_driver ns2_led_driver = {
 369        .probe          = ns2_led_probe,
 370        .remove         = ns2_led_remove,
 371        .driver         = {
 372                .name           = "leds-ns2",
 373                .owner          = THIS_MODULE,
 374                .of_match_table = of_match_ptr(of_ns2_leds_match),
 375        },
 376};
 377
 378module_platform_driver(ns2_led_driver);
 379
 380MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
 381MODULE_DESCRIPTION("Network Space v2 LED driver");
 382MODULE_LICENSE("GPL");
 383MODULE_ALIAS("platform:leds-ns2");
 384
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.