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