.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * LED Class Core |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu> |
---|
5 | 6 | * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com> |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or modify |
---|
8 | | - * it under the terms of the GNU General Public License version 2 as |
---|
9 | | - * published by the Free Software Foundation. |
---|
10 | 7 | */ |
---|
11 | 8 | |
---|
12 | 9 | #include <linux/ctype.h> |
---|
.. | .. |
---|
17 | 14 | #include <linux/leds.h> |
---|
18 | 15 | #include <linux/list.h> |
---|
19 | 16 | #include <linux/module.h> |
---|
| 17 | +#include <linux/property.h> |
---|
20 | 18 | #include <linux/slab.h> |
---|
21 | 19 | #include <linux/spinlock.h> |
---|
22 | 20 | #include <linux/timer.h> |
---|
23 | 21 | #include <uapi/linux/uleds.h> |
---|
| 22 | +#include <linux/of.h> |
---|
24 | 23 | #include "leds.h" |
---|
25 | 24 | |
---|
26 | 25 | static struct class *leds_class; |
---|
.. | .. |
---|
57 | 56 | if (state == LED_OFF) |
---|
58 | 57 | led_trigger_remove(led_cdev); |
---|
59 | 58 | led_set_brightness(led_cdev, state); |
---|
| 59 | + flush_work(&led_cdev->set_brightness_work); |
---|
60 | 60 | |
---|
61 | 61 | ret = size; |
---|
62 | 62 | unlock: |
---|
.. | .. |
---|
75 | 75 | static DEVICE_ATTR_RO(max_brightness); |
---|
76 | 76 | |
---|
77 | 77 | #ifdef CONFIG_LEDS_TRIGGERS |
---|
78 | | -static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); |
---|
79 | | -static struct attribute *led_trigger_attrs[] = { |
---|
80 | | - &dev_attr_trigger.attr, |
---|
| 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 | 81 | NULL, |
---|
82 | 82 | }; |
---|
83 | 83 | static const struct attribute_group led_trigger_group = { |
---|
84 | | - .attrs = led_trigger_attrs, |
---|
| 84 | + .bin_attrs = led_trigger_bin_attrs, |
---|
85 | 85 | }; |
---|
86 | 86 | #endif |
---|
87 | 87 | |
---|
.. | .. |
---|
216 | 216 | |
---|
217 | 217 | static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); |
---|
218 | 218 | |
---|
219 | | -static int match_name(struct device *dev, const void *data) |
---|
| 219 | +/** |
---|
| 220 | + * of_led_get() - request a LED device via the LED framework |
---|
| 221 | + * @np: device node to get the LED device from |
---|
| 222 | + * @index: the index of the LED |
---|
| 223 | + * |
---|
| 224 | + * Returns the LED device parsed from the phandle specified in the "leds" |
---|
| 225 | + * property of a device tree node or a negative error-code on failure. |
---|
| 226 | + */ |
---|
| 227 | +struct led_classdev *of_led_get(struct device_node *np, int index) |
---|
220 | 228 | { |
---|
221 | | - if (!dev_name(dev)) |
---|
222 | | - return 0; |
---|
223 | | - return !strcmp(dev_name(dev), (char *)data); |
---|
| 229 | + struct device *led_dev; |
---|
| 230 | + struct led_classdev *led_cdev; |
---|
| 231 | + struct device_node *led_node; |
---|
| 232 | + |
---|
| 233 | + led_node = of_parse_phandle(np, "leds", index); |
---|
| 234 | + if (!led_node) |
---|
| 235 | + return ERR_PTR(-ENOENT); |
---|
| 236 | + |
---|
| 237 | + led_dev = class_find_device_by_of_node(leds_class, led_node); |
---|
| 238 | + of_node_put(led_node); |
---|
| 239 | + put_device(led_dev); |
---|
| 240 | + |
---|
| 241 | + if (!led_dev) |
---|
| 242 | + return ERR_PTR(-EPROBE_DEFER); |
---|
| 243 | + |
---|
| 244 | + led_cdev = dev_get_drvdata(led_dev); |
---|
| 245 | + |
---|
| 246 | + if (!try_module_get(led_cdev->dev->parent->driver->owner)) { |
---|
| 247 | + put_device(led_cdev->dev); |
---|
| 248 | + return ERR_PTR(-ENODEV); |
---|
| 249 | + } |
---|
| 250 | + |
---|
| 251 | + return led_cdev; |
---|
224 | 252 | } |
---|
| 253 | +EXPORT_SYMBOL_GPL(of_led_get); |
---|
| 254 | + |
---|
| 255 | +/** |
---|
| 256 | + * led_put() - release a LED device |
---|
| 257 | + * @led_cdev: LED device |
---|
| 258 | + */ |
---|
| 259 | +void led_put(struct led_classdev *led_cdev) |
---|
| 260 | +{ |
---|
| 261 | + module_put(led_cdev->dev->parent->driver->owner); |
---|
| 262 | + put_device(led_cdev->dev); |
---|
| 263 | +} |
---|
| 264 | +EXPORT_SYMBOL_GPL(led_put); |
---|
| 265 | + |
---|
| 266 | +static void devm_led_release(struct device *dev, void *res) |
---|
| 267 | +{ |
---|
| 268 | + struct led_classdev **p = res; |
---|
| 269 | + |
---|
| 270 | + led_put(*p); |
---|
| 271 | +} |
---|
| 272 | + |
---|
| 273 | +/** |
---|
| 274 | + * devm_of_led_get - Resource-managed request of a LED device |
---|
| 275 | + * @dev: LED consumer |
---|
| 276 | + * @index: index of the LED to obtain in the consumer |
---|
| 277 | + * |
---|
| 278 | + * The device node of the device is parse to find the request LED device. |
---|
| 279 | + * The LED device returned from this function is automatically released |
---|
| 280 | + * on driver detach. |
---|
| 281 | + * |
---|
| 282 | + * @return a pointer to a LED device or ERR_PTR(errno) on failure. |
---|
| 283 | + */ |
---|
| 284 | +struct led_classdev *__must_check devm_of_led_get(struct device *dev, |
---|
| 285 | + int index) |
---|
| 286 | +{ |
---|
| 287 | + struct led_classdev *led; |
---|
| 288 | + struct led_classdev **dr; |
---|
| 289 | + |
---|
| 290 | + if (!dev) |
---|
| 291 | + return ERR_PTR(-EINVAL); |
---|
| 292 | + |
---|
| 293 | + led = of_led_get(dev->of_node, index); |
---|
| 294 | + if (IS_ERR(led)) |
---|
| 295 | + return led; |
---|
| 296 | + |
---|
| 297 | + dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), |
---|
| 298 | + GFP_KERNEL); |
---|
| 299 | + if (!dr) { |
---|
| 300 | + led_put(led); |
---|
| 301 | + return ERR_PTR(-ENOMEM); |
---|
| 302 | + } |
---|
| 303 | + |
---|
| 304 | + *dr = led; |
---|
| 305 | + devres_add(dev, dr); |
---|
| 306 | + |
---|
| 307 | + return led; |
---|
| 308 | +} |
---|
| 309 | +EXPORT_SYMBOL_GPL(devm_of_led_get); |
---|
225 | 310 | |
---|
226 | 311 | static int led_classdev_next_name(const char *init_name, char *name, |
---|
227 | 312 | size_t len) |
---|
.. | .. |
---|
233 | 318 | strlcpy(name, init_name, len); |
---|
234 | 319 | |
---|
235 | 320 | while ((ret < len) && |
---|
236 | | - (dev = class_find_device(leds_class, NULL, name, match_name))) { |
---|
| 321 | + (dev = class_find_device_by_name(leds_class, name))) { |
---|
237 | 322 | put_device(dev); |
---|
238 | 323 | ret = snprintf(name, len, "%s_%u", init_name, ++i); |
---|
239 | 324 | } |
---|
.. | .. |
---|
245 | 330 | } |
---|
246 | 331 | |
---|
247 | 332 | /** |
---|
248 | | - * of_led_classdev_register - register a new object of led_classdev class. |
---|
| 333 | + * led_classdev_register_ext - register a new object of led_classdev class |
---|
| 334 | + * with init data. |
---|
249 | 335 | * |
---|
250 | 336 | * @parent: parent of LED device |
---|
251 | 337 | * @led_cdev: the led_classdev structure for this device. |
---|
252 | | - * @np: DT node describing this LED |
---|
| 338 | + * @init_data: LED class device initialization data |
---|
253 | 339 | */ |
---|
254 | | -int of_led_classdev_register(struct device *parent, struct device_node *np, |
---|
255 | | - struct led_classdev *led_cdev) |
---|
| 340 | +int led_classdev_register_ext(struct device *parent, |
---|
| 341 | + struct led_classdev *led_cdev, |
---|
| 342 | + struct led_init_data *init_data) |
---|
256 | 343 | { |
---|
257 | | - char name[LED_MAX_NAME_SIZE]; |
---|
| 344 | + char composed_name[LED_MAX_NAME_SIZE]; |
---|
| 345 | + char final_name[LED_MAX_NAME_SIZE]; |
---|
| 346 | + const char *proposed_name = composed_name; |
---|
258 | 347 | int ret; |
---|
259 | 348 | |
---|
260 | | - ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); |
---|
| 349 | + if (init_data) { |
---|
| 350 | + if (init_data->devname_mandatory && !init_data->devicename) { |
---|
| 351 | + dev_err(parent, "Mandatory device name is missing"); |
---|
| 352 | + return -EINVAL; |
---|
| 353 | + } |
---|
| 354 | + ret = led_compose_name(parent, init_data, composed_name); |
---|
| 355 | + if (ret < 0) |
---|
| 356 | + return ret; |
---|
| 357 | + |
---|
| 358 | + if (init_data->fwnode) |
---|
| 359 | + fwnode_property_read_string(init_data->fwnode, |
---|
| 360 | + "linux,default-trigger", |
---|
| 361 | + &led_cdev->default_trigger); |
---|
| 362 | + } else { |
---|
| 363 | + proposed_name = led_cdev->name; |
---|
| 364 | + } |
---|
| 365 | + |
---|
| 366 | + ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); |
---|
261 | 367 | if (ret < 0) |
---|
262 | 368 | return ret; |
---|
263 | 369 | |
---|
264 | 370 | mutex_init(&led_cdev->led_access); |
---|
265 | 371 | mutex_lock(&led_cdev->led_access); |
---|
266 | 372 | led_cdev->dev = device_create_with_groups(leds_class, parent, 0, |
---|
267 | | - led_cdev, led_cdev->groups, "%s", name); |
---|
| 373 | + led_cdev, led_cdev->groups, "%s", final_name); |
---|
268 | 374 | if (IS_ERR(led_cdev->dev)) { |
---|
269 | 375 | mutex_unlock(&led_cdev->led_access); |
---|
270 | 376 | return PTR_ERR(led_cdev->dev); |
---|
271 | 377 | } |
---|
272 | | - led_cdev->dev->of_node = np; |
---|
| 378 | + if (init_data && init_data->fwnode) { |
---|
| 379 | + led_cdev->dev->fwnode = init_data->fwnode; |
---|
| 380 | + led_cdev->dev->of_node = to_of_node(init_data->fwnode); |
---|
| 381 | + } |
---|
273 | 382 | |
---|
274 | 383 | if (ret) |
---|
275 | 384 | dev_warn(parent, "Led %s renamed to %s due to name collision", |
---|
276 | | - led_cdev->name, dev_name(led_cdev->dev)); |
---|
| 385 | + proposed_name, dev_name(led_cdev->dev)); |
---|
277 | 386 | |
---|
278 | 387 | if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { |
---|
279 | 388 | ret = led_add_brightness_hw_changed(led_cdev); |
---|
280 | 389 | if (ret) { |
---|
281 | 390 | device_unregister(led_cdev->dev); |
---|
| 391 | + led_cdev->dev = NULL; |
---|
282 | 392 | mutex_unlock(&led_cdev->led_access); |
---|
283 | 393 | return ret; |
---|
284 | 394 | } |
---|
.. | .. |
---|
314 | 424 | |
---|
315 | 425 | return 0; |
---|
316 | 426 | } |
---|
317 | | -EXPORT_SYMBOL_GPL(of_led_classdev_register); |
---|
| 427 | +EXPORT_SYMBOL_GPL(led_classdev_register_ext); |
---|
318 | 428 | |
---|
319 | 429 | /** |
---|
320 | 430 | * led_classdev_unregister - unregisters a object of led_properties class. |
---|
.. | .. |
---|
324 | 434 | */ |
---|
325 | 435 | void led_classdev_unregister(struct led_classdev *led_cdev) |
---|
326 | 436 | { |
---|
| 437 | + if (IS_ERR_OR_NULL(led_cdev->dev)) |
---|
| 438 | + return; |
---|
| 439 | + |
---|
327 | 440 | #ifdef CONFIG_LEDS_TRIGGERS |
---|
328 | 441 | down_write(&led_cdev->trigger_lock); |
---|
329 | 442 | if (led_cdev->trigger) |
---|
.. | .. |
---|
359 | 472 | } |
---|
360 | 473 | |
---|
361 | 474 | /** |
---|
362 | | - * devm_of_led_classdev_register - resource managed led_classdev_register() |
---|
| 475 | + * devm_led_classdev_register_ext - resource managed led_classdev_register_ext() |
---|
363 | 476 | * |
---|
364 | 477 | * @parent: parent of LED device |
---|
365 | 478 | * @led_cdev: the led_classdev structure for this device. |
---|
| 479 | + * @init_data: LED class device initialization data |
---|
366 | 480 | */ |
---|
367 | | -int devm_of_led_classdev_register(struct device *parent, |
---|
368 | | - struct device_node *np, |
---|
369 | | - struct led_classdev *led_cdev) |
---|
| 481 | +int devm_led_classdev_register_ext(struct device *parent, |
---|
| 482 | + struct led_classdev *led_cdev, |
---|
| 483 | + struct led_init_data *init_data) |
---|
370 | 484 | { |
---|
371 | 485 | struct led_classdev **dr; |
---|
372 | 486 | int rc; |
---|
.. | .. |
---|
375 | 489 | if (!dr) |
---|
376 | 490 | return -ENOMEM; |
---|
377 | 491 | |
---|
378 | | - rc = of_led_classdev_register(parent, np, led_cdev); |
---|
| 492 | + rc = led_classdev_register_ext(parent, led_cdev, init_data); |
---|
379 | 493 | if (rc) { |
---|
380 | 494 | devres_free(dr); |
---|
381 | 495 | return rc; |
---|
.. | .. |
---|
386 | 500 | |
---|
387 | 501 | return 0; |
---|
388 | 502 | } |
---|
389 | | -EXPORT_SYMBOL_GPL(devm_of_led_classdev_register); |
---|
| 503 | +EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext); |
---|
390 | 504 | |
---|
391 | 505 | static int devm_led_classdev_match(struct device *dev, void *res, void *data) |
---|
392 | 506 | { |
---|
393 | | - struct led_cdev **p = res; |
---|
| 507 | + struct led_classdev **p = res; |
---|
394 | 508 | |
---|
395 | 509 | if (WARN_ON(!p || !*p)) |
---|
396 | 510 | return 0; |
---|