[RFC PATCH 9/9] platform/x86: ideapad-laptop: Fully support auto kbd backlight

From: Rong Zhang

Date: Fri Feb 27 2026 - 14:07:55 EST


Currently, the auto brightness mode of keyboard backlight maps to
brightness=0 in LED classdev. The only method to switch to such a mode
is by pressing the manufacturer-defined shortcut (Fn+Space). However, 0
is a multiplexed brightness value; writing 0 simply results in the
backlight being turned off.

With brightness processing code decoupled from LED classdev, we can now
fully support the auto brightness mode. In this mode, the keyboard
backlight is controlled by the EC according to the ambient light sensor
(ALS).

To utilize this, a private hw control trigger "ideapad-auto" is added,
with the event handling procedure calling the
led_trigger_notify_hw_control_changed() interface to activate/deactivate
the private trigger according to the current LED trigger state.

Meanwhile, block brightness changes on exit to prevent the side effect
of LED device unregistration when the private trigger is active from
resetting the brightness to zero, so that we can retain the state of
auto mode among boots.

Signed-off-by: Rong Zhang <i@xxxxxxxx>
---
drivers/platform/x86/lenovo/Kconfig | 1 +
drivers/platform/x86/lenovo/ideapad-laptop.c | 86 ++++++++++++++++++--
2 files changed, 79 insertions(+), 8 deletions(-)

diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index f885127b007f1..626180370add4 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -16,6 +16,7 @@ config IDEAPAD_LAPTOP
select INPUT_SPARSEKMAP
select NEW_LEDS
select LEDS_CLASS
+ select LEDS_TRIGGERS
help
This is a driver for Lenovo IdeaPad netbooks contains drivers for
rfkill switch, hotkey, fan control and backlight control.
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index b9af0294fc933..99cdd18cc1a5d 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -26,6 +26,7 @@
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
@@ -166,6 +167,8 @@ enum {
KBD_BL_TRISTATE_AUTO = 3,
};

+#define KBD_BL_AUTO_MODE_HW_BRIGHTNESS 3
+
#define KBD_BL_QUERY_TYPE 0x1
#define KBD_BL_TRISTATE_TYPE 0x5
#define KBD_BL_TRISTATE_AUTO_TYPE 0x7
@@ -1620,8 +1623,9 @@ static int ideapad_kbd_bl_brightness_parse(struct ideapad_private *priv,
if (hw_brightness <= priv->kbd_bl.led.max_brightness)
return hw_brightness;

- /* Auto, report as off */
- if (hw_brightness == priv->kbd_bl.led.max_brightness + 1)
+ /* Auto (controlled by EC according to ALS), report as off */
+ if (priv->kbd_bl.type == KBD_BL_TRISTATE_AUTO &&
+ hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS)
return 0;

/* Unknown value */
@@ -1709,9 +1713,39 @@ static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev,
{
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led);

+ /*
+ * When deinitializing: It must be the side effect of led_cdev
+ * unregistration when our private trigger is active. We've set
+ * LED_RETAIN_AT_SHUTDOWN to retain led_cdev brightness level. To do the
+ * same for auto mode, gate changes and return early.
+ */
+ if (unlikely(!priv->kbd_bl.initialized))
+ return 0;
+
return ideapad_kbd_bl_brightness_set(priv, brightness);
}

+static int ideapad_kbd_bl_auto_trigger_activate(struct led_classdev *led_cdev)
+{
+ struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led);
+
+ return ideapad_kbd_bl_hw_brightness_set(priv, KBD_BL_AUTO_MODE_HW_BRIGHTNESS);
+}
+
+static bool ideapad_kbd_bl_auto_trigger_offloaded(struct led_classdev *led_cdev)
+{
+ return true;
+}
+
+static struct led_hw_trigger_type ideapad_kbd_bl_auto_trigger_type;
+
+static struct led_trigger ideapad_kbd_bl_auto_trigger = {
+ .name = "ideapad-auto",
+ .trigger_type = &ideapad_kbd_bl_auto_trigger_type,
+ .activate = ideapad_kbd_bl_auto_trigger_activate,
+ .offloaded = ideapad_kbd_bl_auto_trigger_offloaded,
+};
+
static void ideapad_kbd_bl_notify_known(struct ideapad_private *priv, unsigned int brightness)
{
if (brightness == priv->kbd_bl.last_brightness)
@@ -1724,12 +1758,23 @@ static void ideapad_kbd_bl_notify_known(struct ideapad_private *priv, unsigned i

static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
{
- int brightness;
+ int hw_brightness, brightness;

if (!priv->kbd_bl.initialized)
return;

- brightness = ideapad_kbd_bl_brightness_get(priv);
+ hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
+ if (hw_brightness < 0)
+ return;
+
+ if (priv->kbd_bl.type == KBD_BL_TRISTATE_AUTO) {
+ bool activate = hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS;
+
+ led_trigger_notify_hw_control_changed(&priv->kbd_bl.led, activate,
+ &ideapad_kbd_bl_auto_trigger);
+ }
+
+ brightness = ideapad_kbd_bl_brightness_parse(priv, hw_brightness);
if (brightness < 0)
return;

@@ -1738,7 +1783,7 @@ static void ideapad_kbd_bl_notify(struct ideapad_private *priv)

static int ideapad_kbd_bl_init(struct ideapad_private *priv)
{
- int brightness, err;
+ int hw_brightness, brightness, err;

if (!priv->features.kbd_bl)
return -ENODEV;
@@ -1746,12 +1791,37 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
if (WARN_ON(priv->kbd_bl.initialized))
return -EEXIST;

- if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
+ hw_brightness = ideapad_kbd_bl_hw_brightness_get(priv);
+ if (hw_brightness < 0)
+ return hw_brightness;
+
+ switch (priv->kbd_bl.type) {
+ case KBD_BL_TRISTATE_AUTO:
+ err = devm_led_trigger_register(&priv->platform_device->dev,
+ &ideapad_kbd_bl_auto_trigger);
+ if (err)
+ return err;
+
+ priv->kbd_bl.led.trigger_type = &ideapad_kbd_bl_auto_trigger_type;
+ priv->kbd_bl.led.hw_control_trigger = ideapad_kbd_bl_auto_trigger.name;
+
+ /* HW remembers the last brightness level, including auto mode. */
+ if (hw_brightness == KBD_BL_AUTO_MODE_HW_BRIGHTNESS)
+ priv->kbd_bl.led.default_trigger = ideapad_kbd_bl_auto_trigger.name;
+
+ fallthrough;
+ case KBD_BL_TRISTATE:
priv->kbd_bl.led.max_brightness = 2;
- else
+ break;
+ case KBD_BL_STANDARD:
priv->kbd_bl.led.max_brightness = 1;
+ break;
+ default:
+ /* This has already been validated by ideapad_check_features(). */
+ unreachable();
+ }

- brightness = ideapad_kbd_bl_brightness_get(priv);
+ brightness = ideapad_kbd_bl_brightness_parse(priv, hw_brightness);
if (brightness < 0)
return brightness;

--
2.51.0