Skip to contentSkip to conversation

HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL

PATCH Superseded Antheas Kapenekakis 3 participants 37 messages Mar 22, 2025 – Mar 24, 2025

Subsystems (3)

v3 of 12 Superseded Latest is v12 →

lore
Share & tools
lore
#0 lore raw

v3 cover HID: asus: Add RGB Support to Asus Z13, Ally, unify backlight asus-wmi, and Z13 QOL

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/[email protected]/
V1: https://lore.kernel.org/all/[email protected]/

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

Signature · 1 line
2.48.1
#1 lore raw

PATCH v3 1/10 HID: asus: refactor init sequence per spec

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.

---
 drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
 1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +46 -36
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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);
Signature · 1 line
2.48.1
maintainer
#2 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

Quoted 34 lines · 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.

---
  drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
  1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
  {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-	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.

Quoted 53 lines
drivers/hid/hid-asus.c +24 -11
+	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);

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 573

Quoted 10 lines
drivers/hid/hid-asus.c +3 -6
-		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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-		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);
#3 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

Quoted 52 lines · Luke D. Jones
Quoted 34 lines · 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.

---
  drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
  1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
  {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-     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).

Quoted 1 line

Please remove.

It is a context comment but can be removed.

Quoted 62 lines
Quoted 62 lines
drivers/hid/hid-asus.c +27 -17
+     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;
Quoted 8 lines
Quoted 1 line

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?

Quoted 6 lines

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.

Quoted 47 lines

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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-             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);
maintainer
#4 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

Quoted 128 lines · Antheas Kapenekakis
Quoted 52 lines · Luke D. Jones
Quoted 34 lines · 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.

---
   drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
   1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
   {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-     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).

Quoted 1 line

Please remove.

It is a context comment but can be removed.

Quoted 62 lines
Quoted 62 lines
drivers/hid/hid-asus.c +27 -17
+     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;
Quoted 8 lines
Quoted 1 line

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.

Quoted 5 lines

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.

Quoted 8 lines
Quoted 6 lines

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.

Quoted 48 lines
Quoted 48 lines

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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-             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);
#5 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

Quoted 133 lines · Luke D. Jones
Quoted 131 lines · Antheas Kapenekakis
Quoted 52 lines · Luke D. Jones
Quoted 34 lines · 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.

---
   drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
   1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
   {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-     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).

Quoted 1 line

Please remove.

It is a context comment but can be removed.

Quoted 62 lines
Quoted 62 lines
drivers/hid/hid-asus.c +27 -17
+     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;
Quoted 8 lines
Quoted 1 line

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.

Quoted 7 lines
Quoted 2 lines

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

Quoted 69 lines
Quoted 8 lines
Quoted 6 lines

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.

Quoted 48 lines
Quoted 48 lines

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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-             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);
#6 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

Quoted 153 lines · Antheas Kapenekakis
Quoted 133 lines · Luke D. Jones
Quoted 131 lines · Antheas Kapenekakis
Quoted 52 lines · Luke D. Jones
Quoted 34 lines · 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.

---
   drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
   1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
   {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-     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).

Quoted 1 line

Please remove.

It is a context comment but can be removed.

Quoted 62 lines
Quoted 62 lines
drivers/hid/hid-asus.c +27 -17
+     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;
Quoted 8 lines
Quoted 1 line

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.

Quoted 7 lines
Quoted 2 lines

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.

Quoted 3 lines

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

Quoted 71 lines

Antheas

Quoted 69 lines
Quoted 8 lines
Quoted 6 lines

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.

Quoted 48 lines
Quoted 48 lines

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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-             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);
maintainer
#7 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

Quoted 154 lines · Antheas Kapenekakis
Quoted 153 lines · Antheas Kapenekakis
Quoted 133 lines · Luke D. Jones
Quoted 131 lines · Antheas Kapenekakis
Quoted 52 lines · Luke D. Jones
Quoted 34 lines · 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.

---
    drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
    1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
    {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-     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).

Quoted 1 line

Please remove.

It is a context comment but can be removed.

Quoted 62 lines
Quoted 62 lines
drivers/hid/hid-asus.c +27 -17
+     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;
Quoted 8 lines
Quoted 1 line

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.

