Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * LED Class Core
4 : : *
5 : : * Copyright 2005-2006 Openedhand Ltd.
6 : : *
7 : : * Author: Richard Purdie <rpurdie@openedhand.com>
8 : : */
9 : :
10 : : #include <linux/kernel.h>
11 : : #include <linux/leds.h>
12 : : #include <linux/list.h>
13 : : #include <linux/module.h>
14 : : #include <linux/mutex.h>
15 : : #include <linux/of.h>
16 : : #include <linux/property.h>
17 : : #include <linux/rwsem.h>
18 : : #include <linux/slab.h>
19 : : #include <uapi/linux/uleds.h>
20 : : #include "leds.h"
21 : :
22 : : DECLARE_RWSEM(leds_list_lock);
23 : : EXPORT_SYMBOL_GPL(leds_list_lock);
24 : :
25 : : LIST_HEAD(leds_list);
26 : : EXPORT_SYMBOL_GPL(leds_list);
27 : :
28 : : const char * const led_colors[LED_COLOR_ID_MAX] = {
29 : : [LED_COLOR_ID_WHITE] = "white",
30 : : [LED_COLOR_ID_RED] = "red",
31 : : [LED_COLOR_ID_GREEN] = "green",
32 : : [LED_COLOR_ID_BLUE] = "blue",
33 : : [LED_COLOR_ID_AMBER] = "amber",
34 : : [LED_COLOR_ID_VIOLET] = "violet",
35 : : [LED_COLOR_ID_YELLOW] = "yellow",
36 : : [LED_COLOR_ID_IR] = "ir",
37 : : };
38 : : EXPORT_SYMBOL_GPL(led_colors);
39 : :
40 : : static int __led_set_brightness(struct led_classdev *led_cdev,
41 : : enum led_brightness value)
42 : : {
43 : 3 : if (!led_cdev->brightness_set)
44 : : return -ENOTSUPP;
45 : :
46 : 3 : led_cdev->brightness_set(led_cdev, value);
47 : :
48 : : return 0;
49 : : }
50 : :
51 : : static int __led_set_brightness_blocking(struct led_classdev *led_cdev,
52 : : enum led_brightness value)
53 : : {
54 : 0 : if (!led_cdev->brightness_set_blocking)
55 : : return -ENOTSUPP;
56 : :
57 : 0 : return led_cdev->brightness_set_blocking(led_cdev, value);
58 : : }
59 : :
60 : 0 : static void led_timer_function(struct timer_list *t)
61 : : {
62 : 0 : struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer);
63 : : unsigned long brightness;
64 : : unsigned long delay;
65 : :
66 : 0 : if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
67 : 0 : led_set_brightness_nosleep(led_cdev, LED_OFF);
68 : 0 : clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
69 : 0 : return;
70 : : }
71 : :
72 : 0 : if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP,
73 : : &led_cdev->work_flags)) {
74 : 0 : clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
75 : 0 : return;
76 : : }
77 : :
78 : : brightness = led_get_brightness(led_cdev);
79 : 0 : if (!brightness) {
80 : : /* Time to switch the LED on. */
81 : 0 : if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE,
82 : : &led_cdev->work_flags))
83 : 0 : brightness = led_cdev->new_blink_brightness;
84 : : else
85 : 0 : brightness = led_cdev->blink_brightness;
86 : 0 : delay = led_cdev->blink_delay_on;
87 : : } else {
88 : : /* Store the current brightness value to be able
89 : : * to restore it when the delay_off period is over.
90 : : */
91 : 0 : led_cdev->blink_brightness = brightness;
92 : : brightness = LED_OFF;
93 : 0 : delay = led_cdev->blink_delay_off;
94 : : }
95 : :
96 : 0 : led_set_brightness_nosleep(led_cdev, brightness);
97 : :
98 : : /* Return in next iteration if led is in one-shot mode and we are in
99 : : * the final blink state so that the led is toggled each delay_on +
100 : : * delay_off milliseconds in worst case.
101 : : */
102 : 0 : if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) {
103 : 0 : if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) {
104 : 0 : if (brightness)
105 : 0 : set_bit(LED_BLINK_ONESHOT_STOP,
106 : : &led_cdev->work_flags);
107 : : } else {
108 : 0 : if (!brightness)
109 : 0 : set_bit(LED_BLINK_ONESHOT_STOP,
110 : : &led_cdev->work_flags);
111 : : }
112 : : }
113 : :
114 : 0 : mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
115 : : }
116 : :
117 : 0 : static void set_brightness_delayed(struct work_struct *ws)
118 : : {
119 : : struct led_classdev *led_cdev =
120 : 0 : container_of(ws, struct led_classdev, set_brightness_work);
121 : : int ret = 0;
122 : :
123 : 0 : if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
124 : 0 : led_cdev->delayed_set_value = LED_OFF;
125 : 0 : led_stop_software_blink(led_cdev);
126 : : }
127 : :
128 : 0 : ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
129 : 0 : if (ret == -ENOTSUPP)
130 : 0 : ret = __led_set_brightness_blocking(led_cdev,
131 : 0 : led_cdev->delayed_set_value);
132 : 0 : if (ret < 0 &&
133 : : /* LED HW might have been unplugged, therefore don't warn */
134 : 0 : !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) &&
135 : : (led_cdev->flags & LED_HW_PLUGGABLE)))
136 : 0 : dev_err(led_cdev->dev,
137 : : "Setting an LED's brightness failed (%d)\n", ret);
138 : 0 : }
139 : :
140 : 0 : static void led_set_software_blink(struct led_classdev *led_cdev,
141 : : unsigned long delay_on,
142 : : unsigned long delay_off)
143 : : {
144 : : int current_brightness;
145 : :
146 : : current_brightness = led_get_brightness(led_cdev);
147 : 0 : if (current_brightness)
148 : 0 : led_cdev->blink_brightness = current_brightness;
149 : 0 : if (!led_cdev->blink_brightness)
150 : 0 : led_cdev->blink_brightness = led_cdev->max_brightness;
151 : :
152 : 0 : led_cdev->blink_delay_on = delay_on;
153 : 0 : led_cdev->blink_delay_off = delay_off;
154 : :
155 : : /* never on - just set to off */
156 : 0 : if (!delay_on) {
157 : 0 : led_set_brightness_nosleep(led_cdev, LED_OFF);
158 : 0 : return;
159 : : }
160 : :
161 : : /* never off - just set to brightness */
162 : 0 : if (!delay_off) {
163 : 0 : led_set_brightness_nosleep(led_cdev,
164 : 0 : led_cdev->blink_brightness);
165 : 0 : return;
166 : : }
167 : :
168 : 0 : set_bit(LED_BLINK_SW, &led_cdev->work_flags);
169 : 0 : mod_timer(&led_cdev->blink_timer, jiffies + 1);
170 : : }
171 : :
172 : :
173 : 0 : static void led_blink_setup(struct led_classdev *led_cdev,
174 : : unsigned long *delay_on,
175 : : unsigned long *delay_off)
176 : : {
177 : 0 : if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
178 : 0 : led_cdev->blink_set &&
179 : 0 : !led_cdev->blink_set(led_cdev, delay_on, delay_off))
180 : 0 : return;
181 : :
182 : : /* blink with 1 Hz as default if nothing specified */
183 : 0 : if (!*delay_on && !*delay_off)
184 : 0 : *delay_on = *delay_off = 500;
185 : :
186 : 0 : led_set_software_blink(led_cdev, *delay_on, *delay_off);
187 : : }
188 : :
189 : 3 : void led_init_core(struct led_classdev *led_cdev)
190 : : {
191 : 3 : INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
192 : :
193 : 3 : timer_setup(&led_cdev->blink_timer, led_timer_function, 0);
194 : 3 : }
195 : : EXPORT_SYMBOL_GPL(led_init_core);
196 : :
197 : 0 : void led_blink_set(struct led_classdev *led_cdev,
198 : : unsigned long *delay_on,
199 : : unsigned long *delay_off)
200 : : {
201 : 0 : del_timer_sync(&led_cdev->blink_timer);
202 : :
203 : 0 : clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
204 : 0 : clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
205 : 0 : clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
206 : :
207 : 0 : led_blink_setup(led_cdev, delay_on, delay_off);
208 : 0 : }
209 : : EXPORT_SYMBOL_GPL(led_blink_set);
210 : :
211 : 0 : void led_blink_set_oneshot(struct led_classdev *led_cdev,
212 : : unsigned long *delay_on,
213 : : unsigned long *delay_off,
214 : : int invert)
215 : : {
216 : 0 : if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
217 : : timer_pending(&led_cdev->blink_timer))
218 : 0 : return;
219 : :
220 : 0 : set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
221 : 0 : clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
222 : :
223 : 0 : if (invert)
224 : 0 : set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
225 : : else
226 : 0 : clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
227 : :
228 : 0 : led_blink_setup(led_cdev, delay_on, delay_off);
229 : : }
230 : : EXPORT_SYMBOL_GPL(led_blink_set_oneshot);
231 : :
232 : 0 : void led_stop_software_blink(struct led_classdev *led_cdev)
233 : : {
234 : 0 : del_timer_sync(&led_cdev->blink_timer);
235 : 0 : led_cdev->blink_delay_on = 0;
236 : 0 : led_cdev->blink_delay_off = 0;
237 : 0 : clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
238 : 0 : }
239 : : EXPORT_SYMBOL_GPL(led_stop_software_blink);
240 : :
241 : 3 : void led_set_brightness(struct led_classdev *led_cdev,
242 : : enum led_brightness brightness)
243 : : {
244 : : /*
245 : : * If software blink is active, delay brightness setting
246 : : * until the next timer tick.
247 : : */
248 : 3 : if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
249 : : /*
250 : : * If we need to disable soft blinking delegate this to the
251 : : * work queue task to avoid problems in case we are called
252 : : * from hard irq context.
253 : : */
254 : 0 : if (brightness == LED_OFF) {
255 : 0 : set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
256 : 0 : schedule_work(&led_cdev->set_brightness_work);
257 : : } else {
258 : 0 : set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
259 : : &led_cdev->work_flags);
260 : 0 : led_cdev->new_blink_brightness = brightness;
261 : : }
262 : 3 : return;
263 : : }
264 : :
265 : 3 : led_set_brightness_nosleep(led_cdev, brightness);
266 : : }
267 : : EXPORT_SYMBOL_GPL(led_set_brightness);
268 : :
269 : 3 : void led_set_brightness_nopm(struct led_classdev *led_cdev,
270 : : enum led_brightness value)
271 : : {
272 : : /* Use brightness_set op if available, it is guaranteed not to sleep */
273 : 3 : if (!__led_set_brightness(led_cdev, value))
274 : 3 : return;
275 : :
276 : : /* If brightness setting can sleep, delegate it to a work queue task */
277 : 0 : led_cdev->delayed_set_value = value;
278 : 0 : schedule_work(&led_cdev->set_brightness_work);
279 : : }
280 : : EXPORT_SYMBOL_GPL(led_set_brightness_nopm);
281 : :
282 : 3 : void led_set_brightness_nosleep(struct led_classdev *led_cdev,
283 : : enum led_brightness value)
284 : : {
285 : 3 : led_cdev->brightness = min(value, led_cdev->max_brightness);
286 : :
287 : 3 : if (led_cdev->flags & LED_SUSPENDED)
288 : 3 : return;
289 : :
290 : 3 : led_set_brightness_nopm(led_cdev, led_cdev->brightness);
291 : : }
292 : : EXPORT_SYMBOL_GPL(led_set_brightness_nosleep);
293 : :
294 : 0 : int led_set_brightness_sync(struct led_classdev *led_cdev,
295 : : enum led_brightness value)
296 : : {
297 : 0 : if (led_cdev->blink_delay_on || led_cdev->blink_delay_off)
298 : : return -EBUSY;
299 : :
300 : 0 : led_cdev->brightness = min(value, led_cdev->max_brightness);
301 : :
302 : 0 : if (led_cdev->flags & LED_SUSPENDED)
303 : : return 0;
304 : :
305 : 0 : return __led_set_brightness_blocking(led_cdev, led_cdev->brightness);
306 : : }
307 : : EXPORT_SYMBOL_GPL(led_set_brightness_sync);
308 : :
309 : 3 : int led_update_brightness(struct led_classdev *led_cdev)
310 : : {
311 : : int ret = 0;
312 : :
313 : 3 : if (led_cdev->brightness_get) {
314 : 3 : ret = led_cdev->brightness_get(led_cdev);
315 : 3 : if (ret >= 0) {
316 : 3 : led_cdev->brightness = ret;
317 : 3 : return 0;
318 : : }
319 : : }
320 : :
321 : 0 : return ret;
322 : : }
323 : : EXPORT_SYMBOL_GPL(led_update_brightness);
324 : :
325 : 0 : u32 *led_get_default_pattern(struct led_classdev *led_cdev, unsigned int *size)
326 : : {
327 : 0 : struct fwnode_handle *fwnode = led_cdev->dev->fwnode;
328 : : u32 *pattern;
329 : : int count;
330 : :
331 : : count = fwnode_property_count_u32(fwnode, "led-pattern");
332 : 0 : if (count < 0)
333 : : return NULL;
334 : :
335 : 0 : pattern = kcalloc(count, sizeof(*pattern), GFP_KERNEL);
336 : 0 : if (!pattern)
337 : : return NULL;
338 : :
339 : 0 : if (fwnode_property_read_u32_array(fwnode, "led-pattern", pattern, count)) {
340 : 0 : kfree(pattern);
341 : 0 : return NULL;
342 : : }
343 : :
344 : 0 : *size = count;
345 : :
346 : 0 : return pattern;
347 : : }
348 : : EXPORT_SYMBOL_GPL(led_get_default_pattern);
349 : :
350 : : /* Caller must ensure led_cdev->led_access held */
351 : 0 : void led_sysfs_disable(struct led_classdev *led_cdev)
352 : : {
353 : : lockdep_assert_held(&led_cdev->led_access);
354 : :
355 : 0 : led_cdev->flags |= LED_SYSFS_DISABLE;
356 : 0 : }
357 : : EXPORT_SYMBOL_GPL(led_sysfs_disable);
358 : :
359 : : /* Caller must ensure led_cdev->led_access held */
360 : 0 : void led_sysfs_enable(struct led_classdev *led_cdev)
361 : : {
362 : : lockdep_assert_held(&led_cdev->led_access);
363 : :
364 : 0 : led_cdev->flags &= ~LED_SYSFS_DISABLE;
365 : 0 : }
366 : : EXPORT_SYMBOL_GPL(led_sysfs_enable);
367 : :
368 : 3 : static void led_parse_fwnode_props(struct device *dev,
369 : : struct fwnode_handle *fwnode,
370 : : struct led_properties *props)
371 : : {
372 : : int ret;
373 : :
374 : 3 : if (!fwnode)
375 : : return;
376 : :
377 : 3 : if (fwnode_property_present(fwnode, "label")) {
378 : 3 : ret = fwnode_property_read_string(fwnode, "label", &props->label);
379 : 3 : if (ret)
380 : 0 : dev_err(dev, "Error parsing 'label' property (%d)\n", ret);
381 : : return;
382 : : }
383 : :
384 : 0 : if (fwnode_property_present(fwnode, "color")) {
385 : 0 : ret = fwnode_property_read_u32(fwnode, "color", &props->color);
386 : 0 : if (ret)
387 : 0 : dev_err(dev, "Error parsing 'color' property (%d)\n", ret);
388 : 0 : else if (props->color >= LED_COLOR_ID_MAX)
389 : 0 : dev_err(dev, "LED color identifier out of range\n");
390 : : else
391 : 0 : props->color_present = true;
392 : : }
393 : :
394 : :
395 : 0 : if (!fwnode_property_present(fwnode, "function"))
396 : : return;
397 : :
398 : 0 : ret = fwnode_property_read_string(fwnode, "function", &props->function);
399 : 0 : if (ret) {
400 : 0 : dev_err(dev,
401 : : "Error parsing 'function' property (%d)\n",
402 : : ret);
403 : : }
404 : :
405 : 0 : if (!fwnode_property_present(fwnode, "function-enumerator"))
406 : : return;
407 : :
408 : 0 : ret = fwnode_property_read_u32(fwnode, "function-enumerator",
409 : : &props->func_enum);
410 : 0 : if (ret) {
411 : 0 : dev_err(dev,
412 : : "Error parsing 'function-enumerator' property (%d)\n",
413 : : ret);
414 : : } else {
415 : 0 : props->func_enum_present = true;
416 : : }
417 : : }
418 : :
419 : 3 : int led_compose_name(struct device *dev, struct led_init_data *init_data,
420 : : char *led_classdev_name)
421 : : {
422 : 3 : struct led_properties props = {};
423 : 3 : struct fwnode_handle *fwnode = init_data->fwnode;
424 : 3 : const char *devicename = init_data->devicename;
425 : :
426 : 3 : if (!led_classdev_name)
427 : : return -EINVAL;
428 : :
429 : 3 : led_parse_fwnode_props(dev, fwnode, &props);
430 : :
431 : 3 : if (props.label) {
432 : : /*
433 : : * If init_data.devicename is NULL, then it indicates that
434 : : * DT label should be used as-is for LED class device name.
435 : : * Otherwise the label is prepended with devicename to compose
436 : : * the final LED class device name.
437 : : */
438 : 3 : if (!devicename) {
439 : 3 : strscpy(led_classdev_name, props.label,
440 : : LED_MAX_NAME_SIZE);
441 : : } else {
442 : 0 : snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
443 : : devicename, props.label);
444 : : }
445 : 0 : } else if (props.function || props.color_present) {
446 : : char tmp_buf[LED_MAX_NAME_SIZE];
447 : :
448 : 0 : if (props.func_enum_present) {
449 : 0 : snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d",
450 : 0 : props.color_present ? led_colors[props.color] : "",
451 : : props.function ?: "", props.func_enum);
452 : : } else {
453 : 0 : snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s",
454 : 0 : props.color_present ? led_colors[props.color] : "",
455 : : props.function ?: "");
456 : : }
457 : 0 : if (init_data->devname_mandatory) {
458 : 0 : snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
459 : : devicename, tmp_buf);
460 : : } else {
461 : 0 : strscpy(led_classdev_name, tmp_buf, LED_MAX_NAME_SIZE);
462 : :
463 : : }
464 : 0 : } else if (init_data->default_label) {
465 : 0 : if (!devicename) {
466 : 0 : dev_err(dev, "Legacy LED naming requires devicename segment");
467 : 0 : return -EINVAL;
468 : : }
469 : 0 : snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
470 : : devicename, init_data->default_label);
471 : 0 : } else if (is_of_node(fwnode)) {
472 : 0 : strscpy(led_classdev_name, to_of_node(fwnode)->name,
473 : : LED_MAX_NAME_SIZE);
474 : : } else
475 : : return -EINVAL;
476 : :
477 : : return 0;
478 : : }
479 : : EXPORT_SYMBOL_GPL(led_compose_name);
|