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