linux/drivers/leds/led-class.c
<<
>>
Prefs
   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 <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
  32        /* no lock needed for this */
  33        led_update_brightness(led_cdev);
  34
  35        return sprintf(buf, "%u\n", led_cdev->brightness);
  36}
  37
  38static ssize_t brightness_store(struct device *dev,
  39                struct device_attribute *attr, const char *buf, size_t size)
  40{
  41        struct led_classdev *led_cdev = dev_get_drvdata(dev);
  42        unsigned long state;
  43        ssize_t ret;
  44
  45        mutex_lock(&led_cdev->led_access);
  46
  47        if (led_sysfs_is_disabled(led_cdev)) {
  48                ret = -EBUSY;
  49                goto unlock;
  50        }
  51
  52        ret = kstrtoul(buf, 10, &state);
  53        if (ret)
  54                goto unlock;
  55
  56        if (state == LED_OFF)
  57                led_trigger_remove(led_cdev);
  58        led_set_brightness(led_cdev, state);
  59        flush_work(&led_cdev->set_brightness_work);
  60
  61        ret = size;
  62unlock:
  63        mutex_unlock(&led_cdev->led_access);
  64        return ret;
  65}
  66static DEVICE_ATTR_RW(brightness);
  67
  68static ssize_t max_brightness_show(struct device *dev,
  69                struct device_attribute *attr, char *buf)
  70{
  71        struct led_classdev *led_cdev = dev_get_drvdata(dev);
  72
  73        return sprintf(buf, "%u\n", led_cdev->max_brightness);
  74}
  75static DEVICE_ATTR_RO(max_brightness);
  76
  77#ifdef CONFIG_LEDS_TRIGGERS
  78static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
  79static struct bin_attribute *led_trigger_bin_attrs[] = {
  80        &bin_attr_trigger,
  81        NULL,
  82};
  83static const struct attribute_group led_trigger_group = {
  84        .bin_attrs = led_trigger_bin_attrs,
  85};
  86#endif
  87
  88static struct attribute *led_class_attrs[] = {
  89        &dev_attr_brightness.attr,
  90        &dev_attr_max_brightness.attr,
  91        NULL,
  92};
  93
  94static const struct attribute_group led_group = {
  95        .attrs = led_class_attrs,
  96};
  97
  98static const struct attribute_group *led_groups[] = {
  99        &led_group,
 100#ifdef CONFIG_LEDS_TRIGGERS
 101        &led_trigger_group,
 102#endif
 103        NULL,
 104};
 105
 106#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
 107static ssize_t brightness_hw_changed_show(struct device *dev,
 108                struct device_attribute *attr, char *buf)
 109{
 110        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 111
 112        if (led_cdev->brightness_hw_changed == -1)
 113                return -ENODATA;
 114
 115        return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
 116}
 117
 118static DEVICE_ATTR_RO(brightness_hw_changed);
 119
 120static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
 121{
 122        struct device *dev = led_cdev->dev;
 123        int ret;
 124
 125        ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
 126        if (ret) {
 127                dev_err(dev, "Error creating brightness_hw_changed\n");
 128                return ret;
 129        }
 130
 131        led_cdev->brightness_hw_changed_kn =
 132                sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
 133        if (!led_cdev->brightness_hw_changed_kn) {
 134                dev_err(dev, "Error getting brightness_hw_changed kn\n");
 135                device_remove_file(dev, &dev_attr_brightness_hw_changed);
 136                return -ENXIO;
 137        }
 138
 139        return 0;
 140}
 141
 142static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
 143{
 144        sysfs_put(led_cdev->brightness_hw_changed_kn);
 145        device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
 146}
 147
 148void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
 149                                               enum led_brightness brightness)
 150{
 151        if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
 152                return;
 153
 154        led_cdev->brightness_hw_changed = brightness;
 155        sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
 156}
 157EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
 158#else
 159static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
 160{
 161        return 0;
 162}
 163static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
 164{
 165}
 166#endif
 167
 168/**
 169 * led_classdev_suspend - suspend an led_classdev.
 170 * @led_cdev: the led_classdev to suspend.
 171 */
 172void led_classdev_suspend(struct led_classdev *led_cdev)
 173{
 174        led_cdev->flags |= LED_SUSPENDED;
 175        led_set_brightness_nopm(led_cdev, 0);
 176        flush_work(&led_cdev->set_brightness_work);
 177}
 178EXPORT_SYMBOL_GPL(led_classdev_suspend);
 179
 180/**
 181 * led_classdev_resume - resume an led_classdev.
 182 * @led_cdev: the led_classdev to resume.
 183 */
 184void led_classdev_resume(struct led_classdev *led_cdev)
 185{
 186        led_set_brightness_nopm(led_cdev, led_cdev->brightness);
 187
 188        if (led_cdev->flash_resume)
 189                led_cdev->flash_resume(led_cdev);
 190
 191        led_cdev->flags &= ~LED_SUSPENDED;
 192}
 193EXPORT_SYMBOL_GPL(led_classdev_resume);
 194
 195#ifdef CONFIG_PM_SLEEP
 196static int led_suspend(struct device *dev)
 197{
 198        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 199
 200        if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
 201                led_classdev_suspend(led_cdev);
 202
 203        return 0;
 204}
 205
 206static int led_resume(struct device *dev)
 207{
 208        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 209
 210        if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
 211                led_classdev_resume(led_cdev);
 212
 213        return 0;
 214}
 215#endif
 216
 217static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
 218
 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 */
 227struct led_classdev *of_led_get(struct device_node *np, int index)
 228{
 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;
 249}
 250EXPORT_SYMBOL_GPL(of_led_get);
 251
 252/**
 253 * led_put() - release a LED device
 254 * @led_cdev: LED device
 255 */
 256void led_put(struct led_classdev *led_cdev)
 257{
 258        module_put(led_cdev->dev->parent->driver->owner);
 259}
 260EXPORT_SYMBOL_GPL(led_put);
 261
 262static 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 */
 280struct 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        /* Not using device tree? */
 290        if (!IS_ENABLED(CONFIG_OF) || !dev->of_node)
 291                return ERR_PTR(-ENOTSUPP);
 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}
 309EXPORT_SYMBOL_GPL(devm_of_led_get);
 310
 311static int led_classdev_next_name(const char *init_name, char *name,
 312                                  size_t len)
 313{
 314        unsigned int i = 0;
 315        int ret = 0;
 316        struct device *dev;
 317
 318        strlcpy(name, init_name, len);
 319
 320        while ((ret < len) &&
 321               (dev = class_find_device_by_name(leds_class, name))) {
 322                put_device(dev);
 323                ret = snprintf(name, len, "%s_%u", init_name, ++i);
 324        }
 325
 326        if (ret >= len)
 327                return -ENOMEM;
 328
 329        return i;
 330}
 331
 332/**
 333 * led_classdev_register_ext - register a new object of led_classdev class
 334 *                             with init data.
 335 *
 336 * @parent: parent of LED device
 337 * @led_cdev: the led_classdev structure for this device.
 338 * @init_data: LED class device initialization data
 339 */
 340int led_classdev_register_ext(struct device *parent,
 341                              struct led_classdev *led_cdev,
 342                              struct led_init_data *init_data)
 343{
 344        char composed_name[LED_MAX_NAME_SIZE];
 345        char final_name[LED_MAX_NAME_SIZE];
 346        const char *proposed_name = composed_name;
 347        int ret;
 348
 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));
 367        if (ret < 0)
 368                return ret;
 369
 370        mutex_init(&led_cdev->led_access);
 371        mutex_lock(&led_cdev->led_access);
 372        led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
 373                                led_cdev, led_cdev->groups, "%s", final_name);
 374        if (IS_ERR(led_cdev->dev)) {
 375                mutex_unlock(&led_cdev->led_access);
 376                return PTR_ERR(led_cdev->dev);
 377        }
 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        }
 382
 383        if (ret)
 384                dev_warn(parent, "Led %s renamed to %s due to name collision",
 385                                proposed_name, dev_name(led_cdev->dev));
 386
 387        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
 388                ret = led_add_brightness_hw_changed(led_cdev);
 389                if (ret) {
 390                        device_unregister(led_cdev->dev);
 391                        led_cdev->dev = NULL;
 392                        mutex_unlock(&led_cdev->led_access);
 393                        return ret;
 394                }
 395        }
 396
 397        led_cdev->work_flags = 0;
 398#ifdef CONFIG_LEDS_TRIGGERS
 399        init_rwsem(&led_cdev->trigger_lock);
 400#endif
 401#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
 402        led_cdev->brightness_hw_changed = -1;
 403#endif
 404        /* add to the list of leds */
 405        down_write(&leds_list_lock);
 406        list_add_tail(&led_cdev->node, &leds_list);
 407        up_write(&leds_list_lock);
 408
 409        if (!led_cdev->max_brightness)
 410                led_cdev->max_brightness = LED_FULL;
 411
 412        led_update_brightness(led_cdev);
 413
 414        led_init_core(led_cdev);
 415
 416#ifdef CONFIG_LEDS_TRIGGERS
 417        led_trigger_set_default(led_cdev);
 418#endif
 419
 420        mutex_unlock(&led_cdev->led_access);
 421
 422        dev_dbg(parent, "Registered led device: %s\n",
 423                        led_cdev->name);
 424
 425        return 0;
 426}
 427EXPORT_SYMBOL_GPL(led_classdev_register_ext);
 428
 429/**
 430 * led_classdev_unregister - unregisters a object of led_properties class.
 431 * @led_cdev: the led device to unregister
 432 *
 433 * Unregisters a previously registered via led_classdev_register object.
 434 */
 435void led_classdev_unregister(struct led_classdev *led_cdev)
 436{
 437        if (IS_ERR_OR_NULL(led_cdev->dev))
 438                return;
 439
 440#ifdef CONFIG_LEDS_TRIGGERS
 441        down_write(&led_cdev->trigger_lock);
 442        if (led_cdev->trigger)
 443                led_trigger_set(led_cdev, NULL);
 444        up_write(&led_cdev->trigger_lock);
 445#endif
 446
 447        led_cdev->flags |= LED_UNREGISTERING;
 448
 449        /* Stop blinking */
 450        led_stop_software_blink(led_cdev);
 451
 452        led_set_brightness(led_cdev, LED_OFF);
 453
 454        flush_work(&led_cdev->set_brightness_work);
 455
 456        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
 457                led_remove_brightness_hw_changed(led_cdev);
 458
 459        device_unregister(led_cdev->dev);
 460
 461        down_write(&leds_list_lock);
 462        list_del(&led_cdev->node);
 463        up_write(&leds_list_lock);
 464
 465        mutex_destroy(&led_cdev->led_access);
 466}
 467EXPORT_SYMBOL_GPL(led_classdev_unregister);
 468
 469static void devm_led_classdev_release(struct device *dev, void *res)
 470{
 471        led_classdev_unregister(*(struct led_classdev **)res);
 472}
 473
 474/**
 475 * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
 476 *
 477 * @parent: parent of LED device
 478 * @led_cdev: the led_classdev structure for this device.
 479 * @init_data: LED class device initialization data
 480 */
 481int devm_led_classdev_register_ext(struct device *parent,
 482                                   struct led_classdev *led_cdev,
 483                                   struct led_init_data *init_data)
 484{
 485        struct led_classdev **dr;
 486        int rc;
 487
 488        dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
 489        if (!dr)
 490                return -ENOMEM;
 491
 492        rc = led_classdev_register_ext(parent, led_cdev, init_data);
 493        if (rc) {
 494                devres_free(dr);
 495                return rc;
 496        }
 497
 498        *dr = led_cdev;
 499        devres_add(parent, dr);
 500
 501        return 0;
 502}
 503EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
 504
 505static int devm_led_classdev_match(struct device *dev, void *res, void *data)
 506{
 507        struct led_classdev **p = res;
 508
 509        if (WARN_ON(!p || !*p))
 510                return 0;
 511
 512        return *p == data;
 513}
 514
 515/**
 516 * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
 517 * @parent: The device to unregister.
 518 * @led_cdev: the led_classdev structure for this device.
 519 */
 520void devm_led_classdev_unregister(struct device *dev,
 521                                  struct led_classdev *led_cdev)
 522{
 523        WARN_ON(devres_release(dev,
 524                               devm_led_classdev_release,
 525                               devm_led_classdev_match, led_cdev));
 526}
 527EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
 528
 529static int __init leds_init(void)
 530{
 531        leds_class = class_create(THIS_MODULE, "leds");
 532        if (IS_ERR(leds_class))
 533                return PTR_ERR(leds_class);
 534        leds_class->pm = &leds_class_dev_pm_ops;
 535        leds_class->dev_groups = led_groups;
 536        return 0;
 537}
 538
 539static void __exit leds_exit(void)
 540{
 541        class_destroy(leds_class);
 542}
 543
 544subsys_initcall(leds_init);
 545module_exit(leds_exit);
 546
 547MODULE_AUTHOR("John Lenz, Richard Purdie");
 548MODULE_LICENSE("GPL");
 549MODULE_DESCRIPTION("LED Class Interface");
 550