mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/2] leds: trigger: input-events: Restore previous brightness on input
@ 2026-09-30  6:54 Nikolay Plastinin via B4 Relay
  2026-09-30  6:54 ` [PATCH v2 1/2] leds: triggers: Add led_trigger_walk_leds() Nikolay Plastinin via B4 Relay
  2026-09-30  6:54 ` [PATCH v2 2/2] leds: trigger: input-events: Restore previous brightness on input Nikolay Plastinin via B4 Relay
  0 siblings, 2 replies; 3+ messages in thread
From: Nikolay Plastinin via B4 Relay @ 2026-09-30  6:54 UTC (permalink / raw)
  To: Lee Jones, Pavel Machek
  Cc: Hans de Goede, linux-leds, linux-kernel, Nikolay Plastinin

The input-events trigger turns LEDs back on with LED_FULL, so a keyboard
backlight with several levels always comes back at maximum after being
idle. This series restores the level the user picked instead.

Patch 1 adds led_trigger_walk_leds() to the LED core, so that a
trigger which needs a different brightness per LED does not have to walk
trig->led_cdevs itself. Patch 2 converts the input-events trigger to a
regular trigger with per-LED state in trigger_data and uses the helper.

This came up while looking at a keyboard backlight idle timeout for ASUS
laptops in asusctl:
https://github.com/OpenGamingCollective/asusctl/issues/98

Tested on an ASUS ROG Flow X13 GV302XV (asus::kbd_backlight, 3 levels)
with the trigger built out of tree against Fedora's 7.2.7 kernel and a
local copy of the helper, since the helper itself lives in the built-in
LED core.

---
Changes in v2:
- Keep the saved brightness in a per-LED struct allocated in activate()
  and freed in deactivate(), stored in led_cdev->trigger_data, instead of
  overloading blink_brightness (Lee Jones).
- Add led_trigger_walk_leds() to led-triggers.c and use it instead of
  walking led_cdevs/trig_list from the trigger (Lee Jones).
- Use the Assisted-by tag format from coding-assistants.rst.
- Link to v1: https://lore.kernel.org/r/20260924-leds-input-events-brightness-v1-1-b0ceff3a2343@gmail.com

---
Nikolay Plastinin (2):
      leds: triggers: Add led_trigger_walk_leds()
      leds: trigger: input-events: Restore previous brightness on input

 drivers/leds/led-triggers.c                 | 25 ++++++++++
 drivers/leds/trigger/ledtrig-input-events.c | 74 ++++++++++++++++++++++++++---
 include/linux/leds.h                        |  7 +++
 3 files changed, 100 insertions(+), 6 deletions(-)
---
base-commit: f475845eaf3d749114a63270bf2efea459e14dd2
change-id: 20260924-leds-input-events-brightness-a76ae9a77b85

Best regards,
-- 
Nikolay Plastinin <plaztininikolai@gmail.com>



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 1/2] leds: triggers: Add led_trigger_walk_leds()
  2026-09-30  6:54 [PATCH v2 0/2] leds: trigger: input-events: Restore previous brightness on input Nikolay Plastinin via B4 Relay
@ 2026-09-30  6:54 ` Nikolay Plastinin via B4 Relay
  2026-09-30  6:54 ` [PATCH v2 2/2] leds: trigger: input-events: Restore previous brightness on input Nikolay Plastinin via B4 Relay
  1 sibling, 0 replies; 3+ messages in thread
From: Nikolay Plastinin via B4 Relay @ 2026-09-30  6:54 UTC (permalink / raw)
  To: Lee Jones, Pavel Machek
  Cc: Hans de Goede, linux-leds, linux-kernel, Nikolay Plastinin

From: Nikolay Plastinin <plaztininikolai@gmail.com>

led_trigger_event() sets one brightness for all LEDs using a trigger.
A trigger which restores a different brightness per LED, such as the
input-events trigger turning keyboard backlights back on at the level
the user picked, needs to walk the trigger's LEDs itself. That means
using trig->led_cdevs and led_cdev->trig_list outside the LED core.

Add led_trigger_walk_leds(), which calls a function for each LED
using the trigger under rcu_read_lock(), the same way led_trigger_event()
does, so that such triggers do not have to walk the list themselves.

Assisted-by: LLM
Signed-off-by: Nikolay Plastinin <plaztininikolai@gmail.com>
---
 drivers/leds/led-triggers.c | 25 +++++++++++++++++++++++++
 include/linux/leds.h        |  7 +++++++
 2 files changed, 32 insertions(+)

diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index b1223218b..b5fc14e47 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -422,6 +422,31 @@ void led_trigger_event(struct led_trigger *trig,
 }
 EXPORT_SYMBOL_GPL(led_trigger_event);
 