Quoted 7 lines
Quoted 2 lines

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.

Quoted 8 lines
Quoted 3 lines

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.

Quoted 71 lines
Quoted 71 lines

Antheas

Quoted 69 lines
Quoted 8 lines
Quoted 6 lines

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.

Quoted 48 lines
Quoted 48 lines

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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-             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);
maintainer
#8 lore raw

Re: [PATCH v3 01/10] HID: asus: refactor init sequence per spec

In reply to Antheas Kapenekakis
Quoted 146 lines · Antheas Kapenekakis
Quoted 133 lines · Luke D. Jones
Quoted 131 lines · Antheas Kapenekakis
Quoted 52 lines · Luke D. Jones
Quoted 34 lines · 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.

---
    drivers/hid/hid-asus.c | 82 +++++++++++++++++++++++-------------------
    1 file changed, 46 insertions(+), 36 deletions(-)
drivers/hid/hid-asus.c +1 -1
index 46e3e42f9eb5f..8d4df1b6f143b 100644
@@ -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)
    {

drivers/hid/hid-asus.c static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu · line 398

Quoted 10 lines
drivers/hid/hid-asus.c +8 -2
-     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).

Quoted 1 line

Please remove.

It is a context comment but can be removed.

Quoted 62 lines
Quoted 62 lines
drivers/hid/hid-asus.c +27 -17
+     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;
Quoted 8 lines
Quoted 1 line

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.

Quoted 7 lines
Quoted 2 lines

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.

Quoted 6 lines

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.

Quoted 75 lines

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

By the way,

Antheas

Quoted 69 lines
Quoted 8 lines
Quoted 6 lines

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.

Quoted 48 lines
Quoted 48 lines

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

Quoted 34 lines
drivers/hid/hid-asus.c +10 -16
-             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);
#9 lore raw

PATCH v3 2/10 HID: asus: prevent binding to all HID devices on ROG

In reply to 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).

---
 drivers/hid/hid-asus.c | 69 ++++++++++++++++++++++++++++++++----------
 1 file changed, 53 insertions(+), 16 deletions(-)
drivers/hid/hid-asus.c +53 -16
index 8d4df1b6f143b..96461321c191c 100644
@@ -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");
Signature · 1 line
2.48.1
maintainer Reviewed-by on v3 2/10
#10 lore raw

Re: [PATCH v3 02/10] HID: asus: prevent binding to all HID devices on ROG

Quoted 155 lines · 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).

---
  drivers/hid/hid-asus.c | 69 ++++++++++++++++++++++++++++++++----------
  1 file changed, 53 insertions(+), 16 deletions(-)
drivers/hid/hid-asus.c +49 -16
index 8d4df1b6f143b..96461321c191c 100644
@@ -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;

drivers/hid/hid-asus.c static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, · line 1209

Quoted 10 lines
drivers/hid/hid-asus.c +4 -0
@@ -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.

#11 lore raw

PATCH v3 3/10 HID: Asus: add Z13 folio to generic group for multitouch to work

In reply to 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.

---
 drivers/hid/hid-asus.c | 6 +++---
 drivers/hid/hid-ids.h  | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)
drivers/hid/hid-asus.c +3 -3
index 96461321c191c..e97fb76eda619 100644
@@ -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) },
 	{ }
drivers/hid/hid-ids.h +1 -1
index 7e400624908e3..b1fe7582324ff 100644
@@ -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
Signature · 1 line
2.48.1
maintainer
#12 lore raw

Re: [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work

Quoted 10 lines · 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.

---

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.

Quoted 41 lines
  drivers/hid/hid-asus.c | 6 +++---
  drivers/hid/hid-ids.h  | 2 +-
  2 files changed, 4 insertions(+), 4 deletions(-)
drivers/hid/hid-asus.c +3 -3
index 96461321c191c..e97fb76eda619 100644
@@ -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) },
  	{ }
drivers/hid/hid-ids.h +1 -1
index 7e400624908e3..b1fe7582324ff 100644
@@ -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
#13 lore raw

Re: [PATCH v3 03/10] HID: Asus: add Z13 folio to generic group for multitouch to work

Quoted 7 lines · Luke D. Jones
Quoted 5 lines · 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.

Quoted 8 lines
Quoted 5 lines

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

