linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL
@ 2025-03-22 10:27 Antheas Kapenekakis
  2025-03-22 10:27 ` [PATCH v3 01/10] HID: asus: refactor init sequence per spec Antheas Kapenekakis
                   ` (9 more replies)
  0 siblings, 10 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:27 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

This is a three part series which does the following:
  - Clean init sequence, fix the keyboard of the Z13 (touchpad,fan button)
  - Unifies backlight handling to happen under asus-wmi so that all Aura
    devices have synced brightness controls and the backlight button works
    properly when it is on a USB laptop keyboard instead of one w/ WMI.
  - Adds RGB support to hid-asus, solid colors only, and for the ROG Ally
    units and the Asus Z13 2025 first.

In V3, RGB controls are pretty stable, but as per Luke there needs to be
a discussion about how it should be merged, so the last two patches can
be considered as separate. The Z13 Folio has a unique pid, and so do the
ally units, so RGB can be safely enabled on those. For the rest, there
are cases where the same pid is used in laptops that have a white only
keyboard.

For more context, see cover letter of V1.

---
V2: https://lore.kernel.org/all/20250320220924.5023-1-lkml@antheas.dev/
V1: https://lore.kernel.org/all/20250319191320.10092-1-lkml@antheas.dev/

Changes since V2:
  - Check lazy init succeds in asus-wmi before setting register variable
  - make explicit check in asus_hid_register_listener for listener existing
    to avoid re-init
  - rename asus_brt to asus_hid in most places and harmonize everything
  - switch to a spinlock instead of a mutex to avoid kernel ooops
  - fixup hid device quirks to avoid multiple RGB devices while still exposing
    all input vendor devices. This includes moving rgb init to probe
    instead of the input_configured callbacks.
  - Remove fan key (during retest it appears to be 0xae that is already
    supported by hid-asus)
  - Never unregister asus::kbd_backlight while asus-wmi is active, as that
  - removes fds from userspace and breaks backlight functionality. All
  - current mainline drivers do not support backlight hotplugging, so most
    userspace software (e.g., KDE, UPower) is built with that assumption.
    For the Ally, since it disconnects its controller during sleep, this
    caused the backlight slider to not work in KDE.

Changes since V1:
  - Add basic RGB support on hid-asus, (Z13/Ally) tested in KDE/Z13
  - Fix ifdef else having an invalid signature (reported by kernel robot)
  - Restore input arguments to init and keyboard function so they can
    be re-used for RGB controls.
  - Remove Z13 delay (it did not work to fix the touchpad) and replace it
    with a HID_GROUP_GENERIC quirk to allow hid-multitouch to load. Squash
    keyboard rename into it.
  - Unregister brightness listener before removing work queue to avoid
    a race condition causing corruption
  - Remove spurious mutex unlock in asus_brt_event
  - Place mutex lock in kbd_led_set after LED_UNREGISTERING check to avoid
    relocking the mutex and causing a deadlock when unregistering leds
  - Add extra check during unregistering to avoid calling unregister when
    no led device is registered.
  - Temporarily HID_QUIRK_INPUT_PER_APP from the ROG endpoint as it causes
    the driver to create 4 RGB handlers per device. I also suspect some
    extra events sneak through (KDE had the @@@@@@).

Antheas Kapenekakis (10):
  HID: asus: refactor init sequence per spec
  HID: asus: prevent binding to all HID devices on ROG
  HID: Asus: add Z13 folio to generic group for multitouch to work
  platform/x86: asus-wmi: Add support for multiple kbd RGB handlers
  HID: asus: listen to the asus-wmi brightness device instead of
    creating one
  platform/x86: asus-wmi: remove unused keyboard backlight quirk
  platform/x86: asus-wmi: add keyboard brightness event handler
  HID: asus: add support for the asus-wmi brightness handler
  HID: asus: add basic RGB support
  HID: asus: add RGB support to the ROG Ally units

 drivers/hid/hid-asus.c                     | 354 +++++++++++++++------
 drivers/hid/hid-ids.h                      |   2 +-
 drivers/platform/x86/asus-wmi.c            | 152 ++++++++-
 include/linux/platform_data/x86/asus-wmi.h |  67 ++--
 4 files changed, 423 insertions(+), 152 deletions(-)


base-commit: 4701f33a10702d5fc577c32434eb62adde0a1ae1
-- 
2.48.1


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

* [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
@ 2025-03-22 10:27 ` Antheas Kapenekakis
  2025-03-22 22:01   ` Luke D. Jones
  2025-03-22 10:27 ` [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG Antheas Kapenekakis
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:27 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

Currently, asus_kbd_init() uses a reverse engineered init sequence
from Windows, which contains the handshakes from multiple programs.
Keep the main one, which is 0x5a (meant for brightness drivers).

In addition, perform a get_response and check if the response is the
same. To avoid regressions, print an error if the response does not
match instead of rejecting device.

Then, refactor asus_kbd_get_functions() to use the same ID it is called
with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
in the future.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
 1 file changed, 46 insertions(+), 36 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 46e3e42f9eb5f..8d4df1b6f143b 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define FEATURE_REPORT_ID 0x0d
 #define INPUT_REPORT_ID 0x5d
 #define FEATURE_KBD_REPORT_ID 0x5a
-#define FEATURE_KBD_REPORT_SIZE 16
+#define FEATURE_KBD_REPORT_SIZE 64
 #define FEATURE_KBD_LED_REPORT_ID1 0x5d
 #define FEATURE_KBD_LED_REPORT_ID2 0x5e
 
@@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
 
 static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
 {
-	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
-		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
+	/*
+	 * Asus handshake identifying us as a driver (0x5A)
+	 * 0x5A then ASCII for "ASUS Tech.Inc."
+	 * 0x5D is for userspace Windows applications.
+	 *
+	 * The handshake is first sent as a set_report, then retrieved
+	 * from a get_report to verify the response.
+	 */
+	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
+		0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
+	u8 *readbuf;
 	int ret;
 
 	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
-	if (ret < 0)
-		hid_err(hdev, "Asus failed to send init command: %d\n", ret);
+	if (ret < 0) {
+		hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
+		return ret;
+	}
 
+	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
+	if (!readbuf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, report_id, readbuf,
+				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret < 0) {
+		hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
+	} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
+		hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
+			FEATURE_KBD_REPORT_SIZE, readbuf);
+		// Do not return error if handshake is wrong to avoid regressions
+	}
+
+	kfree(readbuf);
 	return ret;
 }
 
@@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
 	if (!readbuf)
 		return -ENOMEM;
 
-	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
+	ret = hid_hw_raw_request(hdev, report_id, readbuf,
 				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
 				 HID_REQ_GET_REPORT);
 	if (ret < 0) {
@@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
 	unsigned char kbd_func;
 	int ret;
 
-	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
-		/* Initialize keyboard */
-		ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
-		if (ret < 0)
-			return ret;
-
-		/* The LED endpoint is initialised in two HID */
-		ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
-		if (ret < 0)
-			return ret;
-
-		ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
-		if (ret < 0)
-			return ret;
+	ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
+	if (ret < 0)
+		return ret;
 
-		if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
-			ret = asus_kbd_disable_oobe(hdev);
-			if (ret < 0)
-				return ret;
-		}
-	} else {
-		/* Initialize keyboard */
-		ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
-		if (ret < 0)
-			return ret;
+	/* Get keyboard functions */
+	ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
+	if (ret < 0)
+		return ret;
 
-		/* Get keyboard functions */
-		ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
+	if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
+		ret = asus_kbd_disable_oobe(hdev);
 		if (ret < 0)
 			return ret;
-
-		/* Check for backlight support */
-		if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
-			return -ENODEV;
 	}
 
+	/* Check for backlight support */
+	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
+		return -ENODEV;
+
 	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
 					      sizeof(struct asus_kbd_leds),
 					      GFP_KERNEL);
-- 
2.48.1


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

* [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
  2025-03-22 10:27 ` [PATCH v3 01/10] HID: asus: refactor init sequence per spec Antheas Kapenekakis
@ 2025-03-22 10:27 ` Antheas Kapenekakis
  2025-03-22 23:21   ` Luke D. Jones
  2025-03-22 10:27 ` [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work Antheas Kapenekakis
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:27 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

ROG keyboards are HID compliant and only care about the endpoint that
produces vendor events (e.g., fan mode) and has the keyboard backlight.

Therefore, handle all of the endpoints of ROG keyboards as compliant,
by adding HID_QUIRK_INPUT_PER_APP and, for devices other than the vendor
one, by adding QUIRK_HANDLE_GENERIC to stop mutating them.

Due to HID_QUIRK_INPUT_PER_APP, rgb register is moved into probe, as
the input_* functions are called multiple times (4 for the Z13).

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 69 ++++++++++++++++++++++++++++++++----------
 1 file changed, 53 insertions(+), 16 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 8d4df1b6f143b..96461321c191c 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -84,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_MEDION_E1239T		BIT(10)
 #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_HANDLE_GENERIC		BIT(13)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -120,7 +121,6 @@ struct asus_drvdata {
 	struct input_dev *tp_kbd_input;
 	struct asus_kbd_leds *kbd_backlight;
 	const struct asus_touchpad_info *tp;
-	bool enable_backlight;
 	struct power_supply *battery;
 	struct power_supply_desc battery_desc;
 	int battery_capacity;
@@ -326,6 +326,10 @@ static int asus_raw_event(struct hid_device *hdev,
 {
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
 
+	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
+		/* NOOP on generic HID devices to avoid side effects. */
+		return 0;
+
 	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
 		return asus_report_battery(drvdata, data, size);
 
@@ -774,6 +778,10 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
 	struct input_dev *input = hi->input;
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
 
+	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
+		/* NOOP on generic HID devices to avoid side effects. */
+		return 0;
+
 	/* T100CHI uses MULTI_INPUT, bind the touchpad to the mouse hid_input */
 	if (drvdata->quirks & QUIRK_T100CHI &&
 	    hi->report->id != T100CHI_MOUSE_REPORT_ID)
@@ -827,11 +835,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
 
 	drvdata->input = input;
 
-	if (drvdata->enable_backlight &&
-	    !asus_kbd_wmi_led_control_present(hdev) &&
-	    asus_kbd_register_leds(hdev))
-		hid_warn(hdev, "Failed to initialize backlight.\n");
-
 	return 0;
 }
 
@@ -851,6 +854,10 @@ static int asus_input_mapping(struct hid_device *hdev,
 		return -1;
 	}
 
+	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
+		/* NOOP on generic HID devices to avoid side effects. */
+		return 0;
+
 	/*
 	 * Ignore a bunch of bogus collections in the T100CHI descriptor.
 	 * This avoids a bunch of non-functional hid_input devices getting
@@ -901,15 +908,6 @@ static int asus_input_mapping(struct hid_device *hdev,
 			return -1;
 		}
 
-		/*
-		 * Check and enable backlight only on devices with UsagePage ==
-		 * 0xff31 to avoid initializing the keyboard firmware multiple
-		 * times on devices with multiple HID descriptors but same
-		 * PID/VID.
-		 */
-		if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
-			drvdata->enable_backlight = true;
-
 		set_bit(EV_REP, hi->input->evbit);
 		return 1;
 	}
@@ -1026,8 +1024,10 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
 
 static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
-	int ret;
+	struct hid_report_enum *rep_enum;
 	struct asus_drvdata *drvdata;
+	struct hid_report *rep;
+	int ret, is_vendor = 0;
 
 	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
 	if (drvdata == NULL) {
@@ -1111,12 +1111,45 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		return ret;
 	}
 
+	/*
+	 * Check for the vendor interface (0xff31) to init the RGB.
+	 * and handle generic devices properly.
+	 */
+	rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
+	list_for_each_entry(rep, &rep_enum->report_list, list) {
+		if ((rep->application & HID_USAGE_PAGE) == 0xff310000)
+			is_vendor = true;
+	}
+
+	/*
+	 * For ROG keyboards, make them hid compliant by
+	 * creating one input per application. For interfaces other than
+	 * the vendor one, disable hid-asus handlers.
+	 */
+	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
+		if (!is_vendor)
+			drvdata->quirks |= QUIRK_HANDLE_GENERIC;
+		hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
+	}
+
 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
 	if (ret) {
 		hid_err(hdev, "Asus hw start failed: %d\n", ret);
 		return ret;
 	}
 
+	if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
+	    !asus_kbd_wmi_led_control_present(hdev) &&
+	    asus_kbd_register_leds(hdev))
+		hid_warn(hdev, "Failed to initialize backlight.\n");
+
+	/*
+	 * For ROG keyboards, skip rename for consistency and
+	 * ->input check as some devices do not have inputs.
+	 */
+	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
+		return 0;
+
 	if (!drvdata->input) {
 		hid_err(hdev, "Asus input not registered\n");
 		ret = -ENOMEM;
@@ -1167,6 +1200,10 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 {
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
 
+	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
+		/* NOOP on generic HID devices to avoid side effects. */
+		return rdesc;
+
 	if (drvdata->quirks & QUIRK_FIX_NOTEBOOK_REPORT &&
 			*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x65) {
 		hid_info(hdev, "Fixing up Asus notebook report descriptor\n");
-- 
2.48.1


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

* [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
  2025-03-22 10:27 ` [PATCH v3 01/10] HID: asus: refactor init sequence per spec Antheas Kapenekakis
  2025-03-22 10:27 ` [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG Antheas Kapenekakis
@ 2025-03-22 10:27 ` Antheas Kapenekakis
  2025-03-22 23:30   ` Luke D. Jones
  2025-03-22 10:27 ` [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers Antheas Kapenekakis
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:27 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

The Asus Z13 folio has a multitouch touchpad that needs to bind
to the hid-multitouch driver in order to work properly. So bind
it to the HID_GROUP_GENERIC group to release the touchpad and
move it to the bottom so that the comment applies to it.

While at it, change the generic KEYBOARD3 name to Z13_FOLIO.

Reviewed-by: Luke D. Jones <luke@ljones.dev>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 6 +++---
 drivers/hid/hid-ids.h  | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 96461321c191c..e97fb76eda619 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1319,9 +1319,6 @@ static const struct hid_device_id asus_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
-	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
@@ -1351,6 +1348,9 @@ static const struct hid_device_id asus_devices[] = {
 	 * Note bind to the HID_GROUP_GENERIC group, so that we only bind to the keyboard
 	 * part, while letting hid-multitouch.c handle the touchpad.
 	 */
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
 	{ }
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 7e400624908e3..b1fe7582324ff 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -209,7 +209,7 @@
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2	0x19b6
-#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3	0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO		0x1a30
 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR		0x18c6
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY		0x1abe
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X		0x1b4c
-- 
2.48.1


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

* [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (2 preceding siblings ...)
  2025-03-22 10:27 ` [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work Antheas Kapenekakis
@ 2025-03-22 10:27 ` Antheas Kapenekakis
  2025-03-23  0:02   ` Luke D. Jones
  2025-03-24 11:31   ` Hans de Goede
  2025-03-22 10:27 ` [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one Antheas Kapenekakis
                   ` (5 subsequent siblings)
  9 siblings, 2 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:27 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

Some devices, such as the Z13 have multiple AURA devices connected
to them by USB. In addition, they might have a WMI interface for
RGB. In Windows, Armoury Crate exposes a unified brightness slider
for all of them, with 3 brightness levels.

Therefore, to be synergistic in Linux, and support existing tooling
such as UPower, allow adding listeners to the RGB device of the WMI
interface. If WMI does not exist, lazy initialize the interface.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
 include/linux/platform_data/x86/asus-wmi.h |  16 +++
 2 files changed, 117 insertions(+), 12 deletions(-)

diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 38ef778e8c19b..95ef9b1d321bb 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -254,6 +254,8 @@ struct asus_wmi {
 	int tpd_led_wk;
 	struct led_classdev kbd_led;
 	int kbd_led_wk;
+	bool kbd_led_avail;
+	bool kbd_led_registered;
 	struct led_classdev lightbar_led;
 	int lightbar_led_wk;
 	struct led_classdev micmute_led;
@@ -1487,6 +1489,53 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
 
 /* LEDs ***********************************************************************/
 
+struct asus_hid_ref {
+	struct list_head listeners;
+	struct asus_wmi *asus;
+	spinlock_t lock;
+};
+
+struct asus_hid_ref asus_ref = {
+	.listeners = LIST_HEAD_INIT(asus_ref.listeners),
+	.asus = NULL,
+	.lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
+};
+
+int asus_hid_register_listener(struct asus_hid_listener *bdev)
+{
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	list_add_tail(&bdev->list, &asus_ref.listeners);
+	if (asus_ref.asus) {
+		if (asus_ref.asus->kbd_led_registered && asus_ref.asus->kbd_led_wk >= 0)
+			bdev->brightness_set(bdev, asus_ref.asus->kbd_led_wk);
+
+		if (!asus_ref.asus->kbd_led_registered) {
+			ret = led_classdev_register(
+				&asus_ref.asus->platform_device->dev,
+				&asus_ref.asus->kbd_led);
+			if (!ret)
+				asus_ref.asus->kbd_led_registered = true;
+		}
+	}
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(asus_hid_register_listener);
+
+void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	list_del(&bdev->list);
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
+}
+EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
+
 /*
  * These functions actually update the LED's, and are called from a
  * workqueue. By doing this as separate work rather than when the LED
@@ -1566,6 +1615,7 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
 
 static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
 {
+	struct asus_hid_listener *listener;
 	struct asus_wmi *asus;
 	int max_level;
 
@@ -1573,25 +1623,39 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
 	max_level = asus->kbd_led.max_brightness;
 
 	asus->kbd_led_wk = clamp_val(value, 0, max_level);
-	kbd_led_update(asus);
+
+	if (asus->kbd_led_avail)
+		kbd_led_update(asus);
+
+	list_for_each_entry(listener, &asus_ref.listeners, list)
+		listener->brightness_set(listener, asus->kbd_led_wk);
 }
 
 static void kbd_led_set(struct led_classdev *led_cdev,
 			enum led_brightness value)
 {
+	unsigned long flags;
+
 	/* Prevent disabling keyboard backlight on module unregister */
 	if (led_cdev->flags & LED_UNREGISTERING)
 		return;
 
+	spin_lock_irqsave(&asus_ref.lock, flags);
 	do_kbd_led_set(led_cdev, value);
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
 }
 
 static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
 {
-	struct led_classdev *led_cdev = &asus->kbd_led;
+	struct led_classdev *led_cdev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	led_cdev = &asus->kbd_led;
 
 	do_kbd_led_set(led_cdev, value);
 	led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
 }
 
 static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
@@ -1601,6 +1665,9 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
 
 	asus = container_of(led_cdev, struct asus_wmi, kbd_led);
 
+	if (!asus->kbd_led_avail)
+		return asus->kbd_led_wk;
+
 	retval = kbd_led_read(asus, &value, NULL);
 	if (retval < 0)
 		return retval;
@@ -1716,7 +1783,14 @@ static int camera_led_set(struct led_classdev *led_cdev,
 
 static void asus_wmi_led_exit(struct asus_wmi *asus)
 {
-	led_classdev_unregister(&asus->kbd_led);
+	unsigned long flags;
+
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	asus_ref.asus = NULL;
+	if (asus->kbd_led_registered)
+		led_classdev_unregister(&asus->kbd_led);
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
+
 	led_classdev_unregister(&asus->tpd_led);
 	led_classdev_unregister(&asus->wlan_led);
 	led_classdev_unregister(&asus->lightbar_led);
@@ -1730,6 +1804,8 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
 static int asus_wmi_led_init(struct asus_wmi *asus)
 {
 	int rv = 0, num_rgb_groups = 0, led_val;
+	unsigned long flags;
+	bool has_listeners;
 
 	if (asus->kbd_rgb_dev)
 		kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
@@ -1754,24 +1830,37 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
 			goto error;
 	}
 
-	if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
-		pr_info("using asus-wmi for asus::kbd_backlight\n");
+	asus->kbd_led.name = "asus::kbd_backlight";
+	asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
+	asus->kbd_led.brightness_set = kbd_led_set;
+	asus->kbd_led.brightness_get = kbd_led_get;
+	asus->kbd_led.max_brightness = 3;
+	asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
+
+	if (asus->kbd_led_avail)
 		asus->kbd_led_wk = led_val;
-		asus->kbd_led.name = "asus::kbd_backlight";
-		asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
-		asus->kbd_led.brightness_set = kbd_led_set;
-		asus->kbd_led.brightness_get = kbd_led_get;
-		asus->kbd_led.max_brightness = 3;
+	else
+		asus->kbd_led_wk = -1;
+
+	if (asus->kbd_led_avail && num_rgb_groups != 0)
+		asus->kbd_led.groups = kbd_rgb_mode_groups;
 
-		if (num_rgb_groups != 0)
-			asus->kbd_led.groups = kbd_rgb_mode_groups;
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	has_listeners = !list_empty(&asus_ref.listeners);
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
 
+	if (asus->kbd_led_avail || has_listeners) {
 		rv = led_classdev_register(&asus->platform_device->dev,
 					   &asus->kbd_led);
 		if (rv)
 			goto error;
+		asus->kbd_led_registered = true;
 	}
 
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	asus_ref.asus = asus;
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
+
 	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
 			&& (asus->driver->quirks->wapf > 0)) {
 		INIT_WORK(&asus->wlan_led_work, wlan_led_update);
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index 783e2a336861b..ec8b0c585a63f 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -157,14 +157,30 @@
 #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK	0x0000FF00
 #define ASUS_WMI_DSTS_LIGHTBAR_MASK	0x0000000F
 
+struct asus_hid_listener {
+	struct list_head list;
+	void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
+};
+
 #if IS_REACHABLE(CONFIG_ASUS_WMI)
 int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
+
+int asus_hid_register_listener(struct asus_hid_listener *cdev);
+void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
 #else
 static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
 					   u32 *retval)
 {
 	return -ENODEV;
 }
+
+static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
+{
+	return -ENODEV;
+}
+static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
+{
+}
 #endif
 
 /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
-- 
2.48.1


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

* [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (3 preceding siblings ...)
  2025-03-22 10:27 ` [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers Antheas Kapenekakis
@ 2025-03-22 10:27 ` Antheas Kapenekakis
  2025-03-23  6:40   ` Luke D. Jones
  2025-03-22 10:28 ` [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk Antheas Kapenekakis
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:27 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

Some ROG laptops expose multiple interfaces for controlling the
keyboard/RGB brightness. This creates a name conflict under
asus::kbd_brightness, where the second device ends up being
named asus::kbd_brightness_1 and they are both broken.

Therefore, register a listener to the asus-wmi brightness device
instead of creating a new one.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 65 +++++++-----------------------------------
 1 file changed, 11 insertions(+), 54 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index e97fb76eda619..c40b5c14c797f 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -96,7 +96,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define TRKID_SGN       ((TRKID_MAX + 1) >> 1)
 
 struct asus_kbd_leds {
-	struct led_classdev cdev;
+	struct asus_hid_listener listener;
 	struct hid_device *hdev;
 	struct work_struct work;
 	unsigned int brightness;
@@ -493,11 +493,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
 	spin_unlock_irqrestore(&led->lock, flags);
 }
 
-static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
-				   enum led_brightness brightness)
+static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
+				   int brightness)
 {
-	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
-						 cdev);
+	struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
+						 listener);
 	unsigned long flags;
 
 	spin_lock_irqsave(&led->lock, flags);
@@ -507,20 +507,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
 	asus_schedule_work(led);
 }
 
-static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
-{
-	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
-						 cdev);
-	enum led_brightness brightness;
-	unsigned long flags;
-
-	spin_lock_irqsave(&led->lock, flags);
-	brightness = led->brightness;
-	spin_unlock_irqrestore(&led->lock, flags);
-
-	return brightness;
-}
-
 static void asus_kbd_backlight_work(struct work_struct *work)
 {
 	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
@@ -537,34 +523,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
 		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
 }
 
-/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
- * precedence. We only activate HID-based backlight control when the
- * WMI control is not available.
- */
-static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
-{
-	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
-	u32 value;
-	int ret;
-
-	if (!IS_ENABLED(CONFIG_ASUS_WMI))
-		return false;
-
-	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
-			dmi_check_system(asus_use_hid_led_dmi_ids)) {
-		hid_info(hdev, "using HID for asus::kbd_backlight\n");
-		return false;
-	}
-
-	ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
-				       ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
-	hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
-	if (ret)
-		return false;
-
-	return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
-}
-
 static int asus_kbd_register_leds(struct hid_device *hdev)
 {
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
@@ -599,14 +557,12 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
 	drvdata->kbd_backlight->removed = false;
 	drvdata->kbd_backlight->brightness = 0;
 	drvdata->kbd_backlight->hdev = hdev;
-	drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
-	drvdata->kbd_backlight->cdev.max_brightness = 3;
-	drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
-	drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
+	drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
 	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
 	spin_lock_init(&drvdata->kbd_backlight->lock);
 
-	ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
+	ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
+
 	if (ret < 0) {
 		/* No need to have this still around */
 		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
@@ -1000,7 +956,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
 
 	if (drvdata->kbd_backlight) {
 		const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
-				drvdata->kbd_backlight->cdev.brightness };
+				drvdata->kbd_backlight->brightness };
 		ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
 		if (ret < 0) {
 			hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
@@ -1139,7 +1095,6 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	}
 
 	if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
-	    !asus_kbd_wmi_led_control_present(hdev) &&
 	    asus_kbd_register_leds(hdev))
 		hid_warn(hdev, "Failed to initialize backlight.\n");
 
@@ -1180,6 +1135,8 @@ static void asus_remove(struct hid_device *hdev)
 	unsigned long flags;
 
 	if (drvdata->kbd_backlight) {
+		asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
+
 		spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
 		drvdata->kbd_backlight->removed = true;
 		spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
-- 
2.48.1


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

* [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (4 preceding siblings ...)
  2025-03-22 10:27 ` [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one Antheas Kapenekakis
@ 2025-03-22 10:28 ` Antheas Kapenekakis
  2025-03-23  0:05   ` Luke D. Jones
  2025-03-22 10:28 ` [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler Antheas Kapenekakis
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:28 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

The quirk for selecting whether keyboard backlight should be controlled
by HID or WMI is not needed anymore, so remove it.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 include/linux/platform_data/x86/asus-wmi.h | 40 ----------------------
 1 file changed, 40 deletions(-)

diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index ec8b0c585a63f..c513b5a732323 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -183,44 +183,4 @@ static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
 }
 #endif
 
-/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
-static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = {
-	{
-		.matches = {
-			DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"),
-		},
-	},
-	{
-		.matches = {
-			DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"),
-		},
-	},
-	{
-		.matches = {
-			DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"),
-		},
-	},
-	{
-		.matches = {
-			DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
-		},
-	},
-	{
-		.matches = {
-			DMI_MATCH(DMI_BOARD_NAME, "GA403U"),
-		},
-	},
-	{
-		.matches = {
-			DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
-		},
-	},
-	{
-		.matches = {
-			DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
-		},
-	},
-	{ },
-};
-
 #endif	/* __PLATFORM_DATA_X86_ASUS_WMI_H */
-- 
2.48.1


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

* [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (5 preceding siblings ...)
  2025-03-22 10:28 ` [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk Antheas Kapenekakis
@ 2025-03-22 10:28 ` Antheas Kapenekakis
  2025-03-23  0:06   ` Luke D. Jones
  2025-03-22 10:28 ` [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler Antheas Kapenekakis
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:28 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

Currenlty, the keyboard brightness control of Asus WMI keyboards is
handled in the kernel, which leads to the shortcut going from
brightness 0, to 1, to 2, and 3.

However, for HID keyboards it is exposed as a key and handled by the
user's desktop environment. For the toggle button, this means that
brightness control becomes on/off. In addition, in the absence of a
DE, the keyboard brightness does not work.

Therefore, expose an event handler for the keyboard brightness control
which can then be used by hid-asus.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/platform/x86/asus-wmi.c            | 39 ++++++++++++++++++++++
 include/linux/platform_data/x86/asus-wmi.h | 11 ++++++
 2 files changed, 50 insertions(+)

diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 95ef9b1d321bb..5ebe141294ecf 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -1536,6 +1536,45 @@ void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
 }
 EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
 
+static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
+
+int asus_hid_event(enum asus_hid_event event)
+{
+	unsigned long flags;
+	int brightness;
+
+	spin_lock_irqsave(&asus_ref.lock, flags);
+	if (!asus_ref.asus || !asus_ref.asus->kbd_led_registered) {
+		spin_unlock_irqrestore(&asus_ref.lock, flags);
+		return -EBUSY;
+	}
+	brightness = asus_ref.asus->kbd_led_wk;
+
+	switch (event) {
+	case ASUS_EV_BRTUP:
+		brightness += 1;
+		break;
+	case ASUS_EV_BRTDOWN:
+		brightness -= 1;
+		break;
+	case ASUS_EV_BRTTOGGLE:
+		if (brightness >= 3)
+			brightness = 0;
+		else
+			brightness += 1;
+		break;
+	}
+
+	do_kbd_led_set(&asus_ref.asus->kbd_led, brightness);
+	led_classdev_notify_brightness_hw_changed(&asus_ref.asus->kbd_led,
+						  asus_ref.asus->kbd_led_wk);
+
+	spin_unlock_irqrestore(&asus_ref.lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_event);
+
 /*
  * These functions actually update the LED's, and are called from a
  * workqueue. By doing this as separate work rather than when the LED
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index c513b5a732323..9adbe8abef090 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -162,11 +162,18 @@ struct asus_hid_listener {
 	void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
 };
 
+enum asus_hid_event {
+	ASUS_EV_BRTUP,
+	ASUS_EV_BRTDOWN,
+	ASUS_EV_BRTTOGGLE,
+};
+
 #if IS_REACHABLE(CONFIG_ASUS_WMI)
 int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
 
 int asus_hid_register_listener(struct asus_hid_listener *cdev);
 void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
+int asus_hid_event(enum asus_hid_event event);
 #else
 static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
 					   u32 *retval)
@@ -181,6 +188,10 @@ static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
 static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
 {
 }
+static inline int asus_hid_event(enum asus_hid_event event)
+{
+	return -ENODEV;
+}
 #endif
 
 #endif	/* __PLATFORM_DATA_X86_ASUS_WMI_H */
-- 
2.48.1


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

* [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (6 preceding siblings ...)
  2025-03-22 10:28 ` [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler Antheas Kapenekakis
@ 2025-03-22 10:28 ` Antheas Kapenekakis
  2025-03-23  0:08   ` Luke D. Jones
  2025-03-22 10:28 ` [PATCH v3 09/10] HID: asus: add basic RGB support Antheas Kapenekakis
  2025-03-22 10:28 ` [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units Antheas Kapenekakis
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:28 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

If the asus-wmi brightness handler is available, send the
keyboard brightness events to it instead of passing them
to userspace. If it is not, fall back to sending them to it.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index c40b5c14c797f..905453a4eb5b7 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -318,6 +318,17 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
 			 usage->hid & HID_USAGE);
 	}
 
+	if (usage->type == EV_KEY && value) {
+		switch (usage->code) {
+		case KEY_KBDILLUMUP:
+			return !asus_hid_event(ASUS_EV_BRTUP);
+		case KEY_KBDILLUMDOWN:
+			return !asus_hid_event(ASUS_EV_BRTDOWN);
+		case KEY_KBDILLUMTOGGLE:
+			return !asus_hid_event(ASUS_EV_BRTTOGGLE);
+		}
+	}
+
 	return 0;
 }
 
-- 
2.48.1


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

* [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (7 preceding siblings ...)
  2025-03-22 10:28 ` [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler Antheas Kapenekakis
@ 2025-03-22 10:28 ` Antheas Kapenekakis
  2025-03-23  6:21   ` Luke D. Jones
  2025-03-23  6:40   ` Luke D. Jones
  2025-03-22 10:28 ` [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units Antheas Kapenekakis
  9 siblings, 2 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:28 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

Adds basic RGB support to hid-asus through multi-index. The interface
works quite well, but has not gone through much stability testing.
Applied on demand, if userspace does not touch the RGB sysfs, not
even initialization is done. Ensuring compatibility with existing
userspace programs.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 155 insertions(+), 14 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 905453a4eb5b7..9d8ccfde5912e 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -30,6 +30,7 @@
 #include <linux/input/mt.h>
 #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
 #include <linux/power_supply.h>
+#include <linux/led-class-multicolor.h>
 #include <linux/leds.h>
 
 #include "hid-ids.h"
@@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
 #define QUIRK_HANDLE_GENERIC		BIT(13)
+#define QUIRK_ROG_NKEY_RGB		BIT(14)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 
 struct asus_kbd_leds {
 	struct asus_hid_listener listener;
+	struct led_classdev_mc mc_led;
+	struct mc_subled subled_info[3];
 	struct hid_device *hdev;
 	struct work_struct work;
 	unsigned int brightness;
+	uint8_t rgb_colors[3];
+	bool rgb_init;
+	bool rgb_set;
+	bool rgb_registered;
 	spinlock_t lock;
 	bool removed;
 };
@@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
 	spin_unlock_irqrestore(&led->lock, flags);
 }
 
-static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
+static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	led->brightness = brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	asus_schedule_work(led);
+}
+
+static void asus_kbd_listener_set(struct asus_hid_listener *listener,
 				   int brightness)
 {
 	struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
 						 listener);
+	do_asus_kbd_backlight_set(led, brightness);
+	if (led->rgb_registered) {
+		led->mc_led.led_cdev.brightness = brightness;
+		led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
+							  brightness);
+	}
+}
+
+static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
+				    enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+	struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
+						 mc_led);
 	unsigned long flags;
 
 	spin_lock_irqsave(&led->lock, flags);
-	led->brightness = brightness;
+	led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
+	led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
+	led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
+	led->rgb_set = true;
 	spin_unlock_irqrestore(&led->lock, flags);
 
-	asus_schedule_work(led);
+	do_asus_kbd_backlight_set(led, brightness);
+}
+
+static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
+{
+	struct led_classdev_mc *mc_led;
+	struct asus_kbd_leds *led;
+	enum led_brightness brightness;
+	unsigned long flags;
+
+	mc_led = lcdev_to_mccdev(led_cdev);
+	led = container_of(mc_led, struct asus_kbd_leds, mc_led);
+
+	spin_lock_irqsave(&led->lock, flags);
+	brightness = led->brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	return brightness;
 }
 
-static void asus_kbd_backlight_work(struct work_struct *work)
+static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
 {
-	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
 	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
 	int ret;
 	unsigned long flags;
@@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
 		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
 }
 
+static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
+{
+	u8 rgb_buf[][7] = {
+		{ FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
+		{ FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
+		{ FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
+	};
+	unsigned long flags;
+	uint8_t colors[3];
+	bool rgb_init, rgb_set;
+	int ret;
+
+	spin_lock_irqsave(&led->lock, flags);
+	rgb_init = led->rgb_init;
+	rgb_set = led->rgb_set;
+	led->rgb_set = false;
+	colors[0] = led->rgb_colors[0];
+	colors[1] = led->rgb_colors[1];
+	colors[2] = led->rgb_colors[2];
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	if (!rgb_set)
+		return;
+
+	if (rgb_init) {
+		ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
+		if (ret < 0) {
+			hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
+			return;
+		}
+		spin_lock_irqsave(&led->lock, flags);
+		led->rgb_init = false;
+		spin_unlock_irqrestore(&led->lock, flags);
+	}
+
+	/* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
+	rgb_buf[0][4] = colors[0];
+	rgb_buf[0][5] = colors[1];
+	rgb_buf[0][6] = colors[2];
+
+	for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
+		ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
+		if (ret < 0) {
+			hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
+			return;
+		}
+	}
+}
+
+static void asus_kbd_work(struct work_struct *work)
+{
+	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
+						 work);
+	asus_kbd_backlight_work(led);
+	asus_kbd_rgb_work(led);
+}
+
 static int asus_kbd_register_leds(struct hid_device *hdev)
 {
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
 	unsigned char kbd_func;
+	struct asus_kbd_leds *leds;
+	bool no_led;
 	int ret;
 
 	ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
@@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
 	if (!drvdata->kbd_backlight)
 		return -ENOMEM;
 
-	drvdata->kbd_backlight->removed = false;
-	drvdata->kbd_backlight->brightness = 0;
-	drvdata->kbd_backlight->hdev = hdev;
-	drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
-	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
+	leds = drvdata->kbd_backlight;
+	leds->removed = false;
+	leds->brightness = 3;
+	leds->hdev = hdev;
+	leds->listener.brightness_set = asus_kbd_listener_set;
+
+	leds->rgb_colors[0] = 0;
+	leds->rgb_colors[1] = 0;
+	leds->rgb_colors[2] = 0;
+	leds->rgb_init = true;
+	leds->rgb_set = false;
+	leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+					"asus-%s-led",
+					strlen(hdev->uniq) ?
+					hdev->uniq : dev_name(&hdev->dev));
+	leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
+	leds->mc_led.led_cdev.max_brightness = 3,
+	leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
+	leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
+	leds->mc_led.subled_info = leds->subled_info,
+	leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
+	leds->subled_info[0].color_index = LED_COLOR_ID_RED;
+	leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+	leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+
+	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
 	spin_lock_init(&drvdata->kbd_backlight->lock);
 
 	ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
+	no_led = !!ret;
+
+	if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
+		ret = devm_led_classdev_multicolor_register(
+			&hdev->dev, &leds->mc_led);
+		if (!ret)
+			leds->rgb_registered = true;
+		no_led &= !!ret;
+	}
 
-	if (ret < 0) {
+	if (no_led) {
 		/* No need to have this still around */
 		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
 	}
 
-	return ret;
+	return no_led ? -ENODEV : 0;
 }
 
 /*
@@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
@@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
 	 */
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
 	{ }
-- 
2.48.1


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

* [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units
  2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
                   ` (8 preceding siblings ...)
  2025-03-22 10:28 ` [PATCH v3 09/10] HID: asus: add basic RGB support Antheas Kapenekakis
@ 2025-03-22 10:28 ` Antheas Kapenekakis
  2025-03-23  6:29   ` Luke D. Jones
  9 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 10:28 UTC (permalink / raw)
  To: platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen,
	Antheas Kapenekakis

Apply the RGB quirk to the QOG Ally units to enable basic RGB support.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 drivers/hid/hid-asus.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 9d8ccfde5912e..1a9cd7f513282 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1433,10 +1433,10 @@ static const struct hid_device_id asus_devices[] = {
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
 	  QUIRK_ROG_CLAYMORE_II_KEYBOARD },
-- 
2.48.1


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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 10:27 ` [PATCH v3 01/10] HID: asus: refactor init sequence per spec Antheas Kapenekakis
@ 2025-03-22 22:01   ` Luke D. Jones
  2025-03-22 23:05     ` Antheas Kapenekakis
  0 siblings, 1 reply; 37+ messages in thread
From: Luke D. Jones @ 2025-03-22 22:01 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:27, Antheas Kapenekakis wrote:
> Currently, asus_kbd_init() uses a reverse engineered init sequence
> from Windows, which contains the handshakes from multiple programs.
> Keep the main one, which is 0x5a (meant for brightness drivers).
> 
> In addition, perform a get_response and check if the response is the
> same. To avoid regressions, print an error if the response does not
> match instead of rejecting device.
> 
> Then, refactor asus_kbd_get_functions() to use the same ID it is called
> with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
> in the future.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
>   1 file changed, 46 insertions(+), 36 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 46e3e42f9eb5f..8d4df1b6f143b 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define FEATURE_REPORT_ID 0x0d
>   #define INPUT_REPORT_ID 0x5d
>   #define FEATURE_KBD_REPORT_ID 0x5a
> -#define FEATURE_KBD_REPORT_SIZE 16
> +#define FEATURE_KBD_REPORT_SIZE 64
>   #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>   #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>   
> @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>   
>   static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>   {
> -	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
> -		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> +	/*
> +	 * Asus handshake identifying us as a driver (0x5A)
> +	 * 0x5A then ASCII for "ASUS Tech.Inc."
> +	 * 0x5D is for userspace Windows applications.
> +	 *
> +	 * The handshake is first sent as a set_report, then retrieved
> +	 * from a get_report to verify the response.
> +	 */

This entire comment is not required, especially not the last paragraph. 
 From what I've seen in .dll reversing attempts there's no real 
distinction from driver/app and it's simply an init/enable sequence 
common to almost every ITE MCU that ASUS have used (slash, anime, Ally).

Please remove.

> +	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
> +		0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> +	u8 *readbuf;
>   	int ret;
>   
>   	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
> -	if (ret < 0)
> -		hid_err(hdev, "Asus failed to send init command: %d\n", ret);
> +	if (ret < 0) {
> +		hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
> +		return ret;
> +	}
>   
> +	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
> +	if (!readbuf)
> +		return -ENOMEM;
> +
> +	ret = hid_hw_raw_request(hdev, report_id, readbuf,
> +				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> +				 HID_REQ_GET_REPORT);
> +	if (ret < 0) {
> +		hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
> +	} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
> +		hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
> +			FEATURE_KBD_REPORT_SIZE, readbuf);
> +		// Do not return error if handshake is wrong to avoid regressions
> +	}
> +
> +	kfree(readbuf);
>   	return ret;
>   }
>   
> @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>   	if (!readbuf)
>   		return -ENOMEM;
>   
> -	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
> +	ret = hid_hw_raw_request(hdev, report_id, readbuf,
>   				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>   				 HID_REQ_GET_REPORT);
>   	if (ret < 0) {
> @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>   	unsigned char kbd_func;
>   	int ret;
>   
> -	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> -		/* Initialize keyboard */
> -		ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> -		if (ret < 0)
> -			return ret;
> -
> -		/* The LED endpoint is initialised in two HID */
> -		ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
> -		if (ret < 0)
> -			return ret;
> -
> -		ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
> -		if (ret < 0)
> -			return ret;
> +	ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> +	if (ret < 0)
> +		return ret;
>  

I don't have any non-ROG equipment to test. There have been some cases 
of Vivobook using the same MCU, but these otherwise used something else.
And the oldest hardware I have uses a 0x1866, which uses the same init 
sequence but works with both 0x5A and 0x5D report ID for init (on same 
endpoint). There are instances of other laptops that accept both 0x5A 
and 0x5D, and some that have only 0x5D.

I think you will need to change this to try both 0x5A and 0x5D sorry. 
The older models using 0x1854, 0x1869, 0x1866 PID may regress and 
although I'm reasonably confident there won't be issues due to age of 
those, it's not a risk I'm willing to take, I've spent all morning 
trawling through archives of info and I can't actually verify this other 
than from my memory.

I mentioned 0x5E being used for some of the oddball devices like slash 
and anime, don't worry about that one, it's a bridge that can be crossed 
at a later time. But it does illustrate that other report ID have been 
used for init.

Otherwise the cleanup is good.

No other comments required and I'll sign off with the above corrections.

Cheers,
Luke

> -		if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> -			ret = asus_kbd_disable_oobe(hdev);
> -			if (ret < 0)
> -				return ret;
> -		}
> -	} else {
> -		/* Initialize keyboard */
> -		ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> -		if (ret < 0)
> -			return ret;
> +	/* Get keyboard functions */
> +	ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> +	if (ret < 0)
> +		return ret;
>   
> -		/* Get keyboard functions */
> -		ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> +	if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> +		ret = asus_kbd_disable_oobe(hdev);
>   		if (ret < 0)
>   			return ret;
> -
> -		/* Check for backlight support */
> -		if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> -			return -ENODEV;
>   	}
>   
> +	/* Check for backlight support */
> +	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> +		return -ENODEV;
> +
>   	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>   					      sizeof(struct asus_kbd_leds),
>   					      GFP_KERNEL);


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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 22:01   ` Luke D. Jones
@ 2025-03-22 23:05     ` Antheas Kapenekakis
  2025-03-22 23:28       ` Luke D. Jones
  0 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 23:05 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sat, 22 Mar 2025 at 23:01, Luke D. Jones <luke@ljones.dev> wrote:
>
> On 22/03/25 23:27, Antheas Kapenekakis wrote:
> > Currently, asus_kbd_init() uses a reverse engineered init sequence
> > from Windows, which contains the handshakes from multiple programs.
> > Keep the main one, which is 0x5a (meant for brightness drivers).
> >
> > In addition, perform a get_response and check if the response is the
> > same. To avoid regressions, print an error if the response does not
> > match instead of rejecting device.
> >
> > Then, refactor asus_kbd_get_functions() to use the same ID it is called
> > with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
> > in the future.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> >   drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
> >   1 file changed, 46 insertions(+), 36 deletions(-)
> >
> > diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > index 46e3e42f9eb5f..8d4df1b6f143b 100644
> > --- a/drivers/hid/hid-asus.c
> > +++ b/drivers/hid/hid-asus.c
> > @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >   #define FEATURE_REPORT_ID 0x0d
> >   #define INPUT_REPORT_ID 0x5d
> >   #define FEATURE_KBD_REPORT_ID 0x5a
> > -#define FEATURE_KBD_REPORT_SIZE 16
> > +#define FEATURE_KBD_REPORT_SIZE 64
> >   #define FEATURE_KBD_LED_REPORT_ID1 0x5d
> >   #define FEATURE_KBD_LED_REPORT_ID2 0x5e
> >
> > @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
> >
> >   static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
> >   {
> > -     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
> > -                  0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> > +     /*
> > +      * Asus handshake identifying us as a driver (0x5A)
> > +      * 0x5A then ASCII for "ASUS Tech.Inc."
> > +      * 0x5D is for userspace Windows applications.
> > +      *
> > +      * The handshake is first sent as a set_report, then retrieved
> > +      * from a get_report to verify the response.
> > +      */
>
> This entire comment is not required, especially not the last paragraph.
>  From what I've seen in .dll reversing attempts there's no real
> distinction from driver/app and it's simply an init/enable sequence
> common to almost every ITE MCU that ASUS have used (slash, anime, Ally).
>
> Please remove.

It is a context comment but can be removed.

> > +     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
> > +             0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> > +     u8 *readbuf;
> >       int ret;
> >
> >       ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
> > -     if (ret < 0)
> > -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
> > +     if (ret < 0) {
> > +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
> > +             return ret;
> > +     }
> >
> > +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
> > +     if (!readbuf)
> > +             return -ENOMEM;
> > +
> > +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> > +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> > +                              HID_REQ_GET_REPORT);
> > +     if (ret < 0) {
> > +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
> > +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
> > +             hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
> > +                     FEATURE_KBD_REPORT_SIZE, readbuf);
> > +             // Do not return error if handshake is wrong to avoid regressions
> > +     }
> > +
> > +     kfree(readbuf);
> >       return ret;
> >   }
> >
> > @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
> >       if (!readbuf)
> >               return -ENOMEM;
> >
> > -     ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
> > +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> >                                FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> >                                HID_REQ_GET_REPORT);
> >       if (ret < 0) {
> > @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> >       unsigned char kbd_func;
> >       int ret;
> >
> > -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> > -             /* Initialize keyboard */
> > -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > -             if (ret < 0)
> > -                     return ret;
> > -
> > -             /* The LED endpoint is initialised in two HID */
> > -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
> > -             if (ret < 0)
> > -                     return ret;
> > -
> > -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
> > -             if (ret < 0)
> > -                     return ret;
> > +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > +     if (ret < 0)
> > +             return ret;
> >
>
> I don't have any non-ROG equipment to test. There have been some cases
> of Vivobook using the same MCU, but these otherwise used something else.
> And the oldest hardware I have uses a 0x1866, which uses the same init
> sequence but works with both 0x5A and 0x5D report ID for init (on same
> endpoint). There are instances of other laptops that accept both 0x5A
> and 0x5D, and some that have only 0x5D.

The driver sets the brightness using 0x5a and accepts keyboard
shortcuts only from 0x5a. Therefore it needs to init only with 0x5a.

How would those laptops regress and in what way?

> I think you will need to change this to try both 0x5A and 0x5D sorry.
> The older models using 0x1854, 0x1869, 0x1866 PID may regress and
> although I'm reasonably confident there won't be issues due to age of
> those, it's not a risk I'm willing to take, I've spent all morning
> trawling through archives of info and I can't actually verify this other
> than from my memory.

For devices that support RGB, only when RGB is set, 0x5D is initialized too.

> I mentioned 0x5E being used for some of the oddball devices like slash
> and anime, don't worry about that one, it's a bridge that can be crossed
> at a later time. But it does illustrate that other report ID have been
> used for init.
>
> Otherwise the cleanup is good.
>
> No other comments required and I'll sign off with the above corrections.
>
> Cheers,
> Luke
>
> > -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> > -                     ret = asus_kbd_disable_oobe(hdev);
> > -                     if (ret < 0)
> > -                             return ret;
> > -             }
> > -     } else {
> > -             /* Initialize keyboard */
> > -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > -             if (ret < 0)
> > -                     return ret;
> > +     /* Get keyboard functions */
> > +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> > +     if (ret < 0)
> > +             return ret;
> >
> > -             /* Get keyboard functions */
> > -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> > +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> > +             ret = asus_kbd_disable_oobe(hdev);
> >               if (ret < 0)
> >                       return ret;
> > -
> > -             /* Check for backlight support */
> > -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> > -                     return -ENODEV;
> >       }
> >
> > +     /* Check for backlight support */
> > +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> > +             return -ENODEV;
> > +
> >       drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
> >                                             sizeof(struct asus_kbd_leds),
> >                                             GFP_KERNEL);
>

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

