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 DEFINE_MUTEX(leds_lookup_lock); |
26 | static LIST_HEAD(leds_lookup_list); |
27 | |
28 | static ssize_t brightness_show(struct device *dev, |
29 | struct device_attribute *attr, char *buf) |
30 | { |
31 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
32 | |
33 | /* no lock needed for this */ |
34 | led_update_brightness(led_cdev); |
35 | |
36 | return sprintf(buf, fmt: "%u\n" , led_cdev->brightness); |
37 | } |
38 | |
39 | static ssize_t brightness_store(struct device *dev, |
40 | struct device_attribute *attr, const char *buf, size_t size) |
41 | { |
42 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
43 | unsigned long state; |
44 | ssize_t ret; |
45 | |
46 | mutex_lock(&led_cdev->led_access); |
47 | |
48 | if (led_sysfs_is_disabled(led_cdev)) { |
49 | ret = -EBUSY; |
50 | goto unlock; |
51 | } |
52 | |
53 | ret = kstrtoul(s: buf, base: 10, res: &state); |
54 | if (ret) |
55 | goto unlock; |
56 | |
57 | if (state == LED_OFF) |
58 | led_trigger_remove(led_cdev); |
59 | led_set_brightness(led_cdev, brightness: state); |
60 | flush_work(work: &led_cdev->set_brightness_work); |
61 | |
62 | ret = size; |
63 | unlock: |
64 | mutex_unlock(lock: &led_cdev->led_access); |
65 | return ret; |
66 | } |
67 | static DEVICE_ATTR_RW(brightness); |
68 | |
69 | static ssize_t max_brightness_show(struct device *dev, |
70 | struct device_attribute *attr, char *buf) |
71 | { |
72 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
73 | |
74 | return sprintf(buf, fmt: "%u\n" , led_cdev->max_brightness); |
75 | } |
76 | static DEVICE_ATTR_RO(max_brightness); |
77 | |
78 | #ifdef CONFIG_LEDS_TRIGGERS |
79 | static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); |
80 | static struct bin_attribute *led_trigger_bin_attrs[] = { |
81 | &bin_attr_trigger, |
82 | NULL, |
83 | }; |
84 | static const struct attribute_group led_trigger_group = { |
85 | .bin_attrs = led_trigger_bin_attrs, |
86 | }; |
87 | #endif |
88 | |
89 | static struct attribute *led_class_attrs[] = { |
90 | &dev_attr_brightness.attr, |
91 | &dev_attr_max_brightness.attr, |
92 | NULL, |
93 | }; |
94 | |
95 | static const struct attribute_group led_group = { |
96 | .attrs = led_class_attrs, |
97 | }; |
98 | |
99 | static const struct attribute_group *led_groups[] = { |
100 | &led_group, |
101 | #ifdef CONFIG_LEDS_TRIGGERS |
102 | &led_trigger_group, |
103 | #endif |
104 | NULL, |
105 | }; |
106 | |
107 | #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED |
108 | static ssize_t brightness_hw_changed_show(struct device *dev, |
109 | struct device_attribute *attr, char *buf) |
110 | { |
111 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
112 | |
113 | if (led_cdev->brightness_hw_changed == -1) |
114 | return -ENODATA; |
115 | |
116 | return sprintf(buf, fmt: "%u\n" , led_cdev->brightness_hw_changed); |
117 | } |
118 | |
119 | static DEVICE_ATTR_RO(brightness_hw_changed); |
120 | |
121 | static int led_add_brightness_hw_changed(struct led_classdev *led_cdev) |
122 | { |
123 | struct device *dev = led_cdev->dev; |
124 | int ret; |
125 | |
126 | ret = device_create_file(device: dev, entry: &dev_attr_brightness_hw_changed); |
127 | if (ret) { |
128 | dev_err(dev, "Error creating brightness_hw_changed\n" ); |
129 | return ret; |
130 | } |
131 | |
132 | led_cdev->brightness_hw_changed_kn = |
133 | sysfs_get_dirent(parent: dev->kobj.sd, name: "brightness_hw_changed" ); |
134 | if (!led_cdev->brightness_hw_changed_kn) { |
135 | dev_err(dev, "Error getting brightness_hw_changed kn\n" ); |
136 | device_remove_file(dev, attr: &dev_attr_brightness_hw_changed); |
137 | return -ENXIO; |
138 | } |
139 | |
140 | return 0; |
141 | } |
142 | |
143 | static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) |
144 | { |
145 | sysfs_put(kn: led_cdev->brightness_hw_changed_kn); |
146 | device_remove_file(dev: led_cdev->dev, attr: &dev_attr_brightness_hw_changed); |
147 | } |
148 | |
149 | void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, unsigned int 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(kn: 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 | void led_classdev_suspend(struct led_classdev *led_cdev) |
173 | { |
174 | led_cdev->flags |= LED_SUSPENDED; |
175 | led_set_brightness_nopm(led_cdev, value: 0); |
176 | flush_work(work: &led_cdev->set_brightness_work); |
177 | } |
178 | EXPORT_SYMBOL_GPL(led_classdev_suspend); |
179 | |
180 | /** |
181 | * led_classdev_resume - resume an led_classdev. |
182 | * @led_cdev: the led_classdev to resume. |
183 | */ |
184 | void led_classdev_resume(struct led_classdev *led_cdev) |
185 | { |
186 | led_set_brightness_nopm(led_cdev, value: led_cdev->brightness); |
187 | |
188 | if (led_cdev->flash_resume) |
189 | led_cdev->flash_resume(led_cdev); |
190 | |
191 | led_cdev->flags &= ~LED_SUSPENDED; |
192 | } |
193 | EXPORT_SYMBOL_GPL(led_classdev_resume); |
194 | |
195 | #ifdef CONFIG_PM_SLEEP |
196 | static int led_suspend(struct device *dev) |
197 | { |
198 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
199 | |
200 | if (led_cdev->flags & LED_CORE_SUSPENDRESUME) |
201 | led_classdev_suspend(led_cdev); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static int led_resume(struct device *dev) |
207 | { |
208 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
209 | |
210 | if (led_cdev->flags & LED_CORE_SUSPENDRESUME) |
211 | led_classdev_resume(led_cdev); |
212 | |
213 | return 0; |
214 | } |
215 | #endif |
216 | |
217 | static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); |
218 | |
219 | static struct led_classdev *led_module_get(struct device *led_dev) |
220 | { |
221 | struct led_classdev *led_cdev; |
222 | |
223 | if (!led_dev) |
224 | return ERR_PTR(error: -EPROBE_DEFER); |
225 | |
226 | led_cdev = dev_get_drvdata(dev: led_dev); |
227 | |
228 | if (!try_module_get(module: led_cdev->dev->parent->driver->owner)) { |
229 | put_device(dev: led_cdev->dev); |
230 | return ERR_PTR(error: -ENODEV); |
231 | } |
232 | |
233 | return led_cdev; |
234 | } |
235 | |
236 | static const struct class leds_class = { |
237 | .name = "leds" , |
238 | .dev_groups = led_groups, |
239 | .pm = &leds_class_dev_pm_ops, |
240 | }; |
241 | |
242 | /** |
243 | * of_led_get() - request a LED device via the LED framework |
244 | * @np: device node to get the LED device from |
245 | * @index: the index of the LED |
246 | * |
247 | * Returns the LED device parsed from the phandle specified in the "leds" |
248 | * property of a device tree node or a negative error-code on failure. |
249 | */ |
250 | struct led_classdev *of_led_get(struct device_node *np, int index) |
251 | { |
252 | struct device *led_dev; |
253 | struct device_node *led_node; |
254 | |
255 | led_node = of_parse_phandle(np, phandle_name: "leds" , index); |
256 | if (!led_node) |
257 | return ERR_PTR(error: -ENOENT); |
258 | |
259 | led_dev = class_find_device_by_of_node(class: &leds_class, np: led_node); |
260 | of_node_put(node: led_node); |
261 | put_device(dev: led_dev); |
262 | |
263 | return led_module_get(led_dev); |
264 | } |
265 | EXPORT_SYMBOL_GPL(of_led_get); |
266 | |
267 | /** |
268 | * led_put() - release a LED device |
269 | * @led_cdev: LED device |
270 | */ |
271 | void led_put(struct led_classdev *led_cdev) |
272 | { |
273 | module_put(module: led_cdev->dev->parent->driver->owner); |
274 | put_device(dev: led_cdev->dev); |
275 | } |
276 | EXPORT_SYMBOL_GPL(led_put); |
277 | |
278 | static void devm_led_release(struct device *dev, void *res) |
279 | { |
280 | struct led_classdev **p = res; |
281 | |
282 | led_put(*p); |
283 | } |
284 | |
285 | static struct led_classdev *__devm_led_get(struct device *dev, struct led_classdev *led) |
286 | { |
287 | struct led_classdev **dr; |
288 | |
289 | dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), GFP_KERNEL); |
290 | if (!dr) { |
291 | led_put(led); |
292 | return ERR_PTR(error: -ENOMEM); |
293 | } |
294 | |
295 | *dr = led; |
296 | devres_add(dev, res: dr); |
297 | |
298 | return led; |
299 | } |
300 | |
301 | /** |
302 | * devm_of_led_get - Resource-managed request of a LED device |
303 | * @dev: LED consumer |
304 | * @index: index of the LED to obtain in the consumer |
305 | * |
306 | * The device node of the device is parse to find the request LED device. |
307 | * The LED device returned from this function is automatically released |
308 | * on driver detach. |
309 | * |
310 | * @return a pointer to a LED device or ERR_PTR(errno) on failure. |
311 | */ |
312 | struct led_classdev *__must_check devm_of_led_get(struct device *dev, |
313 | int index) |
314 | { |
315 | struct led_classdev *led; |
316 | |
317 | if (!dev) |
318 | return ERR_PTR(error: -EINVAL); |
319 | |
320 | led = of_led_get(dev->of_node, index); |
321 | if (IS_ERR(ptr: led)) |
322 | return led; |
323 | |
324 | return __devm_led_get(dev, led); |
325 | } |
326 | EXPORT_SYMBOL_GPL(devm_of_led_get); |
327 | |
328 | /** |
329 | * led_get() - request a LED device via the LED framework |
330 | * @dev: device for which to get the LED device |
331 | * @con_id: name of the LED from the device's point of view |
332 | * |
333 | * @return a pointer to a LED device or ERR_PTR(errno) on failure. |
334 | */ |
335 | struct led_classdev *led_get(struct device *dev, char *con_id) |
336 | { |
337 | struct led_lookup_data *lookup; |
338 | const char *provider = NULL; |
339 | struct device *led_dev; |
340 | |
341 | mutex_lock(&leds_lookup_lock); |
342 | list_for_each_entry(lookup, &leds_lookup_list, list) { |
343 | if (!strcmp(lookup->dev_id, dev_name(dev)) && |
344 | !strcmp(lookup->con_id, con_id)) { |
345 | provider = kstrdup_const(s: lookup->provider, GFP_KERNEL); |
346 | break; |
347 | } |
348 | } |
349 | mutex_unlock(lock: &leds_lookup_lock); |
350 | |
351 | if (!provider) |
352 | return ERR_PTR(error: -ENOENT); |
353 | |
354 | led_dev = class_find_device_by_name(class: &leds_class, name: provider); |
355 | kfree_const(x: provider); |
356 | |
357 | return led_module_get(led_dev); |
358 | } |
359 | EXPORT_SYMBOL_GPL(led_get); |
360 | |
361 | /** |
362 | * devm_led_get() - request a LED device via the LED framework |
363 | * @dev: device for which to get the LED device |
364 | * @con_id: name of the LED from the device's point of view |
365 | * |
366 | * The LED device returned from this function is automatically released |
367 | * on driver detach. |
368 | * |
369 | * @return a pointer to a LED device or ERR_PTR(errno) on failure. |
370 | */ |
371 | struct led_classdev *devm_led_get(struct device *dev, char *con_id) |
372 | { |
373 | struct led_classdev *led; |
374 | |
375 | led = led_get(dev, con_id); |
376 | if (IS_ERR(ptr: led)) |
377 | return led; |
378 | |
379 | return __devm_led_get(dev, led); |
380 | } |
381 | EXPORT_SYMBOL_GPL(devm_led_get); |
382 | |
383 | /** |
384 | * led_add_lookup() - Add a LED lookup table entry |
385 | * @led_lookup: the lookup table entry to add |
386 | * |
387 | * Add a LED lookup table entry. On systems without devicetree the lookup table |
388 | * is used by led_get() to find LEDs. |
389 | */ |
390 | void led_add_lookup(struct led_lookup_data *led_lookup) |
391 | { |
392 | mutex_lock(&leds_lookup_lock); |
393 | list_add_tail(new: &led_lookup->list, head: &leds_lookup_list); |
394 | mutex_unlock(lock: &leds_lookup_lock); |
395 | } |
396 | EXPORT_SYMBOL_GPL(led_add_lookup); |
397 | |
398 | /** |
399 | * led_remove_lookup() - Remove a LED lookup table entry |
400 | * @led_lookup: the lookup table entry to remove |
401 | */ |
402 | void led_remove_lookup(struct led_lookup_data *led_lookup) |
403 | { |
404 | mutex_lock(&leds_lookup_lock); |
405 | list_del(entry: &led_lookup->list); |
406 | mutex_unlock(lock: &leds_lookup_lock); |
407 | } |
408 | EXPORT_SYMBOL_GPL(led_remove_lookup); |
409 | |
410 | /** |
411 | * devm_of_led_get_optional - Resource-managed request of an optional LED device |
412 | * @dev: LED consumer |
413 | * @index: index of the LED to obtain in the consumer |
414 | * |
415 | * The device node of the device is parsed to find the requested LED device. |
416 | * The LED device returned from this function is automatically released |
417 | * on driver detach. |
418 | * |
419 | * @return a pointer to a LED device, ERR_PTR(errno) on failure and NULL if the |
420 | * led was not found. |
421 | */ |
422 | struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev, |
423 | int index) |
424 | { |
425 | struct led_classdev *led; |
426 | |
427 | led = devm_of_led_get(dev, index); |
428 | if (IS_ERR(ptr: led) && PTR_ERR(ptr: led) == -ENOENT) |
429 | return NULL; |
430 | |
431 | return led; |
432 | } |
433 | EXPORT_SYMBOL_GPL(devm_of_led_get_optional); |
434 | |
435 | static int led_classdev_next_name(const char *init_name, char *name, |
436 | size_t len) |
437 | { |
438 | unsigned int i = 0; |
439 | int ret = 0; |
440 | struct device *dev; |
441 | |
442 | strscpy(name, init_name, len); |
443 | |
444 | while ((ret < len) && |
445 | (dev = class_find_device_by_name(class: &leds_class, name))) { |
446 | put_device(dev); |
447 | ret = snprintf(buf: name, size: len, fmt: "%s_%u" , init_name, ++i); |
448 | } |
449 | |
450 | if (ret >= len) |
451 | return -ENOMEM; |
452 | |
453 | return i; |
454 | } |
455 | |
456 | /** |
457 | * led_classdev_register_ext - register a new object of led_classdev class |
458 | * with init data. |
459 | * |
460 | * @parent: parent of LED device |
461 | * @led_cdev: the led_classdev structure for this device. |
462 | * @init_data: LED class device initialization data |
463 | */ |
464 | int led_classdev_register_ext(struct device *parent, |
465 | struct led_classdev *led_cdev, |
466 | struct led_init_data *init_data) |
467 | { |
468 | char composed_name[LED_MAX_NAME_SIZE]; |
469 | char final_name[LED_MAX_NAME_SIZE]; |
470 | const char *proposed_name = composed_name; |
471 | int ret; |
472 | |
473 | if (init_data) { |
474 | if (init_data->devname_mandatory && !init_data->devicename) { |
475 | dev_err(parent, "Mandatory device name is missing" ); |
476 | return -EINVAL; |
477 | } |
478 | ret = led_compose_name(dev: parent, init_data, led_classdev_name: composed_name); |
479 | if (ret < 0) |
480 | return ret; |
481 | |
482 | if (init_data->fwnode) { |
483 | fwnode_property_read_string(fwnode: init_data->fwnode, |
484 | propname: "linux,default-trigger" , |
485 | val: &led_cdev->default_trigger); |
486 | |
487 | if (fwnode_property_present(fwnode: init_data->fwnode, |
488 | propname: "retain-state-shutdown" )) |
489 | led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; |
490 | |
491 | fwnode_property_read_u32(fwnode: init_data->fwnode, |
492 | propname: "max-brightness" , |
493 | val: &led_cdev->max_brightness); |
494 | |
495 | if (fwnode_property_present(fwnode: init_data->fwnode, propname: "color" )) |
496 | fwnode_property_read_u32(fwnode: init_data->fwnode, propname: "color" , |
497 | val: &led_cdev->color); |
498 | } |
499 | } else { |
500 | proposed_name = led_cdev->name; |
501 | } |
502 | |
503 | ret = led_classdev_next_name(init_name: proposed_name, name: final_name, len: sizeof(final_name)); |
504 | if (ret < 0) |
505 | return ret; |
506 | |
507 | if (led_cdev->color >= LED_COLOR_ID_MAX) |
508 | dev_warn(parent, "LED %s color identifier out of range\n" , final_name); |
509 | |
510 | mutex_init(&led_cdev->led_access); |
511 | mutex_lock(&led_cdev->led_access); |
512 | led_cdev->dev = device_create_with_groups(cls: &leds_class, parent, devt: 0, |
513 | drvdata: led_cdev, groups: led_cdev->groups, fmt: "%s" , final_name); |
514 | if (IS_ERR(ptr: led_cdev->dev)) { |
515 | mutex_unlock(lock: &led_cdev->led_access); |
516 | return PTR_ERR(ptr: led_cdev->dev); |
517 | } |
518 | if (init_data && init_data->fwnode) |
519 | device_set_node(dev: led_cdev->dev, fwnode: init_data->fwnode); |
520 | |
521 | if (ret) |
522 | dev_warn(parent, "Led %s renamed to %s due to name collision" , |
523 | proposed_name, dev_name(led_cdev->dev)); |
524 | |
525 | if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { |
526 | ret = led_add_brightness_hw_changed(led_cdev); |
527 | if (ret) { |
528 | device_unregister(dev: led_cdev->dev); |
529 | led_cdev->dev = NULL; |
530 | mutex_unlock(lock: &led_cdev->led_access); |
531 | return ret; |
532 | } |
533 | } |
534 | |
535 | led_cdev->work_flags = 0; |
536 | #ifdef CONFIG_LEDS_TRIGGERS |
537 | init_rwsem(&led_cdev->trigger_lock); |
538 | #endif |
539 | #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED |
540 | led_cdev->brightness_hw_changed = -1; |
541 | #endif |
542 | /* add to the list of leds */ |
543 | down_write(sem: &leds_list_lock); |
544 | list_add_tail(new: &led_cdev->node, head: &leds_list); |
545 | up_write(sem: &leds_list_lock); |
546 | |
547 | if (!led_cdev->max_brightness) |
548 | led_cdev->max_brightness = LED_FULL; |
549 | |
550 | led_update_brightness(led_cdev); |
551 | |
552 | led_init_core(led_cdev); |
553 | |
554 | #ifdef CONFIG_LEDS_TRIGGERS |
555 | /* |
556 | * If no default trigger was given and hw_control_trigger is set, |
557 | * make it the default trigger. |
558 | */ |
559 | if (!led_cdev->default_trigger && led_cdev->hw_control_trigger) |
560 | led_cdev->default_trigger = led_cdev->hw_control_trigger; |
561 | led_trigger_set_default(led_cdev); |
562 | #endif |
563 | |
564 | mutex_unlock(lock: &led_cdev->led_access); |
565 | |
566 | dev_dbg(parent, "Registered led device: %s\n" , |
567 | led_cdev->name); |
568 | |
569 | return 0; |
570 | } |
571 | EXPORT_SYMBOL_GPL(led_classdev_register_ext); |
572 | |
573 | /** |
574 | * led_classdev_unregister - unregisters a object of led_properties class. |
575 | * @led_cdev: the led device to unregister |
576 | * |
577 | * Unregisters a previously registered via led_classdev_register object. |
578 | */ |
579 | void led_classdev_unregister(struct led_classdev *led_cdev) |
580 | { |
581 | if (IS_ERR_OR_NULL(ptr: led_cdev->dev)) |
582 | return; |
583 | |
584 | #ifdef CONFIG_LEDS_TRIGGERS |
585 | down_write(sem: &led_cdev->trigger_lock); |
586 | if (led_cdev->trigger) |
587 | led_trigger_set(led_cdev, NULL); |
588 | up_write(sem: &led_cdev->trigger_lock); |
589 | #endif |
590 | |
591 | led_cdev->flags |= LED_UNREGISTERING; |
592 | |
593 | /* Stop blinking */ |
594 | led_stop_software_blink(led_cdev); |
595 | |
596 | if (!(led_cdev->flags & LED_RETAIN_AT_SHUTDOWN)) |
597 | led_set_brightness(led_cdev, brightness: LED_OFF); |
598 | |
599 | flush_work(work: &led_cdev->set_brightness_work); |
600 | |
601 | if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) |
602 | led_remove_brightness_hw_changed(led_cdev); |
603 | |
604 | device_unregister(dev: led_cdev->dev); |
605 | |
606 | down_write(sem: &leds_list_lock); |
607 | list_del(entry: &led_cdev->node); |
608 | up_write(sem: &leds_list_lock); |
609 | |
610 | mutex_destroy(lock: &led_cdev->led_access); |
611 | } |
612 | EXPORT_SYMBOL_GPL(led_classdev_unregister); |
613 | |
614 | static void devm_led_classdev_release(struct device *dev, void *res) |
615 | { |
616 | led_classdev_unregister(*(struct led_classdev **)res); |
617 | } |
618 | |
619 | /** |
620 | * devm_led_classdev_register_ext - resource managed led_classdev_register_ext() |
621 | * |
622 | * @parent: parent of LED device |
623 | * @led_cdev: the led_classdev structure for this device. |
624 | * @init_data: LED class device initialization data |
625 | */ |
626 | int devm_led_classdev_register_ext(struct device *parent, |
627 | struct led_classdev *led_cdev, |
628 | struct led_init_data *init_data) |
629 | { |
630 | struct led_classdev **dr; |
631 | int rc; |
632 | |
633 | dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL); |
634 | if (!dr) |
635 | return -ENOMEM; |
636 | |
637 | rc = led_classdev_register_ext(parent, led_cdev, init_data); |
638 | if (rc) { |
639 | devres_free(res: dr); |
640 | return rc; |
641 | } |
642 | |
643 | *dr = led_cdev; |
644 | devres_add(dev: parent, res: dr); |
645 | |
646 | return 0; |
647 | } |
648 | EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext); |
649 | |
650 | static int devm_led_classdev_match(struct device *dev, void *res, void *data) |
651 | { |
652 | struct led_classdev **p = res; |
653 | |
654 | if (WARN_ON(!p || !*p)) |
655 | return 0; |
656 | |
657 | return *p == data; |
658 | } |
659 | |
660 | /** |
661 | * devm_led_classdev_unregister() - resource managed led_classdev_unregister() |
662 | * @dev: The device to unregister. |
663 | * @led_cdev: the led_classdev structure for this device. |
664 | */ |
665 | void devm_led_classdev_unregister(struct device *dev, |
666 | struct led_classdev *led_cdev) |
667 | { |
668 | WARN_ON(devres_release(dev, |
669 | devm_led_classdev_release, |
670 | devm_led_classdev_match, led_cdev)); |
671 | } |
672 | EXPORT_SYMBOL_GPL(devm_led_classdev_unregister); |
673 | |
674 | static int __init leds_init(void) |
675 | { |
676 | return class_register(class: &leds_class); |
677 | } |
678 | |
679 | static void __exit leds_exit(void) |
680 | { |
681 | class_unregister(class: &leds_class); |
682 | } |
683 | |
684 | subsys_initcall(leds_init); |
685 | module_exit(leds_exit); |
686 | |
687 | MODULE_AUTHOR("John Lenz, Richard Purdie" ); |
688 | MODULE_LICENSE("GPL" ); |
689 | MODULE_DESCRIPTION("LED Class Interface" ); |
690 | |