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/slab.h>
  18#include <linux/spinlock.h>
  19#include <linux/timer.h>
  20#include <uapi/linux/uleds.h>
  21#include "leds.h"
  22
  23static struct class *leds_class;
  24
  25static ssize_t brightness_show(struct device *dev,
  26                struct device_attribute *attr, char *buf)
  27{
  28        struct led_classdev *led_cdev = dev_get_drvdata(dev);
  29
  30        /* no lock needed for this */
  31        led_update_brightness(led_cdev);
  32
  33        return sprintf(buf, "%u\n", led_cdev->brightness);
  34}
  35
  36static ssize_t brightness_store(struct device *dev,
  37                struct device_attribute *attr, const char *buf, size_t size)
  38{
  39        struct led_classdev *led_cdev = dev_get_drvdata(dev);
  40        unsigned long state;
  41        ssize_t ret;
  42
  43        mutex_lock(&led_cdev->led_access);
  44
  45        if (led_sysfs_is_disabled(led_cdev)) {
  46                ret = -EBUSY;
  47                goto unlock;
  48        }
  49
  50        ret = kstrtoul(buf, 10, &state);
  51        if (ret)
  52                goto unlock;
  53
  54        if (state == LED_OFF)
  55                led_trigger_remove(led_cdev);
  56        led_set_brightness(led_cdev, state);
  57        flush_work(&led_cdev->set_brightness_work);
  58
  59        ret = size;
  60unlock:
  61        mutex_unlock(&led_cdev->led_access);
  62        return ret;
  63}
  64static DEVICE_ATTR_RW(brightness);
  65
  66static ssize_t max_brightness_show(struct device *dev,
  67                struct device_attribute *attr, char *buf)
  68{
  69        struct led_classdev *led_cdev = dev_get_drvdata(dev);
  70
  71        return sprintf(buf, "%u\n", led_cdev->max_brightness);
  72}
  73static DEVICE_ATTR_RO(max_brightness);
  74
  75#ifdef CONFIG_LEDS_TRIGGERS
  76static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
  77static struct attribute *led_trigger_attrs[] = {
  78        &dev_attr_trigger.attr,
  79        NULL,
  80};
  81static const struct attribute_group led_trigger_group = {
  82        .attrs = led_trigger_attrs,
  83};
  84#endif
  85
  86static struct attribute *led_class_attrs[] = {
  87        &dev_attr_brightness.attr,
  88        &dev_attr_max_brightness.attr,
  89        NULL,
  90};
  91
  92static const struct attribute_group led_group = {
  93        .attrs = led_class_attrs,
  94};
  95
  96static const struct attribute_group *led_groups[] = {
  97        &led_group,
  98#ifdef CONFIG_LEDS_TRIGGERS
  99        &led_trigger_group,
 100#endif
 101        NULL,
 102};
 103
 104#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
 105static ssize_t brightness_hw_changed_show(struct device *dev,
 106                struct device_attribute *attr, char *buf)
 107{
 108        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 109
 110        if (led_cdev->brightness_hw_changed == -1)
 111                return -ENODATA;
 112
 113        return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
 114}
 115
 116static DEVICE_ATTR_RO(brightness_hw_changed);
 117
 118static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
 119{
 120        struct device *dev = led_cdev->dev;
 121        int ret;
 122
 123        ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
 124        if (ret) {
 125                dev_err(dev, "Error creating brightness_hw_changed\n");
 126                return ret;
 127        }
 128
 129        led_cdev->brightness_hw_changed_kn =
 130                sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
 131        if (!led_cdev->brightness_hw_changed_kn) {
 132                dev_err(dev, "Error getting brightness_hw_changed kn\n");
 133                device_remove_file(dev, &dev_attr_brightness_hw_changed);
 134                return -ENXIO;
 135        }
 136
 137        return 0;
 138}
 139
 140static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
 141{
 142        sysfs_put(led_cdev->brightness_hw_changed_kn);
 143        device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
 144}
 145
 146void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
 147                                               enum led_brightness brightness)
 148{
 149        if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
 150                return;
 151
 152        led_cdev->brightness_hw_changed = brightness;
 153        sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
 154}
 155EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
 156#else
 157static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
 158{
 159        return 0;
 160}
 161static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
 162{
 163}
 164#endif
 165
 166/**
 167 * led_classdev_suspend - suspend an led_classdev.
 168 * @led_cdev: the led_classdev to suspend.
 169 */
 170void led_classdev_suspend(struct led_classdev *led_cdev)
 171{
 172        led_cdev->flags |= LED_SUSPENDED;
 173        led_set_brightness_nopm(led_cdev, 0);
 174}
 175EXPORT_SYMBOL_GPL(led_classdev_suspend);
 176
 177/**
 178 * led_classdev_resume - resume an led_classdev.
 179 * @led_cdev: the led_classdev to resume.
 180 */
 181void led_classdev_resume(struct led_classdev *led_cdev)
 182{
 183        led_set_brightness_nopm(led_cdev, led_cdev->brightness);
 184
 185        if (led_cdev->flash_resume)
 186                led_cdev->flash_resume(led_cdev);
 187
 188        led_cdev->flags &= ~LED_SUSPENDED;
 189}
 190EXPORT_SYMBOL_GPL(led_classdev_resume);
 191
 192#ifdef CONFIG_PM_SLEEP
 193static int led_suspend(struct device *dev)
 194{
 195        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 196
 197        if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
 198                led_classdev_suspend(led_cdev);
 199
 200        return 0;
 201}
 202
 203static int led_resume(struct device *dev)
 204{
 205        struct led_classdev *led_cdev = dev_get_drvdata(dev);
 206
 207        if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
 208                led_classdev_resume(led_cdev);
 209
 210        return 0;
 211}
 212#endif
 213
 214static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
 215
 216static int match_name(struct device *dev, const void *data)
 217{
 218        if (!dev_name(dev))
 219                return 0;
 220        return !strcmp(dev_name(dev), (char *)data);
 221}
 222
 223static int led_classdev_next_name(const char *init_name, char *name,
 224                                  size_t len)
 225{
 226        unsigned int i = 0;
 227        int ret = 0;
 228        struct device *dev;
 229
 230        strlcpy(name, init_name, len);
 231
 232        while ((ret < len) &&
 233               (dev = class_find_device(leds_class, NULL, name, match_name))) {
 234                put_device(dev);
 235                ret = snprintf(name, len, "%s_%u", init_name, ++i);
 236        }
 237
 238        if (ret >= len)
 239                return -ENOMEM;
 240
 241        return i;
 242}
 243
 244/**
 245 * of_led_classdev_register - register a new object of led_classdev class.
 246 *
 247 * @parent: parent of LED device
 248 * @led_cdev: the led_classdev structure for this device.
 249 * @np: DT node describing this LED
 250 */
 251int of_led_classdev_register(struct device *parent, struct device_node *np,
 252                            struct led_classdev *led_cdev)
 253{
 254        char name[LED_MAX_NAME_SIZE];
 255        int ret;
 256
 257        ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
 258        if (ret < 0)
 259                return ret;
 260
 261        mutex_init(&led_cdev->led_access);
 262        mutex_lock(&led_cdev->led_access);
 263        led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
 264                                led_cdev, led_cdev->groups, "%s", name);
 265        if (IS_ERR(led_cdev->dev)) {
 266                mutex_unlock(&led_cdev->led_access);
 267                return PTR_ERR(led_cdev->dev);
 268        }
 269        led_cdev->dev->of_node = np;
 270
 271        if (ret)
 272                dev_warn(parent, "Led %s renamed to %s due to name collision",
 273                                led_cdev->name, dev_name(led_cdev->dev));
 274
 275        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
 276                ret = led_add_brightness_hw_changed(led_cdev);
 277                if (ret) {
 278                        device_unregister(led_cdev->dev);
 279                        mutex_unlock(&led_cdev->led_access);
 280                        return ret;
 281                }
 282        }
 283
 284        led_cdev->work_flags = 0;
 285#ifdef CONFIG_LEDS_TRIGGERS
 286        init_rwsem(&led_cdev->trigger_lock);
 287#endif
 288#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
 289        led_cdev->brightness_hw_changed = -1;
 290#endif
 291        /* add to the list of leds */
 292        down_write(&leds_list_lock);
 293        list_add_tail(&led_cdev->node, &leds_list);
 294        up_write(&leds_list_lock);
 295
 296        if (!led_cdev->max_brightness)
 297                led_cdev->max_brightness = LED_FULL;
 298
 299        led_update_brightness(led_cdev);
 300
 301        led_init_core(led_cdev);
 302
 303#ifdef CONFIG_LEDS_TRIGGERS
 304        led_trigger_set_default(led_cdev);
 305#endif
 306
 307        mutex_unlock(&led_cdev->led_access);
 308
 309        dev_dbg(parent, "Registered led device: %s\n",
 310                        led_cdev->name);
 311
 312        return 0;
 313}
 314EXPORT_SYMBOL_GPL(of_led_classdev_register);
 315
 316/**
 317 * led_classdev_unregister - unregisters a object of led_properties class.
 318 * @led_cdev: the led device to unregister
 319 *
 320 * Unregisters a previously registered via led_classdev_register object.
 321 */
 322void led_classdev_unregister(struct led_classdev *led_cdev)
 323{
 324#ifdef CONFIG_LEDS_TRIGGERS
 325        down_write(&led_cdev->trigger_lock);
 326        if (led_cdev->trigger)
 327                led_trigger_set(led_cdev, NULL);
 328        up_write(&led_cdev->trigger_lock);
 329#endif
 330
 331        led_cdev->flags |= LED_UNREGISTERING;
 332
 333        /* Stop blinking */
 334        led_stop_software_blink(led_cdev);
 335
 336        led_set_brightness(led_cdev, LED_OFF);
 337
 338        flush_work(&led_cdev->set_brightness_work);
 339
 340        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
 341                led_remove_brightness_hw_changed(led_cdev);
 342
 343        device_unregister(led_cdev->dev);
 344
 345        down_write(&leds_list_lock);
 346        list_del(&led_cdev->node);
 347        up_write(&leds_list_lock);
 348
 349        mutex_destroy(&led_cdev->led_access);
 350}
 351EXPORT_SYMBOL_GPL(led_classdev_unregister);
 352
 353static void devm_led_classdev_release(struct device *dev, void *res)
 354{
 355        led_classdev_unregister(*(struct led_classdev **)res);
 356}
 357
 358/**
 359 * devm_of_led_classdev_register - resource managed led_classdev_register()
 360 *
 361 * @parent: parent of LED device
 362 * @led_cdev: the led_classdev structure for this device.
 363 */
 364int devm_of_led_classdev_register(struct device *parent,
 365                                  struct device_node *np,
 366                                  struct led_classdev *led_cdev)
 367{
 368        struct led_classdev **dr;
 369        int rc;
 370
 371        dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
 372        if (!dr)
 373                return -ENOMEM;
 374
 375        rc = of_led_classdev_register(parent, np, led_cdev);
 376        if (rc) {
 377                devres_free(dr);
 378                return rc;
 379        }
 380
 381        *dr = led_cdev;
 382        devres_add(parent, dr);
 383
 384        return 0;
 385}
 386EXPORT_SYMBOL_GPL(devm_of_led_classdev_register);
 387
 388static int devm_led_classdev_match(struct device *dev, void *res, void *data)
 389{
 390        struct led_cdev **p = res;
 391
 392        if (WARN_ON(!p || !*p))
 393                return 0;
 394
 395        return *p == data;
 396}
 397
 398/**
 399 * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
 400 * @parent: The device to unregister.
 401 * @led_cdev: the led_classdev structure for this device.
 402 */
 403void devm_led_classdev_unregister(struct device *dev,
 404                                  struct led_classdev *led_cdev)
 405{
 406        WARN_ON(devres_release(dev,
 407                               devm_led_classdev_release,
 408                               devm_led_classdev_match, led_cdev));
 409}
 410EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
 411
 412static int __init leds_init(void)
 413{
 414        leds_class = class_create(THIS_MODULE, "leds");
 415        if (IS_ERR(leds_class))
 416                return PTR_ERR(leds_class);
 417        leds_class->pm = &leds_class_dev_pm_ops;
 418        leds_class->dev_groups = led_groups;
 419        return 0;
 420}
 421
 422static void __exit leds_exit(void)
 423{
 424        class_destroy(leds_class);
 425}
 426
 427subsys_initcall(leds_init);
 428module_exit(leds_exit);
 429
 430MODULE_AUTHOR("John Lenz, Richard Purdie");
 431MODULE_LICENSE("GPL");
 432MODULE_DESCRIPTION("LED Class Interface");
 433