* Re: [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG
  2025-03-22 10:27 ` [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG Antheas Kapenekakis
@ 2025-03-22 23:21   ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-22 23:21 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:27, Antheas Kapenekakis wrote:
> ROG keyboards are HID compliant and only care about the endpoint that
> produces vendor events (e.g., fan mode) and has the keyboard backlight.
> 
> Therefore, handle all of the endpoints of ROG keyboards as compliant,
> by adding HID_QUIRK_INPUT_PER_APP and, for devices other than the vendor
> one, by adding QUIRK_HANDLE_GENERIC to stop mutating them.
> 
> Due to HID_QUIRK_INPUT_PER_APP, rgb register is moved into probe, as
> the input_* functions are called multiple times (4 for the Z13).
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 69 ++++++++++++++++++++++++++++++++----------
>   1 file changed, 53 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 8d4df1b6f143b..96461321c191c 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -84,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define QUIRK_MEDION_E1239T		BIT(10)
>   #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
>   #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> +#define QUIRK_HANDLE_GENERIC		BIT(13)
>   
>   #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>   						 QUIRK_NO_INIT_REPORTS | \
> @@ -120,7 +121,6 @@ struct asus_drvdata {
>   	struct input_dev *tp_kbd_input;
>   	struct asus_kbd_leds *kbd_backlight;
>   	const struct asus_touchpad_info *tp;
> -	bool enable_backlight;
>   	struct power_supply *battery;
>   	struct power_supply_desc battery_desc;
>   	int battery_capacity;
> @@ -326,6 +326,10 @@ static int asus_raw_event(struct hid_device *hdev,
>   {
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>   
> +	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
> +		/* NOOP on generic HID devices to avoid side effects. */
> +		return 0;
> +
>   	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
>   		return asus_report_battery(drvdata, data, size);
>   
> @@ -774,6 +778,10 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
>   	struct input_dev *input = hi->input;
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>   
> +	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
> +		/* NOOP on generic HID devices to avoid side effects. */
> +		return 0;
> +
>   	/* T100CHI uses MULTI_INPUT, bind the touchpad to the mouse hid_input */
>   	if (drvdata->quirks & QUIRK_T100CHI &&
>   	    hi->report->id != T100CHI_MOUSE_REPORT_ID)
> @@ -827,11 +835,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
>   
>   	drvdata->input = input;
>   
> -	if (drvdata->enable_backlight &&
> -	    !asus_kbd_wmi_led_control_present(hdev) &&
> -	    asus_kbd_register_leds(hdev))
> -		hid_warn(hdev, "Failed to initialize backlight.\n");
> -
>   	return 0;
>   }
>   
> @@ -851,6 +854,10 @@ static int asus_input_mapping(struct hid_device *hdev,
>   		return -1;
>   	}
>   
> +	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
> +		/* NOOP on generic HID devices to avoid side effects. */
> +		return 0;
> +
>   	/*
>   	 * Ignore a bunch of bogus collections in the T100CHI descriptor.
>   	 * This avoids a bunch of non-functional hid_input devices getting
> @@ -901,15 +908,6 @@ static int asus_input_mapping(struct hid_device *hdev,
>   			return -1;
>   		}
>   
> -		/*
> -		 * Check and enable backlight only on devices with UsagePage ==
> -		 * 0xff31 to avoid initializing the keyboard firmware multiple
> -		 * times on devices with multiple HID descriptors but same
> -		 * PID/VID.
> -		 */
> -		if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
> -			drvdata->enable_backlight = true;
> -
>   		set_bit(EV_REP, hi->input->evbit);
>   		return 1;
>   	}
> @@ -1026,8 +1024,10 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
>   
>   static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
>   {
> -	int ret;
> +	struct hid_report_enum *rep_enum;
>   	struct asus_drvdata *drvdata;
> +	struct hid_report *rep;
> +	int ret, is_vendor = 0;
>   
>   	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
>   	if (drvdata == NULL) {
> @@ -1111,12 +1111,45 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
>   		return ret;
>   	}
>   
> +	/*
> +	 * Check for the vendor interface (0xff31) to init the RGB.
> +	 * and handle generic devices properly.
> +	 */
> +	rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
> +	list_for_each_entry(rep, &rep_enum->report_list, list) {
> +		if ((rep->application & HID_USAGE_PAGE) == 0xff310000)
> +			is_vendor = true;
> +	}
> +
> +	/*
> +	 * For ROG keyboards, make them hid compliant by
> +	 * creating one input per application. For interfaces other than
> +	 * the vendor one, disable hid-asus handlers.
> +	 */
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> +		if (!is_vendor)
> +			drvdata->quirks |= QUIRK_HANDLE_GENERIC;
> +		hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
> +	}
> +
>   	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
>   	if (ret) {
>   		hid_err(hdev, "Asus hw start failed: %d\n", ret);
>   		return ret;
>   	}
>   
> +	if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
> +	    !asus_kbd_wmi_led_control_present(hdev) &&
> +	    asus_kbd_register_leds(hdev))
> +		hid_warn(hdev, "Failed to initialize backlight.\n");
> +
> +	/*
> +	 * For ROG keyboards, skip rename for consistency and
> +	 * ->input check as some devices do not have inputs.
> +	 */
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
> +		return 0;
> +
>   	if (!drvdata->input) {
>   		hid_err(hdev, "Asus input not registered\n");
>   		ret = -ENOMEM;
> @@ -1167,6 +1200,10 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
>   {
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>   
> +	if (drvdata->quirks & QUIRK_HANDLE_GENERIC)
> +		/* NOOP on generic HID devices to avoid side effects. */
> +		return rdesc;
> +
>   	if (drvdata->quirks & QUIRK_FIX_NOTEBOOK_REPORT &&
>   			*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x65) {
>   		hid_info(hdev, "Fixing up Asus notebook report descriptor\n");

Ilpo might have some feedback. I'm otherwise happy with this.

Reviewed-by: Luke D. Jones <luke@ljones.dev>


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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 23:05     ` Antheas Kapenekakis
@ 2025-03-22 23:28       ` Luke D. Jones
  2025-03-22 23:53         ` Antheas Kapenekakis
  0 siblings, 1 reply; 37+ messages in thread
From: Luke D. Jones @ 2025-03-22 23:28 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On 23/03/25 12:05, Antheas Kapenekakis wrote:
> On Sat, 22 Mar 2025 at 23:01, Luke D. Jones <luke@ljones.dev> wrote:
>>
>> On 22/03/25 23:27, Antheas Kapenekakis wrote:
>>> Currently, asus_kbd_init() uses a reverse engineered init sequence
>>> from Windows, which contains the handshakes from multiple programs.
>>> Keep the main one, which is 0x5a (meant for brightness drivers).
>>>
>>> In addition, perform a get_response and check if the response is the
>>> same. To avoid regressions, print an error if the response does not
>>> match instead of rejecting device.
>>>
>>> Then, refactor asus_kbd_get_functions() to use the same ID it is called
>>> with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
>>> in the future.
>>>
>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>> ---
>>>    drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
>>>    1 file changed, 46 insertions(+), 36 deletions(-)
>>>
>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>> index 46e3e42f9eb5f..8d4df1b6f143b 100644
>>> --- a/drivers/hid/hid-asus.c
>>> +++ b/drivers/hid/hid-asus.c
>>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>    #define FEATURE_REPORT_ID 0x0d
>>>    #define INPUT_REPORT_ID 0x5d
>>>    #define FEATURE_KBD_REPORT_ID 0x5a
>>> -#define FEATURE_KBD_REPORT_SIZE 16
>>> +#define FEATURE_KBD_REPORT_SIZE 64
>>>    #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>>>    #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>>>
>>> @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>>>
>>>    static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>>>    {
>>> -     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
>>> -                  0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>> +     /*
>>> +      * Asus handshake identifying us as a driver (0x5A)
>>> +      * 0x5A then ASCII for "ASUS Tech.Inc."
>>> +      * 0x5D is for userspace Windows applications.
>>> +      *
>>> +      * The handshake is first sent as a set_report, then retrieved
>>> +      * from a get_report to verify the response.
>>> +      */
>>
>> This entire comment is not required, especially not the last paragraph.
>>   From what I've seen in .dll reversing attempts there's no real
>> distinction from driver/app and it's simply an init/enable sequence
>> common to almost every ITE MCU that ASUS have used (slash, anime, Ally).
>>
>> Please remove.
> 
> It is a context comment but can be removed.
> 
>>> +     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
>>> +             0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>> +     u8 *readbuf;
>>>        int ret;
>>>
>>>        ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
>>> -     if (ret < 0)
>>> -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
>>> +     if (ret < 0) {
>>> +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
>>> +             return ret;
>>> +     }
>>>
>>> +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
>>> +     if (!readbuf)
>>> +             return -ENOMEM;
>>> +
>>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>> +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>> +                              HID_REQ_GET_REPORT);
>>> +     if (ret < 0) {
>>> +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
>>> +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
>>> +             hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
>>> +                     FEATURE_KBD_REPORT_SIZE, readbuf);
>>> +             // Do not return error if handshake is wrong to avoid regressions
>>> +     }
>>> +
>>> +     kfree(readbuf);
>>>        return ret;
>>>    }
>>>
>>> @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>>>        if (!readbuf)
>>>                return -ENOMEM;
>>>
>>> -     ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
>>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>>                                 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>>                                 HID_REQ_GET_REPORT);
>>>        if (ret < 0) {
>>> @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>        unsigned char kbd_func;
>>>        int ret;
>>>
>>> -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>> -             /* Initialize keyboard */
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> -
>>> -             /* The LED endpoint is initialised in two HID */
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> -
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> +     if (ret < 0)
>>> +             return ret;
>>>
>>
>> I don't have any non-ROG equipment to test. There have been some cases
>> of Vivobook using the same MCU, but these otherwise used something else.
>> And the oldest hardware I have uses a 0x1866, which uses the same init
>> sequence but works with both 0x5A and 0x5D report ID for init (on same
>> endpoint). There are instances of other laptops that accept both 0x5A
>> and 0x5D, and some that have only 0x5D.
> 
> The driver sets the brightness using 0x5a and accepts keyboard
> shortcuts only from 0x5a. Therefore it needs to init only with 0x5a.
> 
> How would those laptops regress and in what way?
> 

The media keys fail to work (vol, mic, rog). Can you please accept that 
I do know some laptops used only 0x5D, and these are older models, 
around 5+ years. The only thing I have to go on is my memory 
unfortunately, and I've been trying to find the concrete examples.

>> I think you will need to change this to try both 0x5A and 0x5D sorry.
>> The older models using 0x1854, 0x1869, 0x1866 PID may regress and
>> although I'm reasonably confident there won't be issues due to age of
>> those, it's not a risk I'm willing to take, I've spent all morning
>> trawling through archives of info and I can't actually verify this other
>> than from my memory.
> 
> For devices that support RGB, only when RGB is set, 0x5D is initialized too.

Sure. But as I've said above.. Please add both to init. It's only done 
once, and it doesn't hurt anything plus doesn't risk regressing older 
hardware.

If I can get the proper evidence that only 0x5A is required I'm happy to 
use only that, but until then I don't want that risk. And it's only a 
small thing here.

Cheers,
Luke.

> 
>> I mentioned 0x5E being used for some of the oddball devices like slash
>> and anime, don't worry about that one, it's a bridge that can be crossed
>> at a later time. But it does illustrate that other report ID have been
>> used for init.
>>
>> Otherwise the cleanup is good.
>>
>> No other comments required and I'll sign off with the above corrections.
>>
>> Cheers,
>> Luke
>>
>>> -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>> -                     ret = asus_kbd_disable_oobe(hdev);
>>> -                     if (ret < 0)
>>> -                             return ret;
>>> -             }
>>> -     } else {
>>> -             /* Initialize keyboard */
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> +     /* Get keyboard functions */
>>> +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>> +     if (ret < 0)
>>> +             return ret;
>>>
>>> -             /* Get keyboard functions */
>>> -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>> +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>> +             ret = asus_kbd_disable_oobe(hdev);
>>>                if (ret < 0)
>>>                        return ret;
>>> -
>>> -             /* Check for backlight support */
>>> -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>> -                     return -ENODEV;
>>>        }
>>>
>>> +     /* Check for backlight support */
>>> +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>> +             return -ENODEV;
>>> +
>>>        drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>>>                                              sizeof(struct asus_kbd_leds),
>>>                                              GFP_KERNEL);
>>


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