+/**
+ * led_trigger_walk_leds - call a function for each LED using a trigger
+ * @trig: the trigger, may be NULL
+ * @fn: the function to call for each LED
+ * @data: pointer passed to @fn
+ *
+ * Walks the LEDs currently using @trig under rcu_read_lock() and calls @fn
+ * for each of them. This is for triggers which set a different brightness
+ * per LED, where led_trigger_event() is not enough. @fn must not sleep.
+ */
+void led_trigger_walk_leds(struct led_trigger *trig, led_trigger_walk_fn_t fn,
+			   void *data)
+{
+	struct led_classdev *led_cdev;
+
+	if (!trig)
+		return;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list)
+		fn(led_cdev, data);
+	rcu_read_unlock();
+}
+EXPORT_SYMBOL_GPL(led_trigger_walk_leds);
+
 void led_mc_trigger_event(struct led_trigger *trig,
 			  unsigned int *intensity_value, unsigned int num_colors,
 			  enum led_brightness brightness)
diff --git a/include/linux/leds.h b/include/linux/leds.h
index a515f075c..c971a6aa5 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -476,6 +476,9 @@ static inline bool led_sysfs_is_disabled(struct led_classdev *led_cdev)
 #define DEFINE_LED_TRIGGER(x)		static struct led_trigger *x;
 #define DEFINE_LED_TRIGGER_GLOBAL(x)	struct led_trigger *x;
 
+/* Callback for led_trigger_walk_leds() */
+typedef void (*led_trigger_walk_fn_t)(struct led_classdev *led_cdev, void *data);
+
 #ifdef CONFIG_LEDS_TRIGGERS
 
 #define TRIG_NAME_MAX 50
@@ -521,6 +524,8 @@ void led_trigger_register_simple(const char *name,
 				struct led_trigger **trigger);
 void led_trigger_unregister_simple(struct led_trigger *trigger);
 void led_trigger_event(struct led_trigger *trigger,  enum led_brightness event);
+void led_trigger_walk_leds(struct led_trigger *trig, led_trigger_walk_fn_t fn,
+			   void *data);
 void led_mc_trigger_event(struct led_trigger *trig,
 			  unsigned int *intensity_value, unsigned int num_colors,
 			  enum led_brightness brightness);
@@ -566,6 +571,8 @@ static inline void led_trigger_register_simple(const char *name,
 static inline void led_trigger_unregister_simple(struct led_trigger *trigger) {}
 static inline void led_trigger_event(struct led_trigger *trigger,
 				enum led_brightness event) {}
+static inline void led_trigger_walk_leds(struct led_trigger *trig,
+					 led_trigger_walk_fn_t fn, void *data) {}
 static inline void led_mc_trigger_event(struct led_trigger *trig,
 				unsigned int *intensity_value, unsigned int num_colors,
 				enum led_brightness brightness) {}

-- 
2.55.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 2/2] leds: trigger: input-events: Restore previous brightness on input
  2026-09-30  6:54 [PATCH v2 0/2] leds: trigger: input-events: Restore previous brightness on input Nikolay Plastinin via B4 Relay
  2026-09-30  6:54 ` [PATCH v2 1/2] leds: triggers: Add led_trigger_walk_leds() Nikolay Plastinin via B4 Relay
@ 2026-09-30  6:54 ` Nikolay Plastinin via B4 Relay
  1 sibling, 0 replies; 3+ messages in thread
From: Nikolay Plastinin via B4 Relay @ 2026-09-30  6:54 UTC (permalink / raw)
  To: Lee Jones, Pavel Machek
  Cc: Hans de Goede, linux-leds, linux-kernel, Nikolay Plastinin

From: Nikolay Plastinin <plaztininikolai@gmail.com>

The trigger turns the LEDs back on with LED_FULL, which the LED core
clamps to max_brightness. For keyboard backlights with several levels,
such as asus::kbd_backlight, this means the backlight always comes back
at maximum after being idle instead of at the level the user picked.

Remember the brightness per LED before turning it off and restore it on
the next input event. The level is kept in a small per-LED state struct
allocated in activate() and freed in deactivate(), so the trigger is
converted from the simple trigger API to a regular trigger. Brightness
changes made while the trigger is active, for example with the keyboard
brightness hotkeys, are picked up too. The LEDs are walked with the new
led_trigger_walk_leds() helper instead of the trigger touching
trig->led_cdevs itself.

Tested on an ASUS ROG Flow X13 GV302XV: with the keyboard backlight at
level 1, it now comes back at level 1 after going idle instead of at 3.
A level changed with the brightness hotkeys while the trigger is active
is restored after the next idle period as well.