---

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

Quoted 42 lines
Quoted 41 lines
  drivers/hid/hid-asus.c | 6 +++---
  drivers/hid/hid-ids.h  | 2 +-
  2 files changed, 4 insertions(+), 4 deletions(-)
drivers/hid/hid-asus.c +3 -3
index 96461321c191c..e97fb76eda619 100644
@@ -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) },
      { }
drivers/hid/hid-ids.h +1 -1
index 7e400624908e3..b1fe7582324ff 100644
@@ -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
#14 lore raw

PATCH v3 4/10 platform/x86: asus-wmi: Add support for multiple kbd RGB handlers

In reply to 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.

---
 drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
 include/linux/platform_data/x86/asus-wmi.h |  16 +++
 2 files changed, 117 insertions(+), 12 deletions(-)
drivers/platform/x86/asus-wmi.c +101 -12
index 38ef778e8c19b..95ef9b1d321bb 100644
@@ -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);
include/linux/platform_data/x86/asus-wmi.h +16 -0
index 783e2a336861b..ec8b0c585a63f 100644
@@ -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 */
Signature · 1 line
2.48.1
maintainer Reviewed-by Tested-by on v3 4/10
#15 lore raw

Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers

Quoted 148 lines · 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.

---
  drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
  include/linux/platform_data/x86/asus-wmi.h |  16 +++
  2 files changed, 117 insertions(+), 12 deletions(-)
drivers/platform/x86/asus-wmi.c +70 -3
index 38ef778e8c19b..95ef9b1d321bb 100644
@@ -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;

drivers/platform/x86/asus-wmi.c static int camera_led_set(struct led_classdev *led_cdev, · line 1796

Quoted 10 lines
drivers/platform/x86/asus-wmi.c +7 -0
+
+	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:

drivers/platform/x86/asus-wmi.c static int camera_led_set(struct led_classdev *led_cdev, · line 1791

Quoted 2 lines
drivers/platform/x86/asus-wmi.c +2 -0
+	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.

Quoted 81 lines
drivers/platform/x86/asus-wmi.c +24 -9
@@ -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);
include/linux/platform_data/x86/asus-wmi.h +9 -0
index 783e2a336861b..ec8b0c585a63f 100644
@@ -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;
  }
+

include/linux/platform_data/x86/asus-wmi.h line 186

Quoted 10 lines
include/linux/platform_data/x86/asus-wmi.h +7 -0
+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:

#16 lore raw

Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers

In reply to Antheas Kapenekakis

Hi Antheas,

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

Quoted 188 lines · 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.

---
 drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
 include/linux/platform_data/x86/asus-wmi.h |  16 +++
 2 files changed, 117 insertions(+), 12 deletions(-)
drivers/platform/x86/asus-wmi.c +88 -10
index 38ef778e8c19b..95ef9b1d321bb 100644
@@ -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

drivers/platform/x86/asus-wmi.c static int asus_wmi_led_init(struct asus_wmi *asus) · line 1850

Quoted 10 lines
drivers/platform/x86/asus-wmi.c +7 -2
+		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 ?

drivers/platform/x86/asus-wmi.c static int asus_wmi_led_init(struct asus_wmi *asus) · line 1861

Quoted 11 lines
drivers/platform/x86/asus-wmi.c +4 -0
+	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

Quoted 40 lines
drivers/platform/x86/asus-wmi.c +2 -0
+	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);
include/linux/platform_data/x86/asus-wmi.h +16 -0
index 783e2a336861b..ec8b0c585a63f 100644
@@ -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 */
#17 lore raw

Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers

Quoted 200 lines · Hans de Goede

Hi Antheas,

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

Quoted 193 lines · 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.

---
 drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
 include/linux/platform_data/x86/asus-wmi.h |  16 +++
 2 files changed, 117 insertions(+), 12 deletions(-)
drivers/platform/x86/asus-wmi.c +92 -10
index 38ef778e8c19b..95ef9b1d321bb 100644
@@ -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;
 
Quoted 10 lines

drivers/platform/x86/asus-wmi.c static int asus_wmi_led_init(struct asus_wmi *asus) · line 1850

Quoted 5 lines
drivers/platform/x86/asus-wmi.c +3 -2
-             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.

