| .. | .. |
|---|
| 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 | + |
|---|
| 240 | + if (!led_dev) |
|---|
| 241 | + return ERR_PTR(-EPROBE_DEFER); |
|---|
| 242 | + |
|---|
| 243 | + led_cdev = dev_get_drvdata(led_dev); |
|---|
| 244 | + |
|---|
| 245 | + if (!try_module_get(led_cdev->dev->parent->driver->owner)) |
|---|
| 246 | + return ERR_PTR(-ENODEV); |
|---|
| 247 | + |
|---|
| 248 | + return led_cdev; |
|---|
| 224 | 249 | } |
|---|
| 250 | +EXPORT_SYMBOL_GPL(of_led_get); |
|---|
| 251 | + |
|---|
| 252 | +/** |
|---|
| 253 | + * led_put() - release a LED device |
|---|
| 254 | + * @led_cdev: LED device |
|---|
| 255 | + */ |
|---|
| 256 | +void led_put(struct led_classdev *led_cdev) |
|---|
| 257 | +{ |
|---|
| 258 | + module_put(led_cdev->dev->parent->driver->owner); |
|---|
| 259 | +} |
|---|
| 260 | +EXPORT_SYMBOL_GPL(led_put); |
|---|
| 261 | + |
|---|
| 262 | +static void devm_led_release(struct device *dev, void *res) |
|---|
| 263 | +{ |
|---|
| 264 | + struct led_classdev **p = res; |
|---|
| 265 | + |
|---|
| 266 | + led_put(*p); |
|---|
| 267 | +} |
|---|
| 268 | + |
|---|
| 269 | +/** |
|---|
| 270 | + * devm_of_led_get - Resource-managed request of a LED device |
|---|
| 271 | + * @dev: LED consumer |
|---|
| 272 | + * @index: index of the LED to obtain in the consumer |
|---|
| 273 | + * |
|---|
| 274 | + * The device node of the device is parse to find the request LED device. |
|---|
| 275 | + * The LED device returned from this function is automatically released |
|---|
| 276 | + * on driver detach. |
|---|
| 277 | + * |
|---|
| 278 | + * @return a pointer to a LED device or ERR_PTR(errno) on failure. |
|---|
| 279 | + */ |
|---|
| 280 | +struct led_classdev *__must_check devm_of_led_get(struct device *dev, |
|---|
| 281 | + int index) |
|---|
| 282 | +{ |
|---|
| 283 | + struct led_classdev *led; |
|---|
| 284 | + struct led_classdev **dr; |
|---|
| 285 | + |
|---|
| 286 | + if (!dev) |
|---|
| 287 | + return ERR_PTR(-EINVAL); |
|---|
| 288 | + |
|---|
| 289 | + led = of_led_get(dev->of_node, index); |
|---|
| 290 | + if (IS_ERR(led)) |
|---|
| 291 | + return led; |
|---|
| 292 | + |
|---|
| 293 | + dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), |
|---|
| 294 | + GFP_KERNEL); |
|---|
| 295 | + if (!dr) { |
|---|
| 296 | + led_put(led); |
|---|
| 297 | + return ERR_PTR(-ENOMEM); |
|---|
| 298 | + } |
|---|
| 299 | + |
|---|
| 300 | + *dr = led; |
|---|
| 301 | + devres_add(dev, dr); |
|---|
| 302 | + |
|---|
| 303 | + return led; |
|---|
| 304 | +} |
|---|
| 305 | +EXPORT_SYMBOL_GPL(devm_of_led_get); |
|---|
| 225 | 306 | |
|---|
| 226 | 307 | static int led_classdev_next_name(const char *init_name, char *name, |
|---|
| 227 | 308 | size_t len) |
|---|
| .. | .. |
|---|
| 233 | 314 | strlcpy(name, init_name, len); |
|---|
| 234 | 315 | |
|---|
| 235 | 316 | while ((ret < len) && |
|---|
| 236 | | - (dev = class_find_device(leds_class, NULL, name, match_name))) { |
|---|
| 317 | + (dev = class_find_device_by_name(leds_class, name))) { |
|---|
| 237 | 318 | put_device(dev); |
|---|
| 238 | 319 | ret = snprintf(name, len, "%s_%u", init_name, ++i); |
|---|
| 239 | 320 | } |
|---|
| .. | .. |
|---|
| 245 | 326 | } |
|---|
| 246 | 327 | |
|---|
| 247 | 328 | /** |
|---|
| 248 | | - * of_led_classdev_register - register a new object of led_classdev class. |
|---|
| 329 | + * led_classdev_register_ext - register a new object of led_classdev class |
|---|
| 330 | + * with init data. |
|---|
| 249 | 331 | * |
|---|
| 250 | 332 | * @parent: parent of LED device |
|---|
| 251 | 333 | * @led_cdev: the led_classdev structure for this device. |
|---|
| 252 | | - * @np: DT node describing this LED |
|---|
| 334 | + * @init_data: LED class device initialization data |
|---|
| 253 | 335 | */ |
|---|
| 254 | | -int of_led_classdev_register(struct device *parent, struct device_node *np, |
|---|
| 255 | | - struct led_classdev *led_cdev) |
|---|
| 336 | +int led_classdev_register_ext(struct device *parent, |
|---|
| 337 | + struct led_classdev *led_cdev, |
|---|
| 338 | + struct led_init_data *init_data) |
|---|
| 256 | 339 | { |
|---|
| 257 | | - char name[LED_MAX_NAME_SIZE]; |
|---|
| 340 | + char composed_name[LED_MAX_NAME_SIZE]; |
|---|
| 341 | + char final_name[LED_MAX_NAME_SIZE]; |
|---|
| 342 | + const char *proposed_name = composed_name; |
|---|
| 258 | 343 | int ret; |
|---|
| 259 | 344 | |
|---|
| 260 | | - ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); |
|---|
| 345 | + if (init_data) { |
|---|
| 346 | + if (init_data->devname_mandatory && !init_data->devicename) { |
|---|
| 347 | + dev_err(parent, "Mandatory device name is missing"); |
|---|
| 348 | + return -EINVAL; |
|---|
| 349 | + } |
|---|
| 350 | + ret = led_compose_name(parent, init_data, composed_name); |
|---|
| 351 | + if (ret < 0) |
|---|
| 352 | + return ret; |
|---|
| 353 | + |
|---|
| 354 | + if (init_data->fwnode) |
|---|
| 355 | + fwnode_property_read_string(init_data->fwnode, |
|---|
| 356 | + "linux,default-trigger", |
|---|
| 357 | + &led_cdev->default_trigger); |
|---|
| 358 | + } else { |
|---|
| 359 | + proposed_name = led_cdev->name; |
|---|
| 360 | + } |
|---|
| 361 | + |
|---|
| 362 | + ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); |
|---|
| 261 | 363 | if (ret < 0) |
|---|
| 262 | 364 | return ret; |
|---|
| 263 | 365 | |
|---|
| 264 | 366 | mutex_init(&led_cdev->led_access); |
|---|
| 265 | 367 | mutex_lock(&led_cdev->led_access); |
|---|
| 266 | 368 | led_cdev->dev = device_create_with_groups(leds_class, parent, 0, |
|---|
| 267 | | - led_cdev, led_cdev->groups, "%s", name); |
|---|
| 369 | + led_cdev, led_cdev->groups, "%s", final_name); |
|---|
| 268 | 370 | if (IS_ERR(led_cdev->dev)) { |
|---|
| 269 | 371 | mutex_unlock(&led_cdev->led_access); |
|---|
| 270 | 372 | return PTR_ERR(led_cdev->dev); |
|---|
| 271 | 373 | } |
|---|
| 272 | | - led_cdev->dev->of_node = np; |
|---|
| 374 | + if (init_data && init_data->fwnode) { |
|---|
| 375 | + led_cdev->dev->fwnode = init_data->fwnode; |
|---|
| 376 | + led_cdev->dev->of_node = to_of_node(init_data->fwnode); |
|---|
| 377 | + } |
|---|
| 273 | 378 | |
|---|
| 274 | 379 | if (ret) |
|---|
| 275 | 380 | dev_warn(parent, "Led %s renamed to %s due to name collision", |
|---|
| 276 | | - led_cdev->name, dev_name(led_cdev->dev)); |
|---|
| 381 | + proposed_name, dev_name(led_cdev->dev)); |
|---|
| 277 | 382 | |
|---|
| 278 | 383 | if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { |
|---|
| 279 | 384 | ret = led_add_brightness_hw_changed(led_cdev); |
|---|
| 280 | 385 | if (ret) { |
|---|
| 281 | 386 | device_unregister(led_cdev->dev); |
|---|
| 387 | + led_cdev->dev = NULL; |
|---|
| 282 | 388 | mutex_unlock(&led_cdev->led_access); |
|---|
| 283 | 389 | return ret; |
|---|
| 284 | 390 | } |
|---|
| .. | .. |
|---|
| 314 | 420 | |
|---|
| 315 | 421 | return 0; |
|---|
| 316 | 422 | } |
|---|
| 317 | | -EXPORT_SYMBOL_GPL(of_led_classdev_register); |
|---|
| 423 | +EXPORT_SYMBOL_GPL(led_classdev_register_ext); |
|---|
| 318 | 424 | |
|---|
| 319 | 425 | /** |
|---|
| 320 | 426 | * led_classdev_unregister - unregisters a object of led_properties class. |
|---|
| .. | .. |
|---|
| 324 | 430 | */ |
|---|
| 325 | 431 | void led_classdev_unregister(struct led_classdev *led_cdev) |
|---|
| 326 | 432 | { |
|---|
| 433 | + if (IS_ERR_OR_NULL(led_cdev->dev)) |
|---|
| 434 | + return; |
|---|
| 435 | + |
|---|
| 327 | 436 | #ifdef CONFIG_LEDS_TRIGGERS |
|---|
| 328 | 437 | down_write(&led_cdev->trigger_lock); |
|---|
| 329 | 438 | if (led_cdev->trigger) |
|---|
| .. | .. |
|---|
| 359 | 468 | } |
|---|
| 360 | 469 | |
|---|
| 361 | 470 | /** |
|---|
| 362 | | - * devm_of_led_classdev_register - resource managed led_classdev_register() |
|---|
| 471 | + * devm_led_classdev_register_ext - resource managed led_classdev_register_ext() |
|---|
| 363 | 472 | * |
|---|
| 364 | 473 | * @parent: parent of LED device |
|---|
| 365 | 474 | * @led_cdev: the led_classdev structure for this device. |
|---|
| 475 | + * @init_data: LED class device initialization data |
|---|
| 366 | 476 | */ |
|---|
| 367 | | -int devm_of_led_classdev_register(struct device *parent, |
|---|
| 368 | | - struct device_node *np, |
|---|
| 369 | | - struct led_classdev *led_cdev) |
|---|
| 477 | +int devm_led_classdev_register_ext(struct device *parent, |
|---|
| 478 | + struct led_classdev *led_cdev, |
|---|
| 479 | + struct led_init_data *init_data) |
|---|
| 370 | 480 | { |
|---|
| 371 | 481 | struct led_classdev **dr; |
|---|
| 372 | 482 | int rc; |
|---|
| .. | .. |
|---|
| 375 | 485 | if (!dr) |
|---|
| 376 | 486 | return -ENOMEM; |
|---|
| 377 | 487 | |
|---|
| 378 | | - rc = of_led_classdev_register(parent, np, led_cdev); |
|---|
| 488 | + rc = led_classdev_register_ext(parent, led_cdev, init_data); |
|---|
| 379 | 489 | if (rc) { |
|---|
| 380 | 490 | devres_free(dr); |
|---|
| 381 | 491 | return rc; |
|---|
| .. | .. |
|---|
| 386 | 496 | |
|---|
| 387 | 497 | return 0; |
|---|
| 388 | 498 | } |
|---|
| 389 | | -EXPORT_SYMBOL_GPL(devm_of_led_classdev_register); |
|---|
| 499 | +EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext); |
|---|
| 390 | 500 | |
|---|
| 391 | 501 | static int devm_led_classdev_match(struct device *dev, void *res, void *data) |
|---|
| 392 | 502 | { |
|---|
| 393 | | - struct led_cdev **p = res; |
|---|
| 503 | + struct led_classdev **p = res; |
|---|
| 394 | 504 | |
|---|
| 395 | 505 | if (WARN_ON(!p || !*p)) |
|---|
| 396 | 506 | return 0; |
|---|