diff options
Diffstat (limited to 'drivers/leds/led-class.c')
-rw-r--r-- | drivers/leds/led-class.c | 161 |
1 files changed, 136 insertions, 25 deletions
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 4793e77808e2..1fc40e8af75e 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -14,10 +14,12 @@ #include <linux/leds.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/property.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/timer.h> #include <uapi/linux/uleds.h> +#include <linux/of.h> #include "leds.h" static struct class *leds_class; @@ -73,13 +75,13 @@ static ssize_t max_brightness_show(struct device *dev, static DEVICE_ATTR_RO(max_brightness); #ifdef CONFIG_LEDS_TRIGGERS -static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); -static struct attribute *led_trigger_attrs[] = { - &dev_attr_trigger.attr, +static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); +static struct bin_attribute *led_trigger_bin_attrs[] = { + &bin_attr_trigger, NULL, }; static const struct attribute_group led_trigger_group = { - .attrs = led_trigger_attrs, + .bin_attrs = led_trigger_bin_attrs, }; #endif @@ -213,12 +215,97 @@ static int led_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); -static int match_name(struct device *dev, const void *data) +/** + * of_led_get() - request a LED device via the LED framework + * @np: device node to get the LED device from + * @index: the index of the LED + * + * Returns the LED device parsed from the phandle specified in the "leds" + * property of a device tree node or a negative error-code on failure. + */ +struct led_classdev *of_led_get(struct device_node *np, int index) { - if (!dev_name(dev)) - return 0; - return !strcmp(dev_name(dev), (char *)data); + struct device *led_dev; + struct led_classdev *led_cdev; + struct device_node *led_node; + + led_node = of_parse_phandle(np, "leds", index); + if (!led_node) + return ERR_PTR(-ENOENT); + + led_dev = class_find_device_by_of_node(leds_class, led_node); + of_node_put(led_node); + + if (!led_dev) + return ERR_PTR(-EPROBE_DEFER); + + led_cdev = dev_get_drvdata(led_dev); + + if (!try_module_get(led_cdev->dev->parent->driver->owner)) + return ERR_PTR(-ENODEV); + + return led_cdev; +} +EXPORT_SYMBOL_GPL(of_led_get); + +/** + * led_put() - release a LED device + * @led_cdev: LED device + */ +void led_put(struct led_classdev *led_cdev) +{ + module_put(led_cdev->dev->parent->driver->owner); +} +EXPORT_SYMBOL_GPL(led_put); + +static void devm_led_release(struct device *dev, void *res) +{ + struct led_classdev **p = res; + + led_put(*p); +} + +/** + * devm_of_led_get - Resource-managed request of a LED device + * @dev: LED consumer + * @index: index of the LED to obtain in the consumer + * + * The device node of the device is parse to find the request LED device. + * The LED device returned from this function is automatically released + * on driver detach. + * + * @return a pointer to a LED device or ERR_PTR(errno) on failure. + */ +struct led_classdev *__must_check devm_of_led_get(struct device *dev, + int index) +{ + struct led_classdev *led; + struct led_classdev **dr; + + if (!dev) + return ERR_PTR(-EINVAL); + + /* Not using device tree? */ + if (!IS_ENABLED(CONFIG_OF) || !dev->of_node) + return ERR_PTR(-ENOTSUPP); + + led = of_led_get(dev->of_node, index); + if (IS_ERR(led)) + return led; + + dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), + GFP_KERNEL); + if (!dr) { + led_put(led); + return ERR_PTR(-ENOMEM); + } + + *dr = led; + devres_add(dev, dr); + + return led; } +EXPORT_SYMBOL_GPL(devm_of_led_get); static int led_classdev_next_name(const char *init_name, char *name, size_t len) @@ -230,7 +317,7 @@ static int led_classdev_next_name(const char *init_name, char *name, strlcpy(name, init_name, len); while ((ret < len) && - (dev = class_find_device(leds_class, NULL, name, match_name))) { + (dev = class_find_device_by_name(leds_class, name))) { put_device(dev); ret = snprintf(name, len, "%s_%u", init_name, ++i); } @@ -242,31 +329,50 @@ static int led_classdev_next_name(const char *init_name, char *name, } /** - * of_led_classdev_register - register a new object of led_classdev class. + * led_classdev_register_ext - register a new object of led_classdev class + * with init data. * * @parent: parent of LED device * @led_cdev: the led_classdev structure for this device. - * @np: DT node describing this LED + * @init_data: LED class device initialization data */ -int of_led_classdev_register(struct device *parent, struct device_node *np, - struct led_classdev *led_cdev) +int led_classdev_register_ext(struct device *parent, + struct led_classdev *led_cdev, + struct led_init_data *init_data) { - char name[LED_MAX_NAME_SIZE]; + char composed_name[LED_MAX_NAME_SIZE]; + char final_name[LED_MAX_NAME_SIZE]; + const char *proposed_name = composed_name; int ret; - ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); + if (init_data) { + if (init_data->devname_mandatory && !init_data->devicename) { + dev_err(parent, "Mandatory device name is missing"); + return -EINVAL; + } + ret = led_compose_name(parent, init_data, composed_name); + if (ret < 0) + return ret; + } else { + proposed_name = led_cdev->name; + } + + ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); if (ret < 0) return ret; mutex_init(&led_cdev->led_access); mutex_lock(&led_cdev->led_access); led_cdev->dev = device_create_with_groups(leds_class, parent, 0, - led_cdev, led_cdev->groups, "%s", name); + led_cdev, led_cdev->groups, "%s", final_name); if (IS_ERR(led_cdev->dev)) { mutex_unlock(&led_cdev->led_access); return PTR_ERR(led_cdev->dev); } - led_cdev->dev->of_node = np; + if (init_data && init_data->fwnode) { + led_cdev->dev->fwnode = init_data->fwnode; + led_cdev->dev->of_node = to_of_node(init_data->fwnode); + } if (ret) dev_warn(parent, "Led %s renamed to %s due to name collision", @@ -276,6 +382,7 @@ int of_led_classdev_register(struct device *parent, struct device_node *np, ret = led_add_brightness_hw_changed(led_cdev); if (ret) { device_unregister(led_cdev->dev); + led_cdev->dev = NULL; mutex_unlock(&led_cdev->led_access); return ret; } @@ -311,7 +418,7 @@ int of_led_classdev_register(struct device *parent, struct device_node *np, return 0; } -EXPORT_SYMBOL_GPL(of_led_classdev_register); +EXPORT_SYMBOL_GPL(led_classdev_register_ext); /** * led_classdev_unregister - unregisters a object of led_properties class. @@ -321,6 +428,9 @@ EXPORT_SYMBOL_GPL(of_led_classdev_register); */ void led_classdev_unregister(struct led_classdev *led_cdev) { + if (IS_ERR_OR_NULL(led_cdev->dev)) + return; + #ifdef CONFIG_LEDS_TRIGGERS down_write(&led_cdev->trigger_lock); if (led_cdev->trigger) @@ -356,14 +466,15 @@ static void devm_led_classdev_release(struct device *dev, void *res) } /** - * devm_of_led_classdev_register - resource managed led_classdev_register() + * devm_led_classdev_register_ext - resource managed led_classdev_register_ext() * * @parent: parent of LED device * @led_cdev: the led_classdev structure for this device. + * @init_data: LED class device initialization data */ -int devm_of_led_classdev_register(struct device *parent, - struct device_node *np, - struct led_classdev *led_cdev) +int devm_led_classdev_register_ext(struct device *parent, + struct led_classdev *led_cdev, + struct led_init_data *init_data) { struct led_classdev **dr; int rc; @@ -372,7 +483,7 @@ int devm_of_led_classdev_register(struct device *parent, if (!dr) return -ENOMEM; - rc = of_led_classdev_register(parent, np, led_cdev); + rc = led_classdev_register_ext(parent, led_cdev, init_data); if (rc) { devres_free(dr); return rc; @@ -383,11 +494,11 @@ int devm_of_led_classdev_register(struct device *parent, return 0; } -EXPORT_SYMBOL_GPL(devm_of_led_classdev_register); +EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext); static int devm_led_classdev_match(struct device *dev, void *res, void *data) { - struct led_cdev **p = res; + struct led_classdev **p = res; if (WARN_ON(!p || !*p)) return 0; |