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(regulator: supply, selector: 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(regulator: supply, min_uV: voltage, max_uV: voltage); |
43 | if (ret < 0) |
44 | return 1; |
45 | |
46 | return regulator_count_voltages(regulator: 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(regulator: supply, selector: 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(regulator: 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(regulator: 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(supply: led->vcc, brightness: value); |
107 | dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n" , |
108 | value, voltage); |
109 | |
110 | ret = regulator_set_voltage(regulator: led->vcc, min_uV: voltage, max_uV: 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(lock: &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(dev: &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, id: "vled" ); |
134 | if (IS_ERR(ptr: vcc)) { |
135 | dev_err(dev, "Cannot get vcc\n" ); |
136 | return PTR_ERR(ptr: vcc); |
137 | } |
138 | |
139 | led = devm_kzalloc(dev, size: 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(supply: 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(regulator: led->vcc)) |
163 | led->enabled = 1; |
164 | |
165 | mutex_init(&led->mutex); |
166 | |
167 | platform_set_drvdata(pdev, data: led); |
168 | |
169 | ret = led_classdev_register_ext(parent: dev, led_cdev: &led->cdev, init_data: &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: &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 | |