162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * LED Class Core 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu> 662306a36Sopenharmony_ci * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/ctype.h> 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/leds.h> 1562306a36Sopenharmony_ci#include <linux/list.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/property.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/spinlock.h> 2062306a36Sopenharmony_ci#include <linux/timer.h> 2162306a36Sopenharmony_ci#include <uapi/linux/uleds.h> 2262306a36Sopenharmony_ci#include <linux/of.h> 2362306a36Sopenharmony_ci#include "leds.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic DEFINE_MUTEX(leds_lookup_lock); 2662306a36Sopenharmony_cistatic LIST_HEAD(leds_lookup_list); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic ssize_t brightness_show(struct device *dev, 2962306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci /* no lock needed for this */ 3462306a36Sopenharmony_ci led_update_brightness(led_cdev); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci return sprintf(buf, "%u\n", led_cdev->brightness); 3762306a36Sopenharmony_ci} 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic ssize_t brightness_store(struct device *dev, 4062306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t size) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 4362306a36Sopenharmony_ci unsigned long state; 4462306a36Sopenharmony_ci ssize_t ret; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci mutex_lock(&led_cdev->led_access); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci if (led_sysfs_is_disabled(led_cdev)) { 4962306a36Sopenharmony_ci ret = -EBUSY; 5062306a36Sopenharmony_ci goto unlock; 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci ret = kstrtoul(buf, 10, &state); 5462306a36Sopenharmony_ci if (ret) 5562306a36Sopenharmony_ci goto unlock; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (state == LED_OFF) 5862306a36Sopenharmony_ci led_trigger_remove(led_cdev); 5962306a36Sopenharmony_ci led_set_brightness(led_cdev, state); 6062306a36Sopenharmony_ci flush_work(&led_cdev->set_brightness_work); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci ret = size; 6362306a36Sopenharmony_ciunlock: 6462306a36Sopenharmony_ci mutex_unlock(&led_cdev->led_access); 6562306a36Sopenharmony_ci return ret; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_cistatic DEVICE_ATTR_RW(brightness); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic ssize_t max_brightness_show(struct device *dev, 7062306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return sprintf(buf, "%u\n", led_cdev->max_brightness); 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_cistatic DEVICE_ATTR_RO(max_brightness); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci#ifdef CONFIG_LEDS_TRIGGERS 7962306a36Sopenharmony_cistatic BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); 8062306a36Sopenharmony_cistatic struct bin_attribute *led_trigger_bin_attrs[] = { 8162306a36Sopenharmony_ci &bin_attr_trigger, 8262306a36Sopenharmony_ci NULL, 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_cistatic const struct attribute_group led_trigger_group = { 8562306a36Sopenharmony_ci .bin_attrs = led_trigger_bin_attrs, 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci#endif 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic struct attribute *led_class_attrs[] = { 9062306a36Sopenharmony_ci &dev_attr_brightness.attr, 9162306a36Sopenharmony_ci &dev_attr_max_brightness.attr, 9262306a36Sopenharmony_ci NULL, 9362306a36Sopenharmony_ci}; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic const struct attribute_group led_group = { 9662306a36Sopenharmony_ci .attrs = led_class_attrs, 9762306a36Sopenharmony_ci}; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic const struct attribute_group *led_groups[] = { 10062306a36Sopenharmony_ci &led_group, 10162306a36Sopenharmony_ci#ifdef CONFIG_LEDS_TRIGGERS 10262306a36Sopenharmony_ci &led_trigger_group, 10362306a36Sopenharmony_ci#endif 10462306a36Sopenharmony_ci NULL, 10562306a36Sopenharmony_ci}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 10862306a36Sopenharmony_cistatic ssize_t brightness_hw_changed_show(struct device *dev, 10962306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (led_cdev->brightness_hw_changed == -1) 11462306a36Sopenharmony_ci return -ENODATA; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(brightness_hw_changed); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic int led_add_brightness_hw_changed(struct led_classdev *led_cdev) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci struct device *dev = led_cdev->dev; 12462306a36Sopenharmony_ci int ret; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci ret = device_create_file(dev, &dev_attr_brightness_hw_changed); 12762306a36Sopenharmony_ci if (ret) { 12862306a36Sopenharmony_ci dev_err(dev, "Error creating brightness_hw_changed\n"); 12962306a36Sopenharmony_ci return ret; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci led_cdev->brightness_hw_changed_kn = 13362306a36Sopenharmony_ci sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed"); 13462306a36Sopenharmony_ci if (!led_cdev->brightness_hw_changed_kn) { 13562306a36Sopenharmony_ci dev_err(dev, "Error getting brightness_hw_changed kn\n"); 13662306a36Sopenharmony_ci device_remove_file(dev, &dev_attr_brightness_hw_changed); 13762306a36Sopenharmony_ci return -ENXIO; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci sysfs_put(led_cdev->brightness_hw_changed_kn); 14662306a36Sopenharmony_ci device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed); 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_civoid led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, unsigned int brightness) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci if (WARN_ON(!led_cdev->brightness_hw_changed_kn)) 15262306a36Sopenharmony_ci return; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci led_cdev->brightness_hw_changed = brightness; 15562306a36Sopenharmony_ci sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn); 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed); 15862306a36Sopenharmony_ci#else 15962306a36Sopenharmony_cistatic int led_add_brightness_hw_changed(struct led_classdev *led_cdev) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci return 0; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_cistatic void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci#endif 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci/** 16962306a36Sopenharmony_ci * led_classdev_suspend - suspend an led_classdev. 17062306a36Sopenharmony_ci * @led_cdev: the led_classdev to suspend. 17162306a36Sopenharmony_ci */ 17262306a36Sopenharmony_civoid led_classdev_suspend(struct led_classdev *led_cdev) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci led_cdev->flags |= LED_SUSPENDED; 17562306a36Sopenharmony_ci led_set_brightness_nopm(led_cdev, 0); 17662306a36Sopenharmony_ci flush_work(&led_cdev->set_brightness_work); 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_classdev_suspend); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/** 18162306a36Sopenharmony_ci * led_classdev_resume - resume an led_classdev. 18262306a36Sopenharmony_ci * @led_cdev: the led_classdev to resume. 18362306a36Sopenharmony_ci */ 18462306a36Sopenharmony_civoid led_classdev_resume(struct led_classdev *led_cdev) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci led_set_brightness_nopm(led_cdev, led_cdev->brightness); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (led_cdev->flash_resume) 18962306a36Sopenharmony_ci led_cdev->flash_resume(led_cdev); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci led_cdev->flags &= ~LED_SUSPENDED; 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_classdev_resume); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 19662306a36Sopenharmony_cistatic int led_suspend(struct device *dev) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (led_cdev->flags & LED_CORE_SUSPENDRESUME) 20162306a36Sopenharmony_ci led_classdev_suspend(led_cdev); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return 0; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic int led_resume(struct device *dev) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (led_cdev->flags & LED_CORE_SUSPENDRESUME) 21162306a36Sopenharmony_ci led_classdev_resume(led_cdev); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci return 0; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci#endif 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic struct led_classdev *led_module_get(struct device *led_dev) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci struct led_classdev *led_cdev; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci if (!led_dev) 22462306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci led_cdev = dev_get_drvdata(led_dev); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (!try_module_get(led_cdev->dev->parent->driver->owner)) { 22962306a36Sopenharmony_ci put_device(led_cdev->dev); 23062306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return led_cdev; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic const struct class leds_class = { 23762306a36Sopenharmony_ci .name = "leds", 23862306a36Sopenharmony_ci .dev_groups = led_groups, 23962306a36Sopenharmony_ci .pm = &leds_class_dev_pm_ops, 24062306a36Sopenharmony_ci}; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci/** 24362306a36Sopenharmony_ci * of_led_get() - request a LED device via the LED framework 24462306a36Sopenharmony_ci * @np: device node to get the LED device from 24562306a36Sopenharmony_ci * @index: the index of the LED 24662306a36Sopenharmony_ci * 24762306a36Sopenharmony_ci * Returns the LED device parsed from the phandle specified in the "leds" 24862306a36Sopenharmony_ci * property of a device tree node or a negative error-code on failure. 24962306a36Sopenharmony_ci */ 25062306a36Sopenharmony_cistruct led_classdev *of_led_get(struct device_node *np, int index) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct device *led_dev; 25362306a36Sopenharmony_ci struct device_node *led_node; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci led_node = of_parse_phandle(np, "leds", index); 25662306a36Sopenharmony_ci if (!led_node) 25762306a36Sopenharmony_ci return ERR_PTR(-ENOENT); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci led_dev = class_find_device_by_of_node(&leds_class, led_node); 26062306a36Sopenharmony_ci of_node_put(led_node); 26162306a36Sopenharmony_ci put_device(led_dev); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return led_module_get(led_dev); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(of_led_get); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci/** 26862306a36Sopenharmony_ci * led_put() - release a LED device 26962306a36Sopenharmony_ci * @led_cdev: LED device 27062306a36Sopenharmony_ci */ 27162306a36Sopenharmony_civoid led_put(struct led_classdev *led_cdev) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci module_put(led_cdev->dev->parent->driver->owner); 27462306a36Sopenharmony_ci put_device(led_cdev->dev); 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_put); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_cistatic void devm_led_release(struct device *dev, void *res) 27962306a36Sopenharmony_ci{ 28062306a36Sopenharmony_ci struct led_classdev **p = res; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci led_put(*p); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic struct led_classdev *__devm_led_get(struct device *dev, struct led_classdev *led) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci struct led_classdev **dr; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), GFP_KERNEL); 29062306a36Sopenharmony_ci if (!dr) { 29162306a36Sopenharmony_ci led_put(led); 29262306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci *dr = led; 29662306a36Sopenharmony_ci devres_add(dev, dr); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci return led; 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci/** 30262306a36Sopenharmony_ci * devm_of_led_get - Resource-managed request of a LED device 30362306a36Sopenharmony_ci * @dev: LED consumer 30462306a36Sopenharmony_ci * @index: index of the LED to obtain in the consumer 30562306a36Sopenharmony_ci * 30662306a36Sopenharmony_ci * The device node of the device is parse to find the request LED device. 30762306a36Sopenharmony_ci * The LED device returned from this function is automatically released 30862306a36Sopenharmony_ci * on driver detach. 30962306a36Sopenharmony_ci * 31062306a36Sopenharmony_ci * @return a pointer to a LED device or ERR_PTR(errno) on failure. 31162306a36Sopenharmony_ci */ 31262306a36Sopenharmony_cistruct led_classdev *__must_check devm_of_led_get(struct device *dev, 31362306a36Sopenharmony_ci int index) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci struct led_classdev *led; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (!dev) 31862306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci led = of_led_get(dev->of_node, index); 32162306a36Sopenharmony_ci if (IS_ERR(led)) 32262306a36Sopenharmony_ci return led; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci return __devm_led_get(dev, led); 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_of_led_get); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci/** 32962306a36Sopenharmony_ci * led_get() - request a LED device via the LED framework 33062306a36Sopenharmony_ci * @dev: device for which to get the LED device 33162306a36Sopenharmony_ci * @con_id: name of the LED from the device's point of view 33262306a36Sopenharmony_ci * 33362306a36Sopenharmony_ci * @return a pointer to a LED device or ERR_PTR(errno) on failure. 33462306a36Sopenharmony_ci */ 33562306a36Sopenharmony_cistruct led_classdev *led_get(struct device *dev, char *con_id) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci struct led_lookup_data *lookup; 33862306a36Sopenharmony_ci const char *provider = NULL; 33962306a36Sopenharmony_ci struct device *led_dev; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci mutex_lock(&leds_lookup_lock); 34262306a36Sopenharmony_ci list_for_each_entry(lookup, &leds_lookup_list, list) { 34362306a36Sopenharmony_ci if (!strcmp(lookup->dev_id, dev_name(dev)) && 34462306a36Sopenharmony_ci !strcmp(lookup->con_id, con_id)) { 34562306a36Sopenharmony_ci provider = kstrdup_const(lookup->provider, GFP_KERNEL); 34662306a36Sopenharmony_ci break; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci mutex_unlock(&leds_lookup_lock); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (!provider) 35262306a36Sopenharmony_ci return ERR_PTR(-ENOENT); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci led_dev = class_find_device_by_name(&leds_class, provider); 35562306a36Sopenharmony_ci kfree_const(provider); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci return led_module_get(led_dev); 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_get); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci/** 36262306a36Sopenharmony_ci * devm_led_get() - request a LED device via the LED framework 36362306a36Sopenharmony_ci * @dev: device for which to get the LED device 36462306a36Sopenharmony_ci * @con_id: name of the LED from the device's point of view 36562306a36Sopenharmony_ci * 36662306a36Sopenharmony_ci * The LED device returned from this function is automatically released 36762306a36Sopenharmony_ci * on driver detach. 36862306a36Sopenharmony_ci * 36962306a36Sopenharmony_ci * @return a pointer to a LED device or ERR_PTR(errno) on failure. 37062306a36Sopenharmony_ci */ 37162306a36Sopenharmony_cistruct led_classdev *devm_led_get(struct device *dev, char *con_id) 37262306a36Sopenharmony_ci{ 37362306a36Sopenharmony_ci struct led_classdev *led; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci led = led_get(dev, con_id); 37662306a36Sopenharmony_ci if (IS_ERR(led)) 37762306a36Sopenharmony_ci return led; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return __devm_led_get(dev, led); 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_led_get); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci/** 38462306a36Sopenharmony_ci * led_add_lookup() - Add a LED lookup table entry 38562306a36Sopenharmony_ci * @led_lookup: the lookup table entry to add 38662306a36Sopenharmony_ci * 38762306a36Sopenharmony_ci * Add a LED lookup table entry. On systems without devicetree the lookup table 38862306a36Sopenharmony_ci * is used by led_get() to find LEDs. 38962306a36Sopenharmony_ci */ 39062306a36Sopenharmony_civoid led_add_lookup(struct led_lookup_data *led_lookup) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci mutex_lock(&leds_lookup_lock); 39362306a36Sopenharmony_ci list_add_tail(&led_lookup->list, &leds_lookup_list); 39462306a36Sopenharmony_ci mutex_unlock(&leds_lookup_lock); 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_add_lookup); 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci/** 39962306a36Sopenharmony_ci * led_remove_lookup() - Remove a LED lookup table entry 40062306a36Sopenharmony_ci * @led_lookup: the lookup table entry to remove 40162306a36Sopenharmony_ci */ 40262306a36Sopenharmony_civoid led_remove_lookup(struct led_lookup_data *led_lookup) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci mutex_lock(&leds_lookup_lock); 40562306a36Sopenharmony_ci list_del(&led_lookup->list); 40662306a36Sopenharmony_ci mutex_unlock(&leds_lookup_lock); 40762306a36Sopenharmony_ci} 40862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_remove_lookup); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci/** 41162306a36Sopenharmony_ci * devm_of_led_get_optional - Resource-managed request of an optional LED device 41262306a36Sopenharmony_ci * @dev: LED consumer 41362306a36Sopenharmony_ci * @index: index of the LED to obtain in the consumer 41462306a36Sopenharmony_ci * 41562306a36Sopenharmony_ci * The device node of the device is parsed to find the requested LED device. 41662306a36Sopenharmony_ci * The LED device returned from this function is automatically released 41762306a36Sopenharmony_ci * on driver detach. 41862306a36Sopenharmony_ci * 41962306a36Sopenharmony_ci * @return a pointer to a LED device, ERR_PTR(errno) on failure and NULL if the 42062306a36Sopenharmony_ci * led was not found. 42162306a36Sopenharmony_ci */ 42262306a36Sopenharmony_cistruct led_classdev *__must_check devm_of_led_get_optional(struct device *dev, 42362306a36Sopenharmony_ci int index) 42462306a36Sopenharmony_ci{ 42562306a36Sopenharmony_ci struct led_classdev *led; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci led = devm_of_led_get(dev, index); 42862306a36Sopenharmony_ci if (IS_ERR(led) && PTR_ERR(led) == -ENOENT) 42962306a36Sopenharmony_ci return NULL; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci return led; 43262306a36Sopenharmony_ci} 43362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_of_led_get_optional); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cistatic int led_classdev_next_name(const char *init_name, char *name, 43662306a36Sopenharmony_ci size_t len) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci unsigned int i = 0; 43962306a36Sopenharmony_ci int ret = 0; 44062306a36Sopenharmony_ci struct device *dev; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci strscpy(name, init_name, len); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci while ((ret < len) && 44562306a36Sopenharmony_ci (dev = class_find_device_by_name(&leds_class, name))) { 44662306a36Sopenharmony_ci put_device(dev); 44762306a36Sopenharmony_ci ret = snprintf(name, len, "%s_%u", init_name, ++i); 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci if (ret >= len) 45162306a36Sopenharmony_ci return -ENOMEM; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci return i; 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci/** 45762306a36Sopenharmony_ci * led_classdev_register_ext - register a new object of led_classdev class 45862306a36Sopenharmony_ci * with init data. 45962306a36Sopenharmony_ci * 46062306a36Sopenharmony_ci * @parent: parent of LED device 46162306a36Sopenharmony_ci * @led_cdev: the led_classdev structure for this device. 46262306a36Sopenharmony_ci * @init_data: LED class device initialization data 46362306a36Sopenharmony_ci */ 46462306a36Sopenharmony_ciint led_classdev_register_ext(struct device *parent, 46562306a36Sopenharmony_ci struct led_classdev *led_cdev, 46662306a36Sopenharmony_ci struct led_init_data *init_data) 46762306a36Sopenharmony_ci{ 46862306a36Sopenharmony_ci char composed_name[LED_MAX_NAME_SIZE]; 46962306a36Sopenharmony_ci char final_name[LED_MAX_NAME_SIZE]; 47062306a36Sopenharmony_ci const char *proposed_name = composed_name; 47162306a36Sopenharmony_ci int ret; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci if (init_data) { 47462306a36Sopenharmony_ci if (init_data->devname_mandatory && !init_data->devicename) { 47562306a36Sopenharmony_ci dev_err(parent, "Mandatory device name is missing"); 47662306a36Sopenharmony_ci return -EINVAL; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci ret = led_compose_name(parent, init_data, composed_name); 47962306a36Sopenharmony_ci if (ret < 0) 48062306a36Sopenharmony_ci return ret; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci if (init_data->fwnode) { 48362306a36Sopenharmony_ci fwnode_property_read_string(init_data->fwnode, 48462306a36Sopenharmony_ci "linux,default-trigger", 48562306a36Sopenharmony_ci &led_cdev->default_trigger); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci if (fwnode_property_present(init_data->fwnode, 48862306a36Sopenharmony_ci "retain-state-shutdown")) 48962306a36Sopenharmony_ci led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci fwnode_property_read_u32(init_data->fwnode, 49262306a36Sopenharmony_ci "max-brightness", 49362306a36Sopenharmony_ci &led_cdev->max_brightness); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci if (fwnode_property_present(init_data->fwnode, "color")) 49662306a36Sopenharmony_ci fwnode_property_read_u32(init_data->fwnode, "color", 49762306a36Sopenharmony_ci &led_cdev->color); 49862306a36Sopenharmony_ci } 49962306a36Sopenharmony_ci } else { 50062306a36Sopenharmony_ci proposed_name = led_cdev->name; 50162306a36Sopenharmony_ci } 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); 50462306a36Sopenharmony_ci if (ret < 0) 50562306a36Sopenharmony_ci return ret; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci if (led_cdev->color >= LED_COLOR_ID_MAX) 50862306a36Sopenharmony_ci dev_warn(parent, "LED %s color identifier out of range\n", final_name); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci mutex_init(&led_cdev->led_access); 51162306a36Sopenharmony_ci mutex_lock(&led_cdev->led_access); 51262306a36Sopenharmony_ci led_cdev->dev = device_create_with_groups(&leds_class, parent, 0, 51362306a36Sopenharmony_ci led_cdev, led_cdev->groups, "%s", final_name); 51462306a36Sopenharmony_ci if (IS_ERR(led_cdev->dev)) { 51562306a36Sopenharmony_ci mutex_unlock(&led_cdev->led_access); 51662306a36Sopenharmony_ci return PTR_ERR(led_cdev->dev); 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci if (init_data && init_data->fwnode) 51962306a36Sopenharmony_ci device_set_node(led_cdev->dev, init_data->fwnode); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (ret) 52262306a36Sopenharmony_ci dev_warn(parent, "Led %s renamed to %s due to name collision", 52362306a36Sopenharmony_ci proposed_name, dev_name(led_cdev->dev)); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { 52662306a36Sopenharmony_ci ret = led_add_brightness_hw_changed(led_cdev); 52762306a36Sopenharmony_ci if (ret) { 52862306a36Sopenharmony_ci device_unregister(led_cdev->dev); 52962306a36Sopenharmony_ci led_cdev->dev = NULL; 53062306a36Sopenharmony_ci mutex_unlock(&led_cdev->led_access); 53162306a36Sopenharmony_ci return ret; 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci } 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci led_cdev->work_flags = 0; 53662306a36Sopenharmony_ci#ifdef CONFIG_LEDS_TRIGGERS 53762306a36Sopenharmony_ci init_rwsem(&led_cdev->trigger_lock); 53862306a36Sopenharmony_ci#endif 53962306a36Sopenharmony_ci#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 54062306a36Sopenharmony_ci led_cdev->brightness_hw_changed = -1; 54162306a36Sopenharmony_ci#endif 54262306a36Sopenharmony_ci /* add to the list of leds */ 54362306a36Sopenharmony_ci down_write(&leds_list_lock); 54462306a36Sopenharmony_ci list_add_tail(&led_cdev->node, &leds_list); 54562306a36Sopenharmony_ci up_write(&leds_list_lock); 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci if (!led_cdev->max_brightness) 54862306a36Sopenharmony_ci led_cdev->max_brightness = LED_FULL; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci led_update_brightness(led_cdev); 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci led_init_core(led_cdev); 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci#ifdef CONFIG_LEDS_TRIGGERS 55562306a36Sopenharmony_ci led_trigger_set_default(led_cdev); 55662306a36Sopenharmony_ci#endif 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci mutex_unlock(&led_cdev->led_access); 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci dev_dbg(parent, "Registered led device: %s\n", 56162306a36Sopenharmony_ci led_cdev->name); 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci return 0; 56462306a36Sopenharmony_ci} 56562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_classdev_register_ext); 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci/** 56862306a36Sopenharmony_ci * led_classdev_unregister - unregisters a object of led_properties class. 56962306a36Sopenharmony_ci * @led_cdev: the led device to unregister 57062306a36Sopenharmony_ci * 57162306a36Sopenharmony_ci * Unregisters a previously registered via led_classdev_register object. 57262306a36Sopenharmony_ci */ 57362306a36Sopenharmony_civoid led_classdev_unregister(struct led_classdev *led_cdev) 57462306a36Sopenharmony_ci{ 57562306a36Sopenharmony_ci if (IS_ERR_OR_NULL(led_cdev->dev)) 57662306a36Sopenharmony_ci return; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci#ifdef CONFIG_LEDS_TRIGGERS 57962306a36Sopenharmony_ci down_write(&led_cdev->trigger_lock); 58062306a36Sopenharmony_ci if (led_cdev->trigger) 58162306a36Sopenharmony_ci led_trigger_set(led_cdev, NULL); 58262306a36Sopenharmony_ci up_write(&led_cdev->trigger_lock); 58362306a36Sopenharmony_ci#endif 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci led_cdev->flags |= LED_UNREGISTERING; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* Stop blinking */ 58862306a36Sopenharmony_ci led_stop_software_blink(led_cdev); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci if (!(led_cdev->flags & LED_RETAIN_AT_SHUTDOWN)) 59162306a36Sopenharmony_ci led_set_brightness(led_cdev, LED_OFF); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci flush_work(&led_cdev->set_brightness_work); 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) 59662306a36Sopenharmony_ci led_remove_brightness_hw_changed(led_cdev); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci device_unregister(led_cdev->dev); 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci down_write(&leds_list_lock); 60162306a36Sopenharmony_ci list_del(&led_cdev->node); 60262306a36Sopenharmony_ci up_write(&leds_list_lock); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci mutex_destroy(&led_cdev->led_access); 60562306a36Sopenharmony_ci} 60662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_classdev_unregister); 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_cistatic void devm_led_classdev_release(struct device *dev, void *res) 60962306a36Sopenharmony_ci{ 61062306a36Sopenharmony_ci led_classdev_unregister(*(struct led_classdev **)res); 61162306a36Sopenharmony_ci} 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci/** 61462306a36Sopenharmony_ci * devm_led_classdev_register_ext - resource managed led_classdev_register_ext() 61562306a36Sopenharmony_ci * 61662306a36Sopenharmony_ci * @parent: parent of LED device 61762306a36Sopenharmony_ci * @led_cdev: the led_classdev structure for this device. 61862306a36Sopenharmony_ci * @init_data: LED class device initialization data 61962306a36Sopenharmony_ci */ 62062306a36Sopenharmony_ciint devm_led_classdev_register_ext(struct device *parent, 62162306a36Sopenharmony_ci struct led_classdev *led_cdev, 62262306a36Sopenharmony_ci struct led_init_data *init_data) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci struct led_classdev **dr; 62562306a36Sopenharmony_ci int rc; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL); 62862306a36Sopenharmony_ci if (!dr) 62962306a36Sopenharmony_ci return -ENOMEM; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci rc = led_classdev_register_ext(parent, led_cdev, init_data); 63262306a36Sopenharmony_ci if (rc) { 63362306a36Sopenharmony_ci devres_free(dr); 63462306a36Sopenharmony_ci return rc; 63562306a36Sopenharmony_ci } 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci *dr = led_cdev; 63862306a36Sopenharmony_ci devres_add(parent, dr); 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci return 0; 64162306a36Sopenharmony_ci} 64262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_led_classdev_register_ext); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic int devm_led_classdev_match(struct device *dev, void *res, void *data) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct led_classdev **p = res; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci if (WARN_ON(!p || !*p)) 64962306a36Sopenharmony_ci return 0; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci return *p == data; 65262306a36Sopenharmony_ci} 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci/** 65562306a36Sopenharmony_ci * devm_led_classdev_unregister() - resource managed led_classdev_unregister() 65662306a36Sopenharmony_ci * @dev: The device to unregister. 65762306a36Sopenharmony_ci * @led_cdev: the led_classdev structure for this device. 65862306a36Sopenharmony_ci */ 65962306a36Sopenharmony_civoid devm_led_classdev_unregister(struct device *dev, 66062306a36Sopenharmony_ci struct led_classdev *led_cdev) 66162306a36Sopenharmony_ci{ 66262306a36Sopenharmony_ci WARN_ON(devres_release(dev, 66362306a36Sopenharmony_ci devm_led_classdev_release, 66462306a36Sopenharmony_ci devm_led_classdev_match, led_cdev)); 66562306a36Sopenharmony_ci} 66662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_led_classdev_unregister); 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_cistatic int __init leds_init(void) 66962306a36Sopenharmony_ci{ 67062306a36Sopenharmony_ci return class_register(&leds_class); 67162306a36Sopenharmony_ci} 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_cistatic void __exit leds_exit(void) 67462306a36Sopenharmony_ci{ 67562306a36Sopenharmony_ci class_unregister(&leds_class); 67662306a36Sopenharmony_ci} 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_cisubsys_initcall(leds_init); 67962306a36Sopenharmony_cimodule_exit(leds_exit); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ciMODULE_AUTHOR("John Lenz, Richard Purdie"); 68262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 68362306a36Sopenharmony_ciMODULE_DESCRIPTION("LED Class Interface"); 684