Assisted-by: LLM
Signed-off-by: Nikolay Plastinin <plaztininikolai@gmail.com>
---
 drivers/leds/trigger/ledtrig-input-events.c | 74 ++++++++++++++++++++++++++---
 1 file changed, 68 insertions(+), 6 deletions(-)

diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c
index d057b2a23..4729c8507 100644
--- a/drivers/leds/trigger/ledtrig-input-events.c
+++ b/drivers/leds/trigger/ledtrig-input-events.c
@@ -28,7 +28,67 @@ static struct input_events_data {
 	unsigned long led_off_time;
 } input_events_data;
 
-static struct led_trigger *input_events_led_trigger;
+struct input_events_led {
+	/* Brightness to restore on the next input event */
+	int brightness;
+};
+
+static int input_events_activate(struct led_classdev *led_cdev)
+{
+	struct input_events_data *data = &input_events_data;
+	struct input_events_led *led;
+	unsigned long flags;
+
+	led = kzalloc_obj(*led);
+	if (!led)
+		return -ENOMEM;
+
+	/* Start with the current brightness, or full if the LED is off */
+	led->brightness = led_cdev->brightness ?: led_cdev->max_brightness;
+
+	spin_lock_irqsave(&data->lock, flags);
+	led_set_trigger_data(led_cdev, led);
+	led_set_brightness(led_cdev, data->led_on ? led->brightness : LED_OFF);
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	return 0;
+}
+
+static void input_events_deactivate(struct led_classdev *led_cdev)
+{
+	kfree(led_get_trigger_data(led_cdev));
+}
+
+static struct led_trigger input_events_led_trigger = {
+	.name = "input-events",
+	.activate = input_events_activate,
+	.deactivate = input_events_deactivate,
+};
+
+static void input_events_set_led(struct led_classdev *led_cdev, void *arg)
+{
+	struct input_events_led *led = led_get_trigger_data(led_cdev);
+	bool on = *(bool *)arg;
+
+	/* Added to the trigger, but activate() has not run yet */
+	if (!led)
+		return;
+
+	/*
+	 * Remember the brightness the LED was last set to, e.g. by a hotkey
+	 * while it was on, so that it is restored instead of max brightness.
+	 */
+	if (led_cdev->brightness)
+		led->brightness = led_cdev->brightness;
+
+	led_set_brightness(led_cdev, on ? led->brightness : LED_OFF);
+}
+
+/* Must be called with input_events_data.lock held */
+static void input_events_set_leds(bool on)
+{
+	led_trigger_walk_leds(&input_events_led_trigger, input_events_set_led, &on);
+}
 
 static void led_input_events_work(struct work_struct *work)
 {
@@ -42,7 +102,7 @@ static void led_input_events_work(struct work_struct *work)
 	 * running before a new event pushed led_off_time back.
 	 */
 	if (time_after_eq(jiffies, data->led_off_time)) {
-		led_trigger_event(input_events_led_trigger, LED_OFF);
+		input_events_set_leds(false);
 		data->led_on = false;
 	}
 
@@ -59,7 +119,7 @@ static void input_events_event(struct input_handle *handle, unsigned int type,
 	spin_lock_irqsave(&data->lock, flags);
 
 	if (!data->led_on) {
-		led_trigger_event(input_events_led_trigger, LED_FULL);
+		input_events_set_leds(true);
 		data->led_on = true;
 	}
 	data->led_off_time = jiffies + led_off_delay;
@@ -138,11 +198,13 @@ static int __init input_events_init(void)
 	INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work);
 	spin_lock_init(&input_events_data.lock);
 
-	led_trigger_register_simple("input-events", &input_events_led_trigger);
+	ret = led_trigger_register(&input_events_led_trigger);
+	if (ret)
+		return ret;
 
 	ret = input_register_handler(&input_events_handler);
 	if (ret) {
-		led_trigger_unregister_simple(input_events_led_trigger);
+		led_trigger_unregister(&input_events_led_trigger);
 		return ret;
 	}
 
@@ -153,7 +215,7 @@ static void __exit input_events_exit(void)
 {
 	input_unregister_handler(&input_events_handler);
 	cancel_delayed_work_sync(&input_events_data.work);
-	led_trigger_unregister_simple(input_events_led_trigger);
+	led_trigger_unregister(&input_events_led_trigger);
 }
 
 module_init(input_events_init);

-- 
2.55.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-30  6:54 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30  6:54 [PATCH v2 0/2] leds: trigger: input-events: Restore previous brightness on input Nikolay Plastinin via B4 Relay
2026-09-30  6:54 ` [PATCH v2 1/2] leds: triggers: Add led_trigger_walk_leds() Nikolay Plastinin via B4 Relay
2026-09-30  6:54 ` [PATCH v2 2/2] leds: trigger: input-events: Restore previous brightness on input Nikolay Plastinin via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®