1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * LEDs driver for GPIOs |
4 | * |
5 | * Copyright (C) 2007 8D Technologies inc. |
6 | * Raphael Assenat <raph@8d.com> |
7 | * Copyright (C) 2008 Freescale Semiconductor, Inc. |
8 | */ |
9 | #include <linux/container_of.h> |
10 | #include <linux/device.h> |
11 | #include <linux/err.h> |
12 | #include <linux/gpio.h> |
13 | #include <linux/gpio/consumer.h> |
14 | #include <linux/leds.h> |
15 | #include <linux/mod_devicetable.h> |
16 | #include <linux/module.h> |
17 | #include <linux/overflow.h> |
18 | #include <linux/pinctrl/consumer.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/property.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/types.h> |
23 | |
24 | #include "leds.h" |
25 | |
26 | struct gpio_led_data { |
27 | struct led_classdev cdev; |
28 | struct gpio_desc *gpiod; |
29 | u8 can_sleep; |
30 | u8 blinking; |
31 | gpio_blink_set_t platform_gpio_blink_set; |
32 | }; |
33 | |
34 | static inline struct gpio_led_data * |
35 | cdev_to_gpio_led_data(struct led_classdev *led_cdev) |
36 | { |
37 | return container_of(led_cdev, struct gpio_led_data, cdev); |
38 | } |
39 | |
40 | static void gpio_led_set(struct led_classdev *led_cdev, |
41 | enum led_brightness value) |
42 | { |
43 | struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); |
44 | int level; |
45 | |
46 | if (value == LED_OFF) |
47 | level = 0; |
48 | else |
49 | level = 1; |
50 | |
51 | if (led_dat->blinking) { |
52 | led_dat->platform_gpio_blink_set(led_dat->gpiod, level, |
53 | NULL, NULL); |
54 | led_dat->blinking = 0; |
55 | } else { |
56 | if (led_dat->can_sleep) |
57 | gpiod_set_value_cansleep(desc: led_dat->gpiod, value: level); |
58 | else |
59 | gpiod_set_value(desc: led_dat->gpiod, value: level); |
60 | } |
61 | } |
62 | |
63 | static int gpio_led_set_blocking(struct led_classdev *led_cdev, |
64 | enum led_brightness value) |
65 | { |
66 | gpio_led_set(led_cdev, value); |
67 | return 0; |
68 | } |
69 | |
70 | static int gpio_blink_set(struct led_classdev *led_cdev, |
71 | unsigned long *delay_on, unsigned long *delay_off) |
72 | { |
73 | struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); |
74 | |
75 | led_dat->blinking = 1; |
76 | return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK, |
77 | delay_on, delay_off); |
78 | } |
79 | |
80 | static int create_gpio_led(const struct gpio_led *template, |
81 | struct gpio_led_data *led_dat, struct device *parent, |
82 | struct fwnode_handle *fwnode, gpio_blink_set_t blink_set) |
83 | { |
84 | struct led_init_data init_data = {}; |
85 | struct pinctrl *pinctrl; |
86 | int ret, state; |
87 | |
88 | led_dat->cdev.default_trigger = template->default_trigger; |
89 | led_dat->can_sleep = gpiod_cansleep(desc: led_dat->gpiod); |
90 | if (!led_dat->can_sleep) |
91 | led_dat->cdev.brightness_set = gpio_led_set; |
92 | else |
93 | led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking; |
94 | led_dat->blinking = 0; |
95 | if (blink_set) { |
96 | led_dat->platform_gpio_blink_set = blink_set; |
97 | led_dat->cdev.blink_set = gpio_blink_set; |
98 | } |
99 | if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) { |
100 | state = gpiod_get_value_cansleep(desc: led_dat->gpiod); |
101 | if (state < 0) |
102 | return state; |
103 | } else { |
104 | state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); |
105 | } |
106 | led_dat->cdev.brightness = state; |
107 | led_dat->cdev.max_brightness = 1; |
108 | if (!template->retain_state_suspended) |
109 | led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; |
110 | if (template->panic_indicator) |
111 | led_dat->cdev.flags |= LED_PANIC_INDICATOR; |
112 | if (template->retain_state_shutdown) |
113 | led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN; |
114 | |
115 | ret = gpiod_direction_output(desc: led_dat->gpiod, value: state); |
116 | if (ret < 0) |
117 | return ret; |
118 | |
119 | if (template->name) { |
120 | led_dat->cdev.name = template->name; |
121 | ret = devm_led_classdev_register(parent, led_cdev: &led_dat->cdev); |
122 | } else { |
123 | init_data.fwnode = fwnode; |
124 | ret = devm_led_classdev_register_ext(parent, led_cdev: &led_dat->cdev, |
125 | init_data: &init_data); |
126 | } |
127 | |
128 | if (ret) |
129 | return ret; |
130 | |
131 | pinctrl = devm_pinctrl_get_select_default(dev: led_dat->cdev.dev); |
132 | ret = PTR_ERR_OR_ZERO(ptr: pinctrl); |
133 | /* pinctrl-%d not present, not an error */ |
134 | if (ret == -ENODEV) |
135 | ret = 0; |
136 | if (ret) { |
137 | dev_warn(led_dat->cdev.dev, "Failed to select %pfw pinctrl: %d\n" , |
138 | fwnode, ret); |
139 | } |
140 | |
141 | return ret; |
142 | } |
143 | |
144 | struct gpio_leds_priv { |
145 | int num_leds; |
146 | struct gpio_led_data leds[] __counted_by(num_leds); |
147 | }; |
148 | |
149 | static struct gpio_leds_priv *gpio_leds_create(struct device *dev) |
150 | { |
151 | struct fwnode_handle *child; |
152 | struct gpio_leds_priv *priv; |
153 | int count, ret; |
154 | |
155 | count = device_get_child_node_count(dev); |
156 | if (!count) |
157 | return ERR_PTR(error: -ENODEV); |
158 | |
159 | priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL); |
160 | if (!priv) |
161 | return ERR_PTR(error: -ENOMEM); |
162 | |
163 | device_for_each_child_node(dev, child) { |
164 | struct gpio_led_data *led_dat = &priv->leds[priv->num_leds]; |
165 | struct gpio_led led = {}; |
166 | |
167 | /* |
168 | * Acquire gpiod from DT with uninitialized label, which |
169 | * will be updated after LED class device is registered, |
170 | * Only then the final LED name is known. |
171 | */ |
172 | led.gpiod = devm_fwnode_gpiod_get(dev, fwnode: child, NULL, flags: GPIOD_ASIS, |
173 | NULL); |
174 | if (IS_ERR(ptr: led.gpiod)) { |
175 | fwnode_handle_put(fwnode: child); |
176 | return ERR_CAST(ptr: led.gpiod); |
177 | } |
178 | |
179 | led_dat->gpiod = led.gpiod; |
180 | |
181 | led.default_state = led_init_default_state_get(fwnode: child); |
182 | |
183 | if (fwnode_property_present(fwnode: child, propname: "retain-state-suspended" )) |
184 | led.retain_state_suspended = 1; |
185 | if (fwnode_property_present(fwnode: child, propname: "retain-state-shutdown" )) |
186 | led.retain_state_shutdown = 1; |
187 | if (fwnode_property_present(fwnode: child, propname: "panic-indicator" )) |
188 | led.panic_indicator = 1; |
189 | |
190 | ret = create_gpio_led(template: &led, led_dat, parent: dev, fwnode: child, NULL); |
191 | if (ret < 0) { |
192 | fwnode_handle_put(fwnode: child); |
193 | return ERR_PTR(error: ret); |
194 | } |
195 | /* Set gpiod label to match the corresponding LED name. */ |
196 | gpiod_set_consumer_name(desc: led_dat->gpiod, |
197 | name: led_dat->cdev.dev->kobj.name); |
198 | priv->num_leds++; |
199 | } |
200 | |
201 | return priv; |
202 | } |
203 | |
204 | static const struct of_device_id of_gpio_leds_match[] = { |
205 | { .compatible = "gpio-leds" , }, |
206 | {}, |
207 | }; |
208 | |
209 | MODULE_DEVICE_TABLE(of, of_gpio_leds_match); |
210 | |
211 | static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, |
212 | const struct gpio_led *template) |
213 | { |
214 | struct gpio_desc *gpiod; |
215 | unsigned long flags = GPIOF_OUT_INIT_LOW; |
216 | int ret; |
217 | |
218 | /* |
219 | * This means the LED does not come from the device tree |
220 | * or ACPI, so let's try just getting it by index from the |
221 | * device, this will hit the board file, if any and get |
222 | * the GPIO from there. |
223 | */ |
224 | gpiod = devm_gpiod_get_index_optional(dev, NULL, index: idx, flags: GPIOD_OUT_LOW); |
225 | if (IS_ERR(ptr: gpiod)) |
226 | return gpiod; |
227 | if (gpiod) { |
228 | gpiod_set_consumer_name(desc: gpiod, name: template->name); |
229 | return gpiod; |
230 | } |
231 | |
232 | /* |
233 | * This is the legacy code path for platform code that |
234 | * still uses GPIO numbers. Ultimately we would like to get |
235 | * rid of this block completely. |
236 | */ |
237 | |
238 | /* skip leds that aren't available */ |
239 | if (!gpio_is_valid(number: template->gpio)) |
240 | return ERR_PTR(error: -ENOENT); |
241 | |
242 | if (template->active_low) |
243 | flags |= GPIOF_ACTIVE_LOW; |
244 | |
245 | ret = devm_gpio_request_one(dev, gpio: template->gpio, flags, |
246 | label: template->name); |
247 | if (ret < 0) |
248 | return ERR_PTR(error: ret); |
249 | |
250 | gpiod = gpio_to_desc(gpio: template->gpio); |
251 | if (!gpiod) |
252 | return ERR_PTR(error: -EINVAL); |
253 | |
254 | return gpiod; |
255 | } |
256 | |
257 | static int gpio_led_probe(struct platform_device *pdev) |
258 | { |
259 | struct device *dev = &pdev->dev; |
260 | struct gpio_led_platform_data *pdata = dev_get_platdata(dev); |
261 | struct gpio_leds_priv *priv; |
262 | int i, ret; |
263 | |
264 | if (pdata && pdata->num_leds) { |
265 | priv = devm_kzalloc(dev, struct_size(priv, leds, pdata->num_leds), GFP_KERNEL); |
266 | if (!priv) |
267 | return -ENOMEM; |
268 | |
269 | priv->num_leds = pdata->num_leds; |
270 | for (i = 0; i < priv->num_leds; i++) { |
271 | const struct gpio_led *template = &pdata->leds[i]; |
272 | struct gpio_led_data *led_dat = &priv->leds[i]; |
273 | |
274 | if (template->gpiod) |
275 | led_dat->gpiod = template->gpiod; |
276 | else |
277 | led_dat->gpiod = |
278 | gpio_led_get_gpiod(dev, idx: i, template); |
279 | if (IS_ERR(ptr: led_dat->gpiod)) { |
280 | dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n" , |
281 | template->gpio, template->name); |
282 | continue; |
283 | } |
284 | |
285 | ret = create_gpio_led(template, led_dat, parent: dev, NULL, |
286 | blink_set: pdata->gpio_blink_set); |
287 | if (ret < 0) |
288 | return ret; |
289 | } |
290 | } else { |
291 | priv = gpio_leds_create(dev); |
292 | if (IS_ERR(ptr: priv)) |
293 | return PTR_ERR(ptr: priv); |
294 | } |
295 | |
296 | platform_set_drvdata(pdev, data: priv); |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static void gpio_led_shutdown(struct platform_device *pdev) |
302 | { |
303 | struct gpio_leds_priv *priv = platform_get_drvdata(pdev); |
304 | int i; |
305 | |
306 | for (i = 0; i < priv->num_leds; i++) { |
307 | struct gpio_led_data *led = &priv->leds[i]; |
308 | |
309 | if (!(led->cdev.flags & LED_RETAIN_AT_SHUTDOWN)) |
310 | gpio_led_set(led_cdev: &led->cdev, value: LED_OFF); |
311 | } |
312 | } |
313 | |
314 | static struct platform_driver gpio_led_driver = { |
315 | .probe = gpio_led_probe, |
316 | .shutdown = gpio_led_shutdown, |
317 | .driver = { |
318 | .name = "leds-gpio" , |
319 | .of_match_table = of_gpio_leds_match, |
320 | }, |
321 | }; |
322 | |
323 | module_platform_driver(gpio_led_driver); |
324 | |
325 | MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>" ); |
326 | MODULE_DESCRIPTION("GPIO LED driver" ); |
327 | MODULE_LICENSE("GPL" ); |
328 | MODULE_ALIAS("platform:leds-gpio" ); |
329 | |