mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] HID: asus: support the Zenbook A14 (UX3407RA) keyboard backlight
@ 2026-10-31 21:57 Jesper B. Christensen
  0 siblings, 0 replies; only message in thread
From: Jesper B. Christensen @ 2026-10-31 21:57 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-input, jikos, bentiss, Jesper B. Christensen

The ASUS Zenbook A14 (UX3407RA), a Qualcomm Snapdragon X2 Elite
(Hamoa)-based laptop, carries its main keyboard on an I2C-HID device
with the ID 0B05:0220. Its Fn/media hotkeys are emitted as vendor-page
(HID_UP_ASUSVENDOR) usages on report 0x5A.

In addition to the the standard I2C keyboard quirks and the keyboard
backlight quirk, a few new have been added:

  QUIRK_KBD_REPORT_SIZE: The UX3407RA seems to only accept 64 byte
   reports
  QUIRK_KBD_OOBE: Refactor existing Out-Of-Box mitigation into a
   quirk.

The keyboard backlight is software controlled. Since WMI is not
available on Snapdragon based platforms this change registers a
separate led device that sends hid reports to the Embedded Controller
(EC).

Signed-off-by: Jesper B. Christensen <jaytersen@gmail.com>
---
 drivers/hid/hid-asus.c | 88 ++++++++++++++++++++++++++++++++++++++++--
 drivers/hid/hid-ids.h  |  1 +
 2 files changed, 85 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index c38a2b3b33d9..072df9816e50 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -66,6 +66,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define ASUS_FAN_CTRL_KEY_CODE 0xae
 
 #define SUPPORT_KBD_BACKLIGHT BIT(0)
+#define EC_KBD_MAX_BACKLIGHT 3
 
 #define MAX_TOUCH_MAJOR 8
 #define MAX_PRESSURE 128
@@ -100,6 +101,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_ROG_ALLY_XPAD		BIT(13)
 #define QUIRK_HID_FN_LOCK		BIT(14)
 #define QUIRK_FILTER_CAMERA_COMPANION	BIT(15)
+#define QUIRK_KBD_REPORT_SIZE           BIT(16)
+#define QUIRK_KBD_OOBE			BIT(17)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -169,10 +172,12 @@ struct asus_drvdata {
 	bool battery_in_query;
 	unsigned long battery_next_query;
 	struct asus_hid_listener listener;
+	struct led_classdev kbd_led_cdev;
 	bool fn_lock;
 };
 
 static int asus_report_battery(struct asus_drvdata *, u8 *, int);
