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                ret = regulator_enable(led->dcdc);
 133                if (ret != 0)
 134                        dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n",
 135                                ret);
 136                return;
 137        }
 138
 139        led->enabled = 0;
 140}
 141
 142static void led_work(struct work_struct *work)
 143{
 144        struct wm8350_led *led = container_of(work, struct wm8350_led, work);
 145        int ret;
 146        int uA;
 147        unsigned long flags;
 148
 149        mutex_lock(&led->mutex);
 150
 151        spin_lock_irqsave(&led->value_lock, flags);
 152
 153        if (led->value == LED_OFF) {
 154                spin_unlock_irqrestore(&led->value_lock, flags);
 155                wm8350_led_disable(led);
 156                goto out;
 157        }
 158
 159        /* This scales linearly into the index of valid current
 160         * settings which results in a linear scaling of perceived
 161         * brightness due to the non-linear current settings provided
 162         * by the hardware.
 163         */
 164        uA = (led->max_uA_index * led->value) / LED_FULL;
 165        spin_unlock_irqrestore(&led->value_lock, flags);
 166        BUG_ON(uA >= ARRAY_SIZE(isink_cur));
 167
 168        ret = regulator_set_current_limit(led->isink, isink_cur[uA],
 169                                          isink_cur[uA]);
 170        if (ret != 0)
 171                dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
 172                        isink_cur[uA], ret);
 173
 174        wm8350_led_enable(led);
 175
 176out:
 177        mutex_unlock(&led->mutex);
 178}
 179
 180static void wm8350_led_set(struct led_classdev *led_cdev,
 181                           enum led_brightness value)
 182{
 183        struct wm8350_led *led = to_wm8350_led(led_cdev);
 184        unsigned long flags;
 185
 186        spin_lock_irqsave(&led->value_lock, flags);
 187        led->value = value;
 188        schedule_work(&led->work);
 189        spin_unlock_irqrestore(&led->value_lock, flags);
 190}
 191
 192static void wm8350_led_shutdown(struct platform_device *pdev)
 193{
 194        struct wm8350_led *led = platform_get_drvdata(pdev);
 195
 196        mutex_lock(&led->mutex);
 197        led->value = LED_OFF;
 198        wm8350_led_disable(led);
 199        mutex_unlock(&led->mutex);
 200}
 201
 202static int wm8350_led_probe(struct platform_device *pdev)
 203{
 204        struct regulator *isink, *dcdc;
 205        struct wm8350_led *led;
 206        struct wm8350_led_platform_data *pdata = pdev->dev.platform_data;
 207        int i;
 208
 209        if (pdata == NULL) {
 210                dev_err(&pdev->dev, "no platform data\n");
 211                return -ENODEV;
 212        }
 213
 214        if (pdata->max_uA < isink_cur[0]) {
 215                dev_err(&pdev->dev, "Invalid maximum current %duA\n",
 216                        pdata->max_uA);
 217                return -EINVAL;
 218        }
 219
 220        isink = devm_regulator_get(&pdev->dev, "led_isink");
 221        if (IS_ERR(isink)) {
 222                dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__);
 223                return PTR_ERR(isink);
 224        }
 225
 226        dcdc = devm_regulator_get(&pdev->dev, "led_vcc");
 227        if (IS_ERR(dcdc)) {
 228                dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__);
 229                return PTR_ERR(dcdc);
 230        }
 231
 232        led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
 233        if (led == NULL)
 234                return -ENOMEM;
 235
 236        led->cdev.brightness_set = wm8350_led_set;
 237        led->cdev.default_trigger = pdata->default_trigger;
 238        led->cdev.name = pdata->name;
 239        led->cdev.flags |= LED_CORE_SUSPENDRESUME;
 240        led->enabled = regulator_is_enabled(isink);
 241        led->isink = isink;
 242        led->dcdc = dcdc;
 243
 244        for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++)
 245                if (isink_cur[i] >= pdata->max_uA)
 246                        break;
 247        led->max_uA_index = i;
 248        if (pdata->max_uA != isink_cur[i])
 249                dev_warn(&pdev->dev,
 250                         "Maximum current %duA is not directly supported,"
 251                         " check platform data\n",
 252                         pdata->max_uA);
 253
 254        spin_lock_init(&led->value_lock);
 255        mutex_init(&led->mutex);
 256        INIT_WORK(&led->work, led_work);
 257        led->value = LED_OFF;
 258        platform_set_drvdata(pdev, led);
 259
 260        return led_classdev_register(&pdev->dev, &led->cdev);
 261}
 262
 263static int wm8350_led_remove(struct platform_device *pdev)
 264{
 265        struct wm8350_led *led = platform_get_drvdata(pdev);
 266
 267        led_classdev_unregister(&led->cdev);
 268        flush_work(&led->work);
 269        wm8350_led_disable(led);
 270        return 0;
 271}
 272
 273static struct platform_driver wm8350_led_driver = {
 274        .driver = {
 275                   .name = "wm8350-led",
 276                   .owner = THIS_MODULE,
 277                   },
 278        .probe = wm8350_led_probe,
 279        .remove = wm8350_led_remove,
 280        .shutdown = wm8350_led_shutdown,
 281};
 282
 283module_platform_driver(wm8350_led_driver);
 284
 285MODULE_AUTHOR("Mark Brown");
 286MODULE_DESCRIPTION("WM8350 LED driver");
 287MODULE_LICENSE("GPL");
 288MODULE_ALIAS("platform:wm8350-led");
 289