[PATCH RFC v3 02/11] leds: class: Remove hardware control trigger when writing brightness
From: Rong Zhang
Date: Sat Jul 18 2026 - 13:09:55 EST
Since commit b819dc7d8fb2 ("leds: core: Report ENODATA for brightness of
hardware controlled LED"), the brightness attribute becomes write-only
when the LED is controlled fully by the hardware. A write-only attribute
is very confusing.
Moreover, most LED drivers set hardware brightness innocently with the
side effect of disabling hardware control, but the hardware control
trigger remains active, resulting in the software and hardware being out
of sync.
Fix it by removing the hardware control trigger when writing the
brightness attribute.
This should also match the semantics of hardware control:
When the LED is in hw control, no software blink is possible and
doing so will effectively disable hw control.
Fixes: b819dc7d8fb2 ("leds: core: Report ENODATA for brightness of hardware controlled LED")
Signed-off-by: Rong Zhang <i@xxxxxxxx>
---
Changes in v3:
- New patch in the series, integrated from https://lore.kernel.org/all/20260712-leds-hw-control-brightness-set-v1-1-1de593b09d26@xxxxxxxx/
- The following patches will improve __led_trigger_is_hw_controlled()
to include offloaded generic triggers and take the advantage of it
---
drivers/leds/led-class.c | 3 +++
drivers/leds/led-triggers.c | 9 +++++++++
include/linux/leds.h | 2 ++
3 files changed, 14 insertions(+)
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 1b8b688aaaaf..ab61e41a00a3 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -64,6 +64,9 @@ static ssize_t brightness_store(struct device *dev,
if (state == LED_OFF)
led_trigger_remove(led_cdev);
+ else
+ led_trigger_remove_hw_control(led_cdev);
+
led_set_brightness(led_cdev, state);
ret = size;
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index bf2543538ed0..804a04b326c4 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -287,6 +287,15 @@ void led_trigger_remove(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_trigger_remove);
+void led_trigger_remove_hw_control(struct led_classdev *led_cdev)
+{
+ guard(rwsem_write)(&led_cdev->trigger_lock);
+
+ if (__led_trigger_is_hw_controlled(led_cdev))
+ led_trigger_set(led_cdev, NULL);
+}
+EXPORT_SYMBOL_GPL(led_trigger_remove_hw_control);
+
static bool led_match_default_trigger(struct led_classdev *led_cdev,
struct led_trigger *trig)
{
diff --git a/include/linux/leds.h b/include/linux/leds.h
index a630f5a79f6b..d7d3dd905432 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -533,6 +533,7 @@ void led_trigger_blink_oneshot(struct led_trigger *trigger,
void led_trigger_set_default(struct led_classdev *led_cdev);
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger);
void led_trigger_remove(struct led_classdev *led_cdev);
+void led_trigger_remove_hw_control(struct led_classdev *led_cdev);
bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev);
@@ -586,6 +587,7 @@ static inline int led_trigger_set(struct led_classdev *led_cdev,
}
static inline void led_trigger_remove(struct led_classdev *led_cdev) {}
+static inline void led_trigger_remove_hw_control(struct led_classdev *led_cdev) {}
static inline bool led_trigger_is_hw_controlled(struct led_classdev *led_cdev)
{
--
2.53.0