Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * LED Class Core
0004  *
0005  * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
0006  * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com>
0007  */
0008 
0009 #include <linux/ctype.h>
0010 #include <linux/device.h>
0011 #include <linux/err.h>
0012 #include <linux/init.h>
0013 #include <linux/kernel.h>
0014 #include <linux/leds.h>
0015 #include <linux/list.h>
0016 #include <linux/module.h>
0017 #include <linux/property.h>
0018 #include <linux/slab.h>
0019 #include <linux/spinlock.h>
0020 #include <linux/timer.h>
0021 #include <uapi/linux/uleds.h>
0022 #include <linux/of.h>
0023 #include "leds.h"
0024 
0025 static struct class *leds_class;
0026 
0027 static ssize_t brightness_show(struct device *dev,
0028         struct device_attribute *attr, char *buf)
0029 {
0030     struct led_classdev *led_cdev = dev_get_drvdata(dev);
0031 
0032     /* no lock needed for this */
0033     led_update_brightness(led_cdev);
0034 
0035     return sprintf(buf, "%u\n", led_cdev->brightness);
0036 }
0037 
0038 static ssize_t brightness_store(struct device *dev,
0039         struct device_attribute *attr, const char *buf, size_t size)
0040 {
0041     struct led_classdev *led_cdev = dev_get_drvdata(dev);
0042     unsigned long state;
0043     ssize_t ret;
0044 
0045     mutex_lock(&led_cdev->led_access);
0046 
0047     if (led_sysfs_is_disabled(led_cdev)) {
0048         ret = -EBUSY;
0049         goto unlock;
0050     }
0051 
0052     ret = kstrtoul(buf, 10, &state);
0053     if (ret)
0054         goto unlock;
0055 
0056     if (state == LED_OFF)
0057         led_trigger_remove(led_cdev);
0058     led_set_brightness(led_cdev, state);
0059     flush_work(&led_cdev->set_brightness_work);
0060 
0061     ret = size;
0062 unlock:
0063     mutex_unlock(&led_cdev->led_access);
0064     return ret;
0065 }
0066 static DEVICE_ATTR_RW(brightness);
0067 
0068 static ssize_t max_brightness_show(struct device *dev,
0069         struct device_attribute *attr, char *buf)
0070 {
0071     struct led_classdev *led_cdev = dev_get_drvdata(dev);
0072 
0073     return sprintf(buf, "%u\n", led_cdev->max_brightness);
0074 }
0075 static DEVICE_ATTR_RO(max_brightness);
0076 
0077 #ifdef CONFIG_LEDS_TRIGGERS
0078 static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
0079 static struct bin_attribute *led_trigger_bin_attrs[] = {
0080     &bin_attr_trigger,
0081     NULL,
0082 };
0083 static const struct attribute_group led_trigger_group = {
0084     .bin_attrs = led_trigger_bin_attrs,
0085 };
0086 #endif
0087 
0088 static struct attribute *led_class_attrs[] = {
0089     &dev_attr_brightness.attr,
0090     &dev_attr_max_brightness.attr,
0091     NULL,
0092 };
0093 
0094 static const struct attribute_group led_group = {
0095     .attrs = led_class_attrs,
0096 };
0097 
0098 static const struct attribute_group *led_groups[] = {
0099     &led_group,
0100 #ifdef CONFIG_LEDS_TRIGGERS
0101     &led_trigger_group,
0102 #endif
0103     NULL,
0104 };
0105 
0106 #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
0107 static ssize_t brightness_hw_changed_show(struct device *dev,
0108         struct device_attribute *attr, char *buf)
0109 {
0110     struct led_classdev *led_cdev = dev_get_drvdata(dev);
0111 
0112     if (led_cdev->brightness_hw_changed == -1)
0113         return -ENODATA;
0114 
0115     return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
0116 }
0117 
0118 static DEVICE_ATTR_RO(brightness_hw_changed);
0119 
0120 static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
0121 {
0122     struct device *dev = led_cdev->dev;
0123     int ret;
0124 
0125     ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
0126     if (ret) {
0127         dev_err(dev, "Error creating brightness_hw_changed\n");
0128         return ret;
0129     }
0130 
0131     led_cdev->brightness_hw_changed_kn =
0132         sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
0133     if (!led_cdev->brightness_hw_changed_kn) {
0134         dev_err(dev, "Error getting brightness_hw_changed kn\n");
0135         device_remove_file(dev, &dev_attr_brightness_hw_changed);
0136         return -ENXIO;
0137     }
0138 
0139     return 0;
0140 }
0141 
0142 static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
0143 {
0144     sysfs_put(led_cdev->brightness_hw_changed_kn);
0145     device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
0146 }
0147 
0148 void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, unsigned int brightness)
0149 {
0150     if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
0151         return;
0152 
0153     led_cdev->brightness_hw_changed = brightness;
0154     sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
0155 }
0156 EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
0157 #else
0158 static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
0159 {
0160     return 0;
0161 }
0162 static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
0163 {
0164 }
0165 #endif
0166 
0167 /**
0168  * led_classdev_suspend - suspend an led_classdev.
0169  * @led_cdev: the led_classdev to suspend.
0170  */
0171 void led_classdev_suspend(struct led_classdev *led_cdev)
0172 {
0173     led_cdev->flags |= LED_SUSPENDED;
0174     led_set_brightness_nopm(led_cdev, 0);
0175     flush_work(&led_cdev->set_brightness_work);
0176 }
0177 EXPORT_SYMBOL_GPL(led_classdev_suspend);
0178 
0179 /**
0180  * led_classdev_resume - resume an led_classdev.
0181  * @led_cdev: the led_classdev to resume.
0182  */
0183 void led_classdev_resume(struct led_classdev *led_cdev)
0184 {
0185     led_set_brightness_nopm(led_cdev, led_cdev->brightness);
0186 
0187     if (led_cdev->flash_resume)
0188         led_cdev->flash_resume(led_cdev);
0189 
0190     led_cdev->flags &= ~LED_SUSPENDED;
0191 }
0192 EXPORT_SYMBOL_GPL(led_classdev_resume);
0193 
0194 #ifdef CONFIG_PM_SLEEP
0195 static int led_suspend(struct device *dev)
0196 {
0197     struct led_classdev *led_cdev = dev_get_drvdata(dev);
0198 
0199     if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
0200         led_classdev_suspend(led_cdev);
0201 
0202     return 0;
0203 }
0204 
0205 static int led_resume(struct device *dev)
0206 {
0207     struct led_classdev *led_cdev = dev_get_drvdata(dev);
0208 
0209     if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
0210         led_classdev_resume(led_cdev);
0211 
0212     return 0;
0213 }
0214 #endif
0215 
0216 static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
0217 
0218 /**
0219  * of_led_get() - request a LED device via the LED framework
0220  * @np: device node to get the LED device from
0221  * @index: the index of the LED
0222  *
0223  * Returns the LED device parsed from the phandle specified in the "leds"
0224  * property of a device tree node or a negative error-code on failure.
0225  */
0226 struct led_classdev *of_led_get(struct device_node *np, int index)
0227 {
0228     struct device *led_dev;
0229     struct led_classdev *led_cdev;
0230     struct device_node *led_node;
0231 
0232     led_node = of_parse_phandle(np, "leds", index);
0233     if (!led_node)
0234         return ERR_PTR(-ENOENT);
0235 
0236     led_dev = class_find_device_by_of_node(leds_class, led_node);
0237     of_node_put(led_node);
0238 
0239     if (!led_dev)
0240         return ERR_PTR(-EPROBE_DEFER);
0241 
0242     led_cdev = dev_get_drvdata(led_dev);
0243 
0244     if (!try_module_get(led_cdev->dev->parent->driver->owner))
0245         return ERR_PTR(-ENODEV);
0246 
0247     return led_cdev;
0248 }
0249 EXPORT_SYMBOL_GPL(of_led_get);
0250 
0251 /**
0252  * led_put() - release a LED device
0253  * @led_cdev: LED device
0254  */
0255 void led_put(struct led_classdev *led_cdev)
0256 {
0257     module_put(led_cdev->dev->parent->driver->owner);
0258 }
0259 EXPORT_SYMBOL_GPL(led_put);
0260 
0261 static void devm_led_release(struct device *dev, void *res)
0262 {
0263     struct led_classdev **p = res;
0264 
0265     led_put(*p);
0266 }
0267 
0268 /**
0269  * devm_of_led_get - Resource-managed request of a LED device
0270  * @dev:    LED consumer
0271  * @index:  index of the LED to obtain in the consumer
0272  *
0273  * The device node of the device is parse to find the request LED device.
0274  * The LED device returned from this function is automatically released
0275  * on driver detach.
0276  *
0277  * @return a pointer to a LED device or ERR_PTR(errno) on failure.
0278  */
0279 struct led_classdev *__must_check devm_of_led_get(struct device *dev,
0280                           int index)
0281 {
0282     struct led_classdev *led;
0283     struct led_classdev **dr;
0284 
0285     if (!dev)
0286         return ERR_PTR(-EINVAL);
0287 
0288     led = of_led_get(dev->of_node, index);
0289     if (IS_ERR(led))
0290         return led;
0291 
0292     dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *),
0293               GFP_KERNEL);
0294     if (!dr) {
0295         led_put(led);
0296         return ERR_PTR(-ENOMEM);
0297     }
0298 
0299     *dr = led;
0300     devres_add(dev, dr);
0301 
0302     return led;
0303 }
0304 EXPORT_SYMBOL_GPL(devm_of_led_get);
0305 
0306 static int led_classdev_next_name(const char *init_name, char *name,
0307                   size_t len)
0308 {
0309     unsigned int i = 0;
0310     int ret = 0;
0311     struct device *dev;
0312 
0313     strlcpy(name, init_name, len);
0314 
0315     while ((ret < len) &&
0316            (dev = class_find_device_by_name(leds_class, name))) {
0317         put_device(dev);
0318         ret = snprintf(name, len, "%s_%u", init_name, ++i);
0319     }
0320 
0321     if (ret >= len)
0322         return -ENOMEM;
0323 
0324     return i;
0325 }
0326 
0327 /**
0328  * led_classdev_register_ext - register a new object of led_classdev class
0329  *                 with init data.
0330  *
0331  * @parent: parent of LED device
0332  * @led_cdev: the led_classdev structure for this device.
0333  * @init_data: LED class device initialization data
0334  */
0335 int led_classdev_register_ext(struct device *parent,
0336                   struct led_classdev *led_cdev,
0337                   struct led_init_data *init_data)
0338 {
0339     char composed_name[LED_MAX_NAME_SIZE];
0340     char final_name[LED_MAX_NAME_SIZE];
0341     const char *proposed_name = composed_name;
0342     int ret;
0343 
0344     if (init_data) {
0345         if (init_data->devname_mandatory && !init_data->devicename) {
0346             dev_err(parent, "Mandatory device name is missing");
0347             return -EINVAL;
0348         }
0349         ret = led_compose_name(parent, init_data, composed_name);
0350         if (ret < 0)
0351             return ret;
0352 
0353         if (init_data->fwnode) {
0354             fwnode_property_read_string(init_data->fwnode,
0355                 "linux,default-trigger",
0356                 &led_cdev->default_trigger);
0357 
0358             if (fwnode_property_present(init_data->fwnode,
0359                             "retain-state-shutdown"))
0360                 led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
0361         }
0362     } else {
0363         proposed_name = led_cdev->name;
0364     }
0365 
0366     ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
0367     if (ret < 0)
0368         return ret;
0369 
0370     mutex_init(&led_cdev->led_access);
0371     mutex_lock(&led_cdev->led_access);
0372     led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
0373                 led_cdev, led_cdev->groups, "%s", final_name);
0374     if (IS_ERR(led_cdev->dev)) {
0375         mutex_unlock(&led_cdev->led_access);
0376         return PTR_ERR(led_cdev->dev);
0377     }
0378     if (init_data && init_data->fwnode)
0379         device_set_node(led_cdev->dev, init_data->fwnode);
0380 
0381     if (ret)
0382         dev_warn(parent, "Led %s renamed to %s due to name collision",
0383                 proposed_name, dev_name(led_cdev->dev));
0384 
0385     if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
0386         ret = led_add_brightness_hw_changed(led_cdev);
0387         if (ret) {
0388             device_unregister(led_cdev->dev);
0389             led_cdev->dev = NULL;
0390             mutex_unlock(&led_cdev->led_access);
0391             return ret;
0392         }
0393     }
0394 
0395     led_cdev->work_flags = 0;
0396 #ifdef CONFIG_LEDS_TRIGGERS
0397     init_rwsem(&led_cdev->trigger_lock);
0398 #endif
0399 #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
0400     led_cdev->brightness_hw_changed = -1;
0401 #endif
0402     /* add to the list of leds */
0403     down_write(&leds_list_lock);
0404     list_add_tail(&led_cdev->node, &leds_list);
0405     up_write(&leds_list_lock);
0406 
0407     if (!led_cdev->max_brightness)
0408         led_cdev->max_brightness = LED_FULL;
0409 
0410     led_update_brightness(led_cdev);
0411 
0412     led_init_core(led_cdev);
0413 
0414 #ifdef CONFIG_LEDS_TRIGGERS
0415     led_trigger_set_default(led_cdev);
0416 #endif
0417 
0418     mutex_unlock(&led_cdev->led_access);
0419 
0420     dev_dbg(parent, "Registered led device: %s\n",
0421             led_cdev->name);
0422 
0423     return 0;
0424 }
0425 EXPORT_SYMBOL_GPL(led_classdev_register_ext);
0426 
0427 /**
0428  * led_classdev_unregister - unregisters a object of led_properties class.
0429  * @led_cdev: the led device to unregister
0430  *
0431  * Unregisters a previously registered via led_classdev_register object.
0432  */
0433 void led_classdev_unregister(struct led_classdev *led_cdev)
0434 {
0435     if (IS_ERR_OR_NULL(led_cdev->dev))
0436         return;
0437 
0438 #ifdef CONFIG_LEDS_TRIGGERS
0439     down_write(&led_cdev->trigger_lock);
0440     if (led_cdev->trigger)
0441         led_trigger_set(led_cdev, NULL);
0442     up_write(&led_cdev->trigger_lock);
0443 #endif
0444 
0445     led_cdev->flags |= LED_UNREGISTERING;
0446 
0447     /* Stop blinking */
0448     led_stop_software_blink(led_cdev);
0449 
0450     if (!(led_cdev->flags & LED_RETAIN_AT_SHUTDOWN))
0451         led_set_brightness(led_cdev, LED_OFF);
0452 
0453     flush_work(&led_cdev->set_brightness_work);
0454 
0455     if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
0456         led_remove_brightness_hw_changed(led_cdev);
0457 
0458     device_unregister(led_cdev->dev);
0459 
0460     down_write(&leds_list_lock);
0461     list_del(&led_cdev->node);
0462     up_write(&leds_list_lock);
0463 
0464     mutex_destroy(&led_cdev->led_access);
0465 }
0466 EXPORT_SYMBOL_GPL(led_classdev_unregister);
0467 
0468 static void devm_led_classdev_release(struct device *dev, void *res)
0469 {
0470     led_classdev_unregister(*(struct led_classdev **)res);
0471 }
0472 
0473 /**
0474  * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
0475  *
0476  * @parent: parent of LED device
0477  * @led_cdev: the led_classdev structure for this device.
0478  * @init_data: LED class device initialization data
0479  */
0480 int devm_led_classdev_register_ext(struct device *parent,
0481                    struct led_classdev *led_cdev,
0482                    struct led_init_data *init_data)
0483 {
0484     struct led_classdev **dr;
0485     int rc;
0486 
0487     dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
0488     if (!dr)
0489         return -ENOMEM;
0490 
0491     rc = led_classdev_register_ext(parent, led_cdev, init_data);
0492     if (rc) {
0493         devres_free(dr);
0494         return rc;
0495     }
0496 
0497     *dr = led_cdev;
0498     devres_add(parent, dr);
0499 
0500     return 0;
0501 }
0502 EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
0503 
0504 static int devm_led_classdev_match(struct device *dev, void *res, void *data)
0505 {
0506     struct led_classdev **p = res;
0507 
0508     if (WARN_ON(!p || !*p))
0509         return 0;
0510 
0511     return *p == data;
0512 }
0513 
0514 /**
0515  * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
0516  * @dev: The device to unregister.
0517  * @led_cdev: the led_classdev structure for this device.
0518  */
0519 void devm_led_classdev_unregister(struct device *dev,
0520                   struct led_classdev *led_cdev)
0521 {
0522     WARN_ON(devres_release(dev,
0523                    devm_led_classdev_release,
0524                    devm_led_classdev_match, led_cdev));
0525 }
0526 EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
0527 
0528 static int __init leds_init(void)
0529 {
0530     leds_class = class_create(THIS_MODULE, "leds");
0531     if (IS_ERR(leds_class))
0532         return PTR_ERR(leds_class);
0533     leds_class->pm = &leds_class_dev_pm_ops;
0534     leds_class->dev_groups = led_groups;
0535     return 0;
0536 }
0537 
0538 static void __exit leds_exit(void)
0539 {
0540     class_destroy(leds_class);
0541 }
0542 
0543 subsys_initcall(leds_init);
0544 module_exit(leds_exit);
0545 
0546 MODULE_AUTHOR("John Lenz, Richard Purdie");
0547 MODULE_LICENSE("GPL");
0548 MODULE_DESCRIPTION("LED Class Interface");