linux/drivers/leds/leds-regulator.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * leds-regulator.c - LED class driver for regulator driven LEDs.
   4 *
   5 * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
   6 *
   7 * Inspired by leds-wm8350 driver.
   8 */
   9
  10#include <linux/module.h>
  11#include <linux/err.h>
  12#include <linux/slab.h>
  13#include <linux/leds.h>
  14#include <linux/leds-regulator.h>
  15#include <linux/platform_device.h>
  16#include <linux/regulator/consumer.h>
  17
  18#define to_regulator_led(led_cdev) \
  19        container_of(led_cdev, struct regulator_led, cdev)
  20
  21struct regulator_led {
  22        struct led_classdev cdev;
  23        int enabled;
  24        struct mutex mutex;
  25
  26        struct regulator *vcc;
  27};
  28
  29static inline int led_regulator_get_max_brightness(struct regulator *supply)
  30{
  31        int ret;
  32        int voltage = regulator_list_voltage(supply, 0);
  33
  34        if (voltage <= 0)
  35                return 1;
  36
  37        /* even if regulator can't change voltages,
  38         * we still assume it can change status
  39         * and the LED can be turned on and off.
  40         */
  41        ret = regulator_set_voltage(supply, voltage, voltage);
  42        if (ret < 0)
  43                return 1;
  44
  45        return regulator_count_voltages(supply);
  46}
  47
  48static int led_regulator_get_voltage(struct regulator *supply,
  49                enum led_brightness brightness)
  50{
  51        if (brightness == 0)
  52                return -EINVAL;
  53
  54        return regulator_list_voltage(supply, brightness - 1);
  55}
  56
  57
  58static void regulator_led_enable(struct regulator_led *led)
  59{
  60        int ret;
  61
  62        if (led->enabled)
  63                return;
  64
  65        ret = regulator_enable(led->vcc);
  66        if (ret != 0) {
  67                dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret);
  68                return;
  69        }
  70
  71        led->enabled = 1;
  72}
  73
  74static void regulator_led_disable(struct regulator_led *led)
  75{
  76        int ret;
  77
  78        if (!led->enabled)
  79                return;
  80
  81        ret = regulator_disable(led->vcc);
  82        if (ret != 0) {
  83                dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret);
  84                return;
  85        }
  86
  87        led->enabled = 0;
  88}
  89
  90static int regulator_led_brightness_set(struct led_classdev *led_cdev,
  91                                         enum led_brightness value)
  92{
  93        struct regulator_led *led = to_regulator_led(led_cdev);
  94        int voltage;
  95        int ret = 0;
  96
  97        mutex_lock(&led->mutex);
  98
  99        if (value == LED_OFF) {
 100                regulator_led_disable(led);
 101                goto out;
 102        }
 103
 104        if (led->cdev.max_brightness > 1) {
 105                voltage = led_regulator_get_voltage(led->vcc, value);
 106                dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n",
 107                                value, voltage);
 108
 109                ret = regulator_set_voltage(led->vcc, voltage, voltage);
 110                if (ret != 0)
 111                        dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n",
 112                                voltage, ret);
 113        }
 114
 115        regulator_led_enable(led);
 116
 117out:
 118        mutex_unlock(&led->mutex);
 119        return ret;
 120}
 121
 122static int regulator_led_probe(struct platform_device *pdev)
 123{
 124        struct led_regulator_platform_data *pdata =
 125                        dev_get_platdata(&pdev->dev);
 126        struct regulator_led *led;
 127        struct regulator *vcc;
 128        int ret = 0;
 129
 130        if (pdata == NULL) {
 131                dev_err(&pdev->dev, "no platform data\n");
 132                return -ENODEV;
 133        }
 134
 135        vcc = devm_regulator_get_exclusive(&pdev->dev, "vled");
 136        if (IS_ERR(vcc)) {
 137                dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name);
 138                return PTR_ERR(vcc);
 139        }
 140
 141        led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
 142        if (led == NULL)
 143                return -ENOMEM;
 144
 145        led->cdev.max_brightness = led_regulator_get_max_brightness(vcc);
 146        if (pdata->brightness > led->cdev.max_brightness) {
 147                dev_err(&pdev->dev, "Invalid default brightness %d\n",
 148                                pdata->brightness);
 149                return -EINVAL;
 150        }
 151
 152        led->cdev.brightness_set_blocking = regulator_led_brightness_set;
 153        led->cdev.name = pdata->name;
 154        led->cdev.flags |= LED_CORE_SUSPENDRESUME;
 155        led->vcc = vcc;
 156
 157        /* to handle correctly an already enabled regulator */
 158        if (regulator_is_enabled(led->vcc))
 159                led->enabled = 1;
 160
 161        mutex_init(&led->mutex);
 162
 163        platform_set_drvdata(pdev, led);
 164
 165        ret = led_classdev_register(&pdev->dev, &led->cdev);
 166        if (ret < 0)
 167                return ret;
 168
 169        /* to expose the default value to userspace */
 170        led->cdev.brightness = pdata->brightness;
 171
 172        /* Set the default led status */
 173        regulator_led_brightness_set(&led->cdev, led->cdev.brightness);
 174
 175        return 0;
 176}
 177
 178static int regulator_led_remove(struct platform_device *pdev)
 179{
 180        struct regulator_led *led = platform_get_drvdata(pdev);
 181
 182        led_classdev_unregister(&led->cdev);
 183        regulator_led_disable(led);
 184        return 0;
 185}
 186
 187static struct platform_driver regulator_led_driver = {
 188        .driver = {
 189                   .name  = "leds-regulator",
 190                   },
 191        .probe  = regulator_led_probe,
 192        .remove = regulator_led_remove,
 193};
 194
 195module_platform_driver(regulator_led_driver);
 196
 197MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
 198MODULE_DESCRIPTION("Regulator driven LED driver");
 199MODULE_LICENSE("GPL");
 200MODULE_ALIAS("platform:leds-regulator");
 201