From 2667f2d92a37137785145fd1acc9b43531f09558 Mon Sep 17 00:00:00 2001 From: Idotoho Reimon Simanjuntak Date: Thu, 3 Sep 2026 00:47:16 +0700 Subject: [PATCH 1/3] [FROM-ML] platform/x86: asus-wmi: use named masks for TUF RGB commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace open-coded bit shifts and magic numbers in kbd_rgb_mode_store() and kbd_rgb_state_store() with FIELD_PREP() and named GENMASK() masks. Define ASUS_WMI_TUF_RGB_STATE_CMD_ID (0xbd) as a named constant instead of an inline literal, making the required arg0 command ID self-documenting. In kbd_rgb_state_store(), remove the unused intermediate "flags" variable entirely. The old BIT(1)/BIT(3)/BIT(5)/BIT(7) construction is replaced by FIELD_PREP with per-flag named masks (TUF_RGB_STATE_BOOT, _AWAKE, _SLEEP, _KEYBOARD), building arg0 directly. This preserves the exact sysfs input format ("cmd boot awake sleep keyboard") and the resulting WMI argument bit layout — no behavioral change. In kbd_rgb_mode_store(), replace the positional shift expressions for arg1 and arg2 with FIELD_PREP using TUF_RGB_MODE_{CMD,MODE,RED,GREEN} and TUF_RGB_MODE_{BLUE,SPEED} masks respectively. This is a preparatory cleanup to improve readability before adding model-specific quirks that depend on these definitions. Signed-off-by: Idotoho Reimon Simanjuntak Reviewed-by: Denis Benato Link: https://lore.kernel.org/r/20260902174718.16228-2-idotohors@gmail.com Signed-off-by: ido <106871003+idobutidont@users.noreply.github.com> --- drivers/platform/x86/asus-wmi.c | 48 ++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index fd5fbdb0cd8b8..45d6ab3a72307 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -1047,6 +1048,27 @@ static DEVICE_ATTR_RW(gpu_mux_mode); #endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* TUF Laptop Keyboard RGB Modes **********************************************/ + +/* Command IDs passed in arg0 byte 0 for TUF RGB WMI methods */ +#define ASUS_WMI_TUF_RGB_STATE_CMD_ID 0xbd + +/* Bit mask for the save-to-BIOS command flag in kbd_rgb_state_store (arg0 bit 10) */ +#define TUF_RGB_STATE_SAVE GENMASK(10, 10) + +/* Bit masks for kbd_rgb_state_store flags field (arg0 bits [23:16]) */ +#define TUF_RGB_STATE_BOOT GENMASK(17, 17) +#define TUF_RGB_STATE_AWAKE GENMASK(19, 19) +#define TUF_RGB_STATE_SLEEP GENMASK(21, 21) +#define TUF_RGB_STATE_KEYBOARD GENMASK(23, 23) + +/* Bit masks for kbd_rgb_mode_store fields */ +#define TUF_RGB_MODE_CMD GENMASK(7, 0) +#define TUF_RGB_MODE_MODE GENMASK(15, 8) +#define TUF_RGB_MODE_RED GENMASK(23, 16) +#define TUF_RGB_MODE_GREEN GENMASK(31, 24) +#define TUF_RGB_MODE_BLUE GENMASK(7, 0) +#define TUF_RGB_MODE_SPEED GENMASK(15, 8) + static ssize_t kbd_rgb_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1093,7 +1115,12 @@ static ssize_t kbd_rgb_mode_store(struct device *dev, } err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev, - cmd | (mode << 8) | (r << 16) | (g << 24), b | (speed << 8), NULL); + FIELD_PREP(TUF_RGB_MODE_CMD, cmd) | + FIELD_PREP(TUF_RGB_MODE_MODE, mode) | + FIELD_PREP(TUF_RGB_MODE_RED, r) | + FIELD_PREP(TUF_RGB_MODE_GREEN, g), + FIELD_PREP(TUF_RGB_MODE_BLUE, b) | + FIELD_PREP(TUF_RGB_MODE_SPEED, speed), NULL); if (err) return err; @@ -1119,28 +1146,29 @@ static ssize_t kbd_rgb_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - u32 flags, cmd, boot, awake, sleep, keyboard; + u32 cmd, boot, awake, sleep, keyboard; + u32 arg0; int err; if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &keyboard) != 5) return -EINVAL; + arg0 = ASUS_WMI_TUF_RGB_STATE_CMD_ID; + if (cmd) - cmd = BIT(2); + arg0 |= FIELD_PREP(TUF_RGB_STATE_SAVE, 1); - flags = 0; if (boot) - flags |= BIT(1); + arg0 |= FIELD_PREP(TUF_RGB_STATE_BOOT, 1); if (awake) - flags |= BIT(3); + arg0 |= FIELD_PREP(TUF_RGB_STATE_AWAKE, 1); if (sleep) - flags |= BIT(5); + arg0 |= FIELD_PREP(TUF_RGB_STATE_SLEEP, 1); if (keyboard) - flags |= BIT(7); + arg0 |= FIELD_PREP(TUF_RGB_STATE_KEYBOARD, 1); - /* 0xbd is the required default arg0 for the method. Nothing happens otherwise */ err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, - ASUS_WMI_DEVID_TUF_RGB_STATE, 0xbd | cmd << 8 | (flags << 16), 0, NULL); + ASUS_WMI_DEVID_TUF_RGB_STATE, arg0, 0, NULL); if (err) return err; From 20d9cf5746464e6fdfe44cc8edff2e51fb906399 Mon Sep 17 00:00:00 2001 From: Idotoho Reimon Simanjuntak Date: Thu, 3 Sep 2026 00:47:17 +0700 Subject: [PATCH 2/3] [FROM-ML] platform/x86: asus-wmi: enable TUF RGB state for FA401 via DMI quirk The FA401 (TUF Gaming A14) series does not expose the TUF RGB state WMI device (0x00100057) through the standard DSTS probe, yet the EC does support the keyboard backlight power-state flags needed for sleep strobe functionality. Add a DMI-based quirk in asus-nb-wmi.c that matches on DMI_SYS_VENDOR "ASUSTeK COMPUTER INC." and DMI_BOARD_NAME "FA401", setting kbd_rgb_state_available = true in struct quirk_entry. In asus_wmi_add(), change the kbd_rgb_state_available assignment to also consider driver->quirks->kbd_rgb_state_available, so models with this quirk get the RGB state sysfs attributes even when DSTS probing fails. No PM/sleep/backlight changes are included in this commit; those will be addressed separately once the RGB state interface is available. Signed-off-by: Idotoho Reimon Simanjuntak Reviewed-by: Denis Benato Link: https://lore.kernel.org/r/20260902174718.16228-3-idotohors@gmail.com Signed-off-by: ido <106871003+idobutidont@users.noreply.github.com> --- drivers/platform/x86/asus-nb-wmi.c | 13 +++++++++++++ drivers/platform/x86/asus-wmi.c | 4 +++- drivers/platform/x86/asus-wmi.h | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 8005c088e9eee..c89f492f889a1 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -155,6 +155,10 @@ static struct quirk_entry quirk_asus_z13 = { .tablet_switch_mode = asus_wmi_kbd_dock_devid, }; +static struct quirk_entry quirk_asus_fa401 = { + .kbd_rgb_state_available = true, +}; + static int dmi_matched(const struct dmi_system_id *dmi) { pr_info("Identified laptop model '%s'\n", dmi->ident); @@ -562,6 +566,15 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_z13, }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. FA401", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_BOARD_NAME, "FA401"), + }, + .driver_data = &quirk_asus_fa401, + }, {}, }; diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 45d6ab3a72307..88e3b815f3245 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -5213,7 +5213,9 @@ static int asus_wmi_add(struct platform_device *pdev) asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); - asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); + asus->kbd_rgb_state_available = + asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE) || + asus->driver->quirks->kbd_rgb_state_available; if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 5cd4392b964eb..9bc0b6145fed6 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -52,6 +52,7 @@ struct quirk_entry { */ int no_display_toggle; u32 xusb2pr; + bool kbd_rgb_state_available; }; struct asus_wmi_driver { From 7fa8b4a47ccf7233353801b5e94d39f31c9a2b9b Mon Sep 17 00:00:00 2001 From: Idotoho Reimon Simanjuntak Date: Thu, 3 Sep 2026 00:47:18 +0700 Subject: [PATCH 3/3] [FROM-ML] platform/x86: asus-wmi: re-assert FA401 keyboard state before S0ix On ASUS TUF Gaming FA401 laptops, the display server or graphics driver blanks the keyboard backlight (brightness=0) during suspend. When the EC enters S0ix with brightness=0, it disables the sleep-strobe effect. Track the last user-configured non-zero keyboard backlight level in asus->kbd_led_last_level, and re-assert it with the light-on bit (0x80) along with the configured TUF RGB power state in both .prepare and the LPS0 s2idle .prepare hook. Hooking LPS0 ensures the re-assertion happens after all devices (including DRM and display managers) have suspended, immediately prior to low-power S0 idle entry. Only re-assert if the TUF sleep flag is active in kbd_rgb_state_flags, respecting any user decision to disable sleep LEDs. Initialize default flags at probe so the strobe functions out of the box. Signed-off-by: Idotoho Reimon Simanjuntak Link: https://lore.kernel.org/r/20260902174718.16228-4-idotohors@gmail.com Signed-off-by: ido <106871003+idobutidont@users.noreply.github.com> --- drivers/platform/x86/asus-wmi.c | 68 ++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 88e3b815f3245..2fc208e91ee5e 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -257,6 +257,7 @@ struct asus_wmi { int tpd_led_wk; struct led_classdev kbd_led; int kbd_led_wk; + int kbd_led_last_level; bool kbd_led_notify; bool kbd_led_avail; bool kbd_led_registered; @@ -310,6 +311,7 @@ struct asus_wmi { u32 kbd_rgb_dev; bool kbd_rgb_state_available; + u32 kbd_rgb_state_flags; bool oobe_state_available; u8 throttle_thermal_policy_mode; @@ -344,6 +346,7 @@ struct asus_wmi { /* Global to allow setting externally without requiring driver data */ static enum asus_ally_mcu_hack use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_INIT; +static struct asus_wmi *asus_hotk; #if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) static void asus_wmi_show_deprecated(void) @@ -1146,6 +1149,8 @@ static ssize_t kbd_rgb_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct led_classdev *led = dev_get_drvdata(dev); + struct asus_wmi *asus = container_of(led, struct asus_wmi, kbd_led); u32 cmd, boot, awake, sleep, keyboard; u32 arg0; int err; @@ -1172,6 +1177,8 @@ static ssize_t kbd_rgb_state_store(struct device *dev, if (err) return err; + asus->kbd_rgb_state_flags = arg0; + return count; } static DEVICE_ATTR_WO(kbd_rgb_state); @@ -1944,8 +1951,11 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value) asus = container_of(led_cdev, struct asus_wmi, kbd_led); - scoped_guard(spinlock_irqsave, &asus_ref.lock) + scoped_guard(spinlock_irqsave, &asus_ref.lock) { asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS); + if (asus->kbd_led_wk > 0) + asus->kbd_led_last_level = asus->kbd_led_wk; + } if (asus->kbd_led_avail) kbd_led_update(asus); @@ -2155,6 +2165,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus) if (asus->kbd_led_avail) { asus->kbd_led_wk = led_val; + asus->kbd_led_last_level = led_val > 0 ? led_val : ASUS_EV_MAX_BRIGHTNESS; if (num_rgb_groups != 0) asus->kbd_led.groups = kbd_rgb_mode_groups; } else { @@ -5178,6 +5189,7 @@ static int asus_wmi_add(struct platform_device *pdev) asus->platform_device = pdev; wdrv->platform_device = pdev; platform_set_drvdata(asus->platform_device, asus); + asus_hotk = asus; if (wdrv->detect_quirks) wdrv->detect_quirks(asus->driver); @@ -5216,6 +5228,14 @@ static int asus_wmi_add(struct platform_device *pdev) asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE) || asus->driver->quirks->kbd_rgb_state_available; + if (asus->kbd_rgb_state_available) { + asus->kbd_rgb_state_flags = + ASUS_WMI_TUF_RGB_STATE_CMD_ID | + FIELD_PREP(TUF_RGB_STATE_BOOT, 1) | + FIELD_PREP(TUF_RGB_STATE_AWAKE, 1) | + FIELD_PREP(TUF_RGB_STATE_SLEEP, 1) | + FIELD_PREP(TUF_RGB_STATE_KEYBOARD, 1); + } if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; @@ -5356,6 +5376,7 @@ static void asus_wmi_remove(struct platform_device *device) struct asus_wmi *asus; asus = platform_get_drvdata(device); + asus_hotk = NULL; if (asus->driver->i8042_filter) i8042_remove_filter(asus->driver->i8042_filter); wmi_remove_notify_handler(asus->driver->event_guid); @@ -5456,17 +5477,54 @@ static int asus_hotk_restore(struct device *device) return 0; } +static void asus_tuf_reassert_sleep_rgb_state(struct asus_wmi *asus) +{ + if (asus && asus->driver->quirks->kbd_rgb_state_available && + asus->kbd_rgb_state_available && + (asus->kbd_rgb_state_flags & TUF_RGB_STATE_SLEEP)) { + int level; + u32 arg0; + + /* + * Re-assert keyboard backlight using the last user-configured + * brightness level (falling back to max brightness) with the + * light-on bit (0x80) set. + */ + level = asus->kbd_led_last_level ? + asus->kbd_led_last_level : ASUS_EV_MAX_BRIGHTNESS; + asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, + 0x80 | (level & 0x7f), NULL); + + /* Re-assert the last user-configured TUF RGB power state */ + arg0 = asus->kbd_rgb_state_flags | + FIELD_PREP(TUF_RGB_STATE_SAVE, 1); + asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, + ASUS_WMI_DEVID_TUF_RGB_STATE, + arg0, 0, NULL); + } +} + static int asus_hotk_prepare(struct device *device) { + struct asus_wmi *asus = dev_get_drvdata(device); + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) { acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, ASUS_USB0_PWR_EC0_CSEE_OFF); msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); } + + asus_tuf_reassert_sleep_rgb_state(asus); + return 0; } #if defined(CONFIG_SUSPEND) +static void asus_s2idle_prepare(void) +{ + asus_tuf_reassert_sleep_rgb_state(asus_hotk); +} + static void asus_ally_s2idle_restore(void) { if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) { @@ -5476,20 +5534,20 @@ static void asus_ally_s2idle_restore(void) } } -/* Use only for Ally devices due to the wake_on_ac */ -static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = { +static struct acpi_s2idle_dev_ops asus_s2idle_dev_ops = { + .prepare = asus_s2idle_prepare, .restore = asus_ally_s2idle_restore, }; static void asus_s2idle_check_register(void) { - if (acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops)) + if (acpi_register_lps0_dev(&asus_s2idle_dev_ops)) pr_warn("failed to register LPS0 sleep handler in asus-wmi\n"); } static void asus_s2idle_check_unregister(void) { - acpi_unregister_lps0_dev(&asus_ally_s2idle_dev_ops); + acpi_unregister_lps0_dev(&asus_s2idle_dev_ops); } #else static void asus_s2idle_check_register(void) {}