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, unsigned int brightness)
 149{
 150        if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
 151                return;
 152
 153        led_cdev->brightness_hw_changed = brightness;
 154        sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
 155}
 156EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
 157#else
 158static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
 159{
 160        return 0;
 161}
 162static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
 163{
 164}
 165#endif
 166
 167/**
 168 * led_classdev_suspend - suspend an led_classdev.
 169 * @led_cdev: the led_classdev to suspend.
 170 */
 171void led_classdev_suspend(struct led_classdev *led_cdev)
 172{
 173        led_cdev->flags |= LED_SUSPENDED;
 174        led_set_brightness_nopm(led_cdev, 0);
 175        flush_work(&led_cdev->set_brightness_work);
 176}
 177EXPORT_SYMBOL_GPL(led_classdev_suspend);
 178
 179/**
 180 * led_classdev_resume - resume an led_classdev.
 181 * @led_cdev: the led_classdev to resume.
 182 */
 183void led_classdev_resume(struct led_classdev *led_cdev)
 184{
 185        led_set_brightness_nopm(led_cdev, led_cdev->brightness);
 186
 187        if (led_cdev->flash_resume)
 188                led_cdev->flash_resume(led_cdev);
 189
 190        led_cdev->flags &= ~LED_SUSPENDED;
 191}
 192EXPORT_SYMBOL_GPL(led_classdev_resume);
 193
 194#ifdef CONFIG_PM_SLEEP
 195static int led_suspend(struct device *dev)
 196{
 197        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 198
 199        if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
 200                led_classdev_suspend(led_cdev);
 201
 202        return 0;
 203}
 204
 205static int led_resume(struct device *dev)
 206{
 207        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 208
 209        if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
 210                led_classdev_resume(led_cdev);
 211
 212        return 0;
 213}
 214#endif
 215
 216static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
 217
 218/**
 219 * of_led_get() - request a LED device via the LED framework
 220 * @np: device node to get the LED device from
 221 * @index: the index of the LED
 222 *
 223 * Returns the LED device parsed from the phandle specified in the "leds"
 224 * property of a device tree node or a negative error-code on failure.
 225 */
 226struct led_classdev *of_led_get(struct device_node *np, int index)
 227{
 228        struct device *led_dev;
 229        struct led_classdev *led_cdev;
 230        struct device_node *led_node;
 231
 232        led_node = of_parse_phandle(np, "leds", index);
 233        if (!led_node)
 234                return ERR_PTR(-ENOENT);
 235
 236        led_dev = class_find_device_by_of_node(leds_class, led_node);
 237        of_node_put(led_node);
 238
 239        if (!led_dev)
 240                return ERR_PTR(-EPROBE_DEFER);
 241
 242        led_cdev = dev_get_drvdata(led_dev);
 243
 244        if (!try_module_get(led_cdev->dev->parent->driver->owner))
 245                return ERR_PTR(-ENODEV);
 246
 247        return led_cdev;
 248}
 249EXPORT_SYMBOL_GPL(of_led_get);
 250
 251/**
 252 * led_put() - release a LED device
 253 * @led_cdev: LED device
 254 */
 255void led_put(struct led_classdev *led_cdev)
 256{
 257        module_put(led_cdev->dev->parent->driver->owner);
 258}
 259EXPORT_SYMBOL_GPL(led_put);
 260
 261static void devm_led_release(struct device *dev, void *res)
 262{
 263        struct led_classdev **p = res;
 264
 265        led_put(*p);
 266}
 267
 268/**
 269 * devm_of_led_get - Resource-managed request of a LED device
 270 * @dev:        LED consumer
 271 * @index:      index of the LED to obtain in the consumer
 272 *
 273 * The device node of the device is parse to find the request LED device.
 274 * The LED device returned from this function is automatically released
 275 * on driver detach.
 276 *
 277 * @return a pointer to a LED device or ERR_PTR(errno) on failure.
 278 */
 279struct led_classdev *__must_check devm_of_led_get(struct device *dev,
 280                                                  int index)
 281{
 282        struct led_classdev *led;
 283        struct led_classdev **dr;
 284
 285        if (!dev)
 286                return ERR_PTR(-EINVAL);
 287
 288        led = of_led_get(dev->of_node, index);
 289        if (IS_ERR(led))
 290                return led;
 291
 292        dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *),
 293                          GFP_KERNEL);
 294        if (!dr) {
 295                led_put(led);
 296                return ERR_PTR(-ENOMEM);
 297        }
 298
 299        *dr = led;
 300        devres_add(dev, dr);
 301
 302        return led;
 303}
 304EXPORT_SYMBOL_GPL(devm_of_led_get);
 305
 306static int led_classdev_next_name(const char *init_name, char *name,
 307                                  size_t len)
 308{
 309        unsigned int i = 0;
 310        int ret = 0;
 311        struct device *dev;
 312
 313        strlcpy(name, init_name, len);
 314
 315        while ((ret < len) &&
 316               (dev = class_find_device_by_name(leds_class, name))) {
 317                put_device(dev);
 318                ret = snprintf(name, len, "%s_%u", init_name, ++i);
 319        }
 320
 321        if (ret >= len)
 322                return -ENOMEM;
 323
 324        return i;
 325}
 326
 327/**
 328 * led_classdev_register_ext - register a new object of led_classdev class
 329 *                             with init data.
 330 *
 331 * @parent: parent of LED device
 332 * @led_cdev: the led_classdev structure for this device.
 333 * @init_data: LED class device initialization data
 334 */
 335int led_classdev_register_ext(struct device *parent,
 336                              struct led_classdev *led_cdev,
 337                              struct led_init_data *init_data)
 338{
 339        char composed_name[LED_MAX_NAME_SIZE];
 340        char final_name[LED_MAX_NAME_SIZE];
 341        const char *proposed_name = composed_name;
 342        int ret;
 343
 344        if (init_data) {
 345                if (init_data->devname_mandatory && !init_data->devicename) {
 346                        dev_err(parent, "Mandatory device name is missing");
 347                        return -EINVAL;
 348                }
 349                ret = led_compose_name(parent, init_data, composed_name);
 350                if (ret < 0)
 351                        return ret;
 352
 353                if (init_data->fwnode)
 354                        fwnode_property_read_string(init_data->fwnode,
 355                                "linux,default-trigger",
 356                                &led_cdev->default_trigger);
 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 * @dev: 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