+static void asus_kbd_illum_toggle(struct asus_drvdata *);
 
 static const struct asus_touchpad_info asus_i2c_tp = {
 	.max_x = 2794,
@@ -469,6 +474,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
 		case KEY_KBDILLUMDOWN:
 			return !asus_hid_event(ASUS_EV_BRTDOWN);
 		case KEY_KBDILLUMTOGGLE:
+			asus_kbd_illum_toggle(drvdata);
 			return !asus_hid_event(ASUS_EV_BRTTOGGLE);
 		case KEY_FN_ESC:
 			if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
@@ -576,15 +582,23 @@ static int asus_raw_event(struct hid_device *hdev,
 
 static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t buf_size)
 {
-	u8 *dmabuf __free(kfree) = kmemdup(buf, buf_size, GFP_KERNEL);
+	size_t alloc_size = buf_size;
+
+	if (QUIRK_KBD_REPORT_SIZE)
+		alloc_size = (buf_size > FEATURE_KBD_REPORT_SIZE) ?
+			buf_size : FEATURE_KBD_REPORT_SIZE;
+
+	u8 *dmabuf __free(kfree) = kzalloc(alloc_size, GFP_KERNEL);
 	if (!dmabuf)
 		return -ENOMEM;
 
+	memcpy(dmabuf, buf, buf_size);
+
 	/*
 	 * The report ID should be set from the incoming buffer due to LED and key
 	 * interfaces having different pages
 	 */
-	return hid_hw_raw_request(hdev, buf[0], dmabuf, buf_size, HID_FEATURE_REPORT,
+	return hid_hw_raw_request(hdev, buf[0], dmabuf, alloc_size, HID_FEATURE_REPORT,
 				  HID_REQ_SET_REPORT);
 }
 
@@ -702,6 +716,38 @@ static void asus_kbd_set_brightness(struct hid_device *hdev, u8 brightness)
 		hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
 }
 
+static void asus_kbd_set_brightness_cdev(struct led_classdev *led_cdev,
+				    enum led_brightness brightness)
+{
+	struct asus_drvdata *drvdata = container_of(led_cdev, struct asus_drvdata, kbd_led_cdev);
+	u8 level = (u8)brightness;
+
+	if (level > EC_KBD_MAX_BACKLIGHT)
+		level = EC_KBD_MAX_BACKLIGHT;
+
+	asus_kbd_set_brightness(drvdata->hdev, level);
+	drvdata->kbd_backlight_brightness = level;
+}
+
+static enum led_brightness asus_kbd_get_brightness_cdev(struct led_classdev *led_cdev)
+{
+	struct asus_drvdata *drvdata = container_of(led_cdev, struct asus_drvdata, kbd_led_cdev);
+
+	return (enum led_brightness)drvdata->kbd_backlight_brightness;
+}
+
+static void asus_kbd_illum_toggle(struct asus_drvdata *drvdata)
+{
+	if (!drvdata->kbd_led_cdev.brightness_set)
+		return;
+
+	u8 current_level = drvdata->kbd_backlight_brightness;
+	u8 max_level = EC_KBD_MAX_BACKLIGHT;
+	u8 next_level = (current_level + 1) % (max_level + 1);
+
+	asus_kbd_set_brightness_cdev(&drvdata->kbd_led_cdev, next_level);
+}
+
 static void asus_kbd_wmi_fan(struct hid_device *hdev, struct hid_raw_event_data *data)
 {
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
@@ -956,7 +1002,7 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
 	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
 		return -ENODEV;
 
-	if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
+	if (drvdata->quirks & QUIRK_KBD_OOBE) {
 		ret = asus_kbd_disable_oobe(hdev);
 		if (ret < 0)
 			return ret;
@@ -972,10 +1018,28 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
 	drvdata->listener.brightness_set = asus_kbd_backlight_set;
 	ret = asus_hid_register_listener(&drvdata->listener);
 	if (ret < 0) {
-		hid_err(hdev, "Unable to register kbd brightness listener: %d\n", ret);
+		if (ret != -ENODEV) {
+			hid_err(hdev, "Unable to register kbd brightness listener: %d\n", ret);
+		}
 		drvdata->listener.brightness_set = NULL;
 	}
 
+	// Non-x86 architectures do not have WMI, just register a led
+	// device directly
+	if (ret == -ENODEV) {
+		drvdata->kbd_led_cdev.name = "asus::kbd_backlight";
+		drvdata->kbd_led_cdev.brightness_set = asus_kbd_set_brightness_cdev;
+		drvdata->kbd_led_cdev.brightness_get = asus_kbd_get_brightness_cdev;
+		drvdata->kbd_led_cdev.max_brightness = EC_KBD_MAX_BACKLIGHT;
+		ret = led_classdev_register(&hdev->dev, &drvdata->kbd_led_cdev);
+		if (ret < 0) {
+			hid_err(hdev, "Unable to register kbd brightness led: %d\n", ret);
+			drvdata->kbd_led_cdev.brightness_set = NULL;
+		}
+
+		asus_kbd_set_brightness_cdev(&drvdata->kbd_led_cdev, 1);
+	}
+
 	return ret;
 }
 
@@ -1372,6 +1436,9 @@ static int __maybe_unused asus_resume(struct hid_device *hdev)
 	if (drvdata->listener.brightness_set)
 		asus_kbd_backlight_set(&drvdata->listener, drvdata->kbd_backlight_brightness);
 
+	if (drvdata->kbd_led_cdev.brightness_set)
+		asus_kbd_set_brightness_cdev(&drvdata->kbd_led_cdev, drvdata->kbd_backlight_brightness);
+
 	return 0;
 }
 
@@ -1502,6 +1569,9 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		}
 	}
 
+	if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16"))
+		drvdata->quirks |= QUIRK_KBD_OOBE;
+
 	/* Laptops keyboard backlight is always at 0x5a */
 	if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
 	    (asus_has_report_id(hdev, FEATURE_KBD_REPORT_ID)) &&
@@ -1539,6 +1609,9 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	if (drvdata->listener.brightness_set)
 		asus_hid_unregister_listener(&drvdata->listener);
 
+	if (drvdata->kbd_led_cdev.brightness_set)
+		led_classdev_unregister(&drvdata->kbd_led_cdev);
+
 	asus_worker_stop(drvdata->worker);
 	hid_hw_stop(hdev);
 	return ret;
@@ -1551,6 +1624,9 @@ static void asus_remove(struct hid_device *hdev)
 	if (drvdata->listener.brightness_set)
 		asus_hid_unregister_listener(&drvdata->listener);
 
+	if (drvdata->kbd_led_cdev.brightness_set)
+		led_classdev_unregister(&drvdata->kbd_led_cdev);
+
 	asus_worker_stop(drvdata->worker);
 	hid_hw_stop(hdev);
 }
@@ -1673,6 +1749,10 @@ static const struct hid_device_id asus_devices[] = {
 	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD),
 	  I2C_KEYBOARD_QUIRKS | QUIRK_FILTER_CAMERA_COMPANION },
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_A14_KEYBOARD),
+	  I2C_KEYBOARD_QUIRKS | QUIRK_USE_KBD_BACKLIGHT | QUIRK_KBD_REPORT_SIZE |
+	  QUIRK_KBD_OOBE},
 	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index cb7f16d75fce..a992c0aabd45 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -221,6 +221,7 @@
 #define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD	0x184a
 #define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD	0x8585
 #define USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD	0x4b42
+#define USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_A14_KEYBOARD	0x0220
 #define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD	0x0101
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
-- 
2.53.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-31 21:57 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31 21:57 [PATCH] HID: asus: support the Zenbook A14 (UX3407RA) keyboard backlight Jesper B. Christensen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®