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}
 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        /* Not using device tree? */
 289        if (!IS_ENABLED(CONFIG_OF) || !dev->of_node)
 290                return ERR_PTR(-ENOTSUPP);
 291
 292        led = of_led_get(dev->of_node, index);
 293        if (IS_ERR(led))
 294                return led;
 295
 296        dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *),
 297                          GFP_KERNEL);
 298        if (!dr) {
 299                led_put(led);
 300                return ERR_PTR(-ENOMEM);
 301        }
 302
 303        *dr = led;
 304        devres_add(dev, dr);
 305
 306        return led;
 307}
 308EXPORT_SYMBOL_GPL(devm_of_led_get);
 309
 310static int led_classdev_next_name(const char *init_name, char *name,
 311                                  size_t len)
 312{
 313        unsigned int i = 0;
 314        int ret = 0;
 315        struct device *dev;
 316
 317        strlcpy(name, init_name, len);
 318
 319        while ((ret < len) &&
 320               (dev = class_find_device_by_name(leds_class, name))) {
 321                put_device(dev);
 322                ret = snprintf(name, len, "%s_%u", init_name, ++i);
 323        }
 324
 325        if (ret >= len)
 326                return -ENOMEM;
 327
 328        return i;
 329}
 330
 331/**
 332 * led_classdev_register_ext - register a new object of led_classdev class
 333 *                             with init data.
 334 *
 335 * @parent: parent of LED device
 336 * @led_cdev: the led_classdev structure for this device.
 337 * @init_data: LED class device initialization data
 338 */
 339int led_classdev_register_ext(struct device *parent,
 340                              struct led_classdev *led_cdev,
 341                              struct led_init_data *init_data)
 342{
 343        char composed_name[LED_MAX_NAME_SIZE];
 344        char final_name[LED_MAX_NAME_SIZE];
 345        const char *proposed_name = composed_name;
 346        int ret;
 347
 348        if (init_data) {
 349                if (init_data->devname_mandatory && !init_data->devicename) {
 350                        dev_err(parent, "Mandatory device name is missing");
 351                        return -EINVAL;
 352                }
 353                ret = led_compose_name(parent, init_data, composed_name);
 354                if (ret < 0)
 355                        return ret;
 356        } else {
 357                proposed_name = led_cdev->name;
 358        }
 359
 360        ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
 361        if (ret < 0)
 362                return ret;
 363
 364        mutex_init(&led_cdev->led_access);
 365        mutex_lock(&led_cdev->led_access);
 366        led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
 367                                led_cdev, led_cdev->groups, "%s", final_name);
 368        if (IS_ERR(led_cdev->dev)) {
 369                mutex_unlock(&led_cdev->led_access);
 370                return PTR_ERR(led_cdev->dev);
 371        }
 372        if (init_data && init_data->fwnode) {
 373                led_cdev->dev->fwnode = init_data->fwnode;
 374                led_cdev->dev->of_node = to_of_node(init_data->fwnode);
 375        }
 376
 377        if (ret)
 378                dev_warn(parent, "Led %s renamed to %s due to name collision",
 379                                led_cdev->name, dev_name(led_cdev->dev));
 380
 381        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
 382                ret = led_add_brightness_hw_changed(led_cdev);
 383                if (ret) {
 384                        device_unregister(led_cdev->dev);
 385                        led_cdev->dev = NULL;
 386                        mutex_unlock(&led_cdev->led_access);
 387                        return ret;
 388                }
 389        }
 390
 391        led_cdev->work_flags = 0;
 392#ifdef CONFIG_LEDS_TRIGGERS
 393        init_rwsem(&led_cdev->trigger_lock);
 394#endif
 395#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
 396        led_cdev->brightness_hw_changed = -1;
 397#endif
 398        /* add to the list of leds */
 399        down_write(&leds_list_lock);
 400        list_add_tail(&led_cdev->node, &leds_list);
 401        up_write(&leds_list_lock);
 402
 403        if (!led_cdev->max_brightness)
 404                led_cdev->max_brightness = LED_FULL;
 405
 406        led_update_brightness(led_cdev);
 407
 408        led_init_core(led_cdev);
 409
 410#ifdef CONFIG_LEDS_TRIGGERS
 411        led_trigger_set_default(led_cdev);
 412#endif
 413
 414        mutex_unlock(&led_cdev->led_access);
 415
 416        dev_dbg(parent, "Registered led device: %s\n",
 417                        led_cdev->name);
 418
 419        return 0;
 420}
 421EXPORT_SYMBOL_GPL(led_classdev_register_ext);
 422
 423/**
 424 * led_classdev_unregister - unregisters a object of led_properties class.
 425 * @led_cdev: the led device to unregister
 426 *
 427 * Unregisters a previously registered via led_classdev_register object.
 428 */
 429void led_classdev_unregister(struct led_classdev *led_cdev)
 430{
 431        if (IS_ERR_OR_NULL(led_cdev->dev))
 432                return;
 433
 434#ifdef CONFIG_LEDS_TRIGGERS
 435        down_write(&led_cdev->trigger_lock);
 436        if (led_cdev->trigger)
 437                led_trigger_set(led_cdev, NULL);
 438        up_write(&led_cdev->trigger_lock);
 439#endif
 440
 441        led_cdev->flags |= LED_UNREGISTERING;
 442
 443        /* Stop blinking */
 444        led_stop_software_blink(led_cdev);
 445
 446        led_set_brightness(led_cdev, LED_OFF);
 447
 448        flush_work(&led_cdev->set_brightness_work);
 449
 450        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
 451                led_remove_brightness_hw_changed(led_cdev);
 452
 453        device_unregister(led_cdev->dev);
 454
 455        down_write(&leds_list_lock);
 456        list_del(&led_cdev->node);
 457        up_write(&leds_list_lock);
 458
 459        mutex_destroy(&led_cdev->led_access);
 460}
 461EXPORT_SYMBOL_GPL(led_classdev_unregister);
 462
 463static void devm_led_classdev_release(struct device *dev, void *res)
 464{
 465        led_classdev_unregister(*(struct led_classdev **)res);
 466}
 467
 468/**
 469 * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
 470 *
 471 * @parent: parent of LED device
 472 * @led_cdev: the led_classdev structure for this device.
 473 * @init_data: LED class device initialization data
 474 */
 475int devm_led_classdev_register_ext(struct device *parent,
 476                                   struct led_classdev *led_cdev,
 477                                   struct led_init_data *init_data)
 478{
 479        struct led_classdev **dr;
 480        int rc;
 481
 482        dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
 483        if (!dr)
 484                return -ENOMEM;
 485
 486        rc = led_classdev_register_ext(parent, led_cdev, init_data);
 487        if (rc) {
 488                devres_free(dr);
 489                return rc;
 490        }
 491
 492        *dr = led_cdev;
 493        devres_add(parent, dr);
 494
 495        return 0;
 496}
 497EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
 498
 499static int devm_led_classdev_match(struct device *dev, void *res, void *data)
 500{
 501        struct led_classdev **p = res;
 502
 503        if (WARN_ON(!p || !*p))
 504                return 0;
 505
 506        return *p == data;
 507}
 508
 509/**
 510 * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
 511 * @parent: The device to unregister.
 512 * @led_cdev: the led_classdev structure for this device.
 513 */
 514void devm_led_classdev_unregister(struct device *dev,
 515                                  struct led_classdev *led_cdev)
 516{
 517        WARN_ON(devres_release(dev,
 518                               devm_led_classdev_release,
 519                               devm_led_classdev_match, led_cdev));
 520}
 521EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
 522
 523static int __init leds_init(void)
 524{
 525        leds_class = class_create(THIS_MODULE, "leds");
 526        if (IS_ERR(leds_class))
 527                return PTR_ERR(leds_class);
 528        leds_class->pm = &leds_class_dev_pm_ops;
 529        leds_class->dev_groups = led_groups;
 530        return 0;
 531}
 532
 533static void __exit leds_exit(void)
 534{
 535        class_destroy(leds_class);
 536}
 537
 538subsys_initcall(leds_init);
 539module_exit(leds_exit);
 540
 541MODULE_AUTHOR("John Lenz, Richard Purdie");
 542MODULE_LICENSE("GPL");
 543MODULE_DESCRIPTION("LED Class Interface");
 544