linux/drivers/leds/leds-wm8350.c
<<
>>
Prefs
   1/*
   2 * LED driver for WM8350 driven LEDS.
   3 *
   4 * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC.
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 *
  10 */
  11
  12#include <linux/kernel.h>
  13#include <linux/init.h>
  14#include <linux/platform_device.h>
  15#include <linux/leds.h>
  16#include <linux/err.h>
  17#include <linux/mfd/wm8350/pmic.h>
  18#include <linux/regulator/consumer.h>
  19
  20/* Microamps */
  21static const int isink_cur[] = {
  22        4,
  23        5,
  24        6,
  25        7,
  26        8,
  27        10,
  28        11,
  29        14,
  30        16,
  31        19,
  32        23,
  33        27,
  34        32,
  35        39,
  36        46,
  37        54,
  38        65,
  39        77,
  40        92,
  41        109,
  42        130,
  43        154,
  44        183,
  45        218,
  46        259,
  47        308,
  48        367,
  49        436,
  50        518,
  51        616,
  52        733,
  53        872,
  54        1037,
  55        1233,
  56        1466,
  57        1744,
  58        2073,
  59        2466,
  60        2933,
  61        3487,
  62        4147,
  63        4932,
  64        5865,
  65        6975,
  66        8294,
  67        9864,
  68        11730,
  69        13949,
  70        16589,
  71        19728,
  72        23460,
  73        27899,
  74        33178,
  75        39455,
  76        46920,
  77        55798,
  78        66355,
  79        78910,
  80        93840,
  81        111596,
  82        132710,
  83        157820,
  84        187681,
  85        223191
  86};
  87
  88#define to_wm8350_led(led_cdev) \
  89        container_of(led_cdev, struct wm8350_led, cdev)
  90
  91static void wm8350_led_enable(struct wm8350_led *led)
  92{
  93        int ret;
  94
  95        if (led->enabled)
  96                return;
  97
  98        ret = regulator_enable(led->isink);
  99        if (ret != 0) {
 100                dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret);
 101                return;
 102        }
 103
 104        ret = regulator_enable(led->dcdc);
 105        if (ret != 0) {
 106                dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret);
 107                regulator_disable(led->isink);
 108                return;
 109        }
 110
 111        led->enabled = 1;
 112}
 113
 114static void wm8350_led_disable(struct wm8350_led *led)
 115{
 116        int ret;
 117
 118        if (!led->enabled)
 119                return;
 120
 121        ret = regulator_disable(led->dcdc);
 122        if (ret != 0) {
 123                dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret);
 124                return;
 125        }
 126
 127        ret = regulator_disable(led->isink);
 128        if (ret != 0) {
 129                dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret);
 130                regulator_enable(led->dcdc);
 131                return;
 132        }
 133
 134        led->enabled = 0;
 135}
 136
 137static void led_work(struct work_struct *work)
 138{
 139        struct wm8350_led *led = container_of(work, struct wm8350_led, work);
 140        int ret;
 141        int uA;
 142        unsigned long flags;
 143
 144        mutex_lock(&led->mutex);
 145
 146        spin_lock_irqsave(&led->value_lock, flags);
 147
 148        if (led->value == LED_OFF) {
 149                spin_unlock_irqrestore(&led->value_lock, flags);
 150                wm8350_led_disable(led);
 151                goto out;
 152        }
 153
 154        /* This scales linearly into the index of valid current
 155         * settings which results in a linear scaling of perceived
 156         * brightness due to the non-linear current settings provided
 157         * by the hardware.
 158         */
 159        uA = (led->max_uA_index * led->value) / LED_FULL;
 160        spin_unlock_irqrestore(&led->value_lock, flags);
 161        BUG_ON(uA >= ARRAY_SIZE(isink_cur));
 162
 163        ret = regulator_set_current_limit(led->isink, isink_cur[uA],
 164                                          isink_cur[uA]);
 165        if (ret != 0)
 166                dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
 167                        isink_cur[uA], ret);
 168
 169        wm8350_led_enable(led);
 170
 171out:
 172        mutex_unlock(&led->mutex);
 173}
 174
 175static void wm8350_led_set(struct led_classdev *led_cdev,
 176                           enum led_brightness value)
 177{
 178        struct wm8350_led *led = to_wm8350_led(led_cdev);
 179        unsigned long flags;
 180
 181        spin_lock_irqsave(&led->value_lock, flags);
 182        led->value = value;
 183        schedule_work(&led->work);
 184        spin_unlock_irqrestore(&led->value_lock, flags);
 185}
 186
 187static void wm8350_led_shutdown(struct platform_device *pdev)
 188{
 189        struct wm8350_led *led = platform_get_drvdata(pdev);
 190
 191        mutex_lock(&led->mutex);
 192        led->value = LED_OFF;
 193        wm8350_led_disable(led);
 194        mutex_unlock(&led->mutex);
 195}
 196
 197static int wm8350_led_probe(struct platform_device *pdev)
 198{
 199        struct regulator *isink, *dcdc;
 200        struct wm8350_led *led;
 201        struct wm8350_led_platform_data *pdata = pdev->dev.platform_data;
 202        int ret, i;
 203
 204        if (pdata == NULL) {
 205                dev_err(&pdev->dev, "no platform data\n");
 206                return -ENODEV;
 207        }
 208
 209        if (pdata->max_uA < isink_cur[0]) {
 210                dev_err(&pdev->dev, "Invalid maximum current %duA\n",
 211                        pdata->max_uA);
 212                return -EINVAL;
 213        }
 214
 215        isink = regulator_get(&pdev->dev, "led_isink");
 216        if (IS_ERR(isink)) {
 217                printk(KERN_ERR "%s: cant get ISINK\n", __func__);
 218                return PTR_ERR(isink);
 219        }
 220
 221        dcdc = regulator_get(&pdev->dev, "led_vcc");
 222        if (IS_ERR(dcdc)) {
 223                printk(KERN_ERR "%s: cant get DCDC\n", __func__);
 224                ret = PTR_ERR(dcdc);
 225                goto err_isink;
 226        }
 227
 228        led = kzalloc(sizeof(*led), GFP_KERNEL);
 229        if (led == NULL) {
 230                ret = -ENOMEM;
 231                goto err_dcdc;
 232        }
 233
 234        led->cdev.brightness_set = wm8350_led_set;
 235        led->cdev.default_trigger = pdata->default_trigger;
 236        led->cdev.name = pdata->name;
 237        led->cdev.flags |= LED_CORE_SUSPENDRESUME;
 238        led->enabled = regulator_is_enabled(isink);
 239        led->isink = isink;
 240        led->dcdc = dcdc;
 241
 242        for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++)
 243                if (isink_cur[i] >= pdata->max_uA)
 244                        break;
 245        led->max_uA_index = i;
 246        if (pdata->max_uA != isink_cur[i])
 247                dev_warn(&pdev->dev,
 248                         "Maximum current %duA is not directly supported,"
 249                         " check platform data\n",
 250                         pdata->max_uA);
 251
 252        spin_lock_init(&led->value_lock);
 253        mutex_init(&led->mutex);
 254        INIT_WORK(&led->work, led_work);
 255        led->value = LED_OFF;
 256        platform_set_drvdata(pdev, led);
 257
 258        ret = led_classdev_register(&pdev->dev, &led->cdev);
 259        if (ret < 0)
 260                goto err_led;
 261
 262        return 0;
 263
 264 err_led:
 265        kfree(led);
 266 err_dcdc:
 267        regulator_put(dcdc);
 268 err_isink:
 269        regulator_put(isink);
 270        return ret;
 271}
 272
 273static int wm8350_led_remove(struct platform_device *pdev)
 274{
 275        struct wm8350_led *led = platform_get_drvdata(pdev);
 276
 277        led_classdev_unregister(&led->cdev);
 278        flush_scheduled_work();
 279        wm8350_led_disable(led);
 280        regulator_put(led->dcdc);
 281        regulator_put(led->isink);
 282        kfree(led);
 283        return 0;
 284}
 285
 286static struct platform_driver wm8350_led_driver = {
 287        .driver = {
 288                   .name = "wm8350-led",
 289                   .owner = THIS_MODULE,
 290                   },
 291        .probe = wm8350_led_probe,
 292        .remove = wm8350_led_remove,
 293        .shutdown = wm8350_led_shutdown,
 294};
 295
 296static int __devinit wm8350_led_init(void)
 297{
 298        return platform_driver_register(&wm8350_led_driver);
 299}
 300module_init(wm8350_led_init);
 301
 302static void wm8350_led_exit(void)
 303{
 304        platform_driver_unregister(&wm8350_led_driver);
 305}
 306module_exit(wm8350_led_exit);
 307
 308MODULE_AUTHOR("Mark Brown");
 309MODULE_DESCRIPTION("WM8350 LED driver");
 310MODULE_LICENSE("GPL");
 311MODULE_ALIAS("platform:wm8350-led");
 312
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.