* Re: [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work
  2025-03-22 10:27 ` [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work Antheas Kapenekakis
@ 2025-03-22 23:30   ` Luke D. Jones
  2025-03-22 23:32     ` Antheas Kapenekakis
  0 siblings, 1 reply; 37+ messages in thread
From: Luke D. Jones @ 2025-03-22 23:30 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:27, Antheas Kapenekakis wrote:
> The Asus Z13 folio has a multitouch touchpad that needs to bind
> to the hid-multitouch driver in order to work properly. So bind
> it to the HID_GROUP_GENERIC group to release the touchpad and
> move it to the bottom so that the comment applies to it.
> 
> While at it, change the generic KEYBOARD3 name to Z13_FOLIO.
> 
> Reviewed-by: Luke D. Jones <luke@ljones.dev>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---

Never been clear on tag order but I've always put authors at top. Just 
something I noticed as it's different to what i do.

>   drivers/hid/hid-asus.c | 6 +++---
>   drivers/hid/hid-ids.h  | 2 +-
>   2 files changed, 4 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 96461321c191c..e97fb76eda619 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -1319,9 +1319,6 @@ static const struct hid_device_id asus_devices[] = {
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> -	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> -	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> @@ -1351,6 +1348,9 @@ static const struct hid_device_id asus_devices[] = {
>   	 * Note bind to the HID_GROUP_GENERIC group, so that we only bind to the keyboard
>   	 * part, while letting hid-multitouch.c handle the touchpad.
>   	 */
> +	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> +		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>   	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>   		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
>   	{ }
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 7e400624908e3..b1fe7582324ff 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -209,7 +209,7 @@
>   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
>   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
>   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2	0x19b6
> -#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3	0x1a30
> +#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO		0x1a30
>   #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR		0x18c6
>   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY		0x1abe
>   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X		0x1b4c


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

* Re: [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work
  2025-03-22 23:30   ` Luke D. Jones
@ 2025-03-22 23:32     ` Antheas Kapenekakis
  0 siblings, 0 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 23:32 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sun, 23 Mar 2025 at 00:31, Luke D. Jones <luke@ljones.dev> wrote:
>
> On 22/03/25 23:27, Antheas Kapenekakis wrote:
> > The Asus Z13 folio has a multitouch touchpad that needs to bind
> > to the hid-multitouch driver in order to work properly. So bind
> > it to the HID_GROUP_GENERIC group to release the touchpad and
> > move it to the bottom so that the comment applies to it.
> >
> > While at it, change the generic KEYBOARD3 name to Z13_FOLIO.
> >
> > Reviewed-by: Luke D. Jones <luke@ljones.dev>
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
>
> Never been clear on tag order but I've always put authors at top. Just
> something I noticed as it's different to what i do.

Tag was added by b4

Antheas

> >   drivers/hid/hid-asus.c | 6 +++---
> >   drivers/hid/hid-ids.h  | 2 +-
> >   2 files changed, 4 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > index 96461321c191c..e97fb76eda619 100644
> > --- a/drivers/hid/hid-asus.c
> > +++ b/drivers/hid/hid-asus.c
> > @@ -1319,9 +1319,6 @@ static const struct hid_device_id asus_devices[] = {
> >       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >           USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
> >         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > -     { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> > -         USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
> > -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >           USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> >         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > @@ -1351,6 +1348,9 @@ static const struct hid_device_id asus_devices[] = {
> >        * Note bind to the HID_GROUP_GENERIC group, so that we only bind to the keyboard
> >        * part, while letting hid-multitouch.c handle the touchpad.
> >        */
> > +     { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > +             USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> > +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> >               USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
> >       { }
> > diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> > index 7e400624908e3..b1fe7582324ff 100644
> > --- a/drivers/hid/hid-ids.h
> > +++ b/drivers/hid/hid-ids.h
> > @@ -209,7 +209,7 @@
> >   #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
> >   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD     0x1866
> >   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2    0x19b6
> > -#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3     0x1a30
> > +#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO          0x1a30
> >   #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR              0x18c6
> >   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY         0x1abe
> >   #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X               0x1b4c
>

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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 23:28       ` Luke D. Jones
@ 2025-03-22 23:53         ` Antheas Kapenekakis
  2025-03-22 23:54           ` Antheas Kapenekakis
  2025-03-23  0:14           ` Luke D. Jones
  0 siblings, 2 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 23:53 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sun, 23 Mar 2025 at 00:29, Luke D. Jones <luke@ljones.dev> wrote:
>
> On 23/03/25 12:05, Antheas Kapenekakis wrote:
> > On Sat, 22 Mar 2025 at 23:01, Luke D. Jones <luke@ljones.dev> wrote:
> >>
> >> On 22/03/25 23:27, Antheas Kapenekakis wrote:
> >>> Currently, asus_kbd_init() uses a reverse engineered init sequence
> >>> from Windows, which contains the handshakes from multiple programs.
> >>> Keep the main one, which is 0x5a (meant for brightness drivers).
> >>>
> >>> In addition, perform a get_response and check if the response is the
> >>> same. To avoid regressions, print an error if the response does not
> >>> match instead of rejecting device.
> >>>
> >>> Then, refactor asus_kbd_get_functions() to use the same ID it is called
> >>> with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
> >>> in the future.
> >>>
> >>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> >>> ---
> >>>    drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
> >>>    1 file changed, 46 insertions(+), 36 deletions(-)
> >>>
> >>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> >>> index 46e3e42f9eb5f..8d4df1b6f143b 100644
> >>> --- a/drivers/hid/hid-asus.c
> >>> +++ b/drivers/hid/hid-asus.c
> >>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >>>    #define FEATURE_REPORT_ID 0x0d
> >>>    #define INPUT_REPORT_ID 0x5d
> >>>    #define FEATURE_KBD_REPORT_ID 0x5a
> >>> -#define FEATURE_KBD_REPORT_SIZE 16
> >>> +#define FEATURE_KBD_REPORT_SIZE 64
> >>>    #define FEATURE_KBD_LED_REPORT_ID1 0x5d
> >>>    #define FEATURE_KBD_LED_REPORT_ID2 0x5e
> >>>
> >>> @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
> >>>
> >>>    static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
> >>>    {
> >>> -     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
> >>> -                  0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> >>> +     /*
> >>> +      * Asus handshake identifying us as a driver (0x5A)
> >>> +      * 0x5A then ASCII for "ASUS Tech.Inc."
> >>> +      * 0x5D is for userspace Windows applications.
> >>> +      *
> >>> +      * The handshake is first sent as a set_report, then retrieved
> >>> +      * from a get_report to verify the response.
> >>> +      */
> >>
> >> This entire comment is not required, especially not the last paragraph.
> >>   From what I've seen in .dll reversing attempts there's no real
> >> distinction from driver/app and it's simply an init/enable sequence
> >> common to almost every ITE MCU that ASUS have used (slash, anime, Ally).
> >>
> >> Please remove.
> >
> > It is a context comment but can be removed.
> >
> >>> +     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
> >>> +             0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> >>> +     u8 *readbuf;
> >>>        int ret;
> >>>
> >>>        ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
> >>> -     if (ret < 0)
> >>> -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
> >>> +     if (ret < 0) {
> >>> +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
> >>> +             return ret;
> >>> +     }
> >>>
> >>> +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
> >>> +     if (!readbuf)
> >>> +             return -ENOMEM;
> >>> +
> >>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> >>> +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> >>> +                              HID_REQ_GET_REPORT);
> >>> +     if (ret < 0) {
> >>> +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
> >>> +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
> >>> +             hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
> >>> +                     FEATURE_KBD_REPORT_SIZE, readbuf);
> >>> +             // Do not return error if handshake is wrong to avoid regressions
> >>> +     }
> >>> +
> >>> +     kfree(readbuf);
> >>>        return ret;
> >>>    }
> >>>
> >>> @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
> >>>        if (!readbuf)
> >>>                return -ENOMEM;
> >>>
> >>> -     ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
> >>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> >>>                                 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> >>>                                 HID_REQ_GET_REPORT);
> >>>        if (ret < 0) {
> >>> @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> >>>        unsigned char kbd_func;
> >>>        int ret;
> >>>
> >>> -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> >>> -             /* Initialize keyboard */
> >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> >>> -             if (ret < 0)
> >>> -                     return ret;
> >>> -
> >>> -             /* The LED endpoint is initialised in two HID */
> >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
> >>> -             if (ret < 0)
> >>> -                     return ret;
> >>> -
> >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
> >>> -             if (ret < 0)
> >>> -                     return ret;
> >>> +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> >>> +     if (ret < 0)
> >>> +             return ret;
> >>>
> >>
> >> I don't have any non-ROG equipment to test. There have been some cases
> >> of Vivobook using the same MCU, but these otherwise used something else.
> >> And the oldest hardware I have uses a 0x1866, which uses the same init
> >> sequence but works with both 0x5A and 0x5D report ID for init (on same
> >> endpoint). There are instances of other laptops that accept both 0x5A
> >> and 0x5D, and some that have only 0x5D.
> >
> > The driver sets the brightness using 0x5a and accepts keyboard
> > shortcuts only from 0x5a. Therefore it needs to init only with 0x5a.
> >
> > How would those laptops regress and in what way?
> >
>
> The media keys fail to work (vol, mic, rog). Can you please accept that
> I do know some laptops used only 0x5D, and these are older models,
> around 5+ years. The only thing I have to go on is my memory
> unfortunately, and I've been trying to find the concrete examples.

I just looked at the history. Yeah it seems you added ID1 in 2020 with
some other commands. But on the same commit you blocked 0x5d and 0x5e,
so it means those keyboards use 0x5a to send keyboard events.

Nevertheless, it is not worth looking up or risking regressions for
old hardware. I will readd 0x5d, 0x5e for
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD and
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2, since those are the ones you
added the inits with.

But I will still keep them off for the Z13 and Ally.

By the way,

Antheas

> >> I think you will need to change this to try both 0x5A and 0x5D sorry.
> >> The older models using 0x1854, 0x1869, 0x1866 PID may regress and
> >> although I'm reasonably confident there won't be issues due to age of
> >> those, it's not a risk I'm willing to take, I've spent all morning
> >> trawling through archives of info and I can't actually verify this other
> >> than from my memory.
> >
> > For devices that support RGB, only when RGB is set, 0x5D is initialized too.
>
> Sure. But as I've said above.. Please add both to init. It's only done
> once, and it doesn't hurt anything plus doesn't risk regressing older
> hardware.
>
> If I can get the proper evidence that only 0x5A is required I'm happy to
> use only that, but until then I don't want that risk. And it's only a
> small thing here.
>
> Cheers,
> Luke.
>
> >
> >> I mentioned 0x5E being used for some of the oddball devices like slash
> >> and anime, don't worry about that one, it's a bridge that can be crossed
> >> at a later time. But it does illustrate that other report ID have been
> >> used for init.
> >>
> >> Otherwise the cleanup is good.
> >>
> >> No other comments required and I'll sign off with the above corrections.
> >>
> >> Cheers,
> >> Luke
> >>
> >>> -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> >>> -                     ret = asus_kbd_disable_oobe(hdev);
> >>> -                     if (ret < 0)
> >>> -                             return ret;
> >>> -             }
> >>> -     } else {
> >>> -             /* Initialize keyboard */
> >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> >>> -             if (ret < 0)
> >>> -                     return ret;
> >>> +     /* Get keyboard functions */
> >>> +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> >>> +     if (ret < 0)
> >>> +             return ret;
> >>>
> >>> -             /* Get keyboard functions */
> >>> -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> >>> +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> >>> +             ret = asus_kbd_disable_oobe(hdev);
> >>>                if (ret < 0)
> >>>                        return ret;
> >>> -
> >>> -             /* Check for backlight support */
> >>> -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> >>> -                     return -ENODEV;
> >>>        }
> >>>
> >>> +     /* Check for backlight support */
> >>> +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> >>> +             return -ENODEV;
> >>> +
> >>>        drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
> >>>                                              sizeof(struct asus_kbd_leds),
> >>>                                              GFP_KERNEL);
> >>
>

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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 23:53         ` Antheas Kapenekakis
@ 2025-03-22 23:54           ` Antheas Kapenekakis
  2025-03-23  0:10             ` Luke D. Jones
  2025-03-23  0:14           ` Luke D. Jones
  1 sibling, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-22 23:54 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sun, 23 Mar 2025 at 00:53, Antheas Kapenekakis <lkml@antheas.dev> wrote:
>
> On Sun, 23 Mar 2025 at 00:29, Luke D. Jones <luke@ljones.dev> wrote:
> >
> > On 23/03/25 12:05, Antheas Kapenekakis wrote:
> > > On Sat, 22 Mar 2025 at 23:01, Luke D. Jones <luke@ljones.dev> wrote:
> > >>
> > >> On 22/03/25 23:27, Antheas Kapenekakis wrote:
> > >>> Currently, asus_kbd_init() uses a reverse engineered init sequence
> > >>> from Windows, which contains the handshakes from multiple programs.
> > >>> Keep the main one, which is 0x5a (meant for brightness drivers).
> > >>>
> > >>> In addition, perform a get_response and check if the response is the
> > >>> same. To avoid regressions, print an error if the response does not
> > >>> match instead of rejecting device.
> > >>>
> > >>> Then, refactor asus_kbd_get_functions() to use the same ID it is called
> > >>> with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
> > >>> in the future.
> > >>>
> > >>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > >>> ---
> > >>>    drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
> > >>>    1 file changed, 46 insertions(+), 36 deletions(-)
> > >>>
> > >>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > >>> index 46e3e42f9eb5f..8d4df1b6f143b 100644
> > >>> --- a/drivers/hid/hid-asus.c
> > >>> +++ b/drivers/hid/hid-asus.c
> > >>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> > >>>    #define FEATURE_REPORT_ID 0x0d
> > >>>    #define INPUT_REPORT_ID 0x5d
> > >>>    #define FEATURE_KBD_REPORT_ID 0x5a
> > >>> -#define FEATURE_KBD_REPORT_SIZE 16
> > >>> +#define FEATURE_KBD_REPORT_SIZE 64
> > >>>    #define FEATURE_KBD_LED_REPORT_ID1 0x5d
> > >>>    #define FEATURE_KBD_LED_REPORT_ID2 0x5e
> > >>>
> > >>> @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
> > >>>
> > >>>    static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
> > >>>    {
> > >>> -     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
> > >>> -                  0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> > >>> +     /*
> > >>> +      * Asus handshake identifying us as a driver (0x5A)
> > >>> +      * 0x5A then ASCII for "ASUS Tech.Inc."
> > >>> +      * 0x5D is for userspace Windows applications.
> > >>> +      *
> > >>> +      * The handshake is first sent as a set_report, then retrieved
> > >>> +      * from a get_report to verify the response.
> > >>> +      */
> > >>
> > >> This entire comment is not required, especially not the last paragraph.
> > >>   From what I've seen in .dll reversing attempts there's no real
> > >> distinction from driver/app and it's simply an init/enable sequence
> > >> common to almost every ITE MCU that ASUS have used (slash, anime, Ally).
> > >>
> > >> Please remove.
> > >
> > > It is a context comment but can be removed.
> > >
> > >>> +     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
> > >>> +             0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> > >>> +     u8 *readbuf;
> > >>>        int ret;
> > >>>
> > >>>        ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
> > >>> -     if (ret < 0)
> > >>> -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
> > >>> +     if (ret < 0) {
> > >>> +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
> > >>> +             return ret;
> > >>> +     }
> > >>>
> > >>> +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
> > >>> +     if (!readbuf)
> > >>> +             return -ENOMEM;
> > >>> +
> > >>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> > >>> +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> > >>> +                              HID_REQ_GET_REPORT);
> > >>> +     if (ret < 0) {
> > >>> +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
> > >>> +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
> > >>> +             hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
> > >>> +                     FEATURE_KBD_REPORT_SIZE, readbuf);
> > >>> +             // Do not return error if handshake is wrong to avoid regressions
> > >>> +     }
> > >>> +
> > >>> +     kfree(readbuf);
> > >>>        return ret;
> > >>>    }
> > >>>
> > >>> @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
> > >>>        if (!readbuf)
> > >>>                return -ENOMEM;
> > >>>
> > >>> -     ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
> > >>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> > >>>                                 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> > >>>                                 HID_REQ_GET_REPORT);
> > >>>        if (ret < 0) {
> > >>> @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> > >>>        unsigned char kbd_func;
> > >>>        int ret;
> > >>>
> > >>> -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> > >>> -             /* Initialize keyboard */
> > >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > >>> -             if (ret < 0)
> > >>> -                     return ret;
> > >>> -
> > >>> -             /* The LED endpoint is initialised in two HID */
> > >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
> > >>> -             if (ret < 0)
> > >>> -                     return ret;
> > >>> -
> > >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
> > >>> -             if (ret < 0)
> > >>> -                     return ret;
> > >>> +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > >>> +     if (ret < 0)
> > >>> +             return ret;
> > >>>
> > >>
> > >> I don't have any non-ROG equipment to test. There have been some cases
> > >> of Vivobook using the same MCU, but these otherwise used something else.
> > >> And the oldest hardware I have uses a 0x1866, which uses the same init
> > >> sequence but works with both 0x5A and 0x5D report ID for init (on same
> > >> endpoint). There are instances of other laptops that accept both 0x5A
> > >> and 0x5D, and some that have only 0x5D.
> > >
> > > The driver sets the brightness using 0x5a and accepts keyboard
> > > shortcuts only from 0x5a. Therefore it needs to init only with 0x5a.
> > >
> > > How would those laptops regress and in what way?
> > >
> >
> > The media keys fail to work (vol, mic, rog). Can you please accept that
> > I do know some laptops used only 0x5D, and these are older models,
> > around 5+ years. The only thing I have to go on is my memory
> > unfortunately, and I've been trying to find the concrete examples.
>
> I just looked at the history. Yeah it seems you added ID1 in 2020 with
> some other commands. But on the same commit you blocked 0x5d and 0x5e,
> so it means those keyboards use 0x5a to send keyboard events.
>
> Nevertheless, it is not worth looking up or risking regressions for
> old hardware. I will readd 0x5d, 0x5e for
> USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD and
> USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2, since those are the ones you
> added the inits with.
>
> But I will still keep them off for the Z13 and Ally.
>
> By the way,

I guess I did not finish this. I was going to express some concern
about the Claymore keyboard. But it seems it does not have a backlight
quirk so it is ok

> Antheas
>
> > >> I think you will need to change this to try both 0x5A and 0x5D sorry.
> > >> The older models using 0x1854, 0x1869, 0x1866 PID may regress and
> > >> although I'm reasonably confident there won't be issues due to age of
> > >> those, it's not a risk I'm willing to take, I've spent all morning
> > >> trawling through archives of info and I can't actually verify this other
> > >> than from my memory.
> > >
> > > For devices that support RGB, only when RGB is set, 0x5D is initialized too.
> >
> > Sure. But as I've said above.. Please add both to init. It's only done
> > once, and it doesn't hurt anything plus doesn't risk regressing older
> > hardware.
> >
> > If I can get the proper evidence that only 0x5A is required I'm happy to
> > use only that, but until then I don't want that risk. And it's only a
> > small thing here.
> >
> > Cheers,
> > Luke.
> >
> > >
> > >> I mentioned 0x5E being used for some of the oddball devices like slash
> > >> and anime, don't worry about that one, it's a bridge that can be crossed
> > >> at a later time. But it does illustrate that other report ID have been
> > >> used for init.
> > >>
> > >> Otherwise the cleanup is good.
> > >>
> > >> No other comments required and I'll sign off with the above corrections.
> > >>
> > >> Cheers,
> > >> Luke
> > >>
> > >>> -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> > >>> -                     ret = asus_kbd_disable_oobe(hdev);
> > >>> -                     if (ret < 0)
> > >>> -                             return ret;
> > >>> -             }
> > >>> -     } else {
> > >>> -             /* Initialize keyboard */
> > >>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > >>> -             if (ret < 0)
> > >>> -                     return ret;
> > >>> +     /* Get keyboard functions */
> > >>> +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> > >>> +     if (ret < 0)
> > >>> +             return ret;
> > >>>
> > >>> -             /* Get keyboard functions */
> > >>> -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> > >>> +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
> > >>> +             ret = asus_kbd_disable_oobe(hdev);
> > >>>                if (ret < 0)
> > >>>                        return ret;
> > >>> -
> > >>> -             /* Check for backlight support */
> > >>> -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> > >>> -                     return -ENODEV;
> > >>>        }
> > >>>
> > >>> +     /* Check for backlight support */
> > >>> +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
> > >>> +             return -ENODEV;
> > >>> +
> > >>>        drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
> > >>>                                              sizeof(struct asus_kbd_leds),
> > >>>                                              GFP_KERNEL);
> > >>
> >

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

* Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers
  2025-03-22 10:27 ` [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers Antheas Kapenekakis
@ 2025-03-23  0:02   ` Luke D. Jones
  2025-03-24 11:31   ` Hans de Goede
  1 sibling, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  0:02 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:27, Antheas Kapenekakis wrote:
> Some devices, such as the Z13 have multiple AURA devices connected
> to them by USB. In addition, they might have a WMI interface for
> RGB. In Windows, Armoury Crate exposes a unified brightness slider
> for all of them, with 3 brightness levels.
> 
> Therefore, to be synergistic in Linux, and support existing tooling
> such as UPower, allow adding listeners to the RGB device of the WMI
> interface. If WMI does not exist, lazy initialize the interface.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
>   include/linux/platform_data/x86/asus-wmi.h |  16 +++
>   2 files changed, 117 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index 38ef778e8c19b..95ef9b1d321bb 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -254,6 +254,8 @@ struct asus_wmi {
>   	int tpd_led_wk;
>   	struct led_classdev kbd_led;
>   	int kbd_led_wk;
> +	bool kbd_led_avail;
> +	bool kbd_led_registered;
>   	struct led_classdev lightbar_led;
>   	int lightbar_led_wk;
>   	struct led_classdev micmute_led;
> @@ -1487,6 +1489,53 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
>   
>   /* LEDs ***********************************************************************/
>   
> +struct asus_hid_ref {
> +	struct list_head listeners;
> +	struct asus_wmi *asus;
> +	spinlock_t lock;
> +};
> +
> +struct asus_hid_ref asus_ref = {
> +	.listeners = LIST_HEAD_INIT(asus_ref.listeners),
> +	.asus = NULL,
> +	.lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
> +};
> +
> +int asus_hid_register_listener(struct asus_hid_listener *bdev)
> +{
> +	unsigned long flags;
> +	int ret = 0;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	list_add_tail(&bdev->list, &asus_ref.listeners);
> +	if (asus_ref.asus) {
> +		if (asus_ref.asus->kbd_led_registered && asus_ref.asus->kbd_led_wk >= 0)
> +			bdev->brightness_set(bdev, asus_ref.asus->kbd_led_wk);
> +
> +		if (!asus_ref.asus->kbd_led_registered) {
> +			ret = led_classdev_register(
> +				&asus_ref.asus->platform_device->dev,
> +				&asus_ref.asus->kbd_led);
> +			if (!ret)
> +				asus_ref.asus->kbd_led_registered = true;
> +		}
> +	}
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(asus_hid_register_listener);
> +
> +void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	list_del(&bdev->list);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
> +
>   /*
>    * These functions actually update the LED's, and are called from a
>    * workqueue. By doing this as separate work rather than when the LED
> @@ -1566,6 +1615,7 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
>   
>   static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>   {
> +	struct asus_hid_listener *listener;
>   	struct asus_wmi *asus;
>   	int max_level;
>   
> @@ -1573,25 +1623,39 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>   	max_level = asus->kbd_led.max_brightness;
>   
>   	asus->kbd_led_wk = clamp_val(value, 0, max_level);
> -	kbd_led_update(asus);
> +
> +	if (asus->kbd_led_avail)
> +		kbd_led_update(asus);
> +
> +	list_for_each_entry(listener, &asus_ref.listeners, list)
> +		listener->brightness_set(listener, asus->kbd_led_wk);
>   }
>   
>   static void kbd_led_set(struct led_classdev *led_cdev,
>   			enum led_brightness value)
>   {
> +	unsigned long flags;
> +
>   	/* Prevent disabling keyboard backlight on module unregister */
>   	if (led_cdev->flags & LED_UNREGISTERING)
>   		return;
>   
> +	spin_lock_irqsave(&asus_ref.lock, flags);
>   	do_kbd_led_set(led_cdev, value);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
>   }
>   
>   static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
>   {
> -	struct led_classdev *led_cdev = &asus->kbd_led;
> +	struct led_classdev *led_cdev;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	led_cdev = &asus->kbd_led;
>   
>   	do_kbd_led_set(led_cdev, value);
>   	led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
>   }
>   
>   static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> @@ -1601,6 +1665,9 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
>   
>   	asus = container_of(led_cdev, struct asus_wmi, kbd_led);
>   
> +	if (!asus->kbd_led_avail)
> +		return asus->kbd_led_wk;
> +
>   	retval = kbd_led_read(asus, &value, NULL);
>   	if (retval < 0)
>   		return retval;
> @@ -1716,7 +1783,14 @@ static int camera_led_set(struct led_classdev *led_cdev,
>   
>   static void asus_wmi_led_exit(struct asus_wmi *asus)
>   {
> -	led_classdev_unregister(&asus->kbd_led);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	asus_ref.asus = NULL;
> +	if (asus->kbd_led_registered)
> +		led_classdev_unregister(&asus->kbd_led);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
>   	led_classdev_unregister(&asus->tpd_led);
>   	led_classdev_unregister(&asus->wlan_led);
>   	led_classdev_unregister(&asus->lightbar_led);

I'm getting the following on module unload:

[  247.540357] BUG: sleeping function called from invalid context at 
kernel/locking/rwsem.c:1576
[  247.540360] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 
37685, name: rmmod
[  247.540361] preempt_count: 1, expected: 0
[  247.540361] RCU nest depth: 0, expected: 0
[  247.540362] CPU: 6 UID: 0 PID: 37685 Comm: rmmod Not tainted 
6.14.0-rc7+ #164
[  247.540364] Hardware name: ASUSTeK COMPUTER INC. ROG Zephyrus M16 
GU604VY_GU604VY_00130747B/GU604VY, BIOS GU604VY.313 08/13/2024
[  247.540365] Call Trace:
[  247.540366]  <TASK>
[  247.540366]  dump_stack_lvl+0x5d/0x80
[  247.540372]  __might_resched.cold+0xba/0xc9
[  247.540374]  down_write+0x19/0x70
[  247.540375]  led_classdev_unregister+0x31/0x110
[  247.540378]  asus_wmi_led_exit+0x96/0xa0 [asus_wmi]
[  247.540382]  asus_wmi_remove+0x71/0xf0 [asus_wmi]
[  247.540385]  platform_remove+0x1b/0x30
[  247.540386]  device_release_driver_internal+0x197/0x200
[  247.540388]  bus_remove_device+0xc4/0x130
[  247.540389]  device_del+0x15f/0x3e0
[  247.540391]  platform_device_del+0x20/0x80
[  247.540392]  platform_device_unregister+0xd/0x30
[  247.540393]  asus_wmi_unregister_driver+0x20/0x40 [asus_wmi]
[  247.540395]  __do_sys_delete_module+0x1d1/0x310
[  247.540397]  do_syscall_64+0x82/0x160
[  247.540398]  ? syscall_exit_to_user_mode+0x10/0x1e0
[  247.540399]  ? do_syscall_64+0x8e/0x160
[  247.540399]  ? syscall_exit_to_user_mode+0x10/0x1e0
[  247.540400]  ? do_syscall_64+0x8e/0x160
[  247.540401]  ? syscall_exit_to_user_mode+0x10/0x1e0
[  247.540401]  ? do_syscall_64+0x8e/0x160
[  247.540402]  ? exc_page_fault+0x6a/0xc0
[  247.540402]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  247.540404] RIP: 0033:0x7fc1164d3bcb

The error message indicates the code is in an atomic context 
(`in_atomic(): 1`), with interrupts disabled (`irqs_disabled(): 1`).

I would move:

 > +	if (asus->kbd_led_registered)
 > +		led_classdev_unregister(&asus->kbd_led);

to outside of the spinlock. I quickly tested this and it fixes that 
particular issue.

> @@ -1730,6 +1804,8 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
>   static int asus_wmi_led_init(struct asus_wmi *asus)
>   {
>   	int rv = 0, num_rgb_groups = 0, led_val;
> +	unsigned long flags;
> +	bool has_listeners;
>   
>   	if (asus->kbd_rgb_dev)
>   		kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
> @@ -1754,24 +1830,37 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
>   			goto error;
>   	}
>   
> -	if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
> -		pr_info("using asus-wmi for asus::kbd_backlight\n");
> +	asus->kbd_led.name = "asus::kbd_backlight";
> +	asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> +	asus->kbd_led.brightness_set = kbd_led_set;
> +	asus->kbd_led.brightness_get = kbd_led_get;
> +	asus->kbd_led.max_brightness = 3;
> +	asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
> +
> +	if (asus->kbd_led_avail)
>   		asus->kbd_led_wk = led_val;
> -		asus->kbd_led.name = "asus::kbd_backlight";
> -		asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> -		asus->kbd_led.brightness_set = kbd_led_set;
> -		asus->kbd_led.brightness_get = kbd_led_get;
> -		asus->kbd_led.max_brightness = 3;
> +	else
> +		asus->kbd_led_wk = -1;
> +
> +	if (asus->kbd_led_avail && num_rgb_groups != 0)
> +		asus->kbd_led.groups = kbd_rgb_mode_groups;
>   
> -		if (num_rgb_groups != 0)
> -			asus->kbd_led.groups = kbd_rgb_mode_groups;
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	has_listeners = !list_empty(&asus_ref.listeners);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
>   
> +	if (asus->kbd_led_avail || has_listeners) {
>   		rv = led_classdev_register(&asus->platform_device->dev,
>   					   &asus->kbd_led);
>   		if (rv)
>   			goto error;
> +		asus->kbd_led_registered = true;
>   	}
>   
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	asus_ref.asus = asus;
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
>   	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
>   			&& (asus->driver->quirks->wapf > 0)) {
>   		INIT_WORK(&asus->wlan_led_work, wlan_led_update);
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index 783e2a336861b..ec8b0c585a63f 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -157,14 +157,30 @@
>   #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK	0x0000FF00
>   #define ASUS_WMI_DSTS_LIGHTBAR_MASK	0x0000000F
>   
> +struct asus_hid_listener {
> +	struct list_head list;
> +	void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
> +};
> +
>   #if IS_REACHABLE(CONFIG_ASUS_WMI)
>   int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
> +
> +int asus_hid_register_listener(struct asus_hid_listener *cdev);
> +void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
>   #else
>   static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
>   					   u32 *retval)
>   {
>   	return -ENODEV;
>   }
> +
> +static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
> +{
> +	return -ENODEV;
> +}
> +static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> +{
> +}
>   #endif
>   
>   /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */

I did additional testing on the older 0x1866 which has WMI plus HID 
brightness ability and it appears to work fine. I might add debug 
logging later to gie us some insight in to this exact situation though.

Fixing the spinlock, I'll add my tags:

Reviewed-by: Luke D. Jones <luke@ljones.dev>
Tested-by: Luke D. Jones <luke@ljones.dev>


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

* Re: [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk
  2025-03-22 10:28 ` [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk Antheas Kapenekakis
@ 2025-03-23  0:05   ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  0:05 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:28, Antheas Kapenekakis wrote:
> The quirk for selecting whether keyboard backlight should be controlled
> by HID or WMI is not needed anymore, so remove it.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   include/linux/platform_data/x86/asus-wmi.h | 40 ----------------------
>   1 file changed, 40 deletions(-)
> 
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index ec8b0c585a63f..c513b5a732323 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -183,44 +183,4 @@ static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
>   }
>   #endif
>   
> -/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
> -static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = {
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"),
> -		},
> -	},
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"),
> -		},
> -	},
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"),
> -		},
> -	},
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
> -		},
> -	},
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_BOARD_NAME, "GA403U"),
> -		},
> -	},
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
> -		},
> -	},
> -	{
> -		.matches = {
> -			DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
> -		},
> -	},
> -	{ },
> -};
> -
>   #endif	/* __PLATFORM_DATA_X86_ASUS_WMI_H */

I might have mentioned it already but I think you should squash this 
commit into where asus_use_hid_led_dmi_ids use is removed. It's 
something I've been asked to do on a few occasions to prevent possible 
error/warning on patch tests (unused).

Otherwise if it stays separate:

Reviewed-by: Luke D. Jones <luke@ljones.dev>


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

* Re: [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler
  2025-03-22 10:28 ` [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler Antheas Kapenekakis
@ 2025-03-23  0:06   ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  0:06 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:28, Antheas Kapenekakis wrote:
> Currenlty, the keyboard brightness control of Asus WMI keyboards is
> handled in the kernel, which leads to the shortcut going from
> brightness 0, to 1, to 2, and 3.
> 
> However, for HID keyboards it is exposed as a key and handled by the
> user's desktop environment. For the toggle button, this means that
> brightness control becomes on/off. In addition, in the absence of a
> DE, the keyboard brightness does not work.
> 
> Therefore, expose an event handler for the keyboard brightness control
> which can then be used by hid-asus.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/platform/x86/asus-wmi.c            | 39 ++++++++++++++++++++++
>   include/linux/platform_data/x86/asus-wmi.h | 11 ++++++
>   2 files changed, 50 insertions(+)
> 
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index 95ef9b1d321bb..5ebe141294ecf 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -1536,6 +1536,45 @@ void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
>   }
>   EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
>   
> +static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
> +
> +int asus_hid_event(enum asus_hid_event event)
> +{
> +	unsigned long flags;
> +	int brightness;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	if (!asus_ref.asus || !asus_ref.asus->kbd_led_registered) {
> +		spin_unlock_irqrestore(&asus_ref.lock, flags);
> +		return -EBUSY;
> +	}
> +	brightness = asus_ref.asus->kbd_led_wk;
> +
> +	switch (event) {
> +	case ASUS_EV_BRTUP:
> +		brightness += 1;
> +		break;
> +	case ASUS_EV_BRTDOWN:
> +		brightness -= 1;
> +		break;
> +	case ASUS_EV_BRTTOGGLE:
> +		if (brightness >= 3)
> +			brightness = 0;
> +		else
> +			brightness += 1;
> +		break;
> +	}
> +
> +	do_kbd_led_set(&asus_ref.asus->kbd_led, brightness);
> +	led_classdev_notify_brightness_hw_changed(&asus_ref.asus->kbd_led,
> +						  asus_ref.asus->kbd_led_wk);
> +
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(asus_hid_event);
> +
>   /*
>    * These functions actually update the LED's, and are called from a
>    * workqueue. By doing this as separate work rather than when the LED
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index c513b5a732323..9adbe8abef090 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -162,11 +162,18 @@ struct asus_hid_listener {
>   	void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
>   };
>   
> +enum asus_hid_event {
> +	ASUS_EV_BRTUP,
> +	ASUS_EV_BRTDOWN,
> +	ASUS_EV_BRTTOGGLE,
> +};
> +
>   #if IS_REACHABLE(CONFIG_ASUS_WMI)
>   int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
>   
>   int asus_hid_register_listener(struct asus_hid_listener *cdev);
>   void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
> +int asus_hid_event(enum asus_hid_event event);
>   #else
>   static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
>   					   u32 *retval)
> @@ -181,6 +188,10 @@ static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
>   static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
>   {
>   }
> +static inline int asus_hid_event(enum asus_hid_event event)
> +{
> +	return -ENODEV;
> +}
>   #endif
>   
>   #endif	/* __PLATFORM_DATA_X86_ASUS_WMI_H */


Reviewed-by: Luke D. Jones <luke@ljones.dev>
Tested-by: Luke D. Jones <luke@ljones.dev>

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

* Re: [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler
  2025-03-22 10:28 ` [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler Antheas Kapenekakis
@ 2025-03-23  0:08   ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  0:08 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:28, Antheas Kapenekakis wrote:
> If the asus-wmi brightness handler is available, send the
> keyboard brightness events to it instead of passing them
> to userspace. If it is not, fall back to sending them to it.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 11 +++++++++++
>   1 file changed, 11 insertions(+)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index c40b5c14c797f..905453a4eb5b7 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -318,6 +318,17 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
>   			 usage->hid & HID_USAGE);
>   	}
>   
> +	if (usage->type == EV_KEY && value) {
> +		switch (usage->code) {
> +		case KEY_KBDILLUMUP:
> +			return !asus_hid_event(ASUS_EV_BRTUP);
> +		case KEY_KBDILLUMDOWN:
> +			return !asus_hid_event(ASUS_EV_BRTDOWN);
> +		case KEY_KBDILLUMTOGGLE:
> +			return !asus_hid_event(ASUS_EV_BRTTOGGLE);
> +		}
> +	}
> +
>   	return 0;
>   }
>   

Possible commit squash candidate in to patch 7. I'll defer to Ilpo and 
Hans though.

Reviewed-by: Luke D. Jones <luke@ljones.dev>
Tested-by: Luke D. Jones <luke@ljones.dev>

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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 23:54           ` Antheas Kapenekakis
@ 2025-03-23  0:10             ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  0:10 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On 23/03/25 12:54, Antheas Kapenekakis wrote:
> On Sun, 23 Mar 2025 at 00:53, Antheas Kapenekakis <lkml@antheas.dev> wrote:
>>
>> On Sun, 23 Mar 2025 at 00:29, Luke D. Jones <luke@ljones.dev> wrote:
>>>
>>> On 23/03/25 12:05, Antheas Kapenekakis wrote:
>>>> On Sat, 22 Mar 2025 at 23:01, Luke D. Jones <luke@ljones.dev> wrote:
>>>>>
>>>>> On 22/03/25 23:27, Antheas Kapenekakis wrote:
>>>>>> Currently, asus_kbd_init() uses a reverse engineered init sequence
>>>>>> from Windows, which contains the handshakes from multiple programs.
>>>>>> Keep the main one, which is 0x5a (meant for brightness drivers).
>>>>>>
>>>>>> In addition, perform a get_response and check if the response is the
>>>>>> same. To avoid regressions, print an error if the response does not
>>>>>> match instead of rejecting device.
>>>>>>
>>>>>> Then, refactor asus_kbd_get_functions() to use the same ID it is called
>>>>>> with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
>>>>>> in the future.
>>>>>>
>>>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>>>>> ---
>>>>>>     drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
>>>>>>     1 file changed, 46 insertions(+), 36 deletions(-)
>>>>>>
>>>>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>>>>> index 46e3e42f9eb5f..8d4df1b6f143b 100644
>>>>>> --- a/drivers/hid/hid-asus.c
>>>>>> +++ b/drivers/hid/hid-asus.c
>>>>>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>>>>     #define FEATURE_REPORT_ID 0x0d
>>>>>>     #define INPUT_REPORT_ID 0x5d
>>>>>>     #define FEATURE_KBD_REPORT_ID 0x5a
>>>>>> -#define FEATURE_KBD_REPORT_SIZE 16
>>>>>> +#define FEATURE_KBD_REPORT_SIZE 64
>>>>>>     #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>>>>>>     #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>>>>>>
>>>>>> @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>>>>>>
>>>>>>     static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>>>>>>     {
>>>>>> -     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
>>>>>> -                  0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>>>>> +     /*
>>>>>> +      * Asus handshake identifying us as a driver (0x5A)
>>>>>> +      * 0x5A then ASCII for "ASUS Tech.Inc."
>>>>>> +      * 0x5D is for userspace Windows applications.
>>>>>> +      *
>>>>>> +      * The handshake is first sent as a set_report, then retrieved
>>>>>> +      * from a get_report to verify the response.
>>>>>> +      */
>>>>>
>>>>> This entire comment is not required, especially not the last paragraph.
>>>>>    From what I've seen in .dll reversing attempts there's no real
>>>>> distinction from driver/app and it's simply an init/enable sequence
>>>>> common to almost every ITE MCU that ASUS have used (slash, anime, Ally).
>>>>>
>>>>> Please remove.
>>>>
>>>> It is a context comment but can be removed.
>>>>
>>>>>> +     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
>>>>>> +             0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>>>>> +     u8 *readbuf;
>>>>>>         int ret;
>>>>>>
>>>>>>         ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
>>>>>> -     if (ret < 0)
>>>>>> -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
>>>>>> +     if (ret < 0) {
>>>>>> +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
>>>>>> +             return ret;
>>>>>> +     }
>>>>>>
>>>>>> +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
>>>>>> +     if (!readbuf)
>>>>>> +             return -ENOMEM;
>>>>>> +
>>>>>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>>>>> +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>>>>> +                              HID_REQ_GET_REPORT);
>>>>>> +     if (ret < 0) {
>>>>>> +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
>>>>>> +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
>>>>>> +             hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
>>>>>> +                     FEATURE_KBD_REPORT_SIZE, readbuf);
>>>>>> +             // Do not return error if handshake is wrong to avoid regressions
>>>>>> +     }
>>>>>> +
>>>>>> +     kfree(readbuf);
>>>>>>         return ret;
>>>>>>     }
>>>>>>
>>>>>> @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>>>>>>         if (!readbuf)
>>>>>>                 return -ENOMEM;
>>>>>>
>>>>>> -     ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
>>>>>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>>>>>                                  FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>>>>>                                  HID_REQ_GET_REPORT);
>>>>>>         if (ret < 0) {
>>>>>> @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>>>>         unsigned char kbd_func;
>>>>>>         int ret;
>>>>>>
>>>>>> -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>>>>> -             /* Initialize keyboard */
>>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>>>> -             if (ret < 0)
>>>>>> -                     return ret;
>>>>>> -
>>>>>> -             /* The LED endpoint is initialised in two HID */
>>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
>>>>>> -             if (ret < 0)
>>>>>> -                     return ret;
>>>>>> -
>>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
>>>>>> -             if (ret < 0)
>>>>>> -                     return ret;
>>>>>> +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>>>> +     if (ret < 0)
>>>>>> +             return ret;
>>>>>>
>>>>>
>>>>> I don't have any non-ROG equipment to test. There have been some cases
>>>>> of Vivobook using the same MCU, but these otherwise used something else.
>>>>> And the oldest hardware I have uses a 0x1866, which uses the same init
>>>>> sequence but works with both 0x5A and 0x5D report ID for init (on same
>>>>> endpoint). There are instances of other laptops that accept both 0x5A
>>>>> and 0x5D, and some that have only 0x5D.
>>>>
>>>> The driver sets the brightness using 0x5a and accepts keyboard
>>>> shortcuts only from 0x5a. Therefore it needs to init only with 0x5a.
>>>>
>>>> How would those laptops regress and in what way?
>>>>
>>>
>>> The media keys fail to work (vol, mic, rog). Can you please accept that
>>> I do know some laptops used only 0x5D, and these are older models,
>>> around 5+ years. The only thing I have to go on is my memory
>>> unfortunately, and I've been trying to find the concrete examples.
>>
>> I just looked at the history. Yeah it seems you added ID1 in 2020 with
>> some other commands. But on the same commit you blocked 0x5d and 0x5e,
>> so it means those keyboards use 0x5a to send keyboard events.
>>
>> Nevertheless, it is not worth looking up or risking regressions for
>> old hardware. I will readd 0x5d, 0x5e for
>> USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD and
>> USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2, since those are the ones you
>> added the inits with.
>>
>> But I will still keep them off for the Z13 and Ally.
>>
>> By the way,
> 
> I guess I did not finish this. I was going to express some concern
> about the Claymore keyboard. But it seems it does not have a backlight
> quirk so it is ok
> 

The external keyboards are a bit funky yeah. I don't think the Claymore 
is in very wide use at all either. In any case you're correct.

>> Antheas
>>
>>>>> I think you will need to change this to try both 0x5A and 0x5D sorry.
>>>>> The older models using 0x1854, 0x1869, 0x1866 PID may regress and
>>>>> although I'm reasonably confident there won't be issues due to age of
>>>>> those, it's not a risk I'm willing to take, I've spent all morning
>>>>> trawling through archives of info and I can't actually verify this other
>>>>> than from my memory.
>>>>
>>>> For devices that support RGB, only when RGB is set, 0x5D is initialized too.
>>>
>>> Sure. But as I've said above.. Please add both to init. It's only done
>>> once, and it doesn't hurt anything plus doesn't risk regressing older
>>> hardware.
>>>
>>> If I can get the proper evidence that only 0x5A is required I'm happy to
>>> use only that, but until then I don't want that risk. And it's only a
>>> small thing here.
>>>
>>> Cheers,
>>> Luke.
>>>
>>>>
>>>>> I mentioned 0x5E being used for some of the oddball devices like slash
>>>>> and anime, don't worry about that one, it's a bridge that can be crossed
>>>>> at a later time. But it does illustrate that other report ID have been
>>>>> used for init.
>>>>>
>>>>> Otherwise the cleanup is good.
>>>>>
>>>>> No other comments required and I'll sign off with the above corrections.
>>>>>
>>>>> Cheers,
>>>>> Luke
>>>>>
>>>>>> -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>>>>> -                     ret = asus_kbd_disable_oobe(hdev);
>>>>>> -                     if (ret < 0)
>>>>>> -                             return ret;
>>>>>> -             }
>>>>>> -     } else {
>>>>>> -             /* Initialize keyboard */
>>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>>>> -             if (ret < 0)
>>>>>> -                     return ret;
>>>>>> +     /* Get keyboard functions */
>>>>>> +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>>>>> +     if (ret < 0)
>>>>>> +             return ret;
>>>>>>
>>>>>> -             /* Get keyboard functions */
>>>>>> -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>>>>> +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>>>>> +             ret = asus_kbd_disable_oobe(hdev);
>>>>>>                 if (ret < 0)
>>>>>>                         return ret;
>>>>>> -
>>>>>> -             /* Check for backlight support */
>>>>>> -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>>>>> -                     return -ENODEV;
>>>>>>         }
>>>>>>
>>>>>> +     /* Check for backlight support */
>>>>>> +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>>>>> +             return -ENODEV;
>>>>>> +
>>>>>>         drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>>>>>>                                               sizeof(struct asus_kbd_leds),
>>>>>>                                               GFP_KERNEL);
>>>>>
>>>


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

* Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec
  2025-03-22 23:53         ` Antheas Kapenekakis
  2025-03-22 23:54           ` Antheas Kapenekakis
@ 2025-03-23  0:14           ` Luke D. Jones
  1 sibling, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  0:14 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On 23/03/25 12:53, Antheas Kapenekakis wrote:
> On Sun, 23 Mar 2025 at 00:29, Luke D. Jones <luke@ljones.dev> wrote:
>>
>> On 23/03/25 12:05, Antheas Kapenekakis wrote:
>>> On Sat, 22 Mar 2025 at 23:01, Luke D. Jones <luke@ljones.dev> wrote:
>>>>
>>>> On 22/03/25 23:27, Antheas Kapenekakis wrote:
>>>>> Currently, asus_kbd_init() uses a reverse engineered init sequence
>>>>> from Windows, which contains the handshakes from multiple programs.
>>>>> Keep the main one, which is 0x5a (meant for brightness drivers).
>>>>>
>>>>> In addition, perform a get_response and check if the response is the
>>>>> same. To avoid regressions, print an error if the response does not
>>>>> match instead of rejecting device.
>>>>>
>>>>> Then, refactor asus_kbd_get_functions() to use the same ID it is called
>>>>> with, instead of hardcoding it to 0x5a so that it may be used for 0x0d
>>>>> in the future.
>>>>>
>>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>>>> ---
>>>>>     drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
>>>>>     1 file changed, 46 insertions(+), 36 deletions(-)
>>>>>
>>>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>>>> index 46e3e42f9eb5f..8d4df1b6f143b 100644
>>>>> --- a/drivers/hid/hid-asus.c
>>>>> +++ b/drivers/hid/hid-asus.c
>>>>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>>>     #define FEATURE_REPORT_ID 0x0d
>>>>>     #define INPUT_REPORT_ID 0x5d
>>>>>     #define FEATURE_KBD_REPORT_ID 0x5a
>>>>> -#define FEATURE_KBD_REPORT_SIZE 16
>>>>> +#define FEATURE_KBD_REPORT_SIZE 64
>>>>>     #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>>>>>     #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>>>>>
>>>>> @@ -388,14 +388,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>>>>>
>>>>>     static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>>>>>     {
>>>>> -     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
>>>>> -                  0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>>>> +     /*
>>>>> +      * Asus handshake identifying us as a driver (0x5A)
>>>>> +      * 0x5A then ASCII for "ASUS Tech.Inc."
>>>>> +      * 0x5D is for userspace Windows applications.
>>>>> +      *
>>>>> +      * The handshake is first sent as a set_report, then retrieved
>>>>> +      * from a get_report to verify the response.
>>>>> +      */
>>>>
>>>> This entire comment is not required, especially not the last paragraph.
>>>>    From what I've seen in .dll reversing attempts there's no real
>>>> distinction from driver/app and it's simply an init/enable sequence
>>>> common to almost every ITE MCU that ASUS have used (slash, anime, Ally).
>>>>
>>>> Please remove.
>>>
>>> It is a context comment but can be removed.
>>>
>>>>> +     const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20,
>>>>> +             0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>>>> +     u8 *readbuf;
>>>>>         int ret;
>>>>>
>>>>>         ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
>>>>> -     if (ret < 0)
>>>>> -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
>>>>> +     if (ret < 0) {
>>>>> +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
>>>>> +             return ret;
>>>>> +     }
>>>>>
>>>>> +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
>>>>> +     if (!readbuf)
>>>>> +             return -ENOMEM;
>>>>> +
>>>>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>>>> +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>>>> +                              HID_REQ_GET_REPORT);
>>>>> +     if (ret < 0) {
>>>>> +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
>>>>> +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
>>>>> +             hid_err(hdev, "Asus handshake returned invalid response: %*ph\n",
>>>>> +                     FEATURE_KBD_REPORT_SIZE, readbuf);
>>>>> +             // Do not return error if handshake is wrong to avoid regressions
>>>>> +     }
>>>>> +
>>>>> +     kfree(readbuf);
>>>>>         return ret;
>>>>>     }
>>>>>
>>>>> @@ -417,7 +444,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>>>>>         if (!readbuf)
>>>>>                 return -ENOMEM;
>>>>>
>>>>> -     ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
>>>>> +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>>>>                                  FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>>>>                                  HID_REQ_GET_REPORT);
>>>>>         if (ret < 0) {
>>>>> @@ -540,42 +567,25 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>>>         unsigned char kbd_func;
>>>>>         int ret;
>>>>>
>>>>> -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>>>> -             /* Initialize keyboard */
>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>>> -             if (ret < 0)
>>>>> -                     return ret;
>>>>> -
>>>>> -             /* The LED endpoint is initialised in two HID */
>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
>>>>> -             if (ret < 0)
>>>>> -                     return ret;
>>>>> -
>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
>>>>> -             if (ret < 0)
>>>>> -                     return ret;
>>>>> +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>>> +     if (ret < 0)
>>>>> +             return ret;
>>>>>
>>>>
>>>> I don't have any non-ROG equipment to test. There have been some cases
>>>> of Vivobook using the same MCU, but these otherwise used something else.
>>>> And the oldest hardware I have uses a 0x1866, which uses the same init
>>>> sequence but works with both 0x5A and 0x5D report ID for init (on same
>>>> endpoint). There are instances of other laptops that accept both 0x5A
>>>> and 0x5D, and some that have only 0x5D.
>>>
>>> The driver sets the brightness using 0x5a and accepts keyboard
>>> shortcuts only from 0x5a. Therefore it needs to init only with 0x5a.
>>>
>>> How would those laptops regress and in what way?
>>>
>>
>> The media keys fail to work (vol, mic, rog). Can you please accept that
>> I do know some laptops used only 0x5D, and these are older models,
>> around 5+ years. The only thing I have to go on is my memory
>> unfortunately, and I've been trying to find the concrete examples.
> 
> I just looked at the history. Yeah it seems you added ID1 in 2020 with
> some other commands. But on the same commit you blocked 0x5d and 0x5e,
> so it means those keyboards use 0x5a to send keyboard events.
> 
> Nevertheless, it is not worth looking up or risking regressions for
> old hardware. I will readd 0x5d, 0x5e for
> USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD and
> USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2, since those are the ones you
> added the inits with.
> 

Thank you. Please know that I'm not trying to be a pain, I only don't 
want to take that risk when I know things were working for all before 
now, and that I know at least some laptops required 0x5D historically. I 
do know you can drop 0x5E here.

I saw we have:
#define INPUT_REPORT_ID 0x5d
#define FEATURE_KBD_LED_REPORT_ID1 0x5d

Maybe unifying to just one of these can be added as a cleanup?

Cheers,
Luke.

> But I will still keep them off for the Z13 and Ally.
> 
> By the way,
> 
> Antheas
> 
>>>> I think you will need to change this to try both 0x5A and 0x5D sorry.
>>>> The older models using 0x1854, 0x1869, 0x1866 PID may regress and
>>>> although I'm reasonably confident there won't be issues due to age of
>>>> those, it's not a risk I'm willing to take, I've spent all morning
>>>> trawling through archives of info and I can't actually verify this other
>>>> than from my memory.
>>>
>>> For devices that support RGB, only when RGB is set, 0x5D is initialized too.
>>
>> Sure. But as I've said above.. Please add both to init. It's only done
>> once, and it doesn't hurt anything plus doesn't risk regressing older
>> hardware.
>>
>> If I can get the proper evidence that only 0x5A is required I'm happy to
>> use only that, but until then I don't want that risk. And it's only a
>> small thing here.
>>
>> Cheers,
>> Luke.
>>
>>>
>>>> I mentioned 0x5E being used for some of the oddball devices like slash
>>>> and anime, don't worry about that one, it's a bridge that can be crossed
>>>> at a later time. But it does illustrate that other report ID have been
>>>> used for init.
>>>>
>>>> Otherwise the cleanup is good.
>>>>
>>>> No other comments required and I'll sign off with the above corrections.
>>>>
>>>> Cheers,
>>>> Luke
>>>>
>>>>> -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>>>> -                     ret = asus_kbd_disable_oobe(hdev);
>>>>> -                     if (ret < 0)
>>>>> -                             return ret;
>>>>> -             }
>>>>> -     } else {
>>>>> -             /* Initialize keyboard */
>>>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>>> -             if (ret < 0)
>>>>> -                     return ret;
>>>>> +     /* Get keyboard functions */
>>>>> +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>>>> +     if (ret < 0)
>>>>> +             return ret;
>>>>>
>>>>> -             /* Get keyboard functions */
>>>>> -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>>>> +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>>>> +             ret = asus_kbd_disable_oobe(hdev);
>>>>>                 if (ret < 0)
>>>>>                         return ret;
>>>>> -
>>>>> -             /* Check for backlight support */
>>>>> -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>>>> -                     return -ENODEV;
>>>>>         }
>>>>>
>>>>> +     /* Check for backlight support */
>>>>> +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>>>> +             return -ENODEV;
>>>>> +
>>>>>         drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>>>>>                                               sizeof(struct asus_kbd_leds),
>>>>>                                               GFP_KERNEL);
>>>>
>>


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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-22 10:28 ` [PATCH v3 09/10] HID: asus: add basic RGB support Antheas Kapenekakis
@ 2025-03-23  6:21   ` Luke D. Jones
  2025-03-23  6:40   ` Luke D. Jones
  1 sibling, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  6:21 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:28, Antheas Kapenekakis wrote:
> Adds basic RGB support to hid-asus through multi-index. The interface
> works quite well, but has not gone through much stability testing.
> Applied on demand, if userspace does not touch the RGB sysfs, not
> even initialization is done. Ensuring compatibility with existing
> userspace programs.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
>   1 file changed, 155 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 905453a4eb5b7..9d8ccfde5912e 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -30,6 +30,7 @@
>   #include <linux/input/mt.h>
>   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
>   #include <linux/power_supply.h>
> +#include <linux/led-class-multicolor.h>
>   #include <linux/leds.h>
>   
>   #include "hid-ids.h"
> @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
>   #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>   #define QUIRK_HANDLE_GENERIC		BIT(13)
> +#define QUIRK_ROG_NKEY_RGB		BIT(14)
>   
>   #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>   						 QUIRK_NO_INIT_REPORTS | \
> @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   
>   struct asus_kbd_leds {
>   	struct asus_hid_listener listener;
> +	struct led_classdev_mc mc_led;
> +	struct mc_subled subled_info[3];
>   	struct hid_device *hdev;
>   	struct work_struct work;
>   	unsigned int brightness;
> +	uint8_t rgb_colors[3];
> +	bool rgb_init;
> +	bool rgb_set;
> +	bool rgb_registered;
>   	spinlock_t lock;
>   	bool removed;
>   };
> @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
>   	spin_unlock_irqrestore(&led->lock, flags);
>   }
>   
> -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
> +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&led->lock, flags);
> +	led->brightness = brightness;
> +	spin_unlock_irqrestore(&led->lock, flags);
> +
> +	asus_schedule_work(led);
> +}
> +
> +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
>   				   int brightness)
>   {
>   	struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
>   						 listener);
> +	do_asus_kbd_backlight_set(led, brightness);
> +	if (led->rgb_registered) {
> +		led->mc_led.led_cdev.brightness = brightness;
> +		led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
> +							  brightness);
> +	}
> +}
> +
> +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
> +				    enum led_brightness brightness)
> +{
> +	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
> +	struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
> +						 mc_led);
>   	unsigned long flags;
>   
>   	spin_lock_irqsave(&led->lock, flags);
> -	led->brightness = brightness;
> +	led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
> +	led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
> +	led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
> +	led->rgb_set = true;
>   	spin_unlock_irqrestore(&led->lock, flags);
>   
> -	asus_schedule_work(led);
> +	do_asus_kbd_backlight_set(led, brightness);
> +}
> +
> +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
> +{
> +	struct led_classdev_mc *mc_led;
> +	struct asus_kbd_leds *led;
> +	enum led_brightness brightness;
> +	unsigned long flags;
> +
> +	mc_led = lcdev_to_mccdev(led_cdev);
> +	led = container_of(mc_led, struct asus_kbd_leds, mc_led);
> +
> +	spin_lock_irqsave(&led->lock, flags);
> +	brightness = led->brightness;
> +	spin_unlock_irqrestore(&led->lock, flags);
> +
> +	return brightness;
>   }
>   
> -static void asus_kbd_backlight_work(struct work_struct *work)
> +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
>   {
> -	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
>   	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
>   	int ret;
>   	unsigned long flags;
> @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
>   		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
>   }
>   
> +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
> +{
> +	u8 rgb_buf[][7] = {
> +		{ FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
> +		{ FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
> +		{ FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
> +	};
> +	unsigned long flags;
> +	uint8_t colors[3];
> +	bool rgb_init, rgb_set;
> +	int ret;
> +
> +	spin_lock_irqsave(&led->lock, flags);
> +	rgb_init = led->rgb_init;
> +	rgb_set = led->rgb_set;
> +	led->rgb_set = false;
> +	colors[0] = led->rgb_colors[0];
> +	colors[1] = led->rgb_colors[1];
> +	colors[2] = led->rgb_colors[2];
> +	spin_unlock_irqrestore(&led->lock, flags);
> +
> +	if (!rgb_set)
> +		return;
> +
> +	if (rgb_init) {
> +		ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
> +		if (ret < 0) {
> +			hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
> +			return;
> +		}
> +		spin_lock_irqsave(&led->lock, flags);
> +		led->rgb_init = false;
> +		spin_unlock_irqrestore(&led->lock, flags);
> +	}
> +
> +	/* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
> +	rgb_buf[0][4] = colors[0];
> +	rgb_buf[0][5] = colors[1];
> +	rgb_buf[0][6] = colors[2];
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
> +		ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
> +		if (ret < 0) {
> +			hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
> +			return;
> +		}
> +	}
> +}
> +
> +static void asus_kbd_work(struct work_struct *work)
> +{
> +	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
> +						 work);
> +	asus_kbd_backlight_work(led);
> +	asus_kbd_rgb_work(led);
> +}
> +
>   static int asus_kbd_register_leds(struct hid_device *hdev)
>   {
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>   	unsigned char kbd_func;
> +	struct asus_kbd_leds *leds;
> +	bool no_led;
>   	int ret;
>   
>   	ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>   	if (!drvdata->kbd_backlight)
>   		return -ENOMEM;
>   
> -	drvdata->kbd_backlight->removed = false;
> -	drvdata->kbd_backlight->brightness = 0;
> -	drvdata->kbd_backlight->hdev = hdev;
> -	drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
> -	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
> +	leds = drvdata->kbd_backlight;
> +	leds->removed = false;
> +	leds->brightness = 3;
> +	leds->hdev = hdev;
> +	leds->listener.brightness_set = asus_kbd_listener_set;
> +
> +	leds->rgb_colors[0] = 0;
> +	leds->rgb_colors[1] = 0;
> +	leds->rgb_colors[2] = 0;
> +	leds->rgb_init = true;
> +	leds->rgb_set = false;
> +	leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
> +					"asus-%s-led",
> +					strlen(hdev->uniq) ?
> +					hdev->uniq : dev_name(&hdev->dev));
> +	leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
> +	leds->mc_led.led_cdev.max_brightness = 3,
> +	leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
> +	leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
> +	leds->mc_led.subled_info = leds->subled_info,
> +	leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
> +	leds->subled_info[0].color_index = LED_COLOR_ID_RED;
> +	leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
> +	leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
> +
> +	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
>   	spin_lock_init(&drvdata->kbd_backlight->lock);
>   
>   	ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
> +	no_led = !!ret;
> +
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
> +		ret = devm_led_classdev_multicolor_register(
> +			&hdev->dev, &leds->mc_led);
> +		if (!ret)
> +			leds->rgb_registered = true;
> +		no_led &= !!ret;
> +	}
>   
> -	if (ret < 0) {
> +	if (no_led) {
>   		/* No need to have this still around */
>   		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
>   	}
>   
> -	return ret;
> +	return no_led ? -ENODEV : 0;
>   }
>   
>   /*
> @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
>   	 */
>   	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>   		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>   		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
>   	{ }

Tested as working on 0x1866 and 0x19B6 PID devices. No code review.

They have a path of `/sys/class/leds/asus-0003:0B05:19B6.0001-led`, was 
this intentional? I was under the impression that you would have added 
the mcled to the base `asus::kbd_backlight`.. Apologies if the answer is 
in code, I've only tested for now, not much time for full review but I 
do have some questions/opinons:

I *think* the majority of ROG are RGB, but now I regret not actually 
tracking this so I hope ASUS come back to me with some kind of query we 
can ask the MCU for this info.

Because I think the majority are RGB I'm coming around to saying yes to 
this patch but with caveats:

1. mcled added to the base led
2. `asus:rgb:kbd_backlight` name if RGB, `asus:white:kbd_backlight` if 
white only, `asus::kbd_backlight` if unknown
3. a quirk system to label an MCU as white only (since the bulk are RGB)

If you only do 1 + 2, or even just 1 I'm happy with that. I can take 
care of either 3, or 2 + 3. The last one just means I'll have to spend 
some time looking up specs or writing a crawler to find data.

I will try to review the code in the next days, but if you change it 
based on the above I'll hold off until then.

Cheers,
Luke.

P.S: on almost all white-only LED devices I've encountered the RED 
channel works for white intensity (green and blue do nothing). So even 
if we decide to blanket enable RGB for 0x1866 and 0x19B6 it won't cause 
issues beyond the naming and API facing.

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

* Re: [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units
  2025-03-22 10:28 ` [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units Antheas Kapenekakis
@ 2025-03-23  6:29   ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  6:29 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:28, Antheas Kapenekakis wrote:
> Apply the RGB quirk to the QOG Ally units to enable basic RGB support.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 9d8ccfde5912e..1a9cd7f513282 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -1433,10 +1433,10 @@ static const struct hid_device_id asus_devices[] = {
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
>   	  QUIRK_ROG_CLAYMORE_II_KEYBOARD },

This is fine. We can deal with reverting later if required, but even 
with my hid-asus-ally work as is, it doesn't affect it.

Reviewed-by: Luke D. Jones <luke@ljones.dev>

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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-22 10:28 ` [PATCH v3 09/10] HID: asus: add basic RGB support Antheas Kapenekakis
  2025-03-23  6:21   ` Luke D. Jones
@ 2025-03-23  6:40   ` Luke D. Jones
  2025-03-23 11:37     ` Antheas Kapenekakis
  1 sibling, 1 reply; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  6:40 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:28, Antheas Kapenekakis wrote:
> Adds basic RGB support to hid-asus through multi-index. The interface
> works quite well, but has not gone through much stability testing.
> Applied on demand, if userspace does not touch the RGB sysfs, not
> even initialization is done. Ensuring compatibility with existing
> userspace programs.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
>   1 file changed, 155 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 905453a4eb5b7..9d8ccfde5912e 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -30,6 +30,7 @@
>   #include <linux/input/mt.h>
>   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
>   #include <linux/power_supply.h>
> +#include <linux/led-class-multicolor.h>
>   #include <linux/leds.h>
>   
>   #include "hid-ids.h"
> @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
>   #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>   #define QUIRK_HANDLE_GENERIC		BIT(13)
> +#define QUIRK_ROG_NKEY_RGB		BIT(14)
>   
>   #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>   						 QUIRK_NO_INIT_REPORTS | \
> @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   
>   struct asus_kbd_leds {
>   	struct asus_hid_listener listener;
> +	struct led_classdev_mc mc_led;
> +	struct mc_subled subled_info[3];
>   	struct hid_device *hdev;
>   	struct work_struct work;
>   	unsigned int brightness;
> +	uint8_t rgb_colors[3];
> +	bool rgb_init;
> +	bool rgb_set;
> +	bool rgb_registered;
>   	spinlock_t lock;
>   	bool removed;
>   };
> @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
>   	spin_unlock_irqrestore(&led->lock, flags);
>   }
>   
> -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
> +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&led->lock, flags);
> +	led->brightness = brightness;
> +	spin_unlock_irqrestore(&led->lock, flags);
> +
> +	asus_schedule_work(led);
> +}
> +
> +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
>   				   int brightness)
>   {
>   	struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
>   						 listener);
> +	do_asus_kbd_backlight_set(led, brightness);
> +	if (led->rgb_registered) {
> +		led->mc_led.led_cdev.brightness = brightness;
> +		led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
> +							  brightness);
> +	}
> +}
> +
> +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
> +				    enum led_brightness brightness)
> +{
> +	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
> +	struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
> +						 mc_led);
>   	unsigned long flags;
>   
>   	spin_lock_irqsave(&led->lock, flags);
> -	led->brightness = brightness;
> +	led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
> +	led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
> +	led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
> +	led->rgb_set = true;
>   	spin_unlock_irqrestore(&led->lock, flags);
>   
> -	asus_schedule_work(led);
> +	do_asus_kbd_backlight_set(led, brightness);
> +}
> +
> +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
> +{
> +	struct led_classdev_mc *mc_led;
> +	struct asus_kbd_leds *led;
> +	enum led_brightness brightness;
> +	unsigned long flags;
> +
> +	mc_led = lcdev_to_mccdev(led_cdev);
> +	led = container_of(mc_led, struct asus_kbd_leds, mc_led);
> +
> +	spin_lock_irqsave(&led->lock, flags);
> +	brightness = led->brightness;
> +	spin_unlock_irqrestore(&led->lock, flags);
> +
> +	return brightness;
>   }
>   
> -static void asus_kbd_backlight_work(struct work_struct *work)
> +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
>   {
> -	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
>   	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
>   	int ret;
>   	unsigned long flags;
> @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
>   		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
>   }
>   
> +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
> +{
> +	u8 rgb_buf[][7] = {
> +		{ FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
> +		{ FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
> +		{ FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
> +	};
> +	unsigned long flags;
> +	uint8_t colors[3];
> +	bool rgb_init, rgb_set;
> +	int ret;
> +
> +	spin_lock_irqsave(&led->lock, flags);
> +	rgb_init = led->rgb_init;
> +	rgb_set = led->rgb_set;
> +	led->rgb_set = false;
> +	colors[0] = led->rgb_colors[0];
> +	colors[1] = led->rgb_colors[1];
> +	colors[2] = led->rgb_colors[2];
> +	spin_unlock_irqrestore(&led->lock, flags);
> +
> +	if (!rgb_set)
> +		return;
> +
> +	if (rgb_init) {
> +		ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
> +		if (ret < 0) {
> +			hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
> +			return;
> +		}
> +		spin_lock_irqsave(&led->lock, flags);
> +		led->rgb_init = false;
> +		spin_unlock_irqrestore(&led->lock, flags);
> +	}
> +
> +	/* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
> +	rgb_buf[0][4] = colors[0];
> +	rgb_buf[0][5] = colors[1];
> +	rgb_buf[0][6] = colors[2];
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
> +		ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
> +		if (ret < 0) {
> +			hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
> +			return;
> +		}
> +	}
> +}
> +
> +static void asus_kbd_work(struct work_struct *work)
> +{
> +	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
> +						 work);
> +	asus_kbd_backlight_work(led);
> +	asus_kbd_rgb_work(led);
> +}
> +
>   static int asus_kbd_register_leds(struct hid_device *hdev)
>   {
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>   	unsigned char kbd_func;
> +	struct asus_kbd_leds *leds;
> +	bool no_led;
>   	int ret;
>   
>   	ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>   	if (!drvdata->kbd_backlight)
>   		return -ENOMEM;
>   
> -	drvdata->kbd_backlight->removed = false;
> -	drvdata->kbd_backlight->brightness = 0;
> -	drvdata->kbd_backlight->hdev = hdev;
> -	drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
> -	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
> +	leds = drvdata->kbd_backlight;
> +	leds->removed = false;
> +	leds->brightness = 3;
> +	leds->hdev = hdev;
> +	leds->listener.brightness_set = asus_kbd_listener_set;
> +
> +	leds->rgb_colors[0] = 0;
> +	leds->rgb_colors[1] = 0;
> +	leds->rgb_colors[2] = 0;
> +	leds->rgb_init = true;
> +	leds->rgb_set = false;
> +	leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
> +					"asus-%s-led",
> +					strlen(hdev->uniq) ?
> +					hdev->uniq : dev_name(&hdev->dev));

A quick note. This breaks convention for LED names. The style guide is 
at Documentation/leds/leds-class.rst. Per my parallel email to this one 
I would like to see the mentioned naming scheme `asus:rgb:kbd_backlight` 
adopted. Expanding further on one of the points there you might need to 
move the led_classdev_mc in to asus-wmi to fulfil having the single 
sysfs endpoint. Since you're using the listner pattern it shouldn't be 
much work.

> +	leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
> +	leds->mc_led.led_cdev.max_brightness = 3,
> +	leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
> +	leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
> +	leds->mc_led.subled_info = leds->subled_info,
> +	leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
> +	leds->subled_info[0].color_index = LED_COLOR_ID_RED;
> +	leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
> +	leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
> +
> +	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
>   	spin_lock_init(&drvdata->kbd_backlight->lock);
>   
>   	ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
> +	no_led = !!ret;
> +
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
> +		ret = devm_led_classdev_multicolor_register(
> +			&hdev->dev, &leds->mc_led);
> +		if (!ret)
> +			leds->rgb_registered = true;
> +		no_led &= !!ret;
> +	}
>   
> -	if (ret < 0) {
> +	if (no_led) {
>   		/* No need to have this still around */
>   		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
>   	}
>   
> -	return ret;
> +	return no_led ? -ENODEV : 0;
>   }
>   
>   /*
> @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>   	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
>   	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
>   	 */
>   	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>   		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>   	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>   		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
>   	{ }


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

* Re: [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one
  2025-03-22 10:27 ` [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one Antheas Kapenekakis
@ 2025-03-23  6:40   ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23  6:40 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen

On 22/03/25 23:27, Antheas Kapenekakis wrote:
> Some ROG laptops expose multiple interfaces for controlling the
> keyboard/RGB brightness. This creates a name conflict under
> asus::kbd_brightness, where the second device ends up being
> named asus::kbd_brightness_1 and they are both broken.
> 
> Therefore, register a listener to the asus-wmi brightness device
> instead of creating a new one.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>   drivers/hid/hid-asus.c | 65 +++++++-----------------------------------
>   1 file changed, 11 insertions(+), 54 deletions(-)
> 
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index e97fb76eda619..c40b5c14c797f 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -96,7 +96,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>   #define TRKID_SGN       ((TRKID_MAX + 1) >> 1)
>   
>   struct asus_kbd_leds {
> -	struct led_classdev cdev;
> +	struct asus_hid_listener listener;
>   	struct hid_device *hdev;
>   	struct work_struct work;
>   	unsigned int brightness;
> @@ -493,11 +493,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
>   	spin_unlock_irqrestore(&led->lock, flags);
>   }
>   
> -static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
> -				   enum led_brightness brightness)
> +static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
> +				   int brightness)
>   {
> -	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
> -						 cdev);
> +	struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
> +						 listener);
>   	unsigned long flags;
>   
>   	spin_lock_irqsave(&led->lock, flags);
> @@ -507,20 +507,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
>   	asus_schedule_work(led);
>   }
>   
> -static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
> -{
> -	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
> -						 cdev);
> -	enum led_brightness brightness;
> -	unsigned long flags;
> -
> -	spin_lock_irqsave(&led->lock, flags);
> -	brightness = led->brightness;
> -	spin_unlock_irqrestore(&led->lock, flags);
> -
> -	return brightness;
> -}
> -
>   static void asus_kbd_backlight_work(struct work_struct *work)
>   {
>   	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
> @@ -537,34 +523,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
>   		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
>   }
>   
> -/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
> - * precedence. We only activate HID-based backlight control when the
> - * WMI control is not available.
> - */
> -static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
> -{
> -	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> -	u32 value;
> -	int ret;
> -
> -	if (!IS_ENABLED(CONFIG_ASUS_WMI))
> -		return false;
> -
> -	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
> -			dmi_check_system(asus_use_hid_led_dmi_ids)) {
> -		hid_info(hdev, "using HID for asus::kbd_backlight\n");
> -		return false;
> -	}
> -
> -	ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
> -				       ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
> -	hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
> -	if (ret)
> -		return false;
> -
> -	return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
> -}
> -
>   static int asus_kbd_register_leds(struct hid_device *hdev)
>   {
>   	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> @@ -599,14 +557,12 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>   	drvdata->kbd_backlight->removed = false;
>   	drvdata->kbd_backlight->brightness = 0;
>   	drvdata->kbd_backlight->hdev = hdev;
> -	drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
> -	drvdata->kbd_backlight->cdev.max_brightness = 3;
> -	drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
> -	drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
> +	drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
>   	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
>   	spin_lock_init(&drvdata->kbd_backlight->lock);
>   
> -	ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
> +	ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
> +
>   	if (ret < 0) {
>   		/* No need to have this still around */
>   		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
> @@ -1000,7 +956,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
>   
>   	if (drvdata->kbd_backlight) {
>   		const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
> -				drvdata->kbd_backlight->cdev.brightness };
> +				drvdata->kbd_backlight->brightness };
>   		ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
>   		if (ret < 0) {
>   			hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
> @@ -1139,7 +1095,6 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
>   	}
>   
>   	if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
> -	    !asus_kbd_wmi_led_control_present(hdev) &&
>   	    asus_kbd_register_leds(hdev))
>   		hid_warn(hdev, "Failed to initialize backlight.\n");
>   
> @@ -1180,6 +1135,8 @@ static void asus_remove(struct hid_device *hdev)
>   	unsigned long flags;
>   
>   	if (drvdata->kbd_backlight) {
> +		asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
> +
>   		spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
>   		drvdata->kbd_backlight->removed = true;
>   		spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);

