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                        gpio_get_value(template->cmd) ?
 197                        GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
 198                        template->name);
 199        if (ret) {
 200                dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
 201                        template->name);
 202                return ret;
 203        }
 204
 205        ret = devm_gpio_request_one(&pdev->dev, template->slow,
 206                        gpio_get_value(template->slow) ?
 207                        GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
 208                        template->name);
 209        if (ret) {
 210                dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
 211                        template->name);
 212                return ret;
 213        }
 214
 215        rwlock_init(&led_dat->rw_lock);
 216
 217        led_dat->cdev.name = template->name;
 218        led_dat->cdev.default_trigger = template->default_trigger;
 219        led_dat->cdev.blink_set = NULL;
 220        led_dat->cdev.brightness_set = ns2_led_set;
 221        led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
 222        led_dat->cmd = template->cmd;
 223        led_dat->slow = template->slow;
 224
 225        ret = ns2_led_get_mode(led_dat, &mode);
 226        if (ret < 0)
 227                return ret;
 228
 229        /* Set LED initial state. */
 230        led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
 231        led_dat->cdev.brightness =
 232                (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
 233
 234        ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
 235        if (ret < 0)
 236                return ret;
 237
 238        ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
 239        if (ret < 0)
 240                goto err_free_cdev;
 241
 242        return 0;
 243
 244err_free_cdev:
 245        led_classdev_unregister(&led_dat->cdev);
 246        return ret;
 247}
 248
 249static void delete_ns2_led(struct ns2_led_data *led_dat)
 250{
 251        device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
 252        led_classdev_unregister(&led_dat->cdev);
 253}
 254
 255#ifdef CONFIG_OF_GPIO
 256/*
 257 * Translate OpenFirmware node properties into platform_data.
 258 */
 259static int
 260ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
 261{
 262        struct device_node *np = dev->of_node;
 263        struct device_node *child;
 264        struct ns2_led *leds;
 265        int num_leds = 0;
 266        int i = 0;
 267
 268        num_leds = of_get_child_count(np);
 269        if (!num_leds)
 270                return -ENODEV;
 271
 272        leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led),
 273                            GFP_KERNEL);
 274        if (!leds)
 275                return -ENOMEM;
 276
 277        for_each_child_of_node(np, child) {
 278                const char *string;
 279                int ret;
 280
 281                ret = of_get_named_gpio(child, "cmd-gpio", 0);
 282                if (ret < 0)
 283                        return ret;
 284                leds[i].cmd = ret;
 285                ret = of_get_named_gpio(child, "slow-gpio", 0);
 286                if (ret < 0)
 287                        return ret;
 288                leds[i].slow = ret;
 289                ret = of_property_read_string(child, "label", &string);
 290                leds[i].name = (ret == 0) ? string : child->name;
 291                ret = of_property_read_string(child, "linux,default-trigger",
 292                                              &string);
 293                if (ret == 0)
 294                        leds[i].default_trigger = string;
 295
 296                i++;
 297        }
 298
 299        pdata->leds = leds;
 300        pdata->num_leds = num_leds;
 301
 302        return 0;
 303}
 304
 305static const struct of_device_id of_ns2_leds_match[] = {
 306        { .compatible = "lacie,ns2-leds", },
 307        {},
 308};
 309#endif /* CONFIG_OF_GPIO */
 310
 311struct ns2_led_priv {
 312        int num_leds;
 313        struct ns2_led_data leds_data[];
 314};
 315
 316static inline int sizeof_ns2_led_priv(int num_leds)
 317{
 318        return sizeof(struct ns2_led_priv) +
 319                      (sizeof(struct ns2_led_data) * num_leds);
 320}
 321
 322static int ns2_led_probe(struct platform_device *pdev)
 323{
 324        struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
 325        struct ns2_led_priv *priv;
 326        int i;
 327        int ret;
 328
 329#ifdef CONFIG_OF_GPIO
 330        if (!pdata) {
 331                pdata = devm_kzalloc(&pdev->dev,
 332                                     sizeof(struct ns2_led_platform_data),
 333                                     GFP_KERNEL);
 334                if (!pdata)
 335                        return -ENOMEM;
 336
 337                ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
 338                if (ret)
 339                        return ret;
 340        }
 341#else
 342        if (!pdata)
 343                return -EINVAL;
 344#endif /* CONFIG_OF_GPIO */
 345
 346        priv = devm_kzalloc(&pdev->dev,
 347                            sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
 348        if (!priv)
 349                return -ENOMEM;
 350        priv->num_leds = pdata->num_leds;
 351
 352        for (i = 0; i < priv->num_leds; i++) {
 353                ret = create_ns2_led(pdev, &priv->leds_data[i],
 354                                     &pdata->leds[i]);
 355                if (ret < 0) {
 356                        for (i = i - 1; i >= 0; i--)
 357                                delete_ns2_led(&priv->leds_data[i]);
 358                        return ret;
 359                }
 360        }
 361
 362        platform_set_drvdata(pdev, priv);
 363
 364        return 0;
 365}
 366
 367static int ns2_led_remove(struct platform_device *pdev)
 368{
 369        int i;
 370        struct ns2_led_priv *priv;
 371
 372        priv = platform_get_drvdata(pdev);
 373
 374        for (i = 0; i < priv->num_leds; i++)
 375                delete_ns2_led(&priv->leds_data[i]);
 376
 377        platform_set_drvdata(pdev, NULL);
 378
 379        return 0;
 380}
 381
 382static struct platform_driver ns2_led_driver = {
 383        .probe          = ns2_led_probe,
 384        .remove         = ns2_led_remove,
 385        .driver         = {
 386                .name           = "leds-ns2",
 387                .owner          = THIS_MODULE,
 388                .of_match_table = of_match_ptr(of_ns2_leds_match),
 389        },
 390};
 391
 392module_platform_driver(ns2_led_driver);
 393
 394MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
 395MODULE_DESCRIPTION("Network Space v2 LED driver");
 396MODULE_LICENSE("GPL");
 397MODULE_ALIAS("platform:leds-ns2");
 398
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.