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 "leds.h"
23 : :
24 : : static struct class *leds_class;
25 : :
26 : 0 : static ssize_t brightness_show(struct device *dev,
27 : : struct device_attribute *attr, char *buf)
28 : : {
29 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
30 : :
31 : : /* no lock needed for this */
32 : 0 : led_update_brightness(led_cdev);
33 : :
34 : 0 : return sprintf(buf, "%u\n", led_cdev->brightness);
35 : : }
36 : :
37 : 0 : static ssize_t brightness_store(struct device *dev,
38 : : struct device_attribute *attr, const char *buf, size_t size)
39 : : {
40 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
41 : : unsigned long state;
42 : : ssize_t ret;
43 : :
44 : 0 : mutex_lock(&led_cdev->led_access);
45 : :
46 [ # # ]: 0 : if (led_sysfs_is_disabled(led_cdev)) {
47 : : ret = -EBUSY;
48 : : goto unlock;
49 : : }
50 : :
51 : : ret = kstrtoul(buf, 10, &state);
52 [ # # ]: 0 : if (ret)
53 : : goto unlock;
54 : :
55 [ # # ]: 0 : if (state == LED_OFF)
56 : 0 : led_trigger_remove(led_cdev);
57 : 0 : led_set_brightness(led_cdev, state);
58 : 0 : flush_work(&led_cdev->set_brightness_work);
59 : :
60 : 0 : ret = size;
61 : : unlock:
62 : 0 : mutex_unlock(&led_cdev->led_access);
63 : 0 : return ret;
64 : : }
65 : : static DEVICE_ATTR_RW(brightness);
66 : :
67 : 0 : static ssize_t max_brightness_show(struct device *dev,
68 : : struct device_attribute *attr, char *buf)
69 : : {
70 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
71 : :
72 : 0 : return sprintf(buf, "%u\n", led_cdev->max_brightness);
73 : : }
74 : : static DEVICE_ATTR_RO(max_brightness);
75 : :
76 : : #ifdef CONFIG_LEDS_TRIGGERS
77 : : static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
78 : : static struct attribute *led_trigger_attrs[] = {
79 : : &dev_attr_trigger.attr,
80 : : NULL,
81 : : };
82 : : static const struct attribute_group led_trigger_group = {
83 : : .attrs = led_trigger_attrs,
84 : : };
85 : : #endif
86 : :
87 : : static struct attribute *led_class_attrs[] = {
88 : : &dev_attr_brightness.attr,
89 : : &dev_attr_max_brightness.attr,
90 : : NULL,
91 : : };
92 : :
93 : : static const struct attribute_group led_group = {
94 : : .attrs = led_class_attrs,
95 : : };
96 : :
97 : : static const struct attribute_group *led_groups[] = {
98 : : &led_group,
99 : : #ifdef CONFIG_LEDS_TRIGGERS
100 : : &led_trigger_group,
101 : : #endif
102 : : NULL,
103 : : };
104 : :
105 : : #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
106 : : static ssize_t brightness_hw_changed_show(struct device *dev,
107 : : struct device_attribute *attr, char *buf)
108 : : {
109 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
110 : :
111 : : if (led_cdev->brightness_hw_changed == -1)
112 : : return -ENODATA;
113 : :
114 : : return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
115 : : }
116 : :
117 : : static DEVICE_ATTR_RO(brightness_hw_changed);
118 : :
119 : : static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
120 : : {
121 : : struct device *dev = led_cdev->dev;
122 : : int ret;
123 : :
124 : : ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
125 : : if (ret) {
126 : : dev_err(dev, "Error creating brightness_hw_changed\n");
127 : : return ret;
128 : : }
129 : :
130 : : led_cdev->brightness_hw_changed_kn =
131 : : sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
132 : : if (!led_cdev->brightness_hw_changed_kn) {
133 : : dev_err(dev, "Error getting brightness_hw_changed kn\n");
134 : : device_remove_file(dev, &dev_attr_brightness_hw_changed);
135 : : return -ENXIO;
136 : : }
137 : :
138 : : return 0;
139 : : }
140 : :
141 : : static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
142 : : {
143 : : sysfs_put(led_cdev->brightness_hw_changed_kn);
144 : : device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
145 : : }
146 : :
147 : : void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
148 : : enum led_brightness brightness)
149 : : {
150 : : if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
151 : : return;
152 : :
153 : : led_cdev->brightness_hw_changed = brightness;
154 : : sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
155 : : }
156 : : EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
157 : : #else
158 : : static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
159 : : {
160 : : return 0;
161 : : }
162 : : static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
163 : : {
164 : : }
165 : : #endif
166 : :
167 : : /**
168 : : * led_classdev_suspend - suspend an led_classdev.
169 : : * @led_cdev: the led_classdev to suspend.
170 : : */
171 : 0 : void led_classdev_suspend(struct led_classdev *led_cdev)
172 : : {
173 : 0 : led_cdev->flags |= LED_SUSPENDED;
174 : 0 : led_set_brightness_nopm(led_cdev, 0);
175 : 0 : flush_work(&led_cdev->set_brightness_work);
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 : : static int led_suspend(struct device *dev)
196 : : {
197 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
198 : :
199 : : if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
200 : : led_classdev_suspend(led_cdev);
201 : :
202 : : return 0;
203 : : }
204 : :
205 : : static int led_resume(struct device *dev)
206 : : {
207 : : struct led_classdev *led_cdev = dev_get_drvdata(dev);
208 : :
209 : : if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
210 : : led_classdev_resume(led_cdev);
211 : :
212 : : return 0;
213 : : }
214 : : #endif
215 : :
216 : : static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
217 : :
218 : 1863 : static int led_classdev_next_name(const char *init_name, char *name,
219 : : size_t len)
220 : : {
221 : : unsigned int i = 0;
222 : : int ret = 0;
223 : : struct device *dev;
224 : :
225 : 1863 : strlcpy(name, init_name, len);
226 : :
227 [ + - - + ]: 5589 : while ((ret < len) &&
228 : 1863 : (dev = class_find_device_by_name(leds_class, name))) {
229 : 0 : put_device(dev);
230 : 0 : ret = snprintf(name, len, "%s_%u", init_name, ++i);
231 : : }
232 : :
233 [ + - ]: 1863 : if (ret >= len)
234 : : return -ENOMEM;
235 : :
236 : 1863 : return i;
237 : : }
238 : :
239 : : /**
240 : : * led_classdev_register_ext - register a new object of led_classdev class
241 : : * with init data.
242 : : *
243 : : * @parent: parent of LED device
244 : : * @led_cdev: the led_classdev structure for this device.
245 : : * @init_data: LED class device initialization data
246 : : */
247 : 1863 : int led_classdev_register_ext(struct device *parent,
248 : : struct led_classdev *led_cdev,
249 : : struct led_init_data *init_data)
250 : : {
251 : : char composed_name[LED_MAX_NAME_SIZE];
252 : : char final_name[LED_MAX_NAME_SIZE];
253 : : const char *proposed_name = composed_name;
254 : : int ret;
255 : :
256 [ + + ]: 1863 : if (init_data) {
257 [ - + # # ]: 414 : if (init_data->devname_mandatory && !init_data->devicename) {
258 : 0 : dev_err(parent, "Mandatory device name is missing");
259 : 0 : return -EINVAL;
260 : : }
261 : 414 : ret = led_compose_name(parent, init_data, composed_name);
262 [ + - ]: 414 : if (ret < 0)
263 : : return ret;
264 : : } else {
265 : 1449 : proposed_name = led_cdev->name;
266 : : }
267 : :
268 : 1863 : ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
269 [ + - ]: 1863 : if (ret < 0)
270 : : return ret;
271 : :
272 : 1863 : mutex_init(&led_cdev->led_access);
273 : 1863 : mutex_lock(&led_cdev->led_access);
274 : 1863 : led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
275 : : led_cdev, led_cdev->groups, "%s", final_name);
276 [ - + ]: 1863 : if (IS_ERR(led_cdev->dev)) {
277 : 0 : mutex_unlock(&led_cdev->led_access);
278 : 0 : return PTR_ERR(led_cdev->dev);
279 : : }
280 [ + + + - ]: 1863 : if (init_data && init_data->fwnode)
281 : 414 : led_cdev->dev->fwnode = init_data->fwnode;
282 : :
283 [ - + ]: 1863 : if (ret)
284 : 0 : dev_warn(parent, "Led %s renamed to %s due to name collision",
285 : : proposed_name, dev_name(led_cdev->dev));
286 : :
287 : : if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
288 : : ret = led_add_brightness_hw_changed(led_cdev);
289 : : if (ret) {
290 : : device_unregister(led_cdev->dev);
291 : : led_cdev->dev = NULL;
292 : : mutex_unlock(&led_cdev->led_access);
293 : : return ret;
294 : : }
295 : : }
296 : :
297 : 1863 : led_cdev->work_flags = 0;
298 : : #ifdef CONFIG_LEDS_TRIGGERS
299 : 1863 : init_rwsem(&led_cdev->trigger_lock);
300 : : #endif
301 : : #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
302 : : led_cdev->brightness_hw_changed = -1;
303 : : #endif
304 : : /* add to the list of leds */
305 : 1863 : down_write(&leds_list_lock);
306 : 1863 : list_add_tail(&led_cdev->node, &leds_list);
307 : 1863 : up_write(&leds_list_lock);
308 : :
309 [ + + ]: 1863 : if (!led_cdev->max_brightness)
310 : 828 : led_cdev->max_brightness = LED_FULL;
311 : :
312 : 1863 : led_update_brightness(led_cdev);
313 : :
314 : 1863 : led_init_core(led_cdev);
315 : :
316 : : #ifdef CONFIG_LEDS_TRIGGERS
317 : 1863 : led_trigger_set_default(led_cdev);
318 : : #endif
319 : :
320 : 1863 : mutex_unlock(&led_cdev->led_access);
321 : :
322 : : dev_dbg(parent, "Registered led device: %s\n",
323 : : led_cdev->name);
324 : :
325 : 1863 : return 0;
326 : : }
327 : : EXPORT_SYMBOL_GPL(led_classdev_register_ext);
328 : :
329 : : /**
330 : : * led_classdev_unregister - unregisters a object of led_properties class.
331 : : * @led_cdev: the led device to unregister
332 : : *
333 : : * Unregisters a previously registered via led_classdev_register object.
334 : : */
335 : 0 : void led_classdev_unregister(struct led_classdev *led_cdev)
336 : : {
337 [ # # ]: 0 : if (IS_ERR_OR_NULL(led_cdev->dev))
338 : 0 : return;
339 : :
340 : : #ifdef CONFIG_LEDS_TRIGGERS
341 : 0 : down_write(&led_cdev->trigger_lock);
342 [ # # ]: 0 : if (led_cdev->trigger)
343 : 0 : led_trigger_set(led_cdev, NULL);
344 : 0 : up_write(&led_cdev->trigger_lock);
345 : : #endif
346 : :
347 : 0 : led_cdev->flags |= LED_UNREGISTERING;
348 : :
349 : : /* Stop blinking */
350 : 0 : led_stop_software_blink(led_cdev);
351 : :
352 : 0 : led_set_brightness(led_cdev, LED_OFF);
353 : :
354 : 0 : flush_work(&led_cdev->set_brightness_work);
355 : :
356 : : if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
357 : : led_remove_brightness_hw_changed(led_cdev);
358 : :
359 : 0 : device_unregister(led_cdev->dev);
360 : :
361 : 0 : down_write(&leds_list_lock);
362 : : list_del(&led_cdev->node);
363 : 0 : up_write(&leds_list_lock);
364 : :
365 : : mutex_destroy(&led_cdev->led_access);
366 : : }
367 : : EXPORT_SYMBOL_GPL(led_classdev_unregister);
368 : :
369 : 0 : static void devm_led_classdev_release(struct device *dev, void *res)
370 : : {
371 : 0 : led_classdev_unregister(*(struct led_classdev **)res);
372 : 0 : }
373 : :
374 : : /**
375 : : * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
376 : : *
377 : : * @parent: parent of LED device
378 : : * @led_cdev: the led_classdev structure for this device.
379 : : * @init_data: LED class device initialization data
380 : : */
381 : 414 : int devm_led_classdev_register_ext(struct device *parent,
382 : : struct led_classdev *led_cdev,
383 : : struct led_init_data *init_data)
384 : : {
385 : : struct led_classdev **dr;
386 : : int rc;
387 : :
388 : : dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
389 [ + - ]: 414 : if (!dr)
390 : : return -ENOMEM;
391 : :
392 : 414 : rc = led_classdev_register_ext(parent, led_cdev, init_data);
393 [ - + ]: 414 : if (rc) {
394 : 0 : devres_free(dr);
395 : 0 : return rc;
396 : : }
397 : :
398 : 414 : *dr = led_cdev;
399 : 414 : devres_add(parent, dr);
400 : :
401 : 414 : return 0;
402 : : }
403 : : EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
404 : :
405 : 0 : static int devm_led_classdev_match(struct device *dev, void *res, void *data)
406 : : {
407 : : struct led_cdev **p = res;
408 : :
409 [ # # # # : 0 : if (WARN_ON(!p || !*p))
# # # # ]
410 : : return 0;
411 : :
412 : 0 : return *p == data;
413 : : }
414 : :
415 : : /**
416 : : * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
417 : : * @parent: The device to unregister.
418 : : * @led_cdev: the led_classdev structure for this device.
419 : : */
420 : 0 : void devm_led_classdev_unregister(struct device *dev,
421 : : struct led_classdev *led_cdev)
422 : : {
423 [ # # ]: 0 : WARN_ON(devres_release(dev,
424 : : devm_led_classdev_release,
425 : : devm_led_classdev_match, led_cdev));
426 : 0 : }
427 : : EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
428 : :
429 : 207 : static int __init leds_init(void)
430 : : {
431 : 207 : leds_class = class_create(THIS_MODULE, "leds");
432 [ - + ]: 207 : if (IS_ERR(leds_class))
433 : 0 : return PTR_ERR(leds_class);
434 : 207 : leds_class->pm = &leds_class_dev_pm_ops;
435 : 207 : leds_class->dev_groups = led_groups;
436 : 207 : return 0;
437 : : }
438 : :
439 : 0 : static void __exit leds_exit(void)
440 : : {
441 : 0 : class_destroy(leds_class);
442 : 0 : }
443 : :
444 : : subsys_initcall(leds_init);
445 : : module_exit(leds_exit);
446 : :
447 : : MODULE_AUTHOR("John Lenz, Richard Purdie");
448 : : MODULE_LICENSE("GPL");
449 : : MODULE_DESCRIPTION("LED Class Interface");
|