Reviewed-by: Luke D. Jones <luke@ljones.dev>

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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-23  6:40   ` Luke D. Jones
@ 2025-03-23 11:37     ` Antheas Kapenekakis
  2025-03-23 14:45       ` Antheas Kapenekakis
  2025-03-23 20:39       ` Luke D. Jones
  0 siblings, 2 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-23 11:37 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sun, 23 Mar 2025 at 07:40, Luke D. Jones <luke@ljones.dev> wrote:
>
> On 22/03/25 23:28, Antheas Kapenekakis wrote:
> > Adds basic RGB support to hid-asus through multi-index. The interface
> > works quite well, but has not gone through much stability testing.
> > Applied on demand, if userspace does not touch the RGB sysfs, not
> > even initialization is done. Ensuring compatibility with existing
> > userspace programs.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> >   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
> >   1 file changed, 155 insertions(+), 14 deletions(-)
> >
> > diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > index 905453a4eb5b7..9d8ccfde5912e 100644
> > --- a/drivers/hid/hid-asus.c
> > +++ b/drivers/hid/hid-asus.c
> > @@ -30,6 +30,7 @@
> >   #include <linux/input/mt.h>
> >   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
> >   #include <linux/power_supply.h>
> > +#include <linux/led-class-multicolor.h>
> >   #include <linux/leds.h>
> >
> >   #include "hid-ids.h"
> > @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >   #define QUIRK_ROG_NKEY_KEYBOARD             BIT(11)
> >   #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> >   #define QUIRK_HANDLE_GENERIC                BIT(13)
> > +#define QUIRK_ROG_NKEY_RGB           BIT(14)
> >
> >   #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
> >                                                QUIRK_NO_INIT_REPORTS | \
> > @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >
> >   struct asus_kbd_leds {
> >       struct asus_hid_listener listener;
> > +     struct led_classdev_mc mc_led;
> > +     struct mc_subled subled_info[3];
> >       struct hid_device *hdev;
> >       struct work_struct work;
> >       unsigned int brightness;
> > +     uint8_t rgb_colors[3];
> > +     bool rgb_init;
> > +     bool rgb_set;
> > +     bool rgb_registered;
> >       spinlock_t lock;
> >       bool removed;
> >   };
> > @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
> >       spin_unlock_irqrestore(&led->lock, flags);
> >   }
> >
> > -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
> > +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
> > +{
> > +     unsigned long flags;
> > +
> > +     spin_lock_irqsave(&led->lock, flags);
> > +     led->brightness = brightness;
> > +     spin_unlock_irqrestore(&led->lock, flags);
> > +
> > +     asus_schedule_work(led);
> > +}
> > +
> > +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
> >                                  int brightness)
> >   {
> >       struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
> >                                                listener);
> > +     do_asus_kbd_backlight_set(led, brightness);
> > +     if (led->rgb_registered) {
> > +             led->mc_led.led_cdev.brightness = brightness;
> > +             led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
> > +                                                       brightness);
> > +     }
> > +}
> > +
> > +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
> > +                                 enum led_brightness brightness)
> > +{
> > +     struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
> > +     struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
> > +                                              mc_led);
> >       unsigned long flags;
> >
> >       spin_lock_irqsave(&led->lock, flags);
> > -     led->brightness = brightness;
> > +     led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
> > +     led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
> > +     led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
> > +     led->rgb_set = true;
> >       spin_unlock_irqrestore(&led->lock, flags);
> >
> > -     asus_schedule_work(led);
> > +     do_asus_kbd_backlight_set(led, brightness);
> > +}
> > +
> > +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
> > +{
> > +     struct led_classdev_mc *mc_led;
> > +     struct asus_kbd_leds *led;
> > +     enum led_brightness brightness;
> > +     unsigned long flags;
> > +
> > +     mc_led = lcdev_to_mccdev(led_cdev);
> > +     led = container_of(mc_led, struct asus_kbd_leds, mc_led);
> > +
> > +     spin_lock_irqsave(&led->lock, flags);
> > +     brightness = led->brightness;
> > +     spin_unlock_irqrestore(&led->lock, flags);
> > +
> > +     return brightness;
> >   }
> >
> > -static void asus_kbd_backlight_work(struct work_struct *work)
> > +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
> >   {
> > -     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
> >       u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
> >       int ret;
> >       unsigned long flags;
> > @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
> >               hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
> >   }
> >
> > +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
> > +{
> > +     u8 rgb_buf[][7] = {
> > +             { FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
> > +             { FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
> > +             { FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
> > +     };
> > +     unsigned long flags;
> > +     uint8_t colors[3];
> > +     bool rgb_init, rgb_set;
> > +     int ret;
> > +
> > +     spin_lock_irqsave(&led->lock, flags);
> > +     rgb_init = led->rgb_init;
> > +     rgb_set = led->rgb_set;
> > +     led->rgb_set = false;
> > +     colors[0] = led->rgb_colors[0];
> > +     colors[1] = led->rgb_colors[1];
> > +     colors[2] = led->rgb_colors[2];
> > +     spin_unlock_irqrestore(&led->lock, flags);
> > +
> > +     if (!rgb_set)
> > +             return;
> > +
> > +     if (rgb_init) {
> > +             ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
> > +             if (ret < 0) {
> > +                     hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
> > +                     return;
> > +             }
> > +             spin_lock_irqsave(&led->lock, flags);
> > +             led->rgb_init = false;
> > +             spin_unlock_irqrestore(&led->lock, flags);
> > +     }
> > +
> > +     /* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
> > +     rgb_buf[0][4] = colors[0];
> > +     rgb_buf[0][5] = colors[1];
> > +     rgb_buf[0][6] = colors[2];
> > +
> > +     for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
> > +             ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
> > +             if (ret < 0) {
> > +                     hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
> > +                     return;
> > +             }
> > +     }
> > +}
> > +
> > +static void asus_kbd_work(struct work_struct *work)
> > +{
> > +     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
> > +                                              work);
> > +     asus_kbd_backlight_work(led);
> > +     asus_kbd_rgb_work(led);
> > +}
> > +
> >   static int asus_kbd_register_leds(struct hid_device *hdev)
> >   {
> >       struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> >       unsigned char kbd_func;
> > +     struct asus_kbd_leds *leds;
> > +     bool no_led;
> >       int ret;
> >
> >       ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> >       if (!drvdata->kbd_backlight)
> >               return -ENOMEM;
> >
> > -     drvdata->kbd_backlight->removed = false;
> > -     drvdata->kbd_backlight->brightness = 0;
> > -     drvdata->kbd_backlight->hdev = hdev;
> > -     drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
> > -     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
> > +     leds = drvdata->kbd_backlight;
> > +     leds->removed = false;
> > +     leds->brightness = 3;
> > +     leds->hdev = hdev;
> > +     leds->listener.brightness_set = asus_kbd_listener_set;
> > +
> > +     leds->rgb_colors[0] = 0;
> > +     leds->rgb_colors[1] = 0;
> > +     leds->rgb_colors[2] = 0;
> > +     leds->rgb_init = true;
> > +     leds->rgb_set = false;
> > +     leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
> > +                                     "asus-%s-led",
> > +                                     strlen(hdev->uniq) ?
> > +                                     hdev->uniq : dev_name(&hdev->dev));
>
> A quick note. This breaks convention for LED names. The style guide is
> at Documentation/leds/leds-class.rst. Per my parallel email to this one
> I would like to see the mentioned naming scheme `asus:rgb:kbd_backlight`
> adopted.

Perhaps. It would be the first kbd_backlight driver to have "rgb" in
it. It is a bit out of scope for this series as I do not touch the
functionality of it but I can add a patch for it and a fixes
e305a71cea37a64c75 tag.

> Expanding further on one of the points there you might need to
> move the led_classdev_mc in to asus-wmi to fulfil having the single
> sysfs endpoint. Since you're using the listner pattern it shouldn't be
> much work.

I only want the brightness to sync, not the color. Only the brightness
between Aura devices needs to be the same. In this case
asus::kbd_backlight if it has a color controls the wmi color, and the
asus- devices control the usb.

Also, groups are not dynamic so this is not possible. E.g., if you
setup a WMI listener that does not have RGB, and then the USB keyboard
connects you can no longer change the groups unless you reconnect the
device.

> > +     leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
> > +     leds->mc_led.led_cdev.max_brightness = 3,
> > +     leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
> > +     leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
> > +     leds->mc_led.subled_info = leds->subled_info,
> > +     leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
> > +     leds->subled_info[0].color_index = LED_COLOR_ID_RED;
> > +     leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
> > +     leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
> > +
> > +     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
> >       spin_lock_init(&drvdata->kbd_backlight->lock);
> >
> >       ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
> > +     no_led = !!ret;
> > +
> > +     if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
> > +             ret = devm_led_classdev_multicolor_register(
> > +                     &hdev->dev, &leds->mc_led);
> > +             if (!ret)
> > +                     leds->rgb_registered = true;
> > +             no_led &= !!ret;
> > +     }
> >
> > -     if (ret < 0) {
> > +     if (no_led) {
> >               /* No need to have this still around */
> >               devm_kfree(&hdev->dev, drvdata->kbd_backlight);
> >       }
> >
> > -     return ret;
> > +     return no_led ? -ENODEV : 0;
> >   }
> >
> >   /*
> > @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
> >         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >           USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> > -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
> >       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >           USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
> >         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
> >        */
> >       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> >               USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> > -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
> >       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> >               USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
> >       { }
>

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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-23 11:37     ` Antheas Kapenekakis
@ 2025-03-23 14:45       ` Antheas Kapenekakis
  2025-03-23 20:13         ` Luke D. Jones
  2025-03-23 20:39       ` Luke D. Jones
  1 sibling, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-23 14:45 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sun, 23 Mar 2025 at 12:37, Antheas Kapenekakis <lkml@antheas.dev> wrote:
>
> On Sun, 23 Mar 2025 at 07:40, Luke D. Jones <luke@ljones.dev> wrote:
> >
> > On 22/03/25 23:28, Antheas Kapenekakis wrote:
> > > Adds basic RGB support to hid-asus through multi-index. The interface
> > > works quite well, but has not gone through much stability testing.
> > > Applied on demand, if userspace does not touch the RGB sysfs, not
> > > even initialization is done. Ensuring compatibility with existing
> > > userspace programs.
> > >
> > > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > > ---
> > >   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
> > >   1 file changed, 155 insertions(+), 14 deletions(-)
> > >
> > > diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > > index 905453a4eb5b7..9d8ccfde5912e 100644
> > > --- a/drivers/hid/hid-asus.c
> > > +++ b/drivers/hid/hid-asus.c
> > > @@ -30,6 +30,7 @@
> > >   #include <linux/input/mt.h>
> > >   #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
> > >   #include <linux/power_supply.h>
> > > +#include <linux/led-class-multicolor.h>
> > >   #include <linux/leds.h>
> > >
> > >   #include "hid-ids.h"
> > > @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> > >   #define QUIRK_ROG_NKEY_KEYBOARD             BIT(11)
> > >   #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> > >   #define QUIRK_HANDLE_GENERIC                BIT(13)
> > > +#define QUIRK_ROG_NKEY_RGB           BIT(14)
> > >
> > >   #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
> > >                                                QUIRK_NO_INIT_REPORTS | \
> > > @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> > >
> > >   struct asus_kbd_leds {
> > >       struct asus_hid_listener listener;
> > > +     struct led_classdev_mc mc_led;
> > > +     struct mc_subled subled_info[3];
> > >       struct hid_device *hdev;
> > >       struct work_struct work;
> > >       unsigned int brightness;
> > > +     uint8_t rgb_colors[3];
> > > +     bool rgb_init;
> > > +     bool rgb_set;
> > > +     bool rgb_registered;
> > >       spinlock_t lock;
> > >       bool removed;
> > >   };
> > > @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
> > >       spin_unlock_irqrestore(&led->lock, flags);
> > >   }
> > >
> > > -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
> > > +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
> > > +{
> > > +     unsigned long flags;
> > > +
> > > +     spin_lock_irqsave(&led->lock, flags);
> > > +     led->brightness = brightness;
> > > +     spin_unlock_irqrestore(&led->lock, flags);
> > > +
> > > +     asus_schedule_work(led);
> > > +}
> > > +
> > > +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
> > >                                  int brightness)
> > >   {
> > >       struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
> > >                                                listener);
> > > +     do_asus_kbd_backlight_set(led, brightness);
> > > +     if (led->rgb_registered) {
> > > +             led->mc_led.led_cdev.brightness = brightness;
> > > +             led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
> > > +                                                       brightness);
> > > +     }
> > > +}
> > > +
> > > +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
> > > +                                 enum led_brightness brightness)
> > > +{
> > > +     struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
> > > +     struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
> > > +                                              mc_led);
> > >       unsigned long flags;
> > >
> > >       spin_lock_irqsave(&led->lock, flags);
> > > -     led->brightness = brightness;
> > > +     led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
> > > +     led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
> > > +     led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
> > > +     led->rgb_set = true;
> > >       spin_unlock_irqrestore(&led->lock, flags);
> > >
> > > -     asus_schedule_work(led);
> > > +     do_asus_kbd_backlight_set(led, brightness);
> > > +}
> > > +
> > > +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
> > > +{
> > > +     struct led_classdev_mc *mc_led;
> > > +     struct asus_kbd_leds *led;
> > > +     enum led_brightness brightness;
> > > +     unsigned long flags;
> > > +
> > > +     mc_led = lcdev_to_mccdev(led_cdev);
> > > +     led = container_of(mc_led, struct asus_kbd_leds, mc_led);
> > > +
> > > +     spin_lock_irqsave(&led->lock, flags);
> > > +     brightness = led->brightness;
> > > +     spin_unlock_irqrestore(&led->lock, flags);
> > > +
> > > +     return brightness;
> > >   }
> > >
> > > -static void asus_kbd_backlight_work(struct work_struct *work)
> > > +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
> > >   {
> > > -     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
> > >       u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
> > >       int ret;
> > >       unsigned long flags;
> > > @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
> > >               hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
> > >   }
> > >
> > > +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
> > > +{
> > > +     u8 rgb_buf[][7] = {
> > > +             { FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
> > > +             { FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
> > > +             { FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
> > > +     };
> > > +     unsigned long flags;
> > > +     uint8_t colors[3];
> > > +     bool rgb_init, rgb_set;
> > > +     int ret;
> > > +
> > > +     spin_lock_irqsave(&led->lock, flags);
> > > +     rgb_init = led->rgb_init;
> > > +     rgb_set = led->rgb_set;
> > > +     led->rgb_set = false;
> > > +     colors[0] = led->rgb_colors[0];
> > > +     colors[1] = led->rgb_colors[1];
> > > +     colors[2] = led->rgb_colors[2];
> > > +     spin_unlock_irqrestore(&led->lock, flags);
> > > +
> > > +     if (!rgb_set)
> > > +             return;
> > > +
> > > +     if (rgb_init) {
> > > +             ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
> > > +             if (ret < 0) {
> > > +                     hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
> > > +                     return;
> > > +             }
> > > +             spin_lock_irqsave(&led->lock, flags);
> > > +             led->rgb_init = false;
> > > +             spin_unlock_irqrestore(&led->lock, flags);
> > > +     }
> > > +
> > > +     /* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
> > > +     rgb_buf[0][4] = colors[0];
> > > +     rgb_buf[0][5] = colors[1];
> > > +     rgb_buf[0][6] = colors[2];
> > > +
> > > +     for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
> > > +             ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
> > > +             if (ret < 0) {
> > > +                     hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
> > > +                     return;
> > > +             }
> > > +     }
> > > +}
> > > +
> > > +static void asus_kbd_work(struct work_struct *work)
> > > +{
> > > +     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
> > > +                                              work);
> > > +     asus_kbd_backlight_work(led);
> > > +     asus_kbd_rgb_work(led);
> > > +}
> > > +
> > >   static int asus_kbd_register_leds(struct hid_device *hdev)
> > >   {
> > >       struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> > >       unsigned char kbd_func;
> > > +     struct asus_kbd_leds *leds;
> > > +     bool no_led;
> > >       int ret;
> > >
> > >       ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> > > @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> > >       if (!drvdata->kbd_backlight)
> > >               return -ENOMEM;
> > >
> > > -     drvdata->kbd_backlight->removed = false;
> > > -     drvdata->kbd_backlight->brightness = 0;
> > > -     drvdata->kbd_backlight->hdev = hdev;
> > > -     drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
> > > -     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
> > > +     leds = drvdata->kbd_backlight;
> > > +     leds->removed = false;
> > > +     leds->brightness = 3;
> > > +     leds->hdev = hdev;
> > > +     leds->listener.brightness_set = asus_kbd_listener_set;
> > > +
> > > +     leds->rgb_colors[0] = 0;
> > > +     leds->rgb_colors[1] = 0;
> > > +     leds->rgb_colors[2] = 0;
> > > +     leds->rgb_init = true;
> > > +     leds->rgb_set = false;
> > > +     leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
> > > +                                     "asus-%s-led",
> > > +                                     strlen(hdev->uniq) ?
> > > +                                     hdev->uniq : dev_name(&hdev->dev));
> >
> > A quick note. This breaks convention for LED names. The style guide is
> > at Documentation/leds/leds-class.rst. Per my parallel email to this one
> > I would like to see the mentioned naming scheme `asus:rgb:kbd_backlight`
> > adopted.
>
> Perhaps. It would be the first kbd_backlight driver to have "rgb" in
> it. It is a bit out of scope for this series as I do not touch the
> functionality of it but I can add a patch for it and a fixes
> e305a71cea37a64c75 tag.
>
> > Expanding further on one of the points there you might need to
> > move the led_classdev_mc in to asus-wmi to fulfil having the single
> > sysfs endpoint. Since you're using the listner pattern it shouldn't be
> > much work.
>
> I only want the brightness to sync, not the color. Only the brightness
> between Aura devices needs to be the same. In this case
> asus::kbd_backlight if it has a color controls the wmi color, and the
> asus- devices control the usb.
>
> Also, groups are not dynamic so this is not possible. E.g., if you
> setup a WMI listener that does not have RGB, and then the USB keyboard
> connects you can no longer change the groups unless you reconnect the
> device.

Sorry, I confused the patches. Yes you are right. I cannot do
kbd_backlight because userspace might pick the wrong handler. And with
this patch series on the Z13 there are 3, one for the common
backlight, one for the keyboard, and one for the lightbar.

I can do asus-UNIQ:rgb:peripheral though. There is no appropriate
function that is close enough currently. Also, since there can be
multiple devices, UNIQ or something similar needs to be added,
otherwise the led handler will suffix _X.

Antheas

> > > +     leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
> > > +     leds->mc_led.led_cdev.max_brightness = 3,
> > > +     leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
> > > +     leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
> > > +     leds->mc_led.subled_info = leds->subled_info,
> > > +     leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
> > > +     leds->subled_info[0].color_index = LED_COLOR_ID_RED;
> > > +     leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
> > > +     leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
> > > +
> > > +     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
> > >       spin_lock_init(&drvdata->kbd_backlight->lock);
> > >
> > >       ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
> > > +     no_led = !!ret;
> > > +
> > > +     if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
> > > +             ret = devm_led_classdev_multicolor_register(
> > > +                     &hdev->dev, &leds->mc_led);
> > > +             if (!ret)
> > > +                     leds->rgb_registered = true;
> > > +             no_led &= !!ret;
> > > +     }
> > >
> > > -     if (ret < 0) {
> > > +     if (no_led) {
> > >               /* No need to have this still around */
> > >               devm_kfree(&hdev->dev, drvdata->kbd_backlight);
> > >       }
> > >
> > > -     return ret;
> > > +     return no_led ? -ENODEV : 0;
> > >   }
> > >
> > >   /*
> > > @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
> > >         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > >       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> > >           USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> > > -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > > +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
> > >       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> > >           USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
> > >         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > > @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
> > >        */
> > >       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > >               USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> > > -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > > +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
> > >       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > >               USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
> > >       { }
> >

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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-23 14:45       ` Antheas Kapenekakis
@ 2025-03-23 20:13         ` Luke D. Jones
  0 siblings, 0 replies; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23 20:13 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On 24/03/25 03:45, Antheas Kapenekakis wrote:
> On Sun, 23 Mar 2025 at 12:37, Antheas Kapenekakis <lkml@antheas.dev> wrote:
>>
>> On Sun, 23 Mar 2025 at 07:40, Luke D. Jones <luke@ljones.dev> wrote:
>>>
>>> On 22/03/25 23:28, Antheas Kapenekakis wrote:
>>>> Adds basic RGB support to hid-asus through multi-index. The interface
>>>> works quite well, but has not gone through much stability testing.
>>>> Applied on demand, if userspace does not touch the RGB sysfs, not
>>>> even initialization is done. Ensuring compatibility with existing
>>>> userspace programs.
>>>>
>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>>> ---
>>>>    drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
>>>>    1 file changed, 155 insertions(+), 14 deletions(-)
>>>>
>>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>>> index 905453a4eb5b7..9d8ccfde5912e 100644
>>>> --- a/drivers/hid/hid-asus.c
>>>> +++ b/drivers/hid/hid-asus.c
>>>> @@ -30,6 +30,7 @@
>>>>    #include <linux/input/mt.h>
>>>>    #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
>>>>    #include <linux/power_supply.h>
>>>> +#include <linux/led-class-multicolor.h>
>>>>    #include <linux/leds.h>
>>>>
>>>>    #include "hid-ids.h"
>>>> @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>>    #define QUIRK_ROG_NKEY_KEYBOARD             BIT(11)
>>>>    #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>>>>    #define QUIRK_HANDLE_GENERIC                BIT(13)
>>>> +#define QUIRK_ROG_NKEY_RGB           BIT(14)
>>>>
>>>>    #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
>>>>                                                 QUIRK_NO_INIT_REPORTS | \
>>>> @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>>
>>>>    struct asus_kbd_leds {
>>>>        struct asus_hid_listener listener;
>>>> +     struct led_classdev_mc mc_led;
>>>> +     struct mc_subled subled_info[3];
>>>>        struct hid_device *hdev;
>>>>        struct work_struct work;
>>>>        unsigned int brightness;
>>>> +     uint8_t rgb_colors[3];
>>>> +     bool rgb_init;
>>>> +     bool rgb_set;
>>>> +     bool rgb_registered;
>>>>        spinlock_t lock;
>>>>        bool removed;
>>>>    };
>>>> @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
>>>>        spin_unlock_irqrestore(&led->lock, flags);
>>>>    }
>>>>
>>>> -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
>>>> +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
>>>> +{
>>>> +     unsigned long flags;
>>>> +
>>>> +     spin_lock_irqsave(&led->lock, flags);
>>>> +     led->brightness = brightness;
>>>> +     spin_unlock_irqrestore(&led->lock, flags);
>>>> +
>>>> +     asus_schedule_work(led);
>>>> +}
>>>> +
>>>> +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
>>>>                                   int brightness)
>>>>    {
>>>>        struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
>>>>                                                 listener);
>>>> +     do_asus_kbd_backlight_set(led, brightness);
>>>> +     if (led->rgb_registered) {
>>>> +             led->mc_led.led_cdev.brightness = brightness;
>>>> +             led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
>>>> +                                                       brightness);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
>>>> +                                 enum led_brightness brightness)
>>>> +{
>>>> +     struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
>>>> +     struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
>>>> +                                              mc_led);
>>>>        unsigned long flags;
>>>>
>>>>        spin_lock_irqsave(&led->lock, flags);
>>>> -     led->brightness = brightness;
>>>> +     led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
>>>> +     led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
>>>> +     led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
>>>> +     led->rgb_set = true;
>>>>        spin_unlock_irqrestore(&led->lock, flags);
>>>>
>>>> -     asus_schedule_work(led);
>>>> +     do_asus_kbd_backlight_set(led, brightness);
>>>> +}
>>>> +
>>>> +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
>>>> +{
>>>> +     struct led_classdev_mc *mc_led;
>>>> +     struct asus_kbd_leds *led;
>>>> +     enum led_brightness brightness;
>>>> +     unsigned long flags;
>>>> +
>>>> +     mc_led = lcdev_to_mccdev(led_cdev);
>>>> +     led = container_of(mc_led, struct asus_kbd_leds, mc_led);
>>>> +
>>>> +     spin_lock_irqsave(&led->lock, flags);
>>>> +     brightness = led->brightness;
>>>> +     spin_unlock_irqrestore(&led->lock, flags);
>>>> +
>>>> +     return brightness;
>>>>    }
>>>>
>>>> -static void asus_kbd_backlight_work(struct work_struct *work)
>>>> +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
>>>>    {
>>>> -     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
>>>>        u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
>>>>        int ret;
>>>>        unsigned long flags;
>>>> @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
>>>>                hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
>>>>    }
>>>>
>>>> +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
>>>> +{
>>>> +     u8 rgb_buf[][7] = {
>>>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
>>>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
>>>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
>>>> +     };
>>>> +     unsigned long flags;
>>>> +     uint8_t colors[3];
>>>> +     bool rgb_init, rgb_set;
>>>> +     int ret;
>>>> +
>>>> +     spin_lock_irqsave(&led->lock, flags);
>>>> +     rgb_init = led->rgb_init;
>>>> +     rgb_set = led->rgb_set;
>>>> +     led->rgb_set = false;
>>>> +     colors[0] = led->rgb_colors[0];
>>>> +     colors[1] = led->rgb_colors[1];
>>>> +     colors[2] = led->rgb_colors[2];
>>>> +     spin_unlock_irqrestore(&led->lock, flags);
>>>> +
>>>> +     if (!rgb_set)
>>>> +             return;
>>>> +
>>>> +     if (rgb_init) {
>>>> +             ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
>>>> +             if (ret < 0) {
>>>> +                     hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
>>>> +                     return;
>>>> +             }
>>>> +             spin_lock_irqsave(&led->lock, flags);
>>>> +             led->rgb_init = false;
>>>> +             spin_unlock_irqrestore(&led->lock, flags);
>>>> +     }
>>>> +
>>>> +     /* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
>>>> +     rgb_buf[0][4] = colors[0];
>>>> +     rgb_buf[0][5] = colors[1];
>>>> +     rgb_buf[0][6] = colors[2];
>>>> +
>>>> +     for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
>>>> +             ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
>>>> +             if (ret < 0) {
>>>> +                     hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
>>>> +                     return;
>>>> +             }
>>>> +     }
>>>> +}
>>>> +
>>>> +static void asus_kbd_work(struct work_struct *work)
>>>> +{
>>>> +     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
>>>> +                                              work);
>>>> +     asus_kbd_backlight_work(led);
>>>> +     asus_kbd_rgb_work(led);
>>>> +}
>>>> +
>>>>    static int asus_kbd_register_leds(struct hid_device *hdev)
>>>>    {
>>>>        struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>>>>        unsigned char kbd_func;
>>>> +     struct asus_kbd_leds *leds;
>>>> +     bool no_led;
>>>>        int ret;
>>>>
>>>>        ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>>> @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>>        if (!drvdata->kbd_backlight)
>>>>                return -ENOMEM;
>>>>
>>>> -     drvdata->kbd_backlight->removed = false;
>>>> -     drvdata->kbd_backlight->brightness = 0;
>>>> -     drvdata->kbd_backlight->hdev = hdev;
>>>> -     drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
>>>> -     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
>>>> +     leds = drvdata->kbd_backlight;
>>>> +     leds->removed = false;
>>>> +     leds->brightness = 3;
>>>> +     leds->hdev = hdev;
>>>> +     leds->listener.brightness_set = asus_kbd_listener_set;
>>>> +
>>>> +     leds->rgb_colors[0] = 0;
>>>> +     leds->rgb_colors[1] = 0;
>>>> +     leds->rgb_colors[2] = 0;
>>>> +     leds->rgb_init = true;
>>>> +     leds->rgb_set = false;
>>>> +     leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
>>>> +                                     "asus-%s-led",
>>>> +                                     strlen(hdev->uniq) ?
>>>> +                                     hdev->uniq : dev_name(&hdev->dev));
>>>
>>> A quick note. This breaks convention for LED names. The style guide is
>>> at Documentation/leds/leds-class.rst. Per my parallel email to this one
>>> I would like to see the mentioned naming scheme `asus:rgb:kbd_backlight`
>>> adopted.
>>
>> Perhaps. It would be the first kbd_backlight driver to have "rgb" in
>> it. It is a bit out of scope for this series as I do not touch the
>> functionality of it but I can add a patch for it and a fixes
>> e305a71cea37a64c75 tag.
>>
>>> Expanding further on one of the points there you might need to
>>> move the led_classdev_mc in to asus-wmi to fulfil having the single
>>> sysfs endpoint. Since you're using the listner pattern it shouldn't be
>>> much work.
>>
>> I only want the brightness to sync, not the color. Only the brightness
>> between Aura devices needs to be the same. In this case
>> asus::kbd_backlight if it has a color controls the wmi color, and the
>> asus- devices control the usb.
>>
>> Also, groups are not dynamic so this is not possible. E.g., if you
>> setup a WMI listener that does not have RGB, and then the USB keyboard
>> connects you can no longer change the groups unless you reconnect the
>> device.
> 
> Sorry, I confused the patches. Yes you are right. I cannot do
> kbd_backlight because userspace might pick the wrong handler. And with
> this patch series on the Z13 there are 3, one for the common
> backlight, one for the keyboard, and one for the lightbar.
> 
> I can do asus-UNIQ:rgb:peripheral though. There is no appropriate
> function that is close enough currently. Also, since there can be
> multiple devices, UNIQ or something similar needs to be added,
> otherwise the led handler will suffix _X.
> 

That sounds appropriate yeah. Is there an issue with the suffix number 
being added? I've not encountered any myself.

Cheers.
Luke.

> Antheas
> 
>>>> +     leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
>>>> +     leds->mc_led.led_cdev.max_brightness = 3,
>>>> +     leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
>>>> +     leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
>>>> +     leds->mc_led.subled_info = leds->subled_info,
>>>> +     leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
>>>> +     leds->subled_info[0].color_index = LED_COLOR_ID_RED;
>>>> +     leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
>>>> +     leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
>>>> +
>>>> +     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
>>>>        spin_lock_init(&drvdata->kbd_backlight->lock);
>>>>
>>>>        ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
>>>> +     no_led = !!ret;
>>>> +
>>>> +     if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
>>>> +             ret = devm_led_classdev_multicolor_register(
>>>> +                     &hdev->dev, &leds->mc_led);
>>>> +             if (!ret)
>>>> +                     leds->rgb_registered = true;
>>>> +             no_led &= !!ret;
>>>> +     }
>>>>
>>>> -     if (ret < 0) {
>>>> +     if (no_led) {
>>>>                /* No need to have this still around */
>>>>                devm_kfree(&hdev->dev, drvdata->kbd_backlight);
>>>>        }
>>>>
>>>> -     return ret;
>>>> +     return no_led ? -ENODEV : 0;
>>>>    }
>>>>
>>>>    /*
>>>> @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
>>>>          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>>        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>>            USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
>>>> -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>> +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>>>>        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>>            USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
>>>>          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>> @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
>>>>         */
>>>>        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>>>>                USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
>>>> -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>> +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>>>>        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>>>>                USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
>>>>        { }
>>>


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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-23 11:37     ` Antheas Kapenekakis
  2025-03-23 14:45       ` Antheas Kapenekakis