Quoted 9 lines
Quoted 9 lines
drivers/platform/x86/asus-wmi.c +2 -0
+     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;
      }
 
Quoted 8 lines

drivers/platform/x86/asus-wmi.c static int asus_wmi_led_init(struct asus_wmi *asus) · line 1860

Quoted 2 lines
drivers/platform/x86/asus-wmi.c +2 -0
+     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;

Quoted 47 lines

Regards,

Hans

Quoted 40 lines
drivers/platform/x86/asus-wmi.c +2 -0
+     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);
include/linux/platform_data/x86/asus-wmi.h +16 -0
index 783e2a336861b..ec8b0c585a63f 100644
@@ -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 */
#18 lore raw

Re: [PATCH v3 04/10] platform/x86: asus-wmi: Add support for multiple kbd RGB handlers

Hi,

Quoted 235 lines · Antheas Kapenekakis
Quoted 200 lines · Hans de Goede

Hi Antheas,

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

Quoted 193 lines · 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.

---
 drivers/platform/x86/asus-wmi.c            | 113 ++++++++++++++++++---
 include/linux/platform_data/x86/asus-wmi.h |  16 +++
 2 files changed, 117 insertions(+), 12 deletions(-)
drivers/platform/x86/asus-wmi.c +92 -10
index 38ef778e8c19b..95ef9b1d321bb 100644
@@ -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;
 
Quoted 10 lines

drivers/platform/x86/asus-wmi.c static int asus_wmi_led_init(struct asus_wmi *asus) · line 1850

Quoted 5 lines
drivers/platform/x86/asus-wmi.c +3 -2
-             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.

Quoted 9 lines
Quoted 9 lines
drivers/platform/x86/asus-wmi.c +2 -0
+     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;
      }
 
Quoted 8 lines

drivers/platform/x86/asus-wmi.c static int asus_wmi_led_init(struct asus_wmi *asus) · line 1860

Quoted 2 lines
drivers/platform/x86/asus-wmi.c +2 -0
+     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.

Quoted 4 lines

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

Quoted 42 lines
Quoted 41 lines
Quoted 40 lines
drivers/platform/x86/asus-wmi.c +2 -0
+     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);
include/linux/platform_data/x86/asus-wmi.h +16 -0
index 783e2a336861b..ec8b0c585a63f 100644
@@ -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 */
#19 lore raw

PATCH v3 5/10 HID: asus: listen to the asus-wmi brightness device instead of creating one

In reply to 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.

---
 drivers/hid/hid-asus.c | 65 +++++++-----------------------------------
 1 file changed, 11 insertions(+), 54 deletions(-)
drivers/hid/hid-asus.c +11 -54
index e97fb76eda619..c40b5c14c797f 100644
@@ -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);
Signature · 1 line
2.48.1
maintainer Reviewed-by on v3 5/10
#20 lore raw

Re: [PATCH v3 05/10] HID: asus: listen to the asus-wmi brightness device instead of creating one

Quoted 134 lines · 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.

---
  drivers/hid/hid-asus.c | 65 +++++++-----------------------------------
  1 file changed, 11 insertions(+), 54 deletions(-)
drivers/hid/hid-asus.c +9 -54
index e97fb76eda619..c40b5c14c797f 100644
@@ -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");
 

drivers/hid/hid-asus.c static void asus_remove(struct hid_device *hdev) · line 1142

Quoted 8 lines
drivers/hid/hid-asus.c +2 -0
@@ -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);
#21 lore raw

PATCH v3 6/10 platform/x86: asus-wmi: remove unused keyboard backlight quirk

In reply to Antheas Kapenekakis

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

---
 include/linux/platform_data/x86/asus-wmi.h | 40 ----------------------
 1 file changed, 40 deletions(-)
include/linux/platform_data/x86/asus-wmi.h +0 -40
index ec8b0c585a63f..c513b5a732323 100644
@@ -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 */
Signature · 1 line
2.48.1
maintainer Reviewed-by on v3 6/10
#22 lore raw

Re: [PATCH v3 06/10] platform/x86: asus-wmi: remove unused keyboard backlight quirk

Quoted 47 lines · Antheas Kapenekakis

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

---
  include/linux/platform_data/x86/asus-wmi.h | 40 ----------------------
  1 file changed, 40 deletions(-)
