lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251019092331.49531-5-professorjonny98@gmail.com>
Date: Sun, 19 Oct 2025 22:23:27 +1300
From: Jonathan Brophy <professorjonny98@...il.com>
To: lee Jones <lee@...nel.org>,
	Pavel Machek <pavel@...nel.org>,
	Jonathan Brophy <professor_jonny@...mail.com>,
	Rob Herring <robh@...nel.org>,
	Krzysztof Kozlowski <krzk+dt@...nel.org>,
	Conor Dooley <conor+dt@...nel.org>,
	Radoslav Tsvetkov <rtsvetkov@...dotech.eu>
Cc: devicetree@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	linux-leds@...r.kernel.org
Subject: [PATCH v3 4/4] leds: Add virtualcolor LED group driver

From: Jonathan Brophy <professor_jonny@...mail.com>

Introduces a new driver that implements virtual LED groups,
aggregating multiple monochromatic LEDs into virtual groups and providing
priority-based control for concurrent state management.

This driver is a work in progress, I am unable to get the read-only diagnostic
sysfs attributes to show under the led framework.

Author: Radoslav Tsvetkov <rtsvetkov@...dotech.eu>
Co Author: Jonathan Brophy <professor_jonny@...mail.com>
Copyright (C) 2024 Jonathan Brophy <professor_jonny@...mail.com>

Co-developed-by: Radoslav Tsvetkov <rtsvetkov@...dotech.eu>
Signed-off-by: Radoslav Tsvetkov <rtsvetkov@...dotech.eu>
Tested-by: Jonathan Brophy <professor_jonny@...mail.com>
Signed-off-by: Jonathan Brophy <professor_jonny@...mail.com>
---
 drivers/leds/rgb/Kconfig                   |  17 +
 drivers/leds/rgb/Makefile                  |   1 +
 drivers/leds/rgb/leds-group-virtualcolor.c | 513 +++++++++++++++++++++
 3 files changed, 531 insertions(+)
 create mode 100644 drivers/leds/rgb/leds-group-virtualcolor.c

diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 222d943d826a..70a80fd46b9c 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -75,4 +75,21 @@ config LEDS_MT6370_RGB
 	  This driver can also be built as a module. If so, the module
 	  will be called "leds-mt6370-rgb".

+config LEDS_GROUP_VIRTUALCOLOR
+	tristate "Virtual LED Group Driver with Priority Control"
+	depends on OF || COMPILE_TEST
+	help
+	  This option enables support for virtual LED groups that aggregate
+	  multiple monochromatic LEDs with priority-based control. It allows
+	  managing concurrent LED activation requests by ensuring only the
+	  highest-priority LED state is active at any given time.
+
+	  Multiple LEDs can be grouped together and controlled as a single
+	  virtual LED with priority levels and blinking support. This is
+	  useful for systems that need to manage multiple LED indicators
+	  with different priority levels.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called leds-group-virtualcolor.
+
 endif # LEDS_CLASS_MULTICOLOR
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
index a501fd27f179..693fd300b849 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_LEDS_NCP5623)		+= leds-ncp5623.o
 obj-$(CONFIG_LEDS_PWM_MULTICOLOR)	+= leds-pwm-multicolor.o
 obj-$(CONFIG_LEDS_QCOM_LPG)		+= leds-qcom-lpg.o
 obj-$(CONFIG_LEDS_MT6370_RGB)		+= leds-mt6370-rgb.o
