Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * LED Class Core
4 : : *
5 : : * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
6 : : * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com>
7 : : */
8 : :
9 : : #include <linux/ctype.h>
10 : : #include <linux/device.h>
11 : : #include <linux/err.h>
12 : : #include <linux/init.h>
13 : : #include <linux/kernel.h>
14 : : #include <linux/leds.h>
15 : : #include <linux/list.h>
16 : : #include <linux/module.h>
17 : : #include <linux/property.h>
18 : : #include <linux/slab.h>
19 : : #include <linux/spinlock.h>
20 : : #include <linux/timer.h>
21 : : #include <uapi/linux/uleds.h>
22 : : #include <linux/of.h>
23 : : #include "leds.h"
24 : :
25 : : static struct class *leds_class;
26 : :
27 : 0 : static ssize_t brightness_show(struct device *dev,
28 : : struct device_attribute *attr, char *buf)
29 : : {
30 : 0 : struct led_classdev *led_cdev = dev_get_drvdata(dev);
31 : :
32 : : /* no lock needed for this */
33 : 0 : led_update_brightness(led_cdev);
34 : :
35 : 0 : return sprintf(buf, "%u\n", led_cdev->brightness);
36 : : }
37 : :
38 : 0 : static ssize_t brightness_store(struct device *dev,
39 : : struct device_attribute *attr, const char *buf, size_t size)
40 : : {
41 : 0 : struct led_classdev *led_cdev = dev_get_drvdata(dev);
42 : 0 : unsigned long state;
43 : 0 : ssize_t ret;
44 : :
45 : 0 : mutex_lock(&led_cdev->led_access);
46 : :
47 [ # # ]: 0 : if (led_sysfs_is_disabled(led_cdev)) {
48 : 0 : ret = -EBUSY;
49 : 0 : goto unlock;
50 : : }
51 : :
52 : 0 : ret = kstrtoul(buf, 10, &state);
53 [ # # ]: 0 : if (ret)
54 : 0 : goto unlock;
55 : :
56 [ # # ]: 0 : if (state == LED_OFF)
57 : 0 : led_trigger_remove(led_cdev);
58 : 0 : led_set_brightness(led_cdev, state);
59 : 0 : flush_work(&led_cdev->set_brightness_work);
60 : :
61 : 0 : ret = size;
62 : 0 : unlock:
63 : 0 : mutex_unlock(&led_cdev->led_access);
64 : 0 : return ret;
65 : : }
66 : : static DEVICE_ATTR_RW(brightness);
67 : :
68 : 0 : static ssize_t max_brightness_show(struct device *dev,
69 : : struct device_attribute *attr, char *buf)
70 : : {
71 : 0 : struct led_classdev *led_cdev = dev_get_drvdata(dev);
72 : :
73 : 0 : return sprintf(buf, "%u\n", led_cdev->max_brightness);
74 : : }
75 : : static DEVICE_ATTR_RO(max_brightness);
76 : :
77 : : #ifdef CONFIG_LEDS_TRIGGERS
78 : : static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
79 : : static struct bin_attribute *led_trigger_bin_attrs[] = {
80 : : &bin_attr_trigger,
81 : : NULL,
82 : : };
83 : : static const struct attribute_group led_trigger_group = {
84 : : .bin_attrs = led_trigger_bin_attrs,
85 : : };
86 : : #endif
87 : :
88 : : static struct attribute *led_class_attrs[] = {
89 : : &dev_attr_brightness.attr,
90 : : &dev_attr_max_brightness.attr,
91 : : NULL,
92 : : };
93 : :
94 : : static const struct attribute_group led_group = {
95 : : .attrs = led_class_attrs,
96 : : };
97 : :
98 : : static const struct attribute_group *led_groups[] = {
99 : : &led_group,
100 : : #ifdef CONFIG_LEDS_TRIGGERS
101 : : &led_trigger_group,
102 : : #endif
103 : : NULL,
104 : : };
105 : :
106 : : #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
107 : : static ssize_t brightness_hw_changed_show(struct device *dev,
108 : : struct device_attribute *attr, char *buf)
109 : : {
110 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
111 : :
112 : : if (led_cdev->brightness_hw_changed == -1)
113 : : return -ENODATA;
114 : :
115 : : return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
116 : : }
117 : :
118 : : static DEVICE_ATTR_RO(brightness_hw_changed);
119 : :
120 : : static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
121 : : {
122 : : struct device *dev = led_cdev->dev;
123 : : int ret;
124 : :
125 : : ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
126 : : if (ret) {
127 : : dev_err(dev, "Error creating brightness_hw_changed\n");
128 : : return ret;
129 : : }
130 : :
131 : : led_cdev->brightness_hw_changed_kn =
132 : : sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
133 : : if (!led_cdev->brightness_hw_changed_kn) {
134 : : dev_err(dev, "Error getting brightness_hw_changed kn\n");
135 : : device_remove_file(dev, &dev_attr_brightness_hw_changed);
136 : : return -ENXIO;
137 : : }
138 : :
139 : : return 0;
140 : : }
141 : :
142 : : static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
143 : : {
144 : : sysfs_put(led_cdev->brightness_hw_changed_kn);
145 : : device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
146 : : }
147 : :
148 : : void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
149 : : enum led_brightness brightness)
150 : : {
151 : : if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
152 : : return;
153 : :
154 : : led_cdev->brightness_hw_changed = brightness;
155 : : sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
156 : : }
157 : : EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
158 : : #else
159 : : static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
160 : : {
161 : : return 0;
162 : : }
163 : : static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
164 : : {
165 : : }
166 : : #endif
167 : :
168 : : /**
169 : : * led_classdev_suspend - suspend an led_classdev.
170 : : * @led_cdev: the led_classdev to suspend.
171 : : */
172 : 0 : void led_classdev_suspend(struct led_classdev *led_cdev)
173 : : {
174 : 0 : led_cdev->flags |= LED_SUSPENDED;
175 : 0 : led_set_brightness_nopm(led_cdev, 0);
176 : 0 : }
177 : : EXPORT_SYMBOL_GPL(led_classdev_suspend);
178 : :
179 : : /**
180 : : * led_classdev_resume - resume an led_classdev.
181 : : * @led_cdev: the led_classdev to resume.
182 : : */
183 : 0 : void led_classdev_resume(struct led_classdev *led_cdev)
184 : : {
185 : 0 : led_set_brightness_nopm(led_cdev, led_cdev->brightness);
186 : :
187 [ # # ]: 0 : if (led_cdev->flash_resume)
188 : 0 : led_cdev->flash_resume(led_cdev);
189 : :
190 : 0 : led_cdev->flags &= ~LED_SUSPENDED;
191 : 0 : }
192 : : EXPORT_SYMBOL_GPL(led_classdev_resume);
193 : :
194 : : #ifdef CONFIG_PM_SLEEP
195 : 0 : static int led_suspend(struct device *dev)
196 : : {
197 [ # # ]: 0 : struct led_classdev *led_cdev = dev_get_drvdata(dev);
198 : :
199 [ # # ]: 0 : if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
200 : 0 : led_classdev_suspend(led_cdev);
201 : :
202 : 0 : return 0;
203 : : }
204 : :
205 : 0 : static int led_resume(struct device *dev)
206 : : {
207 [ # # ]: 0 : struct led_classdev *led_cdev = dev_get_drvdata(dev);
208 : :
209 [ # # ]: 0 : if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
210 : 0 : led_classdev_resume(led_cdev);
211 : :
212 : 0 : return 0;
213 : : }
214 : : #endif
215 : :
216 : : static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
217 : :
218 : : /**
219 : : * of_led_get() - request a LED device via the LED framework
220 : : * @np: device node to get the LED device from
221 : : * @index: the index of the LED
222 : : *
223 : : * Returns the LED device parsed from the phandle specified in the "leds"
224 : : * property of a device tree node or a negative error-code on failure.
225 : : */
226 : 0 : struct led_classdev *of_led_get(struct device_node *np, int index)
227 : : {
228 : 0 : struct device *led_dev;
229 : 0 : struct led_classdev *led_cdev;
230 : 0 : struct device_node *led_node;
231 : :
232 : 0 : led_node = of_parse_phandle(np, "leds", index);
233 : 0 : if (!led_node)
234 : 0 : return ERR_PTR(-ENOENT);
235 : :
236 : : led_dev = class_find_device_by_of_node(leds_class, led_node);
237 : : of_node_put(led_node);
238 : :
239 : : if (!led_dev)
240 : : return ERR_PTR(-EPROBE_DEFER);
241 : :
242 : : led_cdev = dev_get_drvdata(led_dev);
243 : :
244 : : if (!try_module_get(led_cdev->dev->parent->driver->owner))
245 : : return ERR_PTR(-ENODEV);
246 : :
247 : : return led_cdev;
248 : : }
249 : : EXPORT_SYMBOL_GPL(of_led_get);
250 : :
251 : : /**
252 : : * led_put() - release a LED device
253 : : * @led_cdev: LED device
254 : : */
255 : 0 : void led_put(struct led_classdev *led_cdev)
256 : : {
257 : 0 : module_put(led_cdev->dev->parent->driver->owner);
258 : 0 : }
259 : : EXPORT_SYMBOL_GPL(led_put);
260 : :
261 : : static void devm_led_release(struct device *dev, void *res)
262 : : {
263 : : struct led_classdev **p = res;
264 : :
265 : : led_put(*p);
266 : : }
267 : :
268 : : /**
269 : : * devm_of_led_get - Resource-managed request of a LED device
270 : : * @dev: LED consumer
271 : : * @index: index of the LED to obtain in the consumer
272 : : *
273 : : * The device node of the device is parse to find the request LED device.
274 : : * The LED device returned from this function is automatically released
275 : : * on driver detach.
276 : : *
277 : : * @return a pointer to a LED device or ERR_PTR(errno) on failure.
278 : : */
279 : 0 : struct led_classdev *__must_check devm_of_led_get(struct device *dev,
280 : : int index)
281 : : {
282 : 0 : struct led_classdev *led;
283 : 0 : struct led_classdev **dr;
284 : :
285 [ # # ]: 0 : if (!dev)
286 : : return ERR_PTR(-EINVAL);
287 : :
288 : : /* Not using device tree? */
289 : 0 : if (!IS_ENABLED(CONFIG_OF) || !dev->of_node)
290 : 0 : return ERR_PTR(-ENOTSUPP);
291 : :
292 : : led = of_led_get(dev->of_node, index);
293 : : if (IS_ERR(led))
294 : : return led;
295 : :
296 : : dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *),
297 : : GFP_KERNEL);
298 : : if (!dr) {
299 : : led_put(led);
300 : : return ERR_PTR(-ENOMEM);
301 : : }
302 : :
303 : : *dr = led;
304 : : devres_add(dev, dr);
305 : :
306 : : return led;
307 : : }
308 : : EXPORT_SYMBOL_GPL(devm_of_led_get);
309 : :
310 : 84 : static int led_classdev_next_name(const char *init_name, char *name,
311 : : size_t len)
312 : : {
313 : 84 : unsigned int i = 0;
314 : 84 : int ret = 0;
315 : 84 : struct device *dev;
316 : :
317 : 84 : strlcpy(name, init_name, len);
318 : :
319 [ + - - + ]: 168 : while ((ret < len) &&
320 : 84 : (dev = class_find_device_by_name(leds_class, name))) {
321 : 0 : put_device(dev);
322 : 0 : ret = snprintf(name, len, "%s_%u", init_name, ++i);
323 : : }
324 : :
325 [ + - ]: 84 : if (ret >= len)
326 : : return -ENOMEM;
327 : :
328 : 84 : return i;
329 : : }
330 : :
331 : : /**
332 : : * led_classdev_register_ext - register a new object of led_classdev class
333 : : * with init data.
334 : : *
335 : : * @parent: parent of LED device
336 : : * @led_cdev: the led_classdev structure for this device.
337 : : * @init_data: LED class device initialization data
338 : : */
339 : 84 : int led_classdev_register_ext(struct device *parent,
340 : : struct led_classdev *led_cdev,
341 : : struct led_init_data *init_data)
342 : : {
343 : 84 : char composed_name[LED_MAX_NAME_SIZE];
344 : 84 : char final_name[LED_MAX_NAME_SIZE];
345 : 84 : const char *proposed_name = composed_name;
346 : 84 : int ret;
347 : :
348 [ - + ]: 84 : if (init_data) {
349 [ # # # # ]: 0 : if (init_data->devname_mandatory && !init_data->devicename) {
350 : 0 : dev_err(parent, "Mandatory device name is missing");
351 : 0 : return -EINVAL;
352 : : }
353 : 0 : ret = led_compose_name(parent, init_data, composed_name);
354 [ # # ]: 0 : if (ret < 0)
355 : : return ret;
356 : : } else {
357 : 84 : proposed_name = led_cdev->name;
358 : : }
359 : :
360 : 84 : ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
361 [ + - ]: 84 : if (ret < 0)
362 : : return ret;
363 : :
364 : 84 : mutex_init(&led_cdev->led_access);
365 : 84 : mutex_lock(&led_cdev->led_access);
366 : 84 : led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
367 : : led_cdev, led_cdev->groups, "%s", final_name);
368 [ - + ]: 84 : if (IS_ERR(led_cdev->dev)) {
369 : 0 : mutex_unlock(&led_cdev->led_access);
370 : 0 : return PTR_ERR(led_cdev->dev);
371 : : }
372 [ - + - - ]: 84 : if (init_data && init_data->fwnode) {
373 : 0 : led_cdev->dev->fwnode = init_data->fwnode;
374 : 0 : led_cdev->dev->of_node = to_of_node(init_data->fwnode);
375 : : }
376 : :
377 [ - + ]: 84 : if (ret)
378 [ # # ]: 0 : dev_warn(parent, "Led %s renamed to %s due to name collision",
379 : : led_cdev->name, dev_name(led_cdev->dev));
380 : :
381 : 84 : if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
382 : : ret = led_add_brightness_hw_changed(led_cdev);
383 : : if (ret) {
384 : : device_unregister(led_cdev->dev);
385 : : led_cdev->dev = NULL;
386 : : mutex_unlock(&led_cdev->led_access);
387 : : return ret;
388 : : }
389 : : }
390 : :
391 : 84 : led_cdev->work_flags = 0;
392 : : #ifdef CONFIG_LEDS_TRIGGERS
393 : 84 : init_rwsem(&led_cdev->trigger_lock);
394 : : #endif
395 : : #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
396 : : led_cdev->brightness_hw_changed = -1;
397 : : #endif
398 : : /* add to the list of leds */
399 : 84 : down_write(&leds_list_lock);
400 : 84 : list_add_tail(&led_cdev->node, &leds_list);
401 : 84 : up_write(&leds_list_lock);
402 : :
403 [ - + ]: 84 : if (!led_cdev->max_brightness)
404 : 0 : led_cdev->max_brightness = LED_FULL;
405 : :
406 : 84 : led_update_brightness(led_cdev);
407 : :
408 : 84 : led_init_core(led_cdev);
409 : :
410 : : #ifdef CONFIG_LEDS_TRIGGERS
411 : 84 : led_trigger_set_default(led_cdev);
412 : : #endif
413 : :
414 : 84 : mutex_unlock(&led_cdev->led_access);
415 : :
416 : 84 : dev_dbg(parent, "Registered led device: %s\n",
417 : : led_cdev->name);
418 : :
419 : 84 : return 0;
420 : : }
421 : : EXPORT_SYMBOL_GPL(led_classdev_register_ext);
422 : :
423 : : /**
424 : : * led_classdev_unregister - unregisters a object of led_properties class.
425 : : * @led_cdev: the led device to unregister
426 : : *
427 : : * Unregisters a previously registered via led_classdev_register object.
428 : : */
429 : 0 : void led_classdev_unregister(struct led_classdev *led_cdev)
430 : : {
431 [ # # # # ]: 0 : if (IS_ERR_OR_NULL(led_cdev->dev))
432 : : return;
433 : :
434 : : #ifdef CONFIG_LEDS_TRIGGERS
435 : 0 : down_write(&led_cdev->trigger_lock);
436 [ # # ]: 0 : if (led_cdev->trigger)
437 : 0 : led_trigger_set(led_cdev, NULL);
438 : 0 : up_write(&led_cdev->trigger_lock);
439 : : #endif
440 : :
441 : 0 : led_cdev->flags |= LED_UNREGISTERING;
442 : :
443 : : /* Stop blinking */
444 : 0 : led_stop_software_blink(led_cdev);
445 : :
446 : 0 : led_set_brightness(led_cdev, LED_OFF);
447 : :
448 : 0 : flush_work(&led_cdev->set_brightness_work);
449 : :
450 : 0 : if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
451 : : led_remove_brightness_hw_changed(led_cdev);
452 : :
453 : 0 : device_unregister(led_cdev->dev);
454 : :
455 : 0 : down_write(&leds_list_lock);
456 : 0 : list_del(&led_cdev->node);
457 : 0 : up_write(&leds_list_lock);
458 : :
459 : 0 : mutex_destroy(&led_cdev->led_access);
460 : : }
461 : : EXPORT_SYMBOL_GPL(led_classdev_unregister);
462 : :
463 : 0 : static void devm_led_classdev_release(struct device *dev, void *res)
464 : : {
465 : 0 : led_classdev_unregister(*(struct led_classdev **)res);
466 : 0 : }
467 : :
468 : : /**
469 : : * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
470 : : *
471 : : * @parent: parent of LED device
472 : : * @led_cdev: the led_classdev structure for this device.
473 : : * @init_data: LED class device initialization data
474 : : */
475 : 0 : int devm_led_classdev_register_ext(struct device *parent,
476 : : struct led_classdev *led_cdev,
477 : : struct led_init_data *init_data)
478 : : {
479 : 0 : struct led_classdev **dr;
480 : 0 : int rc;
481 : :
482 : 0 : dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
483 [ # # ]: 0 : if (!dr)
484 : : return -ENOMEM;
485 : :
486 : 0 : rc = led_classdev_register_ext(parent, led_cdev, init_data);
487 [ # # ]: 0 : if (rc) {
488 : 0 : devres_free(dr);
489 : 0 : return rc;
490 : : }
491 : :
492 : 0 : *dr = led_cdev;
493 : 0 : devres_add(parent, dr);
494 : :
495 : 0 : return 0;
496 : : }
497 : : EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
498 : :
499 : 0 : static int devm_led_classdev_match(struct device *dev, void *res, void *data)
500 : : {
501 : 0 : struct led_classdev **p = res;
502 : :
503 [ # # # # : 0 : if (WARN_ON(!p || !*p))
# # # # ]
504 : : return 0;
505 : :
506 : 0 : return *p == data;
507 : : }
508 : :
509 : : /**
510 : : * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
511 : : * @parent: The device to unregister.
512 : : * @led_cdev: the led_classdev structure for this device.
513 : : */
514 : 0 : void devm_led_classdev_unregister(struct device *dev,
515 : : struct led_classdev *led_cdev)
516 : : {
517 [ # # ]: 0 : WARN_ON(devres_release(dev,
518 : : devm_led_classdev_release,
519 : : devm_led_classdev_match, led_cdev));
520 : 0 : }
521 : : EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
522 : :
523 : 28 : static int __init leds_init(void)
524 : : {
525 : 28 : leds_class = class_create(THIS_MODULE, "leds");
526 [ - + ]: 28 : if (IS_ERR(leds_class))
527 : 0 : return PTR_ERR(leds_class);
528 : 28 : leds_class->pm = &leds_class_dev_pm_ops;
529 : 28 : leds_class->dev_groups = led_groups;
530 : 28 : return 0;
531 : : }
532 : :
533 : 0 : static void __exit leds_exit(void)
534 : : {
535 : 0 : class_destroy(leds_class);
536 : 0 : }
537 : :
538 : : subsys_initcall(leds_init);
539 : : module_exit(leds_exit);
540 : :
541 : : MODULE_AUTHOR("John Lenz, Richard Purdie");
542 : : MODULE_LICENSE("GPL");
543 : : MODULE_DESCRIPTION("LED Class Interface");
|