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/platform_device.h>
  27#include <linux/slab.h>
  28#include <linux/gpio.h>
  29#include <linux/leds.h>
  30#include <linux/module.h>
  31#include <linux/platform_data/leds-kirkwood-ns2.h>
  32#include <linux/of.h>
  33#include <linux/of_gpio.h>
  34#include "leds.h"
  35
  36/*
  37 * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
  38 * modes are available: off, on and SATA activity blinking. The LED modes are
  39 * controlled through two GPIOs (command and slow): each combination of values
  40 * for the command/slow GPIOs corresponds to a LED mode.
  41 */
  42
  43struct ns2_led_data {
  44        struct led_classdev     cdev;
  45        unsigned                cmd;
  46        unsigned                slow;
  47        bool                    can_sleep;
  48        int                     mode_index;
  49        unsigned char           sata; /* True when SATA mode active. */
  50        rwlock_t                rw_lock; /* Lock GPIOs. */
  51        struct work_struct      work;
  52        int                     num_modes;
  53        struct ns2_led_modval   *modval;
  54};
  55
  56static void ns2_led_work(struct work_struct *work)
  57{
  58        struct ns2_led_data *led_dat =
  59                container_of(work, struct ns2_led_data, work);
  60        int i = led_dat->mode_index;
  61
  62        gpio_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level);
  63        gpio_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level);
  64}
  65
  66static int ns2_led_get_mode(struct ns2_led_data *led_dat,
  67                            enum ns2_led_modes *mode)
  68{
  69        int i;
  70        int ret = -EINVAL;
  71        int cmd_level;
  72        int slow_level;
  73
  74        cmd_level = gpio_get_value_cansleep(led_dat->cmd);
  75        slow_level = gpio_get_value_cansleep(led_dat->slow);
  76
  77        for (i = 0; i < led_dat->num_modes; i++) {
  78                if (cmd_level == led_dat->modval[i].cmd_level &&
  79                    slow_level == led_dat->modval[i].slow_level) {
  80                        *mode = led_dat->modval[i].mode;
  81                        ret = 0;
  82                        break;
  83                }
  84        }
  85
  86        return ret;
  87}
  88
  89static void ns2_led_set_mode(struct ns2_led_data *led_dat,
  90                             enum ns2_led_modes mode)
  91{
  92        int i;
  93        bool found = false;
  94        unsigned long flags;
  95
  96        for (i = 0; i < led_dat->num_modes; i++)
  97                if (mode == led_dat->modval[i].mode) {
  98                        found = true;
  99                        break;
 100                }
 101
 102        if (!found)
 103                return;
 104
 105        write_lock_irqsave(&led_dat->rw_lock, flags);
 106
 107        if (!led_dat->can_sleep) {
 108                gpio_set_value(led_dat->cmd,
 109                               led_dat->modval[i].cmd_level);
 110                gpio_set_value(led_dat->slow,
 111                               led_dat->modval[i].slow_level);
 112                goto exit_unlock;
 113        }
 114
 115        led_dat->mode_index = i;
 116        schedule_work(&led_dat->work);
 117
 118exit_unlock:
 119        write_unlock_irqrestore(&led_dat->rw_lock, flags);
 120}
 121
 122static 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
 139static ssize_t ns2_led_sata_store(struct device *dev,
 140                                  struct device_attribute *attr,
 141                                  const char *buff, size_t count)
 142{
 143        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 144        struct ns2_led_data *led_dat =
 145                container_of(led_cdev, struct ns2_led_data, cdev);
 146        int ret;
 147        unsigned long enable;
 148
 149        ret = kstrtoul(buff, 10, &enable);
 150        if (ret < 0)
 151                return ret;
 152
 153        enable = !!enable;
 154
 155        if (led_dat->sata == enable)
 156                goto exit;
 157
 158        led_dat->sata = enable;
 159
 160        if (!led_get_brightness(led_cdev))
 161                goto exit;
 162
 163        if (enable)
 164                ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
 165        else
 166                ns2_led_set_mode(led_dat, NS_V2_LED_ON);
 167
 168exit:
 169        return count;
 170}
 171
 172static ssize_t ns2_led_sata_show(struct device *dev,
 173                                 struct device_attribute *attr, char *buf)
 174{
 175        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 176        struct ns2_led_data *led_dat =
 177                container_of(led_cdev, struct ns2_led_data, cdev);
 178
 179        return sprintf(buf, "%d\n", led_dat->sata);
 180}
 181
 182static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
 183
 184static struct attribute *ns2_led_attrs[] = {
 185        &dev_attr_sata.attr,
 186        NULL
 187};
 188ATTRIBUTE_GROUPS(ns2_led);
 189
 190static int
 191create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
 192               const struct ns2_led *template)
 193{
 194        int ret;
 195        enum ns2_led_modes mode;
 196
 197        ret = devm_gpio_request_one(&pdev->dev, template->cmd,
 198                        gpio_get_value_cansleep(template->cmd) ?
 199                        GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
 200                        template->name);
 201        if (ret) {
 202                dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
 203                        template->name);
 204                return ret;
 205        }
 206
 207        ret = devm_gpio_request_one(&pdev->dev, template->slow,
 208                        gpio_get_value_cansleep(template->slow) ?
 209                        GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
 210                        template->name);
 211        if (ret) {
 212                dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
 213                        template->name);
 214                return ret;
 215        }
 216
 217        rwlock_init(&led_dat->rw_lock);
 218
 219        led_dat->cdev.name = template->name;
 220        led_dat->cdev.default_trigger = template->default_trigger;
 221        led_dat->cdev.blink_set = NULL;
 222        led_dat->cdev.brightness_set = ns2_led_set;
 223        led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
 224        led_dat->cdev.groups = ns2_led_groups;
 225        led_dat->cmd = template->cmd;
 226        led_dat->slow = template->slow;
 227        led_dat->can_sleep = gpio_cansleep(led_dat->cmd) |
 228                                gpio_cansleep(led_dat->slow);
 229        led_dat->modval = template->modval;
 230        led_dat->num_modes = template->num_modes;
 231
 232        ret = ns2_led_get_mode(led_dat, &mode);
 233        if (ret < 0)
 234                return ret;
 235
 236        /* Set LED initial state. */
 237        led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
 238        led_dat->cdev.brightness =
 239                (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
 240
 241        INIT_WORK(&led_dat->work, ns2_led_work);
 242
 243        ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
 244        if (ret < 0)
 245                return ret;
 246
 247        return 0;
 248}
 249
 250static void delete_ns2_led(struct ns2_led_data *led_dat)
 251{
 252        led_classdev_unregister(&led_dat->cdev);
 253        cancel_work_sync(&led_dat->work);
 254}
 255
 256#ifdef CONFIG_OF_GPIO
 257/*
 258 * Translate OpenFirmware node properties into platform_data.
 259 */
 260static int
 261ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
 262{
 263        struct device_node *np = dev->of_node;
 264        struct device_node *child;
 265        struct ns2_led *led, *leds;
 266        int num_leds = 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        led = leds;
 278        for_each_child_of_node(np, child) {
 279                const char *string;
 280                int ret, i, num_modes;
 281                struct ns2_led_modval *modval;
 282
 283                ret = of_get_named_gpio(child, "cmd-gpio", 0);
 284                if (ret < 0)
 285                        return ret;
 286                led->cmd = ret;
 287                ret = of_get_named_gpio(child, "slow-gpio", 0);
 288                if (ret < 0)
 289                        return ret;
 290                led->slow = ret;
 291                ret = of_property_read_string(child, "label", &string);
 292                led->name = (ret == 0) ? string : child->name;
 293                ret = of_property_read_string(child, "linux,default-trigger",
 294                                              &string);
 295                if (ret == 0)
 296                        led->default_trigger = string;
 297
 298                ret = of_property_count_u32_elems(child, "modes-map");
 299                if (ret < 0 || ret % 3) {
 300                        dev_err(dev,
 301                                "Missing or malformed modes-map property\n");
 302                        return -EINVAL;
 303                }
 304
 305                num_modes = ret / 3;
 306                modval = devm_kzalloc(dev,
 307                                      num_modes * sizeof(struct ns2_led_modval),
 308                                      GFP_KERNEL);
 309                if (!modval)
 310                        return -ENOMEM;
 311
 312                for (i = 0; i < num_modes; i++) {
 313                        of_property_read_u32_index(child,
 314                                                "modes-map", 3 * i,
 315                                                (u32 *) &modval[i].mode);
 316                        of_property_read_u32_index(child,
 317                                                "modes-map", 3 * i + 1,
 318                                                (u32 *) &modval[i].cmd_level);
 319                        of_property_read_u32_index(child,
 320                                                "modes-map", 3 * i + 2,
 321                                                (u32 *) &modval[i].slow_level);
 322                }
 323
 324                led->num_modes = num_modes;
 325                led->modval = modval;
 326
 327                led++;
 328        }
 329
 330        pdata->leds = leds;
 331        pdata->num_leds = num_leds;
 332
 333        return 0;
 334}
 335
 336static const struct of_device_id of_ns2_leds_match[] = {
 337        { .compatible = "lacie,ns2-leds", },
 338        {},
 339};
 340MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
 341#endif /* CONFIG_OF_GPIO */
 342
 343struct ns2_led_priv {
 344        int num_leds;
 345        struct ns2_led_data leds_data[];
 346};
 347
 348static inline int sizeof_ns2_led_priv(int num_leds)
 349{
 350        return sizeof(struct ns2_led_priv) +
 351                      (sizeof(struct ns2_led_data) * num_leds);
 352}
 353
 354static int ns2_led_probe(struct platform_device *pdev)
 355{
 356        struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
 357        struct ns2_led_priv *priv;
 358        int i;
 359        int ret;
 360
 361#ifdef CONFIG_OF_GPIO
 362        if (!pdata) {
 363                pdata = devm_kzalloc(&pdev->dev,
 364                                     sizeof(struct ns2_led_platform_data),
 365                                     GFP_KERNEL);
 366                if (!pdata)
 367                        return -ENOMEM;
 368
 369                ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
 370                if (ret)
 371                        return ret;
 372        }
 373#else
 374        if (!pdata)
 375                return -EINVAL;
 376#endif /* CONFIG_OF_GPIO */
 377
 378        priv = devm_kzalloc(&pdev->dev,
 379                            sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
 380        if (!priv)
 381                return -ENOMEM;
 382        priv->num_leds = pdata->num_leds;
 383
 384        for (i = 0; i < priv->num_leds; i++) {
 385                ret = create_ns2_led(pdev, &priv->leds_data[i],
 386                                     &pdata->leds[i]);
 387                if (ret < 0) {
 388                        for (i = i - 1; i >= 0; i--)
 389                                delete_ns2_led(&priv->leds_data[i]);
 390                        return ret;
 391                }
 392        }
 393
 394        platform_set_drvdata(pdev, priv);
 395
 396        return 0;
 397}
 398
 399static int ns2_led_remove(struct platform_device *pdev)
 400{
 401        int i;
 402        struct ns2_led_priv *priv;
 403
 404        priv = platform_get_drvdata(pdev);
 405
 406        for (i = 0; i < priv->num_leds; i++)
 407                delete_ns2_led(&priv->leds_data[i]);
 408
 409        return 0;
 410}
 411
 412static struct platform_driver ns2_led_driver = {
 413        .probe          = ns2_led_probe,
 414        .remove         = ns2_led_remove,
 415        .driver         = {
 416                .name           = "leds-ns2",
 417                .of_match_table = of_match_ptr(of_ns2_leds_match),
 418        },
 419};
 420
 421module_platform_driver(ns2_led_driver);
 422
 423MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
 424MODULE_DESCRIPTION("Network Space v2 LED driver");
 425MODULE_LICENSE("GPL");
 426MODULE_ALIAS("platform:leds-ns2");
 427