+obj-$(CONFIG_LEDS_GROUP_VIRTUALCOLOR)	+= leds-group-virtualcolor.o
diff --git a/drivers/leds/rgb/leds-group-virtualcolor.c b/drivers/leds/rgb/leds-group-virtualcolor.c
new file mode 100644
index 000000000000..035985174743
--- /dev/null
+++ b/drivers/leds/rgb/leds-group-virtualcolor.c
@@ -0,0 +1,513 @@
+// PWM RGB LED driver with per-color grouped LED arrays
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+
+#define MAX_LEDS_PER_COLOR 5
+
+enum led_color_channel {
+	LED_RED = 0,
+	LED_GREEN = 1,
+	LED_BLUE = 2,
+	LED_COLOR_MAX
+};
+
+struct color_channel {
+	struct led_classdev **leds;      // Array of LEDs for this color
+	int num_leds;                     // Count of LEDs in this color
+	enum led_brightness brightness;   // Current brightness for this color
+	char *color_name;
+};
+
+struct virtual_led {
+	struct led_classdev cdev;
+	struct led_classdev **monochromatics;     // Regular monochromatic LEDs
+	struct color_channel colors[LED_COLOR_MAX];  // RGB color channels
+	int num_monochromatics;
+	struct leds_virtualcolor *vc_data;
+	int priority;
+	struct list_head list;
+};
+
+struct leds_virtualcolor {
+	struct virtual_led *vleds;
+	int num_vleds;
+	struct list_head active_leds;
+	struct mutex lock;
+};
+
+/**
+ * Apply brightness to all LEDs of a specific color
+ */
+static void color_set_brightness(struct color_channel *color,
+				  enum led_brightness brightness)
+{
+	int i;
+
+	for (i = 0; i < color->num_leds; i++)
+		led_set_brightness(color->leds[i], brightness);
+
+	color->brightness = brightness;
+}
+
+/**
+ * Apply brightness to monochromatic LEDs
+ */
+static void virtual_set_monochromatic_brightness(struct virtual_led *vled,
+						  enum led_brightness brightness)
+{
+	int i;
+
+	for (i = 0; i < vled->num_monochromatics; i++)
+		led_set_brightness(vled->monochromatics[i], brightness);
+}
+
+/**
+ * Apply brightness to all RGB color channels
+ */
+static void virtual_set_rgb_brightness(struct virtual_led *vled,
+				       enum led_brightness brightness)
+{
+	int i;
+
+	for (i = 0; i < LED_COLOR_MAX; i++) {
+		if (vled->colors[i].num_leds > 0)
+			color_set_brightness(&vled->colors[i], brightness);
+	}
+}
+
+/**
+ * Set independent brightness per color (R, G, B)
+ */
+static void virtual_set_rgb_independent(struct virtual_led *vled,
+					 enum led_brightness r,
+					 enum led_brightness g,
+					 enum led_brightness b)
+{
+	if (vled->colors[LED_RED].num_leds > 0)
+		color_set_brightness(&vled->colors[LED_RED], r);
+
+	if (vled->colors[LED_GREEN].num_leds > 0)
+		color_set_brightness(&vled->colors[LED_GREEN], g);
+
+	if (vled->colors[LED_BLUE].num_leds > 0)
+		color_set_brightness(&vled->colors[LED_BLUE], b);
+}
+
+static ssize_t priority_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	int priority;
+
+	mutex_lock(&vled->vc_data->lock);
+	priority = vled->priority;
+	mutex_unlock(&vled->vc_data->lock);
+
+	return sysfs_emit(buf, "%d\n", priority);
+}
+
+static ssize_t priority_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	int new_priority;
+	int ret;
+
+	ret = kstrtoint(buf, 10, &new_priority);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&vled->vc_data->lock);
+	vled->priority = new_priority;
+	mutex_unlock(&vled->vc_data->lock);
+
+	return count;
+}
+
+/**
+ * Per-color brightness sysfs attributes
+ */
+static ssize_t red_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	return sysfs_emit(buf, "%d\n", vled->colors[LED_RED].brightness);
+}
+
+static ssize_t red_brightness_store(struct device *dev, struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	int brightness;
+	int ret;
+
+	ret = kstrtoint(buf, 10, &brightness);
+	if (ret < 0)
+		return ret;
+
+	if (brightness < 0 || brightness > LED_FULL)
+		return -EINVAL;
+
+	mutex_lock(&vled->vc_data->lock);
+	color_set_brightness(&vled->colors[LED_RED], brightness);
+	mutex_unlock(&vled->vc_data->lock);
+
+	return count;
+}
+
+static ssize_t green_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	return sysfs_emit(buf, "%d\n", vled->colors[LED_GREEN].brightness);
+}
+
+static ssize_t green_brightness_store(struct device *dev, struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	int brightness;
+	int ret;
+
+	ret = kstrtoint(buf, 10, &brightness);
+	if (ret < 0)
+		return ret;
+
+	if (brightness < 0 || brightness > LED_FULL)
+		return -EINVAL;
+
+	mutex_lock(&vled->vc_data->lock);
+	color_set_brightness(&vled->colors[LED_GREEN], brightness);
+	mutex_unlock(&vled->vc_data->lock);
+
+	return count;
+}
+
+static ssize_t blue_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	return sysfs_emit(buf, "%d\n", vled->colors[LED_BLUE].brightness);
+}
+
+static ssize_t blue_brightness_store(struct device *dev, struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct virtual_led *vled = dev_get_drvdata(dev);
+	int brightness;
+	int ret;
+
+	ret = kstrtoint(buf, 10, &brightness);
+	if (ret < 0)
+		return ret;
+
+	if (brightness < 0 || brightness > LED_FULL)
+		return -EINVAL;
+
+	mutex_lock(&vled->vc_data->lock);
+	color_set_brightness(&vled->colors[LED_BLUE], brightness);
+	mutex_unlock(&vled->vc_data->lock);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(priority);
+static DEVICE_ATTR_RW(red_brightness);
+static DEVICE_ATTR_RW(green_brightness);
+static DEVICE_ATTR_RW(blue_brightness);
+
+static struct attribute *virtual_led_attrs[] = {
+	&dev_attr_priority.attr,
+	&dev_attr_red_brightness.attr,
+	&dev_attr_green_brightness.attr,
+	&dev_attr_blue_brightness.attr,
+	NULL,
+};
+
+static const struct attribute_group virtual_led_attr_group = {
+	.attrs = virtual_led_attrs,
+};
+
+static bool virtual_led_is_active(struct list_head *head, struct virtual_led *vled)
+{
+	struct virtual_led *entry;
+
+	list_for_each_entry(entry, head, list) {
+		if (entry == vled)
+			return true;
+	}
+
+	return false;
+}
+
+static int virtual_led_brightness_set(struct led_classdev *cdev,
+				      enum led_brightness brightness)
+{
+	struct virtual_led *vled = container_of(cdev, struct virtual_led, cdev);
+	struct leds_virtualcolor *vc_data = vled->vc_data;
+	struct virtual_led *active;
+
+	mutex_lock(&vc_data->lock);
+
+	active = list_first_entry_or_null(&vc_data->active_leds, struct virtual_led, list);
+	if (active) {
+		if (active->priority > vled->priority)
+			goto out_unlock;
+
+		virtual_set_monochromatic_brightness(active, LED_OFF);
+		virtual_set_rgb_brightness(active, LED_OFF);
+	}
+
+	if (brightness == LED_OFF) {
+		if (virtual_led_is_active(&vc_data->active_leds, vled))
+			list_del(&vled->list);
+
+		active = list_first_entry_or_null(&vc_data->active_leds, struct virtual_led, list);
+		if (active) {
+			virtual_set_monochromatic_brightness(active, active->cdev.brightness);
+			/* Restore each color to its saved brightness */
+			virtual_set_rgb_independent(active,
+						    active->colors[LED_RED].brightness,
+						    active->colors[LED_GREEN].brightness,
+						    active->colors[LED_BLUE].brightness);
+		}
+	} else {
+		if (!virtual_led_is_active(&vc_data->active_leds, vled))
+			list_add(&vled->list, &vc_data->active_leds);
+
+		active = list_first_entry_or_null(&vc_data->active_leds, struct virtual_led, list);
+		if (active) {
+			virtual_set_monochromatic_brightness(active, brightness);
+			virtual_set_rgb_brightness(active, brightness);
+		}
+	}
+
+out_unlock:
+	mutex_unlock(&vc_data->lock);
+
+	return 0;
+}
+
+/**
+ * Parse monochromatic LEDs from device tree
+ */
+static int parse_monochromatic_leds(struct device *dev, struct device_node *child,
+				    struct virtual_led *vled)
+{
+	u32 mono_count;
+	int ret, i;
+
+	mono_count = of_property_count_elems_of_size(child, "leds", sizeof(u32));
+	if (mono_count <= 0) {
+		vled->num_monochromatics = 0;
+		return 0;
+	}
+
+	vled->num_monochromatics = mono_count;
+	vled->monochromatics = devm_kcalloc(dev, vled->num_monochromatics,
+					    sizeof(*vled->monochromatics), GFP_KERNEL);
+	if (!vled->monochromatics)
+		return -ENOMEM;
+
+	for (i = 0; i < vled->num_monochromatics; i++) {
+		struct led_classdev *led_cdev;
+
+		led_cdev = devm_of_led_get(dev, i);
+		if (IS_ERR(led_cdev)) {
+			ret = PTR_ERR(led_cdev);
+			return dev_err_probe(dev, ret,
+					     "Failed to get monochromatic LED %d\n", i);
+		}
+
+		vled->monochromatics[i] = led_cdev;
+	}
+
+	return 0;
+}
+
+/**
+ * Parse color-grouped PWM LEDs from device tree
+ * Format:
+ *   pwm-leds-red = <&pwm_r0 &pwm_r1>;
+ *   pwm-leds-green = <&pwm_g0 &pwm_g1>;
+ *   pwm-leds-blue = <&pwm_b0>;
+ */
+static int parse_pwm_color_leds(struct device *dev, struct device_node *child,
+				struct virtual_led *vled)
+{
+	const char *color_props[] = {"pwm-leds-red", "pwm-leds-green", "pwm-leds-blue"};
+	const char *color_names[] = {"Red", "Green", "Blue"};
+	int ret, c, i, count;
+
+	for (c = 0; c < LED_COLOR_MAX; c++) {
+		count = of_property_count_elems_of_size(child, color_props[c], sizeof(u32));
+		if (count < 0) {
+			vled->colors[c].num_leds = 0;
+			continue;
+		}
+
+		if (count > MAX_LEDS_PER_COLOR) {
+			return dev_err_probe(dev, -EINVAL,
+					     "Too many %s LEDs (%d), max %d\n",
+					     color_names[c], count, MAX_LEDS_PER_COLOR);
+		}
+
+		vled->colors[c].num_leds = count;
+		vled->colors[c].color_name = (char *)color_names[c];
+		vled->colors[c].brightness = LED_OFF;
+
+		vled->colors[c].leds = devm_kcalloc(dev, count,
+						    sizeof(*vled->colors[c].leds), GFP_KERNEL);
+		if (!vled->colors[c].leds)
+			return -ENOMEM;
+
+		for (i = 0; i < count; i++) {
+			struct led_classdev *led_cdev;
+
+			led_cdev = devm_of_led_get(dev, i);
+			if (IS_ERR(led_cdev)) {
+				ret = PTR_ERR(led_cdev);
+				return dev_err_probe(dev, ret,
+						     "Failed to get %s LED %d\n",
+						     color_names[c], i);
+			}
+
+			vled->colors[c].leds[i] = led_cdev;
+		}
+
+		dev_dbg(dev, "Parsed %d %s LEDs\n", count, color_names[c]);
+	}
+
+	return 0;
+}
+
+static int leds_virtualcolor_init_vled(struct device *dev, struct device_node *child,
+				       struct virtual_led *vled,
+				       struct leds_virtualcolor *vc_data)
+{
+	struct led_init_data init_data = {};
+	u32 max_brightness;
+	int ret, total_rgb_leds = 0, c;
+
+	ret = of_property_read_u32(child, "priority", &vled->priority);
+	if (ret)
+		vled->priority = 0;
+
+	INIT_LIST_HEAD(&vled->list);
+
+	/* Parse monochromatic LEDs */
+	ret = parse_monochromatic_leds(dev, child, vled);
+	if (ret)
+		return ret;
+
+	/* Parse color-grouped PWM LEDs */
+	ret = parse_pwm_color_leds(dev, child, vled);
+	if (ret)
+		return ret;
+
+	/* Count total RGB LEDs */
+	for (c = 0; c < LED_COLOR_MAX; c++)
+		total_rgb_leds += vled->colors[c].num_leds;
+
+	/* At least one type of LED must be present */
+	if (vled->num_monochromatics == 0 && total_rgb_leds == 0) {
+		return dev_err_probe(dev, -EINVAL,
+				     "No LEDs specified for virtual LED\n");
+	}
+
+	ret = of_property_read_u32(child, "max-brightness", &max_brightness);
+	if (ret)
+		vled->cdev.max_brightness = LED_FULL;
+	else
+		vled->cdev.max_brightness = max_brightness;
+
+	vled->cdev.brightness_set_blocking = virtual_led_brightness_set;
+	vled->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+	ret = devm_led_classdev_register_ext(dev, &vled->cdev, &init_data);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to register virtual LED\n");
+
+	dev_set_drvdata(vled->cdev.dev, vled);
+
+	ret = devm_device_add_group(vled->cdev.dev, &virtual_led_attr_group);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to create sysfs attributes\n");
+
+	vled->vc_data = vc_data;
+
+	dev_info(dev, "Virtual LED: %d mono, %d red, %d green, %d blue\n",
+		 vled->num_monochromatics, vled->colors[LED_RED].num_leds,
+		 vled->colors[LED_GREEN].num_leds, vled->colors[LED_BLUE].num_leds);
+
+	return 0;
+}
+
+static int leds_virtualcolor_probe(struct platform_device *pdev)
+{
+	struct leds_virtualcolor *vc_data;
+	struct device *dev = &pdev->dev;
+	int count = 0;
+	int ret;
+
+	vc_data = devm_kzalloc(dev, sizeof(*vc_data), GFP_KERNEL);
+	if (!vc_data)
+		return -ENOMEM;
+
+	mutex_init(&vc_data->lock);
+
+	ret = devm_add_action_or_reset(dev, (void (*)(void *))mutex_destroy,
+				       &vc_data->lock);
+	if (ret)
+		return ret;
+
+	INIT_LIST_HEAD(&vc_data->active_leds);
+
+	vc_data->num_vleds = of_get_child_count(dev->of_node);
+	if (vc_data->num_vleds == 0) {
+		dev_err(dev, "No virtual LEDs defined\n");
+		return -EINVAL;
+	}
+
+	vc_data->vleds = devm_kcalloc(dev, vc_data->num_vleds, sizeof(*vc_data->vleds), GFP_KERNEL);
+	if (!vc_data->vleds)
+		return -ENOMEM;
+
+	for_each_available_child_of_node_scoped(dev->of_node, child) {
+		struct virtual_led *vled = &vc_data->vleds[count];
+
+		ret = leds_virtualcolor_init_vled(dev, child, vled, vc_data);
+		if (ret)
+			return ret;
+
+		count++;
+	}
+
+	platform_set_drvdata(pdev, vc_data);
+
+	return 0;
+}
+
+static const struct of_device_id leds_virtualcolor_of_match[] = {
+	{ .compatible = "leds-group-virtualcolor" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, leds_virtualcolor_of_match);
+
+static struct platform_driver leds_virtualcolor_driver = {
+	.probe  = leds_virtualcolor_probe,
+	.driver = {
+		.name           = "leds_virtualcolor",
+		.of_match_table = leds_virtualcolor_of_match,
+	},
+};
+
+module_platform_driver(leds_virtualcolor_driver);
+
+MODULE_AUTHOR("Radoslav Tsvetkov <rtsvetkov@...dotech.eu>");
+MODULE_DESCRIPTION("Virtual Color LED Driver with RGB Color Grouping");
+MODULE_LICENSE("GPL");
--
2.43.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