@ 2025-03-23 20:39       ` Luke D. Jones
  2025-03-23 21:08         ` Antheas Kapenekakis
  1 sibling, 1 reply; 37+ messages in thread
From: Luke D. Jones @ 2025-03-23 20:39 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On 24/03/25 00:37, Antheas Kapenekakis wrote:
> On Sun, 23 Mar 2025 at 07:40, Luke D. Jones <luke@ljones.dev> wrote:
>>
>> On 22/03/25 23:28, Antheas Kapenekakis wrote:
>>> Adds basic RGB support to hid-asus through multi-index. The interface
>>> works quite well, but has not gone through much stability testing.
>>> Applied on demand, if userspace does not touch the RGB sysfs, not
>>> even initialization is done. Ensuring compatibility with existing
>>> userspace programs.
>>>
>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>> ---
>>>    drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
>>>    1 file changed, 155 insertions(+), 14 deletions(-)
>>>
>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>> index 905453a4eb5b7..9d8ccfde5912e 100644
>>> --- a/drivers/hid/hid-asus.c
>>> +++ b/drivers/hid/hid-asus.c
>>> @@ -30,6 +30,7 @@
>>>    #include <linux/input/mt.h>
>>>    #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
>>>    #include <linux/power_supply.h>
>>> +#include <linux/led-class-multicolor.h>
>>>    #include <linux/leds.h>
>>>
>>>    #include "hid-ids.h"
>>> @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>    #define QUIRK_ROG_NKEY_KEYBOARD             BIT(11)
>>>    #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>>>    #define QUIRK_HANDLE_GENERIC                BIT(13)
>>> +#define QUIRK_ROG_NKEY_RGB           BIT(14)
>>>
>>>    #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
>>>                                                 QUIRK_NO_INIT_REPORTS | \
>>> @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>
>>>    struct asus_kbd_leds {
>>>        struct asus_hid_listener listener;
>>> +     struct led_classdev_mc mc_led;
>>> +     struct mc_subled subled_info[3];
>>>        struct hid_device *hdev;
>>>        struct work_struct work;
>>>        unsigned int brightness;
>>> +     uint8_t rgb_colors[3];
>>> +     bool rgb_init;
>>> +     bool rgb_set;
>>> +     bool rgb_registered;
>>>        spinlock_t lock;
>>>        bool removed;
>>>    };
>>> @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
>>>        spin_unlock_irqrestore(&led->lock, flags);
>>>    }
>>>
>>> -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
>>> +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
>>> +{
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&led->lock, flags);
>>> +     led->brightness = brightness;
>>> +     spin_unlock_irqrestore(&led->lock, flags);
>>> +
>>> +     asus_schedule_work(led);
>>> +}
>>> +
>>> +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
>>>                                   int brightness)
>>>    {
>>>        struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
>>>                                                 listener);
>>> +     do_asus_kbd_backlight_set(led, brightness);
>>> +     if (led->rgb_registered) {
>>> +             led->mc_led.led_cdev.brightness = brightness;
>>> +             led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
>>> +                                                       brightness);
>>> +     }
>>> +}
>>> +
>>> +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
>>> +                                 enum led_brightness brightness)
>>> +{
>>> +     struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
>>> +     struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
>>> +                                              mc_led);
>>>        unsigned long flags;
>>>
>>>        spin_lock_irqsave(&led->lock, flags);
>>> -     led->brightness = brightness;
>>> +     led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
>>> +     led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
>>> +     led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
>>> +     led->rgb_set = true;
>>>        spin_unlock_irqrestore(&led->lock, flags);
>>>
>>> -     asus_schedule_work(led);
>>> +     do_asus_kbd_backlight_set(led, brightness);
>>> +}
>>> +
>>> +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
>>> +{
>>> +     struct led_classdev_mc *mc_led;
>>> +     struct asus_kbd_leds *led;
>>> +     enum led_brightness brightness;
>>> +     unsigned long flags;
>>> +
>>> +     mc_led = lcdev_to_mccdev(led_cdev);
>>> +     led = container_of(mc_led, struct asus_kbd_leds, mc_led);
>>> +
>>> +     spin_lock_irqsave(&led->lock, flags);
>>> +     brightness = led->brightness;
>>> +     spin_unlock_irqrestore(&led->lock, flags);
>>> +
>>> +     return brightness;
>>>    }
>>>
>>> -static void asus_kbd_backlight_work(struct work_struct *work)
>>> +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
>>>    {
>>> -     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
>>>        u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
>>>        int ret;
>>>        unsigned long flags;
>>> @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
>>>                hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
>>>    }
>>>
>>> +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
>>> +{
>>> +     u8 rgb_buf[][7] = {
>>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
>>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
>>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
>>> +     };
>>> +     unsigned long flags;
>>> +     uint8_t colors[3];
>>> +     bool rgb_init, rgb_set;
>>> +     int ret;
>>> +
>>> +     spin_lock_irqsave(&led->lock, flags);
>>> +     rgb_init = led->rgb_init;
>>> +     rgb_set = led->rgb_set;
>>> +     led->rgb_set = false;
>>> +     colors[0] = led->rgb_colors[0];
>>> +     colors[1] = led->rgb_colors[1];
>>> +     colors[2] = led->rgb_colors[2];
>>> +     spin_unlock_irqrestore(&led->lock, flags);
>>> +
>>> +     if (!rgb_set)
>>> +             return;
>>> +
>>> +     if (rgb_init) {
>>> +             ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
>>> +             if (ret < 0) {
>>> +                     hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
>>> +                     return;
>>> +             }
>>> +             spin_lock_irqsave(&led->lock, flags);
>>> +             led->rgb_init = false;
>>> +             spin_unlock_irqrestore(&led->lock, flags);
>>> +     }
>>> +
>>> +     /* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
>>> +     rgb_buf[0][4] = colors[0];
>>> +     rgb_buf[0][5] = colors[1];
>>> +     rgb_buf[0][6] = colors[2];
>>> +
>>> +     for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
>>> +             ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
>>> +             if (ret < 0) {
>>> +                     hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
>>> +                     return;
>>> +             }
>>> +     }
>>> +}
>>> +
>>> +static void asus_kbd_work(struct work_struct *work)
>>> +{
>>> +     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
>>> +                                              work);
>>> +     asus_kbd_backlight_work(led);
>>> +     asus_kbd_rgb_work(led);
>>> +}
>>> +
>>>    static int asus_kbd_register_leds(struct hid_device *hdev)
>>>    {
>>>        struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
>>>        unsigned char kbd_func;
>>> +     struct asus_kbd_leds *leds;
>>> +     bool no_led;
>>>        int ret;
>>>
>>>        ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>        if (!drvdata->kbd_backlight)
>>>                return -ENOMEM;
>>>
>>> -     drvdata->kbd_backlight->removed = false;
>>> -     drvdata->kbd_backlight->brightness = 0;
>>> -     drvdata->kbd_backlight->hdev = hdev;
>>> -     drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
>>> -     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
>>> +     leds = drvdata->kbd_backlight;
>>> +     leds->removed = false;
>>> +     leds->brightness = 3;
>>> +     leds->hdev = hdev;
>>> +     leds->listener.brightness_set = asus_kbd_listener_set;
>>> +
>>> +     leds->rgb_colors[0] = 0;
>>> +     leds->rgb_colors[1] = 0;
>>> +     leds->rgb_colors[2] = 0;
>>> +     leds->rgb_init = true;
>>> +     leds->rgb_set = false;
>>> +     leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
>>> +                                     "asus-%s-led",
>>> +                                     strlen(hdev->uniq) ?
>>> +                                     hdev->uniq : dev_name(&hdev->dev));
>>
>> A quick note. This breaks convention for LED names. The style guide is
>> at Documentation/leds/leds-class.rst. Per my parallel email to this one
>> I would like to see the mentioned naming scheme `asus:rgb:kbd_backlight`
>> adopted.
> 
> Perhaps. It would be the first kbd_backlight driver to have "rgb" in
> it. It is a bit out of scope for this series as I do not touch the
> functionality of it but I can add a patch for it and a fixes
> e305a71cea37a64c75 tag.
> 

Your proposal for naming in other reply is good :)
If the intent is to keep 0-3 brightness and RGB controls separated the 
kbd_backlight doesn't need adjustment in naming, particularly if the RGB 
is *not* inside `asus:rgb:kbd_backlight`.

>> Expanding further on one of the points there you might need to
>> move the led_classdev_mc in to asus-wmi to fulfil having the single
>> sysfs endpoint. Since you're using the listner pattern it shouldn't be
>> much work.
> 
> I only want the brightness to sync, not the color. Only the brightness
> between Aura devices needs to be the same. In this case
> asus::kbd_backlight if it has a color controls the wmi color, and the
> asus- devices control the usb.
> 

Hmm, what about multicolour brightness? Otherwise yeah, understood and 
I'm fine with that. Given you now ustilise the kbd_dill<up/down> 
directly I don't see any issues there either.

> Also, groups are not dynamic so this is not possible. E.g., if you
> setup a WMI listener that does not have RGB, and then the USB keyboard
> connects you can no longer change the groups unless you reconnect the
> device.

That's a shame. Oh well.

I would have preferred RGB and brightness combined. At a glance I would 
have stored RGB in a global or similar, then if a listener has mcled 
available that value would be applied.

But userpsace should be using udev libs and similar to find devices like 
this, so naming is more of a hint alongside the attributes. Meaning name 
changes or including mcled inside standard led shouldn't break things. 
Neither should keeping them separated as you have.

Apologies for the bikeshedding on this. I had been reluctant to add RGB 
myself for a number of reasons:

1. Windows uses LampArray for the dynamic LED (new)
2. Otherwise you need to use MCU software mode, that's what that mode is for
3. I don't know of a capabilities request for MCU software mode
4. Or you're forced to set MCU mode static/solid
5. Single colour keybaords vs RGB, on same PID :(

I considered adding attributes to mcled in sysfs for 
mode/speed/direction. But then I had to add an enourmous table of 
modes-per-model.

At the end of the day I think your solution is fine and we don't have 
much other choice beyond trying to introduce a new API better suited for 
RGB keyboards with many features. I suspect it will also be fine for 
other laptops since the mode is set on write to RGB, so hidraw is still 
open to userspace.

With the other changes in place I'll experiment a little with the 
laptops I have and see how it works. I have two that i will need to 
check HID and WMI on even though it's not an issue for the Z13 and Ally, 
it might highlight any pain points and improve things further. Could be 
a few days, or coming weekend sorry, I've got a massive amount of work 
on, besides my own kernel patches.

Cheers,
Luke.

>>> +     leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
>>> +     leds->mc_led.led_cdev.max_brightness = 3,
>>> +     leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
>>> +     leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
>>> +     leds->mc_led.subled_info = leds->subled_info,
>>> +     leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
>>> +     leds->subled_info[0].color_index = LED_COLOR_ID_RED;
>>> +     leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
>>> +     leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
>>> +
>>> +     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
>>>        spin_lock_init(&drvdata->kbd_backlight->lock);
>>>
>>>        ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
>>> +     no_led = !!ret;
>>> +
>>> +     if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
>>> +             ret = devm_led_classdev_multicolor_register(
>>> +                     &hdev->dev, &leds->mc_led);
>>> +             if (!ret)
>>> +                     leds->rgb_registered = true;
>>> +             no_led &= !!ret;
>>> +     }
>>>
>>> -     if (ret < 0) {
>>> +     if (no_led) {
>>>                /* No need to have this still around */
>>>                devm_kfree(&hdev->dev, drvdata->kbd_backlight);
>>>        }
>>>
>>> -     return ret;
>>> +     return no_led ? -ENODEV : 0;
>>>    }
>>>
>>>    /*
>>> @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
>>>          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>            USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
>>> -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>> +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>>>        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>            USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
>>>          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>> @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
>>>         */
>>>        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>>>                USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
>>> -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>> +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
>>>        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>>>                USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
>>>        { }
>>


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

