| .. | .. |
|---|
| 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; |
|---|