Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * leds-regulator.c - LED class driver for regulator driven LEDs.
0004  *
0005  * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
0006  *
0007  * Inspired by leds-wm8350 driver.
0008  */
0009 
0010 #include <linux/module.h>
0011 #include <linux/mod_devicetable.h>
0012 #include <linux/err.h>
0013 #include <linux/slab.h>
0014 #include <linux/leds.h>
0015 #include <linux/leds-regulator.h>
0016 #include <linux/platform_device.h>
0017 #include <linux/regulator/consumer.h>
0018 
0019 #define to_regulator_led(led_cdev) \
0020     container_of(led_cdev, struct regulator_led, cdev)
0021 
0022 struct regulator_led {
0023     struct led_classdev cdev;
0024     int enabled;
0025     struct mutex mutex;
0026 
0027     struct regulator *vcc;
0028 };
0029 
0030 static inline int led_regulator_get_max_brightness(struct regulator *supply)
0031 {
0032     int ret;
0033     int voltage = regulator_list_voltage(supply, 0);
0034 
0035     if (voltage <= 0)
0036         return 1;
0037 
0038     /* even if regulator can't change voltages,
0039      * we still assume it can change status
0040      * and the LED can be turned on and off.
0041      */
0042     ret = regulator_set_voltage(supply, voltage, voltage);
0043     if (ret < 0)
0044         return 1;
0045 
0046     return regulator_count_voltages(supply);
0047 }
0048 
0049 static int led_regulator_get_voltage(struct regulator *supply,
0050         enum led_brightness brightness)
0051 {
0052     if (brightness == 0)
0053         return -EINVAL;
0054 
0055     return regulator_list_voltage(supply, brightness - 1);
0056 }
0057 
0058 
0059 static void regulator_led_enable(struct regulator_led *led)
0060 {
0061     int ret;
0062 
0063     if (led->enabled)
0064         return;
0065 
0066     ret = regulator_enable(led->vcc);
0067     if (ret != 0) {
0068         dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret);
0069         return;
0070     }
0071 
0072     led->enabled = 1;
0073 }
0074 
0075 static void regulator_led_disable(struct regulator_led *led)
0076 {
0077     int ret;
0078 
0079     if (!led->enabled)
0080         return;
0081 
0082     ret = regulator_disable(led->vcc);
0083     if (ret != 0) {
0084         dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret);
0085         return;
0086     }
0087 
0088     led->enabled = 0;
0089 }
0090 
0091 static int regulator_led_brightness_set(struct led_classdev *led_cdev,
0092                      enum led_brightness value)
0093 {
0094     struct regulator_led *led = to_regulator_led(led_cdev);
0095     int voltage;
0096     int ret = 0;
0097 
0098     mutex_lock(&led->mutex);
0099 
0100     if (value == LED_OFF) {
0101         regulator_led_disable(led);
0102         goto out;
0103     }
0104 
0105     if (led->cdev.max_brightness > 1) {
0106         voltage = led_regulator_get_voltage(led->vcc, value);
0107         dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n",
0108                 value, voltage);
0109 
0110         ret = regulator_set_voltage(led->vcc, voltage, voltage);
0111         if (ret != 0)
0112             dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n",
0113                 voltage, ret);
0114     }
0115 
0116     regulator_led_enable(led);
0117 
0118 out:
0119     mutex_unlock(&led->mutex);
0120     return ret;
0121 }
0122 
0123 static int regulator_led_probe(struct platform_device *pdev)
0124 {
0125     struct led_regulator_platform_data *pdata =
0126             dev_get_platdata(&pdev->dev);
0127     struct device *dev = &pdev->dev;
0128     struct led_init_data init_data = {};
0129     struct regulator_led *led;
0130     struct regulator *vcc;
0131     int ret = 0;
0132 
0133     vcc = devm_regulator_get_exclusive(dev, "vled");
0134     if (IS_ERR(vcc)) {
0135         dev_err(dev, "Cannot get vcc\n");
0136         return PTR_ERR(vcc);
0137     }
0138 
0139     led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
0140     if (led == NULL)
0141         return -ENOMEM;
0142 
0143     init_data.fwnode = dev->fwnode;
0144 
0145     led->cdev.max_brightness = led_regulator_get_max_brightness(vcc);
0146     /* Legacy platform data label assignment */
0147     if (pdata) {
0148         if (pdata->brightness > led->cdev.max_brightness) {
0149             dev_err(dev, "Invalid default brightness %d\n",
0150                 pdata->brightness);
0151             return -EINVAL;
0152         }
0153         led->cdev.brightness = pdata->brightness;
0154         init_data.default_label = pdata->name;
0155     }
0156 
0157     led->cdev.brightness_set_blocking = regulator_led_brightness_set;
0158     led->cdev.flags |= LED_CORE_SUSPENDRESUME;
0159     led->vcc = vcc;
0160 
0161     /* to handle correctly an already enabled regulator */
0162     if (regulator_is_enabled(led->vcc))
0163         led->enabled = 1;
0164 
0165     mutex_init(&led->mutex);
0166 
0167     platform_set_drvdata(pdev, led);
0168 
0169     ret = led_classdev_register_ext(dev, &led->cdev, &init_data);
0170     if (ret < 0)
0171         return ret;
0172 
0173     return 0;
0174 }
0175 
0176 static int regulator_led_remove(struct platform_device *pdev)
0177 {
0178     struct regulator_led *led = platform_get_drvdata(pdev);
0179 
0180     led_classdev_unregister(&led->cdev);
0181     regulator_led_disable(led);
0182     return 0;
0183 }
0184 
0185 static const struct of_device_id regulator_led_of_match[] = {
0186     { .compatible = "regulator-led", },
0187     {}
0188 };
0189 MODULE_DEVICE_TABLE(of, regulator_led_of_match);
0190 
0191 static struct platform_driver regulator_led_driver = {
0192     .driver = {
0193         .name  = "leds-regulator",
0194         .of_match_table = regulator_led_of_match,
0195     },
0196     .probe  = regulator_led_probe,
0197     .remove = regulator_led_remove,
0198 };
0199 
0200 module_platform_driver(regulator_led_driver);
0201 
0202 MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
0203 MODULE_DESCRIPTION("Regulator driven LED driver");
0204 MODULE_LICENSE("GPL");
0205 MODULE_ALIAS("platform:leds-regulator");