* Re: [PATCH v3 09/10] HID: asus: add basic RGB support
  2025-03-23 20:39       ` Luke D. Jones
@ 2025-03-23 21:08         ` Antheas Kapenekakis
  0 siblings, 0 replies; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-23 21:08 UTC (permalink / raw)
  To: Luke D. Jones
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Hans de Goede,
	Ilpo Järvinen

On Sun, 23 Mar 2025 at 21:39, Luke D. Jones <luke@ljones.dev> wrote:
>
> On 24/03/25 00:37, Antheas Kapenekakis wrote:
> > On Sun, 23 Mar 2025 at 07:40, Luke D. Jones <luke@ljones.dev> wrote:
> >>
> >> On 22/03/25 23:28, Antheas Kapenekakis wrote:
> >>> Adds basic RGB support to hid-asus through multi-index. The interface
> >>> works quite well, but has not gone through much stability testing.
> >>> Applied on demand, if userspace does not touch the RGB sysfs, not
> >>> even initialization is done. Ensuring compatibility with existing
> >>> userspace programs.
> >>>
> >>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> >>> ---
> >>>    drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
> >>>    1 file changed, 155 insertions(+), 14 deletions(-)
> >>>
> >>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> >>> index 905453a4eb5b7..9d8ccfde5912e 100644
> >>> --- a/drivers/hid/hid-asus.c
> >>> +++ b/drivers/hid/hid-asus.c
> >>> @@ -30,6 +30,7 @@
> >>>    #include <linux/input/mt.h>
> >>>    #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
> >>>    #include <linux/power_supply.h>
> >>> +#include <linux/led-class-multicolor.h>
> >>>    #include <linux/leds.h>
> >>>
> >>>    #include "hid-ids.h"
> >>> @@ -85,6 +86,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >>>    #define QUIRK_ROG_NKEY_KEYBOARD             BIT(11)
> >>>    #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> >>>    #define QUIRK_HANDLE_GENERIC                BIT(13)
> >>> +#define QUIRK_ROG_NKEY_RGB           BIT(14)
> >>>
> >>>    #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
> >>>                                                 QUIRK_NO_INIT_REPORTS | \
> >>> @@ -97,9 +99,15 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >>>
> >>>    struct asus_kbd_leds {
> >>>        struct asus_hid_listener listener;
> >>> +     struct led_classdev_mc mc_led;
> >>> +     struct mc_subled subled_info[3];
> >>>        struct hid_device *hdev;
> >>>        struct work_struct work;
> >>>        unsigned int brightness;
> >>> +     uint8_t rgb_colors[3];
> >>> +     bool rgb_init;
> >>> +     bool rgb_set;
> >>> +     bool rgb_registered;
> >>>        spinlock_t lock;
> >>>        bool removed;
> >>>    };
> >>> @@ -504,23 +512,67 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
> >>>        spin_unlock_irqrestore(&led->lock, flags);
> >>>    }
> >>>
> >>> -static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
> >>> +static void do_asus_kbd_backlight_set(struct asus_kbd_leds *led, int brightness)
> >>> +{
> >>> +     unsigned long flags;
> >>> +
> >>> +     spin_lock_irqsave(&led->lock, flags);
> >>> +     led->brightness = brightness;
> >>> +     spin_unlock_irqrestore(&led->lock, flags);
> >>> +
> >>> +     asus_schedule_work(led);
> >>> +}
> >>> +
> >>> +static void asus_kbd_listener_set(struct asus_hid_listener *listener,
> >>>                                   int brightness)
> >>>    {
> >>>        struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
> >>>                                                 listener);
> >>> +     do_asus_kbd_backlight_set(led, brightness);
> >>> +     if (led->rgb_registered) {
> >>> +             led->mc_led.led_cdev.brightness = brightness;
> >>> +             led_classdev_notify_brightness_hw_changed(&led->mc_led.led_cdev,
> >>> +                                                       brightness);
> >>> +     }
> >>> +}
> >>> +
> >>> +static void asus_kbd_brightness_set(struct led_classdev *led_cdev,
> >>> +                                 enum led_brightness brightness)
> >>> +{
> >>> +     struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
> >>> +     struct asus_kbd_leds *led = container_of(mc_cdev, struct asus_kbd_leds,
> >>> +                                              mc_led);
> >>>        unsigned long flags;
> >>>
> >>>        spin_lock_irqsave(&led->lock, flags);
> >>> -     led->brightness = brightness;
> >>> +     led->rgb_colors[0] = mc_cdev->subled_info[0].intensity;
> >>> +     led->rgb_colors[1] = mc_cdev->subled_info[1].intensity;
> >>> +     led->rgb_colors[2] = mc_cdev->subled_info[2].intensity;
> >>> +     led->rgb_set = true;
> >>>        spin_unlock_irqrestore(&led->lock, flags);
> >>>
> >>> -     asus_schedule_work(led);
> >>> +     do_asus_kbd_backlight_set(led, brightness);
> >>> +}
> >>> +
> >>> +static enum led_brightness asus_kbd_brightness_get(struct led_classdev *led_cdev)
> >>> +{
> >>> +     struct led_classdev_mc *mc_led;
> >>> +     struct asus_kbd_leds *led;
> >>> +     enum led_brightness brightness;
> >>> +     unsigned long flags;
> >>> +
> >>> +     mc_led = lcdev_to_mccdev(led_cdev);
> >>> +     led = container_of(mc_led, struct asus_kbd_leds, mc_led);
> >>> +
> >>> +     spin_lock_irqsave(&led->lock, flags);
> >>> +     brightness = led->brightness;
> >>> +     spin_unlock_irqrestore(&led->lock, flags);
> >>> +
> >>> +     return brightness;
> >>>    }
> >>>
> >>> -static void asus_kbd_backlight_work(struct work_struct *work)
> >>> +static void asus_kbd_backlight_work(struct asus_kbd_leds *led)
> >>>    {
> >>> -     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
> >>>        u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
> >>>        int ret;
> >>>        unsigned long flags;
> >>> @@ -534,10 +586,69 @@ static void asus_kbd_backlight_work(struct work_struct *work)
> >>>                hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
> >>>    }
> >>>
> >>> +static void asus_kbd_rgb_work(struct asus_kbd_leds *led)
> >>> +{
> >>> +     u8 rgb_buf[][7] = {
> >>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB3 }, /* set mode */
> >>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB5 }, /* apply mode */
> >>> +             { FEATURE_KBD_LED_REPORT_ID1, 0xB4 }, /* save to mem */
> >>> +     };
> >>> +     unsigned long flags;
> >>> +     uint8_t colors[3];
> >>> +     bool rgb_init, rgb_set;
> >>> +     int ret;
> >>> +
> >>> +     spin_lock_irqsave(&led->lock, flags);
> >>> +     rgb_init = led->rgb_init;
> >>> +     rgb_set = led->rgb_set;
> >>> +     led->rgb_set = false;
> >>> +     colors[0] = led->rgb_colors[0];
> >>> +     colors[1] = led->rgb_colors[1];
> >>> +     colors[2] = led->rgb_colors[2];
> >>> +     spin_unlock_irqrestore(&led->lock, flags);
> >>> +
> >>> +     if (!rgb_set)
> >>> +             return;
> >>> +
> >>> +     if (rgb_init) {
> >>> +             ret = asus_kbd_init(led->hdev, FEATURE_KBD_LED_REPORT_ID1);
> >>> +             if (ret < 0) {
> >>> +                     hid_err(led->hdev, "Asus failed to init RGB: %d\n", ret);
> >>> +                     return;
> >>> +             }
> >>> +             spin_lock_irqsave(&led->lock, flags);
> >>> +             led->rgb_init = false;
> >>> +             spin_unlock_irqrestore(&led->lock, flags);
> >>> +     }
> >>> +
> >>> +     /* Protocol is: 54b3 zone (0=all) mode (0=solid) RGB */
> >>> +     rgb_buf[0][4] = colors[0];
> >>> +     rgb_buf[0][5] = colors[1];
> >>> +     rgb_buf[0][6] = colors[2];
> >>> +
> >>> +     for (size_t i = 0; i < ARRAY_SIZE(rgb_buf); i++) {
> >>> +             ret = asus_kbd_set_report(led->hdev, rgb_buf[i], sizeof(rgb_buf[i]));
> >>> +             if (ret < 0) {
> >>> +                     hid_err(led->hdev, "Asus failed to set RGB: %d\n", ret);
> >>> +                     return;
> >>> +             }
> >>> +     }
> >>> +}
> >>> +
> >>> +static void asus_kbd_work(struct work_struct *work)
> >>> +{
> >>> +     struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds,
> >>> +                                              work);
> >>> +     asus_kbd_backlight_work(led);
> >>> +     asus_kbd_rgb_work(led);
> >>> +}
> >>> +
> >>>    static int asus_kbd_register_leds(struct hid_device *hdev)
> >>>    {
> >>>        struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> >>>        unsigned char kbd_func;
> >>> +     struct asus_kbd_leds *leds;
> >>> +     bool no_led;
> >>>        int ret;
> >>>
> >>>        ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
> >>> @@ -565,21 +676,51 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> >>>        if (!drvdata->kbd_backlight)
> >>>                return -ENOMEM;
> >>>
> >>> -     drvdata->kbd_backlight->removed = false;
> >>> -     drvdata->kbd_backlight->brightness = 0;
> >>> -     drvdata->kbd_backlight->hdev = hdev;
> >>> -     drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
> >>> -     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
> >>> +     leds = drvdata->kbd_backlight;
> >>> +     leds->removed = false;
> >>> +     leds->brightness = 3;
> >>> +     leds->hdev = hdev;
> >>> +     leds->listener.brightness_set = asus_kbd_listener_set;
> >>> +
> >>> +     leds->rgb_colors[0] = 0;
> >>> +     leds->rgb_colors[1] = 0;
> >>> +     leds->rgb_colors[2] = 0;
> >>> +     leds->rgb_init = true;
> >>> +     leds->rgb_set = false;
> >>> +     leds->mc_led.led_cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
> >>> +                                     "asus-%s-led",
> >>> +                                     strlen(hdev->uniq) ?
> >>> +                                     hdev->uniq : dev_name(&hdev->dev));
> >>
> >> A quick note. This breaks convention for LED names. The style guide is
> >> at Documentation/leds/leds-class.rst. Per my parallel email to this one
> >> I would like to see the mentioned naming scheme `asus:rgb:kbd_backlight`
> >> adopted.
> >
> > Perhaps. It would be the first kbd_backlight driver to have "rgb" in
> > it. It is a bit out of scope for this series as I do not touch the
> > functionality of it but I can add a patch for it and a fixes
> > e305a71cea37a64c75 tag.
> >
>
> Your proposal for naming in other reply is good :)
> If the intent is to keep 0-3 brightness and RGB controls separated the
> kbd_backlight doesn't need adjustment in naming, particularly if the RGB
> is *not* inside `asus:rgb:kbd_backlight`.
>
> >> Expanding further on one of the points there you might need to
> >> move the led_classdev_mc in to asus-wmi to fulfil having the single
> >> sysfs endpoint. Since you're using the listner pattern it shouldn't be
> >> much work.
> >
> > I only want the brightness to sync, not the color. Only the brightness
> > between Aura devices needs to be the same. In this case
> > asus::kbd_backlight if it has a color controls the wmi color, and the
> > asus- devices control the usb.
> >
>
> Hmm, what about multicolour brightness? Otherwise yeah, understood and
> I'm fine with that. Given you now ustilise the kbd_dill<up/down>
> directly I don't see any issues there either.
>
> > Also, groups are not dynamic so this is not possible. E.g., if you
> > setup a WMI listener that does not have RGB, and then the USB keyboard
> > connects you can no longer change the groups unless you reconnect the
> > device.
>
> That's a shame. Oh well.
>
> I would have preferred RGB and brightness combined. At a glance I would
> have stored RGB in a global or similar, then if a listener has mcled
> available that value would be applied.

