From nobody Mon Feb 9 01:44:21 2026 Received: from out-177.mta0.migadu.com (out-177.mta0.migadu.com [91.218.175.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7DBB22C3278 for ; Fri, 2 Jan 2026 23:43:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767397437; cv=none; b=AF7+aa3wY/EnmZHkZlVtPgetm0C46tD1VIMjhp6njBz6GwMsCnq7YaLk7KYc3nYpB0owLVkGSyA1OcEO197IKOuUmC+IBzt41bB91cU1k9qQPOyAcWVzXKF9xFNRUNXcjH15TexUQAvFHkMoJYdPSzWeLlMm9SV54Q0vo1LkT30= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767397437; c=relaxed/simple; bh=dZyqR0X89n8KiHLqRhb2/H8+mw5z7cyEfy4PEfDs2YU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=TbwzhXfHEplAskbK2sf6GIbDujWGlsxZYIwpv3jvc0u8wkmfLKfzkSWCSfuTDz/9Cmjt/c4Mi5oRu5//JZZ8euSGoI0+Cw9sAsiT9RY38meLY3FGhiRQTrTb/oqEoMr/ZtjDV3clab+0JnnUlXwANN/9bWDtdMaa/829qlmHwP8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=QYt4/5Y7; arc=none smtp.client-ip=91.218.175.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="QYt4/5Y7" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1767397432; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=HhnRwdCDSPTCFckFhr9hNr7XtyCqrMeKhgfemsuFaNQ=; b=QYt4/5Y7nRW1l2cTk4y1KgBEu0I0bgfwq1KIyp7Wx/J+nYiutytXFTXXpMUKYqjRaVUPWN WyHECtb/pH0SfoIYD3cgZ5BVHaAYaD4SJHVObGUKSbl45Sxu7onQ8g8X3PC8phv4+hGoHx UjrVfciO13hxaYNQ5KNR7XI3BzIu1YI= From: Denis Benato To: linux-kernel@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org, "Hans de Goede" , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= , "Luke D . Jones" , "Mateusz Schyboll" , "Denis Benato" , Denis Benato , =?UTF-8?q?Merthan=20Karaka=C5=9F?= Subject: [PATCH v4 3/3] platform/x86: asus-armoury: add keyboard control firmware attributes Date: Sat, 3 Jan 2026 00:43:44 +0100 Message-ID: <20260102234344.366227-4-denis.benato@linux.dev> In-Reply-To: <20260102234344.366227-1-denis.benato@linux.dev> References: <20260102234344.366227-1-denis.benato@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Implement keyboard status firmware attributes in asus-armoury after deprecating those attribute(s) in asus-wmi to avoid losing the ability to control LEDs status. Signed-off-by: Denis Benato Tested-by: Merthan Karaka=C5=9F --- drivers/platform/x86/asus-armoury.c | 253 +++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 18 ++ 2 files changed, 271 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asu= s-armoury.c index 9c1a9ad42bc4..be803faa1ece 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -76,10 +76,22 @@ struct rog_tunables { u32 nv_tgp; }; =20 +struct asus_armoury_kbd_status { + bool boot; + bool awake; + bool sleep; + bool shutdown; +}; + struct asus_armoury_priv { struct device *fw_attr_dev; struct kset *fw_attr_kset; =20 + struct mutex keyboard_mutex; + + /* Current TUF keyboard RGB state tracking */ + struct asus_armoury_kbd_status *kbd_state; + /* * Mutex to protect eGPU activation/deactivation * sequences and dGPU connection status: @@ -97,6 +109,7 @@ struct asus_armoury_priv { =20 static struct asus_armoury_priv asus_armoury =3D { .egpu_mutex =3D __MUTEX_INITIALIZER(asus_armoury.egpu_mutex), + .keyboard_mutex =3D __MUTEX_INITIALIZER(asus_armoury.keyboard_mutex), }; =20 struct fw_attrs_group { @@ -433,6 +446,164 @@ static ssize_t mini_led_mode_possible_values_show(str= uct kobject *kobj, } ASUS_ATTR_GROUP_ENUM(mini_led_mode, "mini_led_mode", "Set the mini-LED bac= klight mode"); =20 +/* Keyboard power management *********************************************= *****/ + +static int armoury_kbd_state(struct kobj_attribute *attr, + const struct asus_armoury_kbd_status *status) +{ + u32 kbd_state =3D ASUS_WMI_DEVID_TUF_RGB_STATE_EN | ASUS_WMI_DEVID_TUF_RG= B_STATE_CMD; + + kbd_state |=3D FIELD_PREP(ASUS_WMI_DEVID_TUF_RGB_STATE_BOOT, status->boot= ? 1 : 0); + kbd_state |=3D FIELD_PREP(ASUS_WMI_DEVID_TUF_RGB_STATE_AWAKE, status->awa= ke ? 1 : 0); + kbd_state |=3D FIELD_PREP(ASUS_WMI_DEVID_TUF_RGB_STATE_SLEEP, status->sle= ep ? 1 : 0); + kbd_state |=3D FIELD_PREP(ASUS_WMI_DEVID_TUF_RGB_STATE_SHUTDOWN, status->= shutdown ? 1 : 0); + + return armoury_set_devstate(attr, kbd_state, NULL, ASUS_WMI_DEVID_TUF_RGB= _STATE); +} + +enum asus_armoury_kbd_state_field { + ASUS_ARMOURY_KBD_STATE_BOOT, + ASUS_ARMOURY_KBD_STATE_AWAKE, + ASUS_ARMOURY_KBD_STATE_SLEEP, + ASUS_ARMOURY_KBD_STATE_SHUTDOWN, +}; + +static ssize_t armoury_kbd_state_write(struct kobject *kobj, struct kobj_a= ttribute *attr, + const char *buf, size_t count, + enum asus_armoury_kbd_state_field field) +{ + struct asus_armoury_kbd_status kbd_status; + bool enable; + ssize_t err; + + err =3D kstrtobool(buf, &enable); + if (err) + return err; + + scoped_guard(mutex, &asus_armoury.keyboard_mutex) { + memcpy(&kbd_status, asus_armoury.kbd_state, sizeof(kbd_status)); + + switch (field) { + case ASUS_ARMOURY_KBD_STATE_BOOT: + kbd_status.boot =3D enable; + break; + case ASUS_ARMOURY_KBD_STATE_AWAKE: + kbd_status.awake =3D enable; + break; + case ASUS_ARMOURY_KBD_STATE_SLEEP: + kbd_status.sleep =3D enable; + break; + case ASUS_ARMOURY_KBD_STATE_SHUTDOWN: + kbd_status.shutdown =3D enable; + break; + default: + return -EINVAL; + } + + err =3D armoury_kbd_state(attr, &kbd_status); + if (err) + return err; + + memcpy(asus_armoury.kbd_state, &kbd_status, sizeof(kbd_status)); + } + + sysfs_notify(kobj, NULL, attr->attr.name); + + return count; +} + +static ssize_t armoury_kbd_state_read(struct kobject *kobj, struct kobj_at= tribute *attr, + char *buf, enum asus_armoury_kbd_state_field field) +{ + bool *field_ptr, field_enabled; + + switch (field) { + case ASUS_ARMOURY_KBD_STATE_AWAKE: + field_ptr =3D &asus_armoury.kbd_state->awake; + break; + case ASUS_ARMOURY_KBD_STATE_SLEEP: + field_ptr =3D &asus_armoury.kbd_state->sleep; + break; + case ASUS_ARMOURY_KBD_STATE_BOOT: + field_ptr =3D &asus_armoury.kbd_state->boot; + break; + case ASUS_ARMOURY_KBD_STATE_SHUTDOWN: + field_ptr =3D &asus_armoury.kbd_state->shutdown; + break; + default: + return -EINVAL; + } + + scoped_guard(mutex, &asus_armoury.keyboard_mutex) + field_enabled =3D *field_ptr; + + return sysfs_emit(buf, field_enabled ? "1\n" : "0\n"); +} + +static ssize_t kbd_leds_sleep_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return armoury_kbd_state_write(kobj, attr, buf, count, ASUS_ARMOURY_KBD_S= TATE_SLEEP); +} + +static ssize_t kbd_leds_sleep_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return armoury_kbd_state_read(kobj, attr, buf, ASUS_ARMOURY_KBD_STATE_SLE= EP); +} + +ASUS_ATTR_GROUP_BOOL(kbd_leds_sleep, "kbd_leds_sleep", + "Keyboard backlight while system is sleeping"); + +static ssize_t kbd_leds_boot_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return armoury_kbd_state_write(kobj, attr, buf, count, ASUS_ARMOURY_KBD_S= TATE_BOOT); +} + +static ssize_t kbd_leds_boot_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return armoury_kbd_state_read(kobj, attr, buf, ASUS_ARMOURY_KBD_STATE_BOO= T); +} + +ASUS_ATTR_GROUP_BOOL(kbd_leds_boot, "kbd_leds_boot", + "Keyboard backlight while system is booting"); + +static ssize_t kbd_leds_awake_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return armoury_kbd_state_write(kobj, attr, buf, count, ASUS_ARMOURY_KBD_S= TATE_AWAKE); +} + +static ssize_t kbd_leds_awake_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return armoury_kbd_state_read(kobj, attr, buf, ASUS_ARMOURY_KBD_STATE_AWA= KE); +} + +ASUS_ATTR_GROUP_BOOL(kbd_leds_awake, "kbd_leds_awake", + "Keyboard backlight while system is awake"); + +static ssize_t kbd_leds_shutdown_current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return armoury_kbd_state_write(kobj, attr, buf, count, ASUS_ARMOURY_KBD_S= TATE_SHUTDOWN); +} + +static ssize_t kbd_leds_shutdown_current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return armoury_kbd_state_read(kobj, attr, buf, ASUS_ARMOURY_KBD_STATE_SHU= TDOWN); +} + +ASUS_ATTR_GROUP_BOOL(kbd_leds_shutdown, "kbd_leds_shutdown", + "Keyboard backlight while system is shutdown"); + static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) @@ -867,6 +1038,32 @@ static bool has_valid_limit(const char *name, const s= truct power_limits *limits) return limit_value > 0; } =20 +static struct asus_armoury_kbd_status *asus_init_kbd_state(void) +{ + u32 kbd_status; + int err; + + err =3D armoury_get_devstate(NULL, &kbd_status, ASUS_WMI_DEVID_TUF_RGB_ST= ATE); + if (err) { + pr_debug("ACPI does not support keyboard power control: %d\n", err); + return ERR_PTR(-ENODEV); + } + + struct asus_armoury_kbd_status *kbd_state __free(kfree) =3D + kzalloc(sizeof(*kbd_state), GFP_KERNEL); + if (!kbd_state) + return ERR_PTR(-ENODEV); + + /* + * By default leds are off for all states (to spare power) + * except for when laptop is awake, where leds color and + * brightness are controllable by userspace. + */ + kbd_state->awake =3D true; + + return_ptr(kbd_state); +} + static int asus_fw_attr_add(void) { const struct rog_tunables *const ac_rog_tunables =3D @@ -955,8 +1152,64 @@ static int asus_fw_attr_add(void) } } =20 + asus_armoury.kbd_state =3D NULL; + if (armoury_has_devstate(ASUS_WMI_DEVID_TUF_RGB_STATE)) { + asus_armoury.kbd_state =3D asus_init_kbd_state(); + if (IS_ERR(asus_armoury.kbd_state)) { + asus_armoury.kbd_state =3D NULL; + err =3D PTR_ERR(asus_armoury.kbd_state); + pr_err("Failed to get keyboard status: %d\n", err); + goto err_remove_groups; + } + + err =3D sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_s= leep_attr_group); + if (err) { + pr_err("Failed to create sysfs-group for keyboard backlight sleep state= : %d\n", err); + goto err_kbd_state; + } + + err =3D sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_b= oot_attr_group); + if (err) { + pr_err("Failed to create sysfs-group for keyboard backlight boot state:= %d\n", err); + goto err_remove_kbd_sleep_attr; + } + + err =3D sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_a= wake_attr_group); + if (err) { + pr_err("Failed to create sysfs-group for keyboard backlight awake state= : %d\n", err); + goto err_remove_kbd_boot_attr; + } + + err =3D sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_s= hutdown_attr_group); + if (err) { + pr_err("Failed to create sysfs-group for keyboard backlight shutdown st= ate: %d\n", err); + goto err_remove_kbd_awake_attr; + } + + /* + * The attribute is write-only and for the state to be coherent + * a default state has to written: userspace is expected to + * modify it based on user preference. + */ + err =3D armoury_kbd_state(&attr_kbd_leds_awake_current_value, asus_armou= ry.kbd_state); + if (err) { + pr_err("Failed to initialize keyboard backlight states: %d\n", err); + goto err_remove_kbd_shutdown_attr; + } + } + return 0; =20 +err_remove_kbd_shutdown_attr: + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_shutdown_a= ttr_group); +err_remove_kbd_awake_attr: + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_awake_attr= _group); +err_remove_kbd_boot_attr: + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_boot_attr_= group); +err_remove_kbd_sleep_attr: + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_sleep_attr= _group); +err_kbd_state: + kfree(asus_armoury.kbd_state); err_remove_groups: while (i--) { if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/pla= tform_data/x86/asus-wmi.h index 419491d4abca..275933e0d79e 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -2,6 +2,7 @@ #ifndef __PLATFORM_DATA_X86_ASUS_WMI_H #define __PLATFORM_DATA_X86_ASUS_WMI_H =20 +#include #include #include =20 @@ -153,6 +154,23 @@ /* TUF laptop RGB power/state */ #define ASUS_WMI_DEVID_TUF_RGB_STATE 0x00100057 =20 +#define ASUS_WMI_DEVID_TUF_RGB_STATE_EN 0xBD + +#define ASUS_WMI_DEVID_TUF_RGB_STATE_CMD BIT(10) + +/* + * Flags for TUF RGB state to be used with ASUS_WMI_DEVID_TUF_RGB_STATE: + * flags | ASUS_WMI_DEVID_TUF_RGB_STATE_CMD | ASUS_WMI_DEVID_TUF_RGB_STATE= _EN + * + * where ASUS_WMI_DEVID_TUF_RGB_STATE_EN is required for the method call + * to not be discarded, ASUS_WMI_DEVID_TUF_RGB_STATE_EN specifies this is + * a command and flags is a combination of one or more of the following fl= ags. + */ +#define ASUS_WMI_DEVID_TUF_RGB_STATE_BOOT GENMASK(17, 17) +#define ASUS_WMI_DEVID_TUF_RGB_STATE_AWAKE GENMASK(19, 19) +#define ASUS_WMI_DEVID_TUF_RGB_STATE_SLEEP GENMASK(21, 21) +#define ASUS_WMI_DEVID_TUF_RGB_STATE_SHUTDOWN GENMASK(23, 23) + /* Bootup sound control */ #define ASUS_WMI_DEVID_BOOT_SOUND 0x00130022 =20 --=20 2.52.0