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        } else {
 358                proposed_name = led_cdev->name;
 359        }
 360
 361        ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
 362        if (ret < 0)
 363                return ret;
 364
 365        mutex_init(&led_cdev->led_access);
 366        mutex_lock(&led_cdev->led_access);
 367        led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
 368                                led_cdev, led_cdev->groups, "%s", final_name);
 369        if (IS_ERR(led_cdev->dev)) {
 370                mutex_unlock(&led_cdev->led_access);
 371                return PTR_ERR(led_cdev->dev);
 372        }
 373        if (init_data && init_data->fwnode) {
 374                led_cdev->dev->fwnode = init_data->fwnode;
 375                led_cdev->dev->of_node = to_of_node(init_data->fwnode);
 376        }
 377
 378        if (ret)
 379                dev_warn(parent, "Led %s renamed to %s due to name collision",
 380                                proposed_name, dev_name(led_cdev->dev));
 381
 382        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
 383                ret = led_add_brightness_hw_changed(led_cdev);
 384                if (ret) {
 385                        device_unregister(led_cdev->dev);
 386                        led_cdev->dev = NULL;
 387                        mutex_unlock(&led_cdev->led_access);
 388                        return ret;
 389                }
 390        }
 391
 392        led_cdev->work_flags = 0;
 393#ifdef CONFIG_LEDS_TRIGGERS
 394        init_rwsem(&led_cdev->trigger_lock);
 395#endif
 396#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
 397        led_cdev->brightness_hw_changed = -1;
 398#endif
 399        /* add to the list of leds */
 400        down_write(&leds_list_lock);
 401        list_add_tail(&led_cdev->node, &leds_list);
 402        up_write(&leds_list_lock);
 403
 404        if (!led_cdev->max_brightness)
 405                led_cdev->max_brightness = LED_FULL;
 406
 407        led_update_brightness(led_cdev);
 408
 409        led_init_core(led_cdev);
 410
 411#ifdef CONFIG_LEDS_TRIGGERS
 412        led_trigger_set_default(led_cdev);
 413#endif
 414
 415        mutex_unlock(&led_cdev->led_access);
 416
 417        dev_dbg(parent, "Registered led device: %s\n",
 418                        led_cdev->name);
 419
 420        return 0;
 421}
 422EXPORT_SYMBOL_GPL(led_classdev_register_ext);
 423
 424/**
 425 * led_classdev_unregister - unregisters a object of led_properties class.
 426 * @led_cdev: the led device to unregister
 427 *
 428 * Unregisters a previously registered via led_classdev_register object.
 429 */
 430void led_classdev_unregister(struct led_classdev *led_cdev)
 431{
 432        if (IS_ERR_OR_NULL(led_cdev->dev))
 433                return;
 434
 435#ifdef CONFIG_LEDS_TRIGGERS
 436        down_write(&led_cdev->trigger_lock);
 437        if (led_cdev->trigger)
 438                led_trigger_set(led_cdev, NULL);
 439        up_write(&led_cdev->trigger_lock);
 440#endif
 441
 442        led_cdev->flags |= LED_UNREGISTERING;
 443
 444        /* Stop blinking */
 445        led_stop_software_blink(led_cdev);
 446
 447        led_set_brightness(led_cdev, LED_OFF);
 448
 449        flush_work(&led_cdev->set_brightness_work);
 450
 451        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
 452                led_remove_brightness_hw_changed(led_cdev);
 453
 454        device_unregister(led_cdev->dev);
 455
 456        down_write(&leds_list_lock);
 457        list_del(&led_cdev->node);
 458        up_write(&leds_list_lock);
 459
 460        mutex_destroy(&led_cdev->led_access);
 461}
 462EXPORT_SYMBOL_GPL(led_classdev_unregister);
 463
 464static void devm_led_classdev_release(struct device *dev, void *res)
 465{
 466        led_classdev_unregister(*(struct led_classdev **)res);
 467}
 468
 469/**
 470 * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
 471 *
 472 * @parent: parent of LED device
 473 * @led_cdev: the led_classdev structure for this device.
 474 * @init_data: LED class device initialization data
 475 */
 476int devm_led_classdev_register_ext(struct device *parent,
 477                                   struct led_classdev *led_cdev,
 478                                   struct led_init_data *init_data)
 479{
 480        struct led_classdev **dr;
 481        int rc;
 482
 483        dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
 484        if (!dr)
 485                return -ENOMEM;
 486
 487        rc = led_classdev_register_ext(parent, led_cdev, init_data);
 488        if (rc) {
 489                devres_free(dr);
 490                return rc;
 491        }
 492
 493        *dr = led_cdev;
 494        devres_add(parent, dr);
 495
 496        return 0;
 497}
 498EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
 499
 500static int devm_led_classdev_match(struct device *dev, void *res, void *data)
 501{
 502        struct led_classdev **p = res;
 503
 504        if (WARN_ON(!p || !*p))
 505                return 0;
 506
 507        return *p == data;
 508}
 509
 510/**
 511 * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
 512 * @parent: The device to unregister.
 513 * @led_cdev: the led_classdev structure for this device.
 514 */
 515void devm_led_classdev_unregister(struct device *dev,
 516                                  struct led_classdev *led_cdev)
 517{
 518        WARN_ON(devres_release(dev,
 519                               devm_led_classdev_release,
 520                               devm_led_classdev_match, led_cdev));
 521}
 522EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
 523
 524static int __init leds_init(void)
 525{
 526        leds_class = class_create(THIS_MODULE, "leds");
 527        if (IS_ERR(leds_class))
 528                return PTR_ERR(leds_class);
 529        leds_class->pm = &leds_class_dev_pm_ops;
 530        leds_class->dev_groups = led_groups;
 531        return 0;
 532}
 533
 534static void __exit leds_exit(void)
 535{
 536        class_destroy(leds_class);
 537}
 538
 539subsys_initcall(leds_init);
 540module_exit(leds_exit);
 541
 542MODULE_AUTHOR("John Lenz, Richard Purdie");
 543MODULE_LICENSE("GPL");
 544MODULE_DESCRIPTION("LED Class Interface");
 545