There is also a brightness var in the usb endpoint thats duplicated.
So it can be controlled individually. But writing a brightness to
asus::kbd_brightness or using the keyboard shortcut will override it.
Only way I could think of to manage it.

> But userpsace should be using udev libs and similar to find devices like
> this, so naming is more of a hint alongside the attributes. Meaning name
> changes or including mcled inside standard led shouldn't break things.
> Neither should keeping them separated as you have.
>
> Apologies for the bikeshedding on this. I had been reluctant to add RGB
> myself for a number of reasons:
>
> 1. Windows uses LampArray for the dynamic LED (new)

Yeah i played a bit with dynamic lighting on windows on the Ally. It
looks like a mess with Armoury crate and windows fighting over the
leds.

> 2. Otherwise you need to use MCU software mode, that's what that mode is for
> 3. I don't know of a capabilities request for MCU software mode
> 4. Or you're forced to set MCU mode static/solid
> 5. Single colour keybaords vs RGB, on same PID :(
>
> I considered adding attributes to mcled in sysfs for
> mode/speed/direction. But then I had to add an enourmous table of
> modes-per-model.
>
> At the end of the day I think your solution is fine and we don't have
> much other choice beyond trying to introduce a new API better suited for
> RGB keyboards with many features. I suspect it will also be fine for
> other laptops since the mode is set on write to RGB, so hidraw is still
> open to userspace.

I think solid is a great start. I found myself using it this week.
Then getting the KDE guys to add a color picker.

> With the other changes in place I'll experiment a little with the
> laptops I have and see how it works. I have two that i will need to
> check HID and WMI on even though it's not an issue for the Z13 and Ally,
> it might highlight any pain points and improve things further. Could be
> a few days, or coming weekend sorry, I've got a massive amount of work
> on, besides my own kernel patches.

That's alright with me. I plan to send a V4 middle of the week. I will
try to include a patch that inits 0x5d that can be reverted later if
it is not needed.

Also have some other stuff to deal with Bazzite. Mesa 25 was a big PITA

Antheas

> Cheers,
> Luke.
>
> >>> +     leds->mc_led.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
> >>> +     leds->mc_led.led_cdev.max_brightness = 3,
> >>> +     leds->mc_led.led_cdev.brightness_set = asus_kbd_brightness_set,
> >>> +     leds->mc_led.led_cdev.brightness_get = asus_kbd_brightness_get,
> >>> +     leds->mc_led.subled_info = leds->subled_info,
> >>> +     leds->mc_led.num_colors = ARRAY_SIZE(leds->subled_info),
> >>> +     leds->subled_info[0].color_index = LED_COLOR_ID_RED;
> >>> +     leds->subled_info[1].color_index = LED_COLOR_ID_GREEN;
> >>> +     leds->subled_info[2].color_index = LED_COLOR_ID_BLUE;
> >>> +
> >>> +     INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_work);
> >>>        spin_lock_init(&drvdata->kbd_backlight->lock);
> >>>
> >>>        ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
> >>> +     no_led = !!ret;
> >>> +
> >>> +     if (drvdata->quirks & QUIRK_ROG_NKEY_RGB) {
> >>> +             ret = devm_led_classdev_multicolor_register(
> >>> +                     &hdev->dev, &leds->mc_led);
> >>> +             if (!ret)
> >>> +                     leds->rgb_registered = true;
> >>> +             no_led &= !!ret;
> >>> +     }
> >>>
> >>> -     if (ret < 0) {
> >>> +     if (no_led) {
> >>>                /* No need to have this still around */
> >>>                devm_kfree(&hdev->dev, drvdata->kbd_backlight);
> >>>        }
> >>>
> >>> -     return ret;
> >>> +     return no_led ? -ENODEV : 0;
> >>>    }
> >>>
> >>>    /*
> >>> @@ -1289,7 +1430,7 @@ static const struct hid_device_id asus_devices[] = {
> >>>          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>>        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>>            USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> >>> -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>> +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
> >>>        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>>            USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
> >>>          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>> @@ -1318,7 +1459,7 @@ static const struct hid_device_id asus_devices[] = {
> >>>         */
> >>>        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> >>>                USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
> >>> -       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>> +       QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_RGB },
> >>>        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> >>>                USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
> >>>        { }
> >>
>

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

* Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers
  2025-03-22 10:27 ` [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers Antheas Kapenekakis
  2025-03-23  0:02   ` Luke D. Jones
@ 2025-03-24 11:31   ` Hans de Goede
  2025-03-24 12:29     ` Antheas Kapenekakis
  1 sibling, 1 reply; 37+ messages in thread
From: Hans de Goede @ 2025-03-24 11:31 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Ilpo Järvinen

Hi Antheas,

Note not a full review, just taking a generic look at the new API
between asus-wmi and asus-hid.

