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