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