On 22-Mar-25 11:27, Antheas Kapenekakis wrote:
> Some devices, such as the Z13 have multiple AURA devices connected
> to them by USB. In addition, they might have a WMI interface for
> RGB. In Windows, Armoury Crate exposes a unified brightness slider
> for all of them, with 3 brightness levels.
> 
> Therefore, to be synergistic in Linux, and support existing tooling
> such as UPower, allow adding listeners to the RGB device of the WMI
> interface. If WMI does not exist, lazy initialize the interface.
> 
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>  drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
>  include/linux/platform_data/x86/asus-wmi.h |  16 +++
>  2 files changed, 117 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index 38ef778e8c19b..95ef9b1d321bb 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -254,6 +254,8 @@ struct asus_wmi {
>  	int tpd_led_wk;
>  	struct led_classdev kbd_led;
>  	int kbd_led_wk;
> +	bool kbd_led_avail;
> +	bool kbd_led_registered;
>  	struct led_classdev lightbar_led;
>  	int lightbar_led_wk;
>  	struct led_classdev micmute_led;
> @@ -1487,6 +1489,53 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
>  
>  /* LEDs ***********************************************************************/
>  
> +struct asus_hid_ref {
> +	struct list_head listeners;
> +	struct asus_wmi *asus;
> +	spinlock_t lock;
> +};
> +
> +struct asus_hid_ref asus_ref = {
> +	.listeners = LIST_HEAD_INIT(asus_ref.listeners),
> +	.asus = NULL,
> +	.lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
> +};
> +
> +int asus_hid_register_listener(struct asus_hid_listener *bdev)
> +{
> +	unsigned long flags;
> +	int ret = 0;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	list_add_tail(&bdev->list, &asus_ref.listeners);
> +	if (asus_ref.asus) {
> +		if (asus_ref.asus->kbd_led_registered && asus_ref.asus->kbd_led_wk >= 0)
> +			bdev->brightness_set(bdev, asus_ref.asus->kbd_led_wk);
> +
> +		if (!asus_ref.asus->kbd_led_registered) {
> +			ret = led_classdev_register(
> +				&asus_ref.asus->platform_device->dev,
> +				&asus_ref.asus->kbd_led);
> +			if (!ret)
> +				asus_ref.asus->kbd_led_registered = true;
> +		}
> +	}
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(asus_hid_register_listener);
> +
> +void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	list_del(&bdev->list);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
> +
>  /*
>   * These functions actually update the LED's, and are called from a
>   * workqueue. By doing this as separate work rather than when the LED
> @@ -1566,6 +1615,7 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
>  
>  static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>  {
> +	struct asus_hid_listener *listener;
>  	struct asus_wmi *asus;
>  	int max_level;
>  
> @@ -1573,25 +1623,39 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>  	max_level = asus->kbd_led.max_brightness;
>  
>  	asus->kbd_led_wk = clamp_val(value, 0, max_level);
> -	kbd_led_update(asus);
> +
> +	if (asus->kbd_led_avail)
> +		kbd_led_update(asus);
> +
> +	list_for_each_entry(listener, &asus_ref.listeners, list)
> +		listener->brightness_set(listener, asus->kbd_led_wk);
>  }
>  
>  static void kbd_led_set(struct led_classdev *led_cdev,
>  			enum led_brightness value)
>  {
> +	unsigned long flags;
> +
>  	/* Prevent disabling keyboard backlight on module unregister */
>  	if (led_cdev->flags & LED_UNREGISTERING)
>  		return;
>  
> +	spin_lock_irqsave(&asus_ref.lock, flags);
>  	do_kbd_led_set(led_cdev, value);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
>  }
>  
>  static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
>  {
> -	struct led_classdev *led_cdev = &asus->kbd_led;
> +	struct led_classdev *led_cdev;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	led_cdev = &asus->kbd_led;
>  
>  	do_kbd_led_set(led_cdev, value);
>  	led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
>  }
>  
>  static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> @@ -1601,6 +1665,9 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
>  
>  	asus = container_of(led_cdev, struct asus_wmi, kbd_led);
>  
> +	if (!asus->kbd_led_avail)
> +		return asus->kbd_led_wk;
> +
>  	retval = kbd_led_read(asus, &value, NULL);
>  	if (retval < 0)
>  		return retval;
> @@ -1716,7 +1783,14 @@ static int camera_led_set(struct led_classdev *led_cdev,
>  
>  static void asus_wmi_led_exit(struct asus_wmi *asus)
>  {
> -	led_classdev_unregister(&asus->kbd_led);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	asus_ref.asus = NULL;
> +	if (asus->kbd_led_registered)
> +		led_classdev_unregister(&asus->kbd_led);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
>  	led_classdev_unregister(&asus->tpd_led);
>  	led_classdev_unregister(&asus->wlan_led);
>  	led_classdev_unregister(&asus->lightbar_led);
> @@ -1730,6 +1804,8 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
>  static int asus_wmi_led_init(struct asus_wmi *asus)
>  {
>  	int rv = 0, num_rgb_groups = 0, led_val;
> +	unsigned long flags;
> +	bool has_listeners;
>  
>  	if (asus->kbd_rgb_dev)
>  		kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
> @@ -1754,24 +1830,37 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
>  			goto error;
>  	}
>  
> -	if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
> -		pr_info("using asus-wmi for asus::kbd_backlight\n");
> +	asus->kbd_led.name = "asus::kbd_backlight";
> +	asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> +	asus->kbd_led.brightness_set = kbd_led_set;
> +	asus->kbd_led.brightness_get = kbd_led_get;
> +	asus->kbd_led.max_brightness = 3;
> +	asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
> +
> +	if (asus->kbd_led_avail)
>  		asus->kbd_led_wk = led_val;
> -		asus->kbd_led.name = "asus::kbd_backlight";
> -		asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> -		asus->kbd_led.brightness_set = kbd_led_set;
> -		asus->kbd_led.brightness_get = kbd_led_get;
> -		asus->kbd_led.max_brightness = 3;
> +	else
> +		asus->kbd_led_wk = -1;
> +
> +	if (asus->kbd_led_avail && num_rgb_groups != 0)
> +		asus->kbd_led.groups = kbd_rgb_mode_groups;
>  
> -		if (num_rgb_groups != 0)
> -			asus->kbd_led.groups = kbd_rgb_mode_groups;
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	has_listeners = !list_empty(&asus_ref.listeners);
> +	spin_unlock_irqrestore(&asus_ref.lock, flags);

It seems to me that you should also call brightness_set()
on all the kbds already in the list so that their brightness
gets synced with the wmi kbd-backlight brightness when
the wmi driver loads later then the hid driver ?

>  
> +	if (asus->kbd_led_avail || has_listeners) {
>  		rv = led_classdev_register(&asus->platform_device->dev,
>  					   &asus->kbd_led);
>  		if (rv)
>  			goto error;
> +		asus->kbd_led_registered = true;
>  	}
>  
> +	spin_lock_irqsave(&asus_ref.lock, flags);
> +	asus_ref.asus = asus;

There is race here where a hid keyboard might show up between
the 2 places you take the lock, in that case if there is
no wmi kbd-backlight then you will not register the led_classdev
when asus_hid_register_listener() gets called between the unlock
and the lock...  I'm not sure what the best way is to fix this.

Regards,

Hans



> +	spin_unlock_irqrestore(&asus_ref.lock, flags);
> +
>  	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
>  			&& (asus->driver->quirks->wapf > 0)) {
>  		INIT_WORK(&asus->wlan_led_work, wlan_led_update);
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index 783e2a336861b..ec8b0c585a63f 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -157,14 +157,30 @@
>  #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK	0x0000FF00
>  #define ASUS_WMI_DSTS_LIGHTBAR_MASK	0x0000000F
>  
> +struct asus_hid_listener {
> +	struct list_head list;
> +	void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
> +};
> +
>  #if IS_REACHABLE(CONFIG_ASUS_WMI)
>  int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
> +
> +int asus_hid_register_listener(struct asus_hid_listener *cdev);
> +void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
>  #else
>  static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
>  					   u32 *retval)
>  {
>  	return -ENODEV;
>  }
> +
> +static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
> +{
> +	return -ENODEV;
> +}
> +static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> +{
> +}
>  #endif
>  
>  /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */


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

* Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers
  2025-03-24 11:31   ` Hans de Goede
@ 2025-03-24 12:29     ` Antheas Kapenekakis
  2025-03-24 12:37       ` Hans de Goede
  0 siblings, 1 reply; 37+ messages in thread
From: Antheas Kapenekakis @ 2025-03-24 12:29 UTC (permalink / raw)
  To: Hans de Goede
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Luke D . Jones,
	Ilpo Järvinen

On Mon, 24 Mar 2025 at 12:31, Hans de Goede <hdegoede@redhat.com> wrote:
>
> Hi Antheas,
>
> Note not a full review, just taking a generic look at the new API
> between asus-wmi and asus-hid.
>
> On 22-Mar-25 11:27, Antheas Kapenekakis wrote:
> > Some devices, such as the Z13 have multiple AURA devices connected
> > to them by USB. In addition, they might have a WMI interface for
> > RGB. In Windows, Armoury Crate exposes a unified brightness slider
> > for all of them, with 3 brightness levels.
> >
> > Therefore, to be synergistic in Linux, and support existing tooling
> > such as UPower, allow adding listeners to the RGB device of the WMI
> > interface. If WMI does not exist, lazy initialize the interface.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> >  drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
> >  include/linux/platform_data/x86/asus-wmi.h |  16 +++
> >  2 files changed, 117 insertions(+), 12 deletions(-)
> >
> > diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> > index 38ef778e8c19b..95ef9b1d321bb 100644
> > --- a/drivers/platform/x86/asus-wmi.c
> > +++ b/drivers/platform/x86/asus-wmi.c
> > @@ -254,6 +254,8 @@ struct asus_wmi {
> >       int tpd_led_wk;
> >       struct led_classdev kbd_led;
> >       int kbd_led_wk;
> > +     bool kbd_led_avail;
> > +     bool kbd_led_registered;
> >       struct led_classdev lightbar_led;
> >       int lightbar_led_wk;
> >       struct led_classdev micmute_led;
> > @@ -1487,6 +1489,53 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
> >
> >  /* LEDs ***********************************************************************/
> >
> > +struct asus_hid_ref {
> > +     struct list_head listeners;
> > +     struct asus_wmi *asus;
> > +     spinlock_t lock;
> > +};
> > +
> > +struct asus_hid_ref asus_ref = {
> > +     .listeners = LIST_HEAD_INIT(asus_ref.listeners),
> > +     .asus = NULL,
> > +     .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
> > +};
> > +
> > +int asus_hid_register_listener(struct asus_hid_listener *bdev)
> > +{
> > +     unsigned long flags;
> > +     int ret = 0;
> > +
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> > +     list_add_tail(&bdev->list, &asus_ref.listeners);
> > +     if (asus_ref.asus) {
> > +             if (asus_ref.asus->kbd_led_registered && asus_ref.asus->kbd_led_wk >= 0)
> > +                     bdev->brightness_set(bdev, asus_ref.asus->kbd_led_wk);
> > +
> > +             if (!asus_ref.asus->kbd_led_registered) {
> > +                     ret = led_classdev_register(
> > +                             &asus_ref.asus->platform_device->dev,
> > +                             &asus_ref.asus->kbd_led);
> > +                     if (!ret)
> > +                             asus_ref.asus->kbd_led_registered = true;
> > +             }
> > +     }
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
> > +
> > +     return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(asus_hid_register_listener);
> > +
> > +void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> > +{
> > +     unsigned long flags;
> > +
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> > +     list_del(&bdev->list);
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
> > +}
> > +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
> > +
> >  /*
> >   * These functions actually update the LED's, and are called from a
> >   * workqueue. By doing this as separate work rather than when the LED
> > @@ -1566,6 +1615,7 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
> >
> >  static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
> >  {
> > +     struct asus_hid_listener *listener;
> >       struct asus_wmi *asus;
> >       int max_level;
> >
> > @@ -1573,25 +1623,39 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
> >       max_level = asus->kbd_led.max_brightness;
> >
> >       asus->kbd_led_wk = clamp_val(value, 0, max_level);
> > -     kbd_led_update(asus);
> > +
> > +     if (asus->kbd_led_avail)
> > +             kbd_led_update(asus);
> > +
> > +     list_for_each_entry(listener, &asus_ref.listeners, list)
> > +             listener->brightness_set(listener, asus->kbd_led_wk);
> >  }
> >
> >  static void kbd_led_set(struct led_classdev *led_cdev,
> >                       enum led_brightness value)
> >  {
> > +     unsigned long flags;
> > +
> >       /* Prevent disabling keyboard backlight on module unregister */
> >       if (led_cdev->flags & LED_UNREGISTERING)
> >               return;
> >
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> >       do_kbd_led_set(led_cdev, value);
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
> >  }
> >
> >  static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
> >  {
> > -     struct led_classdev *led_cdev = &asus->kbd_led;
> > +     struct led_classdev *led_cdev;
> > +     unsigned long flags;
> > +
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> > +     led_cdev = &asus->kbd_led;
> >
> >       do_kbd_led_set(led_cdev, value);
> >       led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
> >  }
> >
> >  static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> > @@ -1601,6 +1665,9 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> >
> >       asus = container_of(led_cdev, struct asus_wmi, kbd_led);
> >
> > +     if (!asus->kbd_led_avail)
> > +             return asus->kbd_led_wk;
> > +
> >       retval = kbd_led_read(asus, &value, NULL);
> >       if (retval < 0)
> >               return retval;
> > @@ -1716,7 +1783,14 @@ static int camera_led_set(struct led_classdev *led_cdev,
> >
> >  static void asus_wmi_led_exit(struct asus_wmi *asus)
> >  {
> > -     led_classdev_unregister(&asus->kbd_led);
> > +     unsigned long flags;
> > +
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> > +     asus_ref.asus = NULL;
> > +     if (asus->kbd_led_registered)
> > +             led_classdev_unregister(&asus->kbd_led);
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
> > +
> >       led_classdev_unregister(&asus->tpd_led);
> >       led_classdev_unregister(&asus->wlan_led);
> >       led_classdev_unregister(&asus->lightbar_led);
> > @@ -1730,6 +1804,8 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
> >  static int asus_wmi_led_init(struct asus_wmi *asus)
> >  {
> >       int rv = 0, num_rgb_groups = 0, led_val;
> > +     unsigned long flags;
> > +     bool has_listeners;
> >
> >       if (asus->kbd_rgb_dev)
> >               kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
> > @@ -1754,24 +1830,37 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
> >                       goto error;
> >       }
> >
> > -     if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
> > -             pr_info("using asus-wmi for asus::kbd_backlight\n");
> > +     asus->kbd_led.name = "asus::kbd_backlight";
> > +     asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> > +     asus->kbd_led.brightness_set = kbd_led_set;
> > +     asus->kbd_led.brightness_get = kbd_led_get;
> > +     asus->kbd_led.max_brightness = 3;
> > +     asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
> > +
> > +     if (asus->kbd_led_avail)
> >               asus->kbd_led_wk = led_val;
> > -             asus->kbd_led.name = "asus::kbd_backlight";
> > -             asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> > -             asus->kbd_led.brightness_set = kbd_led_set;
> > -             asus->kbd_led.brightness_get = kbd_led_get;
> > -             asus->kbd_led.max_brightness = 3;
> > +     else
> > +             asus->kbd_led_wk = -1;
> > +
> > +     if (asus->kbd_led_avail && num_rgb_groups != 0)
> > +             asus->kbd_led.groups = kbd_rgb_mode_groups;
> >
> > -             if (num_rgb_groups != 0)
> > -                     asus->kbd_led.groups = kbd_rgb_mode_groups;
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> > +     has_listeners = !list_empty(&asus_ref.listeners);
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>
> It seems to me that you should also call brightness_set()
> on all the kbds already in the list so that their brightness
> gets synced with the wmi kbd-backlight brightness when
> the wmi driver loads later then the hid driver ?

You raise a good point here. Let me think about it.

But yes, if we do a notify when kbd_led_wk != -1 if the hid device
connects after, we should do it for the before case too.

> >
> > +     if (asus->kbd_led_avail || has_listeners) {
> >               rv = led_classdev_register(&asus->platform_device->dev,
> >                                          &asus->kbd_led);
> >               if (rv)
> >                       goto error;
> > +             asus->kbd_led_registered = true;
> >       }
> >
> > +     spin_lock_irqsave(&asus_ref.lock, flags);
> > +     asus_ref.asus = asus;
>
> There is race here where a hid keyboard might show up between
> the 2 places you take the lock, in that case if there is
> no wmi kbd-backlight then you will not register the led_classdev
> when asus_hid_register_listener() gets called between the unlock
> and the lock...  I'm not sure what the best way is to fix this.

Thanks for catching that, probably a recheck:

has_listeners = !list_empty(&asus_ref.listeners);
if (has_listeners) goto register;

> Regards,
>
> Hans
>
>
>
> > +     spin_unlock_irqrestore(&asus_ref.lock, flags);
> > +
> >       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
> >                       && (asus->driver->quirks->wapf > 0)) {
> >               INIT_WORK(&asus->wlan_led_work, wlan_led_update);
> > diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> > index 783e2a336861b..ec8b0c585a63f 100644
> > --- a/include/linux/platform_data/x86/asus-wmi.h
> > +++ b/include/linux/platform_data/x86/asus-wmi.h
> > @@ -157,14 +157,30 @@
> >  #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK        0x0000FF00
> >  #define ASUS_WMI_DSTS_LIGHTBAR_MASK  0x0000000F
> >
> > +struct asus_hid_listener {
> > +     struct list_head list;
> > +     void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
> > +};
> > +
> >  #if IS_REACHABLE(CONFIG_ASUS_WMI)
> >  int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
> > +
> > +int asus_hid_register_listener(struct asus_hid_listener *cdev);
> > +void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
> >  #else
> >  static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
> >                                          u32 *retval)
> >  {
> >       return -ENODEV;
> >  }
> > +
> > +static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
> > +{
> > +     return -ENODEV;
> > +}
> > +static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> > +{
> > +}
> >  #endif
> >
> >  /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
>

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

* Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers
  2025-03-24 12:29     ` Antheas Kapenekakis
@ 2025-03-24 12:37       ` Hans de Goede
  0 siblings, 0 replies; 37+ messages in thread
From: Hans de Goede @ 2025-03-24 12:37 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Luke D . Jones,
	Ilpo Järvinen

Hi,

On 24-Mar-25 13:29, Antheas Kapenekakis wrote:
> On Mon, 24 Mar 2025 at 12:31, Hans de Goede <hdegoede@redhat.com> wrote:
>>
>> Hi Antheas,
>>
>> Note not a full review, just taking a generic look at the new API
>> between asus-wmi and asus-hid.
>>
>> On 22-Mar-25 11:27, Antheas Kapenekakis wrote:
>>> Some devices, such as the Z13 have multiple AURA devices connected
>>> to them by USB. In addition, they might have a WMI interface for
>>> RGB. In Windows, Armoury Crate exposes a unified brightness slider
>>> for all of them, with 3 brightness levels.
>>>
>>> Therefore, to be synergistic in Linux, and support existing tooling
>>> such as UPower, allow adding listeners to the RGB device of the WMI
>>> interface. If WMI does not exist, lazy initialize the interface.
>>>
>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>> ---
>>>  drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
>>>  include/linux/platform_data/x86/asus-wmi.h |  16 +++
>>>  2 files changed, 117 insertions(+), 12 deletions(-)
>>>
>>> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
>>> index 38ef778e8c19b..95ef9b1d321bb 100644
>>> --- a/drivers/platform/x86/asus-wmi.c
>>> +++ b/drivers/platform/x86/asus-wmi.c
>>> @@ -254,6 +254,8 @@ struct asus_wmi {
>>>       int tpd_led_wk;
>>>       struct led_classdev kbd_led;
>>>       int kbd_led_wk;
>>> +     bool kbd_led_avail;
>>> +     bool kbd_led_registered;
>>>       struct led_classdev lightbar_led;
>>>       int lightbar_led_wk;
>>>       struct led_classdev micmute_led;
>>> @@ -1487,6 +1489,53 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
>>>
>>>  /* LEDs ***********************************************************************/
>>>
>>> +struct asus_hid_ref {
>>> +     struct list_head listeners;
>>> +     struct asus_wmi *asus;
>>> +     spinlock_t lock;
>>> +};
>>> +
>>> +struct asus_hid_ref asus_ref = {
>>> +     .listeners = LIST_HEAD_INIT(asus_ref.listeners),
>>> +     .asus = NULL,
>>> +     .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
>>> +};
>>> +
>>> +int asus_hid_register_listener(struct asus_hid_listener *bdev)
>>> +{
>>> +     unsigned long flags;
>>> +     int ret = 0;
>>> +
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>> +     list_add_tail(&bdev->list, &asus_ref.listeners);
>>> +     if (asus_ref.asus) {
>>> +             if (asus_ref.asus->kbd_led_registered && asus_ref.asus->kbd_led_wk >= 0)
>>> +                     bdev->brightness_set(bdev, asus_ref.asus->kbd_led_wk);
>>> +
>>> +             if (!asus_ref.asus->kbd_led_registered) {
>>> +                     ret = led_classdev_register(
>>> +                             &asus_ref.asus->platform_device->dev,
>>> +                             &asus_ref.asus->kbd_led);
>>> +                     if (!ret)
>>> +                             asus_ref.asus->kbd_led_registered = true;
>>> +             }
>>> +     }
>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>> +
>>> +     return ret;
>>> +}
>>> +EXPORT_SYMBOL_GPL(asus_hid_register_listener);
>>> +
>>> +void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
>>> +{
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>> +     list_del(&bdev->list);
>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>> +}
>>> +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
>>> +
>>>  /*
>>>   * These functions actually update the LED's, and are called from a
>>>   * workqueue. By doing this as separate work rather than when the LED
>>> @@ -1566,6 +1615,7 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
>>>
>>>  static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>>>  {
>>> +     struct asus_hid_listener *listener;
>>>       struct asus_wmi *asus;
>>>       int max_level;
>>>
>>> @@ -1573,25 +1623,39 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>>>       max_level = asus->kbd_led.max_brightness;
>>>
>>>       asus->kbd_led_wk = clamp_val(value, 0, max_level);
>>> -     kbd_led_update(asus);
>>> +
>>> +     if (asus->kbd_led_avail)
>>> +             kbd_led_update(asus);
>>> +
>>> +     list_for_each_entry(listener, &asus_ref.listeners, list)
>>> +             listener->brightness_set(listener, asus->kbd_led_wk);
>>>  }
>>>
>>>  static void kbd_led_set(struct led_classdev *led_cdev,
>>>                       enum led_brightness value)
>>>  {
>>> +     unsigned long flags;
>>> +
>>>       /* Prevent disabling keyboard backlight on module unregister */
>>>       if (led_cdev->flags & LED_UNREGISTERING)
>>>               return;
>>>
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>>       do_kbd_led_set(led_cdev, value);
>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>>  }
>>>
>>>  static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
>>>  {
>>> -     struct led_classdev *led_cdev = &asus->kbd_led;
>>> +     struct led_classdev *led_cdev;
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>> +     led_cdev = &asus->kbd_led;
>>>
>>>       do_kbd_led_set(led_cdev, value);
>>>       led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>>  }
>>>
>>>  static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
>>> @@ -1601,6 +1665,9 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
>>>
>>>       asus = container_of(led_cdev, struct asus_wmi, kbd_led);
>>>
>>> +     if (!asus->kbd_led_avail)
>>> +             return asus->kbd_led_wk;
>>> +
>>>       retval = kbd_led_read(asus, &value, NULL);
>>>       if (retval < 0)
>>>               return retval;
>>> @@ -1716,7 +1783,14 @@ static int camera_led_set(struct led_classdev *led_cdev,
>>>
>>>  static void asus_wmi_led_exit(struct asus_wmi *asus)
>>>  {
>>> -     led_classdev_unregister(&asus->kbd_led);
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>> +     asus_ref.asus = NULL;
>>> +     if (asus->kbd_led_registered)
>>> +             led_classdev_unregister(&asus->kbd_led);
>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>> +
>>>       led_classdev_unregister(&asus->tpd_led);
>>>       led_classdev_unregister(&asus->wlan_led);
>>>       led_classdev_unregister(&asus->lightbar_led);
>>> @@ -1730,6 +1804,8 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
>>>  static int asus_wmi_led_init(struct asus_wmi *asus)
>>>  {
>>>       int rv = 0, num_rgb_groups = 0, led_val;
>>> +     unsigned long flags;
>>> +     bool has_listeners;
>>>
>>>       if (asus->kbd_rgb_dev)
>>>               kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
>>> @@ -1754,24 +1830,37 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
>>>                       goto error;
>>>       }
>>>
>>> -     if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
>>> -             pr_info("using asus-wmi for asus::kbd_backlight\n");
>>> +     asus->kbd_led.name = "asus::kbd_backlight";
>>> +     asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
>>> +     asus->kbd_led.brightness_set = kbd_led_set;
>>> +     asus->kbd_led.brightness_get = kbd_led_get;
>>> +     asus->kbd_led.max_brightness = 3;
>>> +     asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
>>> +
>>> +     if (asus->kbd_led_avail)
>>>               asus->kbd_led_wk = led_val;
>>> -             asus->kbd_led.name = "asus::kbd_backlight";
>>> -             asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
>>> -             asus->kbd_led.brightness_set = kbd_led_set;
>>> -             asus->kbd_led.brightness_get = kbd_led_get;
>>> -             asus->kbd_led.max_brightness = 3;
>>> +     else
>>> +             asus->kbd_led_wk = -1;
>>> +
>>> +     if (asus->kbd_led_avail && num_rgb_groups != 0)
>>> +             asus->kbd_led.groups = kbd_rgb_mode_groups;
>>>
>>> -             if (num_rgb_groups != 0)
>>> -                     asus->kbd_led.groups = kbd_rgb_mode_groups;
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>> +     has_listeners = !list_empty(&asus_ref.listeners);
>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>
>> It seems to me that you should also call brightness_set()
>> on all the kbds already in the list so that their brightness
>> gets synced with the wmi kbd-backlight brightness when
>> the wmi driver loads later then the hid driver ?
> 
> You raise a good point here. Let me think about it.
> 
> But yes, if we do a notify when kbd_led_wk != -1 if the hid device
> connects after, we should do it for the before case too.
> 
>>>
>>> +     if (asus->kbd_led_avail || has_listeners) {
>>>               rv = led_classdev_register(&asus->platform_device->dev,
>>>                                          &asus->kbd_led);
>>>               if (rv)
>>>                       goto error;
>>> +             asus->kbd_led_registered = true;
>>>       }
>>>
>>> +     spin_lock_irqsave(&asus_ref.lock, flags);
>>> +     asus_ref.asus = asus;
>>
>> There is race here where a hid keyboard might show up between
>> the 2 places you take the lock, in that case if there is
>> no wmi kbd-backlight then you will not register the led_classdev
>> when asus_hid_register_listener() gets called between the unlock
>> and the lock...  I'm not sure what the best way is to fix this.
> 
> Thanks for catching that, probably a recheck:
> 
> has_listeners = !list_empty(&asus_ref.listeners);
> if (has_listeners) goto register;

I just noticed that in asus_hid_register_listener()
you keep the lock locked while calling led_classdev_register()
so that apparently is ok to do. In which case you can just
hold the lock over the led_classdev_register() call here too.

Regards,

Hans




>>> +     spin_unlock_irqrestore(&asus_ref.lock, flags);
>>> +
>>>       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
>>>                       && (asus->driver->quirks->wapf > 0)) {
>>>               INIT_WORK(&asus->wlan_led_work, wlan_led_update);
>>> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
>>> index 783e2a336861b..ec8b0c585a63f 100644
>>> --- a/include/linux/platform_data/x86/asus-wmi.h
>>> +++ b/include/linux/platform_data/x86/asus-wmi.h
>>> @@ -157,14 +157,30 @@
>>>  #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK        0x0000FF00
>>>  #define ASUS_WMI_DSTS_LIGHTBAR_MASK  0x0000000F
>>>
>>> +struct asus_hid_listener {
>>> +     struct list_head list;
>>> +     void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
>>> +};
>>> +
>>>  #if IS_REACHABLE(CONFIG_ASUS_WMI)
>>>  int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
>>> +
>>> +int asus_hid_register_listener(struct asus_hid_listener *cdev);
>>> +void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
>>>  #else
>>>  static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
>>>                                          u32 *retval)
>>>  {
>>>       return -ENODEV;
>>>  }
>>> +
>>> +static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
>>> +{
>>> +     return -ENODEV;
>>> +}
>>> +static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
>>> +{
>>> +}
>>>  #endif
>>>
>>>  /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
>>
> 


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

end of thread, other threads:[~2025-03-24 12:38 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-22 10:27 [PATCH v3 00/10] HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL Antheas Kapenekakis
2025-03-22 10:27 ` [PATCH v3 01/10] HID: asus: refactor init sequence per spec Antheas Kapenekakis
2025-03-22 22:01   ` Luke D. Jones
2025-03-22 23:05     ` Antheas Kapenekakis
2025-03-22 23:28       ` Luke D. Jones
2025-03-22 23:53         ` Antheas Kapenekakis
2025-03-22 23:54           ` Antheas Kapenekakis
2025-03-23  0:10             ` Luke D. Jones
2025-03-23  0:14           ` Luke D. Jones
2025-03-22 10:27 ` [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG Antheas Kapenekakis
2025-03-22 23:21   ` Luke D. Jones
2025-03-22 10:27 ` [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work Antheas Kapenekakis
2025-03-22 23:30   ` Luke D. Jones
2025-03-22 23:32     ` Antheas Kapenekakis
2025-03-22 10:27 ` [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers Antheas Kapenekakis
2025-03-23  0:02   ` Luke D. Jones
2025-03-24 11:31   ` Hans de Goede
2025-03-24 12:29     ` Antheas Kapenekakis
2025-03-24 12:37       ` Hans de Goede
2025-03-22 10:27 ` [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one Antheas Kapenekakis
2025-03-23  6:40   ` Luke D. Jones
2025-03-22 10:28 ` [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk Antheas Kapenekakis
2025-03-23  0:05   ` Luke D. Jones
2025-03-22 10:28 ` [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler Antheas Kapenekakis
2025-03-23  0:06   ` Luke D. Jones
2025-03-22 10:28 ` [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler Antheas Kapenekakis
2025-03-23  0:08   ` Luke D. Jones
2025-03-22 10:28 ` [PATCH v3 09/10] HID: asus: add basic RGB support Antheas Kapenekakis
2025-03-23  6:21   ` Luke D. Jones
2025-03-23  6:40   ` Luke D. Jones
2025-03-23 11:37     ` Antheas Kapenekakis
2025-03-23 14:45       ` Antheas Kapenekakis
2025-03-23 20:13         ` Luke D. Jones
2025-03-23 20:39       ` Luke D. Jones
2025-03-23 21:08         ` Antheas Kapenekakis
2025-03-22 10:28 ` [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units Antheas Kapenekakis
2025-03-23  6:29   ` Luke D. Jones

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).