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