diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 3132439f99e0..7e017fbe1bb2 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -892,6 +892,20 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_MULTI + tristate "Control multiple LEDs by one trigger" + depends on LEDS_CLASS + help + This is a virtual LED, controlling up to 9 simple LEDs. + Each LED is controlled by a decimal digit of the brightness + of this virtual LED. It may for example control the 3 single LEDs of + an RGB multi-color LED. The timer trigger turns them on and off + while the brightness value controls the color. + Together with the pattern trigger, even "enterprisey" + error codes blinked to users, running lights and other things + are possible without complex user-space code. + If unsure say N. + source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index d7348e8bc019..7e5ff6bf1894 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_MULTI) += leds-multi.o # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o diff --git a/drivers/leds/leds-multi.c b/drivers/leds/leds-multi.c new file mode 100644 index 000000000000..d1d9f57a57ff --- /dev/null +++ b/drivers/leds/leds-multi.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for orchestrating several LEDs + * + * Copyright (C) 2020 Christian Hohnstaedt + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "leds.h" + +#define DRVNAME "multi-led" +#define MAX_LEDS 9 + +struct multi_led { + struct led_classdev cdev; + int num_leds; + struct led_classdev *leds[MAX_LEDS]; +}; + +static void multi_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct multi_led *mled = container_of(led_cdev, struct multi_led, cdev); + int i, v; + + for (i = 0, v = value; i < mled->num_leds; i++, v /= 10) { + int val = mled->leds[i]->max_brightness; + + switch (v % 10) { + case 0: + val = 0; + break; + case 9: + break; + default: + if (val >= 8) + val = val * (v % 10) / 8; + break; + } + led_set_brightness(mled->leds[i], val); + } +} + +static struct led_classdev *find_child_led(phandle phandle) +{ + struct led_classdev *led; + struct device_node *node = of_find_node_by_phandle(phandle); + const char *label; + int len; + + len = strlen(label); + down_read(&leds_list_lock); + list_for_each_entry(led, &leds_list, node) { + if (node == led->dev->of_node) { + up_read(&leds_list_lock); + return led; + } + } + up_read(&leds_list_lock); + return NULL; +} + +static int multi_led_probe(struct platform_device *pdev) +{ + int size, i; + struct multi_led *mled; + const __be32 *list; + struct device_node *np = pdev->dev.of_node; + struct property *prop; + + mled = devm_kzalloc(&pdev->dev, sizeof(*mled), GFP_KERNEL); + if (!mled) + return -ENOMEM; + + prop = of_find_property(np, "leds", &size); + mled->num_leds = size / sizeof(*list); + if (mled->num_leds > MAX_LEDS) + return -EINVAL; + + list = prop->value; + mled->cdev.max_brightness = 1; + mled->cdev.brightness_set = multi_led_set; + + for (i = 0; i < mled->num_leds; i++) { + mled->leds[i] = find_child_led(be32_to_cpup(list++)); + mled->cdev.max_brightness *= 10; + if (mled->leds[i] == NULL) + return -EPROBE_DEFER; + led_trigger_remove(mled->leds[i]); + } + mled->cdev.max_brightness--; + if (of_property_read_string(np, "label", &mled->cdev.name)) + return -EINVAL; + + return devm_led_classdev_register(&pdev->dev, &mled->cdev); +} + +static const struct of_device_id multi_led_of_match[] = { + { .compatible = "multi_led", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, multi_led_of_match); + +static struct platform_driver multi_led_driver = { + .probe = multi_led_probe, + .driver = { + .name = DRVNAME, + .of_match_table = of_match_ptr(multi_led_of_match), + }, +}; + +static int __init multi_led_init(void) +{ + return platform_driver_register(&multi_led_driver); +} + +static void __exit multi_led_exit(void) +{ + platform_driver_unregister(&multi_led_driver); +} + +module_init(multi_led_init); +module_exit(multi_led_exit); + +MODULE_AUTHOR("Christian Hohnstaedt "); +MODULE_DESCRIPTION("Multi LED driver"); +MODULE_LICENSE("GPL"); +