include/linux/platform_data/x86/asus-wmi.h +0 -31
index ec8b0c585a63f..c513b5a732323 100644
@@ -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"),
-		},

include/linux/platform_data/x86/asus-wmi.h static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev) · line 186

Quoted 10 lines
include/linux/platform_data/x86/asus-wmi.h +0 -9
-	},
-	{
-		.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:

#23 lore raw

PATCH v3 7/10 platform/x86: asus-wmi: add keyboard brightness event handler

In reply to 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.

---
 drivers/platform/x86/asus-wmi.c            | 39 ++++++++++++++++++++++
 include/linux/platform_data/x86/asus-wmi.h | 11 ++++++
 2 files changed, 50 insertions(+)
drivers/platform/x86/asus-wmi.c +39 -0
index 95ef9b1d321bb..5ebe141294ecf 100644
@@ -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
include/linux/platform_data/x86/asus-wmi.h +11 -0
index c513b5a732323..9adbe8abef090 100644
@@ -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 */
Signature · 1 line
2.48.1
maintainer Reviewed-by Tested-by on v3 7/10
#24 lore raw

Re: [PATCH v3 07/10] platform/x86: asus-wmi: add keyboard brightness event handler

Quoted 92 lines · 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.

---
  drivers/platform/x86/asus-wmi.c            | 39 ++++++++++++++++++++++
  include/linux/platform_data/x86/asus-wmi.h | 11 ++++++
  2 files changed, 50 insertions(+)
drivers/platform/x86/asus-wmi.c +39 -0
index 95ef9b1d321bb..5ebe141294ecf 100644
@@ -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
include/linux/platform_data/x86/asus-wmi.h +7 -0
index c513b5a732323..9adbe8abef090 100644
@@ -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)

include/linux/platform_data/x86/asus-wmi.h static inline int asus_hid_register_listener(struct asus_hid_listener *bdev) · line 197

Quoted 10 lines
include/linux/platform_data/x86/asus-wmi.h +4 -0
@@ -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 */
#25 lore raw

PATCH v3 8/10 HID: asus: add support for the asus-wmi brightness handler

In reply to 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.

---
 drivers/hid/hid-asus.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
drivers/hid/hid-asus.c +11 -0
index c40b5c14c797f..905453a4eb5b7 100644
@@ -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;
 }
 
Signature · 1 line
2.48.1
maintainer Reviewed-by Tested-by on v3 8/10
#26 lore raw

Re: [PATCH v3 08/10] HID: asus: add support for the asus-wmi brightness handler

Quoted 21 lines · 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.

---
  drivers/hid/hid-asus.c | 11 +++++++++++
  1 file changed, 11 insertions(+)
drivers/hid/hid-asus.c +4 -0
index c40b5c14c797f..905453a4eb5b7 100644
@@ -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);

drivers/hid/hid-asus.c static int asus_event(struct hid_device *hdev, struct hid_field *field, · line 334

Quoted 10 lines
drivers/hid/hid-asus.c +7 -0
+		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.

#27 lore raw

PATCH v3 9/10 HID: asus: add basic RGB support

In reply to 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.

---
 drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +155 -14
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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) },
 	{ }
Signature · 1 line
2.48.1
maintainer
#28 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

Quoted 259 lines · 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.

---
  drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
  1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +154 -13
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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 },

drivers/hid/hid-asus.c static const struct hid_device_id asus_devices[] = { · line 1465

Quoted 8 lines
drivers/hid/hid-asus.c +1 -1
@@ -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.

maintainer
#29 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

In reply to Antheas Kapenekakis
Quoted 204 lines · 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.

---
  drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
  1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +121 -10
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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;

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 693

Quoted 10 lines
drivers/hid/hid-asus.c +10 -0
+
+	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.

Quoted 53 lines
drivers/hid/hid-asus.c +24 -4
+	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) },
  	{ }
#30 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

Quoted 213 lines · Luke D. Jones
Quoted 211 lines · 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.

---
  drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
  1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +128 -10
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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,
Quoted 8 lines

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 693

Quoted 3 lines
drivers/hid/hid-asus.c +3 -0
+                                     "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.

Quoted 4 lines

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.

