From nobody Sun Feb 8 05:40:39 2026 Received: from out-176.mta1.migadu.com (out-176.mta1.migadu.com [95.215.58.176]) (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 C61BE280338 for ; Sun, 28 Dec 2025 20:08:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766952525; cv=none; b=msj7W+3edM3fwJjsEvL9zoF+bxrRZKxzfQUmPArEwAE04dbuybMKnl2uw7qUIi+7nAMIQBKZ5CU/lk8sA6AuQK25MuLBM03ltW79VFzCp5ArZ25OKrpRscn3xccDdhaRZ5kA9k0Bk+zWWNwwrzpeYQIC/2JUwm2MpuIWJmew0NE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766952525; c=relaxed/simple; bh=xNpCwOxwaOG76ZF8+JGwx1bf0TKH5PTav3Z8EYof3Do=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=XIKQqglr8zQ22wABWqIke5NGyc1Cu0yid48q4SlTv40whrhWhsc7EJfZpnaeZiUv5sZCw7HcmzN6kjU4FhyPFG6SHcpV5VM7YjS41i8l+KowZtCK9wUdqmwZLQD+QordSgW7KVyx5Hksd9eyOEDHJaSnzJFvRKetW0dRyLSTb/k= 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=FWNnSSxV; arc=none smtp.client-ip=95.215.58.176 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="FWNnSSxV" 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=1766952519; 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=Bm+U6NjS9pDED/1c1xb6r7X9OG9S7X8uFGKAZ8UMFcg=; b=FWNnSSxVI/n6tg6TLRSkdZIuzbs7oSyFXB1fVLUmsHu/cYK1LZvk2ZY+rZHttiMw4ELSy7 rzvgJvoO10jFa7zFW6dyWASvP9HWKD8uRDRmUwRnXdPa59EapHD9vbpPjSv8xg3iBuAa1O 9GTcUWpwUfSDBkMNvn/O3VuXrP0zmrI= 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" , =?UTF-8?q?Merthan=20Karaka=C5=9F?= , "Krzysztof Kozlowski" , "Denis Benato" , Denis Benato Subject: [PATCH v2 3/3] platform/x86: asus-armoury: add keyboard control firmware attributes Date: Sun, 28 Dec 2025 21:08:02 +0100 Message-ID: <20251228200815.794265-4-denis.benato@linux.dev> In-Reply-To: <20251228200815.794265-1-denis.benato@linux.dev> References: <20251228200815.794265-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 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 | 256 +++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 15 ++ 2 files changed, 271 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asu= s-armoury.c index 9c1a9ad42bc4..1e5bfca77b49 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,169 @@ 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 0xBD | BIT(2) << 8; + + if (status->boot) + kbd_state |=3D BIT(1) << 16; + if (status->awake) + kbd_state |=3D BIT(3) << 16; + if (status->sleep) + kbd_state |=3D BIT(5) << 16; + if (status->shutdown) + kbd_state |=3D BIT(7) << 16; + + 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) +{ + ssize_t err; + bool enable; + struct asus_armoury_kbd_status kbd_status; + + 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 +1043,33 @@ 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) +{ + int err; + u32 kbd_status; + + 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. + */ + memset(kbd_state, 0, sizeof(*kbd_state)); + 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 @@ -926,6 +1129,51 @@ 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)) { + err =3D PTR_ERR(asus_armoury.kbd_state); + pr_err("Failed to get keyboard status: %d\n", err); + goto err_remove_kbd_state; + } + + 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_remove_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_state; + } + + 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_state; + } + + 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_state; + } + + /* + * 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_state; + } + } + for (i =3D 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { if (!armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) continue; @@ -965,6 +1213,14 @@ static int asus_fw_attr_add(void) } if (asus_armoury.gpu_mux_dev_id) sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_= group); +err_remove_kbd_state: + kfree(asus_armoury.kbd_state); + if (asus_armoury.kbd_state) { + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_sleep_att= r_group); + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_boot_attr= _group); + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_awake_att= r_group); + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &kbd_leds_shutdown_= attr_group); + } err_remove_mini_led_group: if (asus_armoury.mini_led_dev_id) sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr= _group); diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/pla= tform_data/x86/asus-wmi.h index 419491d4abca..6667b3b474c2 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -153,6 +153,21 @@ /* TUF laptop RGB power/state */ #define ASUS_WMI_DEVID_TUF_RGB_STATE 0x00100057 =20 +/* + * Flags for TUF RGB state to be used with + * ASUS_WMI_DEVID_TUF_RGB_STATE: + * 0xBD | BIT(1) << 8 | flags + * + * where 0xBD is required for the method call + * to not be discarded, BIT(2)<<8 specified + * this is a command and flags is a combination + * of one or more of the following flags. + */ +#define ASUS_WMI_DEVID_TUF_RGB_STATE_BOOT (BIT(1) << 16u) +#define ASUS_WMI_DEVID_TUF_RGB_STATE_AWAKE (BIT(3) << 16u) +#define ASUS_WMI_DEVID_TUF_RGB_STATE_SLEEP (BIT(5) << 16u) +#define ASUS_WMI_DEVID_TUF_RGB_STATE_SHUTDOWN (BIT(7) << 16u) + /* Bootup sound control */ #define ASUS_WMI_DEVID_BOOT_SOUND 0x00130022 =20 --=20 2.52.0