* [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 related [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