Quoted 54 lines
Quoted 53 lines
drivers/hid/hid-asus.c +24 -4
+     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) },
      { }
#31 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

Quoted 239 lines · Antheas Kapenekakis
Quoted 213 lines · Luke D. Jones
Quoted 211 lines · 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.

---
  drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
  1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +128 -10
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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,
Quoted 8 lines

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 693

Quoted 3 lines
drivers/hid/hid-asus.c +3 -0
+                                     "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.

Quoted 4 lines

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.

Quoted 4 lines

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

Quoted 54 lines
Quoted 54 lines
Quoted 53 lines
drivers/hid/hid-asus.c +24 -4
+     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) },
      { }
maintainer
#32 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

Quoted 250 lines · Antheas Kapenekakis
Quoted 239 lines · Antheas Kapenekakis
Quoted 213 lines · Luke D. Jones
Quoted 211 lines · 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.

---
   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
   1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +128 -10
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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,
Quoted 8 lines

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 693

Quoted 3 lines
drivers/hid/hid-asus.c +3 -0
+                                     "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.

Quoted 4 lines

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.

Quoted 4 lines

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.

Quoted 5 lines

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.

Quoted 56 lines

Antheas

Quoted 54 lines
Quoted 54 lines
Quoted 53 lines
drivers/hid/hid-asus.c +24 -4
+     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) },
       { }
maintainer
#33 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

In reply to Antheas Kapenekakis
Quoted 223 lines · Antheas Kapenekakis
Quoted 213 lines · Luke D. Jones
Quoted 211 lines · 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.

---
   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
   1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +128 -10
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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,
Quoted 8 lines

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 693

Quoted 3 lines
drivers/hid/hid-asus.c +3 -0
+                                     "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.

Quoted 5 lines

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.

Quoted 10 lines
Quoted 4 lines

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.

Quoted 4 lines

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.

Quoted 54 lines
Quoted 54 lines
Quoted 53 lines
drivers/hid/hid-asus.c +24 -4
+     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) },
       { }
#34 lore raw

Re: [PATCH v3 09/10] HID: asus: add basic RGB support

Quoted 256 lines · Luke D. Jones
Quoted 223 lines · Antheas Kapenekakis
Quoted 213 lines · Luke D. Jones
Quoted 211 lines · 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.

---
   drivers/hid/hid-asus.c | 169 +++++++++++++++++++++++++++++++++++++----
   1 file changed, 155 insertions(+), 14 deletions(-)
drivers/hid/hid-asus.c +128 -10
index 905453a4eb5b7..9d8ccfde5912e 100644
@@ -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,
Quoted 8 lines

drivers/hid/hid-asus.c static int asus_kbd_register_leds(struct hid_device *hdev) · line 693

Quoted 3 lines
drivers/hid/hid-asus.c +3 -0
+                                     "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.

Quoted 5 lines

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.

Quoted 10 lines
Quoted 4 lines

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.

Quoted 4 lines

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.

Quoted 5 lines

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.

Quoted 9 lines

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.

Quoted 9 lines

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.

Quoted 5 lines

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.

Quoted 6 lines

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

Quoted 58 lines

Cheers,
Luke.

Quoted 54 lines
Quoted 54 lines
Quoted 53 lines
drivers/hid/hid-asus.c +24 -4
+     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) },
       { }
#35 lore raw

PATCH v3 10/10 HID: asus: add RGB support to the ROG Ally units

In reply to Antheas Kapenekakis

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

---
 drivers/hid/hid-asus.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/hid/hid-asus.c +2 -2
index 9d8ccfde5912e..1a9cd7f513282 100644
@@ -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 },
Signature · 1 line
2.48.1
maintainer Reviewed-by on v3 10/10
#36 lore raw

Re: [PATCH v3 10/10] HID: asus: add RGB support to the ROG Ally units

Quoted 14 lines · Antheas Kapenekakis

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

---
  drivers/hid/hid-asus.c | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
drivers/hid/hid-asus.c +0 -0
index 9d8ccfde5912e..1a9cd7f513282 100644
@@ -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,

drivers/hid/hid-asus.c static const struct hid_device_id asus_devices[] = { · line 1442

Quoted 10 lines
drivers/hid/hid-asus.c +2 -2
  	    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.