[PATCH 1/2] backlight: led_bl: Add devm_led_backlight_register() helper
"A. Sverdlin" <[email protected]>
| Newsgroups | org.kernel.vger.linux-fbdev,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-leds |
|---|---|
| Message-ID | <[email protected]> |
From: Alexander Sverdlin <[email protected]> The led-backlight driver could so far only be instantiated from a device-tree node with the "led-backlight" compatible. This makes it impossible for a self-contained LED provider (e.g. a hot-pluggable I2C LED controller) to expose a backlight interface tied to its own lifetime. Factor the actual backlight registration out of the probe path into a shared led_bl_register() helper and export devm_led_backlight_register(), which registers a backlight class device driven by a single LED, without device tree and bound to the caller's device lifetime. The backlight device and the LED sysfs handover are now devres-managed, so the probe path shrinks and the explicit .remove callback is no longer needed. The exported helper is a no-op when the led-backlight support is not reachable (IS_REACHABLE(CONFIG_BACKLIGHT_LED)), so callers do not need any Kconfig plumbing and are not force-selected to build it. Signed-off-by: Alexander Sverdlin <[email protected]> --- MAINTAINERS | 1 + drivers/video/backlight/led_bl.c | 130 ++++++++++++++++++++----------- include/linux/led_bl.h | 20 +++++ 3 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 include/linux/led_bl.h diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253ed..d525a7c4043aa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4508,6 +4508,7 @@ F: Documentation/ABI/testing/sysfs-class-backlight F: Documentation/devicetree/bindings/leds/backlight F: drivers/video/backlight/ F: include/linux/backlight.h +F: include/linux/led_bl.h F: include/linux/pwm_backlight.h BARCO P50 GPIO DRIVER diff --git a/drivers/video/backlight/led_bl.c b/drivers/video/backlight/led_bl.c index f7ab9b3607313..f87a48f74186a 100644 --- a/drivers/video/backlight/led_bl.c +++ b/drivers/video/backlight/led_bl.c @@ -7,6 +7,7 @@ */ #include <linux/backlight.h> +#include <linux/led_bl.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/of.h> @@ -173,29 +174,25 @@ static int led_bl_parse_levels(struct device *dev, return 0; } -static int led_bl_probe(struct platform_device *pdev) +static void led_bl_disable(void *data) { - struct backlight_properties props; - struct led_bl_data *priv; - int ret, i; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - platform_set_drvdata(pdev, priv); + struct led_bl_data *priv = data; + int i; - priv->dev = &pdev->dev; + led_bl_power_off(priv); + for (i = 0; i < priv->nb_leds; i++) { + mutex_lock(&priv->leds[i]->led_access); + led_sysfs_enable(priv->leds[i]); + mutex_unlock(&priv->leds[i]->led_access); + } +} - ret = led_bl_get_leds(&pdev->dev, priv); - if (ret) - return ret; +static int led_bl_register(struct device *dev, struct led_bl_data *priv) +{ + struct backlight_properties props; + int ret, i; - ret = led_bl_parse_levels(&pdev->dev, priv); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to parse DT data\n"); - return ret; - } + priv->dev = dev; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; @@ -203,24 +200,28 @@ static int led_bl_probe(struct platform_device *pdev) props.brightness = priv->default_brightness; props.power = (priv->default_brightness > 0) ? BACKLIGHT_POWER_OFF : BACKLIGHT_POWER_ON; - priv->bl_dev = backlight_device_register(dev_name(&pdev->dev), - &pdev->dev, priv, &led_bl_ops, &props); - if (IS_ERR(priv->bl_dev)) { - dev_err(&pdev->dev, "Failed to register backlight\n"); - return PTR_ERR(priv->bl_dev); - } + priv->bl_dev = devm_backlight_device_register(dev, dev_name(dev), dev, + priv, &led_bl_ops, &props); + if (IS_ERR(priv->bl_dev)) + return dev_err_probe(dev, PTR_ERR(priv->bl_dev), + "Failed to register backlight\n"); for (i = 0; i < priv->nb_leds; i++) { + struct device *supplier = priv->leds[i]->dev->parent; struct device_link *link; - link = device_link_add(&pdev->dev, priv->leds[i]->dev->parent, - DL_FLAG_AUTOREMOVE_CONSUMER); - if (!link) { - dev_err(&pdev->dev, "Failed to add devlink (consumer %s, supplier %s)\n", - dev_name(&pdev->dev), dev_name(priv->leds[i]->dev->parent)); - backlight_device_unregister(priv->bl_dev); - return -EINVAL; - } + /* + * BL and the LED are the same device if instantiated via + * devm_led_backlight_register() + */ + if (supplier == dev) + continue; + + link = device_link_add(dev, supplier, DL_FLAG_AUTOREMOVE_CONSUMER); + if (!link) + return dev_err_probe(dev, -EINVAL, + "Failed to add devlink (consumer %s, supplier %s)\n", + dev_name(dev), dev_name(supplier)); } for (i = 0; i < priv->nb_leds; i++) { @@ -229,26 +230,68 @@ static int led_bl_probe(struct platform_device *pdev) mutex_unlock(&priv->leds[i]->led_access); } + ret = devm_add_action_or_reset(dev, led_bl_disable, priv); + if (ret) + return ret; + backlight_update_status(priv->bl_dev); return 0; } -static void led_bl_remove(struct platform_device *pdev) +static int led_bl_probe(struct platform_device *pdev) { - struct led_bl_data *priv = platform_get_drvdata(pdev); - struct backlight_device *bl = priv->bl_dev; - int i; + struct led_bl_data *priv; + int ret; - backlight_device_unregister(bl); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; - led_bl_power_off(priv); - for (i = 0; i < priv->nb_leds; i++) { - mutex_lock(&priv->leds[i]->led_access); - led_sysfs_enable(priv->leds[i]); - mutex_unlock(&priv->leds[i]->led_access); + ret = led_bl_get_leds(&pdev->dev, priv); + if (ret) + return ret; + + ret = led_bl_parse_levels(&pdev->dev, priv); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to parse DT data\n"); + return ret; } + + return led_bl_register(&pdev->dev, priv); +} + +/** + * devm_led_backlight_register - expose a LED as a backlight device + * @dev: LED provider device, also the parent and lifecycle owner + * @led: LED class device to drive the backlight + * + * Registers a backlight class device driven by @led, without device tree and + * tied to the lifetime of @dev. This lets self-contained (e.g. hot-pluggable + * I2C) LED drivers offer a backlight interface without static platform + * plumbing. It is a no-op when the led-backlight support is not built in. + * + * Return: 0 on success, negative errno otherwise. + */ +int devm_led_backlight_register(struct device *dev, struct led_classdev *led) +{ + struct led_bl_data *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->leds = devm_kmalloc(dev, sizeof(*priv->leds), GFP_KERNEL); + if (!priv->leds) + return -ENOMEM; + priv->leds[0] = led; + priv->nb_leds = 1; + priv->max_brightness = led->max_brightness; + priv->default_brightness = led->brightness; + + return led_bl_register(dev, priv); } +EXPORT_SYMBOL_GPL(devm_led_backlight_register); static const struct of_device_id led_bl_of_match[] = { { .compatible = "led-backlight" }, @@ -263,7 +306,6 @@ static struct platform_driver led_bl_driver = { .of_match_table = led_bl_of_match, }, .probe = led_bl_probe, - .remove = led_bl_remove, }; module_platform_driver(led_bl_driver); diff --git a/include/linux/led_bl.h b/include/linux/led_bl.h new file mode 100644 index 0000000000000..e38e4d62bf653 --- /dev/null +++ b/include/linux/led_bl.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_LED_BL_H +#define _LINUX_LED_BL_H + +#include <linux/kconfig.h> + +struct device; +struct led_classdev; + +#if IS_REACHABLE(CONFIG_BACKLIGHT_LED) +int devm_led_backlight_register(struct device *dev, struct led_classdev *led); +#else +static inline int devm_led_backlight_register(struct device *dev, + struct led_classdev *led) +{ + return 0; +} +#endif + +#endif /* _LINUX_LED_BL_H */ -- 2.55.0