Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH] HID: asus: add new Asus EC hid device for keyboard backlight and FN HotKeys
@ 2025-12-21 11:12 Alexandru Marc Serdeliuc via B4 Relay
  2026-06-30 16:43 ` Alexandru Serdeliuc
  0 siblings, 1 reply; 3+ messages in thread
From: Alexandru Marc Serdeliuc via B4 Relay @ 2025-12-21 11:12 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-kernel, linux-input, Alexandru Marc Serdeliuc

From: Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>

Add support for Asus Embedded Controlled accessed via HID

Currently working features:
- Keyboard Backlight
- FN HotKeys

Signed-off-by: Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>
---
Add support for Asus Embedded Controlled accessed via HID

Currently working features:
- Keyboard Backlight
- FN HotKeys
---
 drivers/hid/Kconfig       |  10 ++
 drivers/hid/Makefile      |   1 +
 drivers/hid/hid-asus-ec.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 397 insertions(+)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 920a64b66b25b39e8259105c7c9b975fb77b2725..f0fbc951735eeea39198137294a9429f5b9d34b8 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -202,6 +202,16 @@ config HID_ASUS
 	- GL553V series
 	- GL753V series
 
+config HID_ASUS_EC
+	tristate "ASUS EC HID support (Zenbook A14 UX3407QA)"
+	depends on HID && I2C_HID
+	help
+	  Say Y here if you have an ASUS Zenbook A14 (UX3407QA) and want
+	  support for its EC-controlled keyboard backlight and Fn hotkeys
+
+	  This driver is currently only tested on the ASUS Zenbook A14
+	  UX3407QA; behaviour on other models is unknown.
+
 config HID_AUREAL
 	tristate "Aureal"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daedeb85454114def8afb5f58caeab58a00..bacccf00482c1b787ce59660e4366f8224aeefee 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_HID_APPLETB_BL)	+= hid-appletb-bl.o
 obj-$(CONFIG_HID_APPLETB_KBD)	+= hid-appletb-kbd.o
 obj-$(CONFIG_HID_CREATIVE_SB0540)	+= hid-creative-sb0540.o
 obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
+obj-$(CONFIG_HID_ASUS_EC)	+= hid-asus-ec.o
 obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
 obj-$(CONFIG_HID_BELKIN)	+= hid-belkin.o
 obj-$(CONFIG_HID_BETOP_FF)	+= hid-betopff.o
diff --git a/drivers/hid/hid-asus-ec.c b/drivers/hid/hid-asus-ec.c
new file mode 100644
index 0000000000000000000000000000000000000000..1cf61fb2468d079827bfdb1db49daca905e53874
--- /dev/null
+++ b/drivers/hid/hid-asus-ec.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ASUS EC HID driver for Zenbook A14 (UX3407QA)
+ *
+ * EC I2C HID driver for keyboard backlight and Fn hotkeys.
+ *
+ * Copyright (c) 2025 Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+
+#define ASUS_VENDOR_ID		0x0B05
+#define ASUS_PRODUCT_ID		0x0220
+
+#define A14_EC_REPORT_ID	0x5A
+#define A14_EC_REPORT_SIZE	64
+#define A14_EC_MAX_BACKLIGHT	3
+
+#define A14_EC_EVT_KEY_FN_ESC	0x4E
+#define A14_EC_EVT_KEY_FN_F4	0xC7
+#define A14_EC_EVT_KEY_FN_F5	0x10
+#define A14_EC_EVT_KEY_FN_F6	0x20
+#define A14_EC_EVT_KEY_FN_F8	0x7E
+#define A14_EC_EVT_KEY_FN_F9	0x7C
+#define A14_EC_EVT_KEY_FN_F10	0x85
+#define A14_EC_EVT_KEY_FN_F11	0x6B
+#define A14_EC_EVT_KEY_FN_F12	0x86
+#define A14_EC_EVT_KEY_FN_F	0x9d
+
+
+struct asus_hid_data {
+	struct hid_device *hdev;
+	struct led_classdev kbd_led_cdev;
+	struct input_dev *hotkey_input_dev;
+	enum led_brightness saved_brightness;
+};
+
+static struct asus_hid_data *asus_data;
+
+static int asus_send_ec_command(struct hid_device *hdev, u8 *cmd_buf)
+{
+	int ret;
+	u8 report_id = cmd_buf[0];
+
+	ret = hid_hw_raw_request(hdev, report_id, cmd_buf, A14_EC_REPORT_SIZE,
+				     HID_FEATURE_REPORT,
+				     HID_REQ_SET_REPORT);
+
+	if (ret < 0)
+		dev_err(&hdev->dev, "hid_hw_raw_request failed with status: %d\n", ret);
+
+	return ret;
+}
+
+static int asus_kbd_led_init(struct hid_device *hdev)
+{
+	u8 ec_init_cmd[A14_EC_REPORT_SIZE] = { A14_EC_REPORT_ID, 0xD0, 0x8F, 0x01 };
+	int ret;
+
+	ret = asus_send_ec_command(hdev, ec_init_cmd);
+
+	if (ret < 0)
+		return ret;
+
+	u8 brightness_cmd[A14_EC_REPORT_SIZE] = { A14_EC_REPORT_ID, 0xBA, 0xC5,
+				    0xC4, A14_EC_MAX_BACKLIGHT };
+
+	ret = asus_send_ec_command(hdev, brightness_cmd);
+	if (ret < 0)
+		dev_warn(&hdev->dev, "Brightness init failed: %d\n", ret);
+
+	return ret;
+}
+
+static void asus_kbd_set_brightness(struct led_classdev *led_cdev,
+				    enum led_brightness brightness)
+{
+	struct asus_hid_data *data = container_of(led_cdev, struct asus_hid_data, kbd_led_cdev);
+
+	u8 level = (u8)brightness;
+
+	if (level > A14_EC_MAX_BACKLIGHT)
+		level = A14_EC_MAX_BACKLIGHT;
+
+	u8 buf[A14_EC_REPORT_SIZE] = { A14_EC_REPORT_ID, 0xBA, 0xC5, 0xC4, level };
+
+	asus_send_ec_command(data->hdev, buf);
+	msleep(20);
+	data->saved_brightness = (enum led_brightness)level;
+}
+
+static int asus_raw_event(struct hid_device *hdev, struct hid_report *report,
+			  u8 *raw_data, int size)
+{
+	struct asus_hid_data *data = hid_get_drvdata(hdev);
+	struct input_dev *input_dev = data->hotkey_input_dev;
+
+	if (report->id == A14_EC_REPORT_ID && size >= 2) {
+		u8 usage_code = raw_data[1];
+
+		switch (usage_code) {
+		case A14_EC_EVT_KEY_FN_ESC:
+			input_event(input_dev, EV_KEY, KEY_FN_ESC, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_FN_ESC, 0);
+			input_sync(input_dev);
+			return 1;
+
+		/* FN + F1, F2 and F3 are not managed by EC*/
+
+		case A14_EC_EVT_KEY_FN_F4:
+			if (size >= 3) {
+				u8 current_level = data->saved_brightness;
+				u8 max_level = A14_EC_MAX_BACKLIGHT;
+				u8 next_level = (current_level + 1) % (max_level + 1);
+
+				asus_kbd_set_brightness(&data->kbd_led_cdev,
+							(enum led_brightness)next_level);
+				if (next_level > current_level ||
+				    (current_level == max_level && next_level == 0)) {
+					if (next_level == 0) {
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMDOWN, 1);
+						input_sync(input_dev);
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMDOWN, 0);
+						input_sync(input_dev);
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMDOWN, 1);
+						input_sync(input_dev);
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMDOWN, 0);
+						input_sync(input_dev);
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMDOWN, 1);
+						input_sync(input_dev);
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMDOWN, 0);
+						input_sync(input_dev);
+					} else {
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMUP, 1);
+						input_sync(input_dev);
+						input_event(input_dev, EV_KEY,
+							    KEY_KBDILLUMUP, 0);
+						input_sync(input_dev);
+					}
+				} else if (next_level < current_level) {
+					input_event(input_dev, EV_KEY,
+						    KEY_KBDILLUMDOWN, 1);
+					input_sync(input_dev);
+					input_event(input_dev, EV_KEY,
+						    KEY_KBDILLUMDOWN, 0);
+					input_sync(input_dev);
+					input_event(input_dev, EV_KEY,
+						    KEY_KBDILLUMDOWN, 1);
+					input_sync(input_dev);
+					input_event(input_dev, EV_KEY,
+						    KEY_KBDILLUMDOWN, 0);
+					input_sync(input_dev);
+					input_event(input_dev, EV_KEY,
+						    KEY_KBDILLUMDOWN, 1);
+					input_sync(input_dev);
+					input_event(input_dev, EV_KEY,
+						    KEY_KBDILLUMDOWN, 0);
+					input_sync(input_dev);
+				}
+			}
+			return 1;
+		case A14_EC_EVT_KEY_FN_F5:
+			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSDOWN, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSDOWN, 0);
+			input_sync(input_dev);
+			return 1;
+		case A14_EC_EVT_KEY_FN_F6:
+			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSUP, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSUP, 0);
+			input_sync(input_dev);
+			return 1;
+
+		/* FN + F7 is not managed by the EC*/
+
+		case A14_EC_EVT_KEY_FN_F8:
+			input_event(input_dev, EV_KEY, KEY_EMOJI_PICKER, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_EMOJI_PICKER, 0);
+			input_sync(input_dev);
+			return 1;
+		case A14_EC_EVT_KEY_FN_F9:
+			input_event(input_dev, EV_KEY, KEY_MICMUTE, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_MICMUTE, 0);
+			input_sync(input_dev);
+			return 1;
+		case A14_EC_EVT_KEY_FN_F10:
+			input_event(input_dev, EV_KEY, KEY_CAMERA_ACCESS_TOGGLE, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_CAMERA_ACCESS_TOGGLE, 0);
+			input_sync(input_dev);
+			return 1;
+		case A14_EC_EVT_KEY_FN_F11:
+			input_event(input_dev, EV_KEY, KEY_TOUCHPAD_TOGGLE, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_TOUCHPAD_TOGGLE, 0);
+			input_sync(input_dev);
+			return 1;
+		case A14_EC_EVT_KEY_FN_F12:
+			input_event(input_dev, EV_KEY, KEY_PROG1, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_PROG1, 0);
+			input_sync(input_dev);
+			return 1;
+		case A14_EC_EVT_KEY_FN_F:
+			input_event(input_dev, EV_KEY, KEY_PERFORMANCE, 1);
+			input_sync(input_dev);
+			input_event(input_dev, EV_KEY, KEY_PERFORMANCE, 0);
+			input_sync(input_dev);
+			return 1;
+		default:
+			return 0;
+		}
+	}
+	return 0;
+}
+
+static int asus_hid_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	struct asus_hid_data *data = hid_get_drvdata(hdev);
+
+	if (message.event == PM_EVENT_SUSPEND || message.event == PM_EVENT_HIBERNATE) {
+		asus_kbd_set_brightness(&data->kbd_led_cdev, LED_OFF);
+		return 0;
+	}
+
+	dev_dbg(&hdev->dev, "Runtime suspend: turning off keyboard backlight\n");
+	asus_kbd_set_brightness(&data->kbd_led_cdev, LED_OFF);
+	return 0;
+}
+
+
+static int asus_hid_resume(struct hid_device *hdev)
+{
+	struct asus_hid_data *data = hid_get_drvdata(hdev);
+	int ret;
+	int retry_count;
+
+	msleep(40);
+
+	dev_dbg(&hdev->dev, "Re-initialising EC backlight communication\n");
+	retry_count = 0;
+	do {
+		ret = asus_kbd_led_init(hdev);
+		if (ret >= 0) {
+			dev_dbg(&hdev->dev,
+				"EC init successful on attempt %d\n",
+				retry_count + 1);
+			break;
+		}
+		retry_count++;
+		dev_warn(&hdev->dev,
+			 "EC init attempt %d failed: %d, retrying...\n",
+			 retry_count, ret);
+		msleep(300 * retry_count);
+	} while (retry_count < 5);
+
+	if (ret < 0) {
+		dev_err(&hdev->dev,
+			"EC init on resume failed after %d attempts: %d\n",
+			retry_count, ret);
+		dev_err(&hdev->dev,
+			"Keyboard backlight may not work; try reloading the driver\n");
+	} else {
+		dev_dbg(&hdev->dev, "EC backlight communication restored\n");
+	}
+
+	asus_kbd_set_brightness(&data->kbd_led_cdev, data->saved_brightness);
+
+	dev_info(&hdev->dev, "Resume complete\n");
+	return 0;
+}
+
+static const struct hid_device_id asus_hid_devices[] = {
+	/* Tested on ASUS Zenbook A14 (UX3407QA) only. */
+	{ HID_DEVICE(0x18, 0x00, ASUS_VENDOR_ID, ASUS_PRODUCT_ID) },
+	{ } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(hid, asus_hid_devices);
+
+static int asus_hid_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct asus_hid_data *data;
+
+	data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->hdev = hdev;
+	hid_set_drvdata(hdev, data);
+	asus_data = data;
+	data->saved_brightness = A14_EC_MAX_BACKLIGHT;
+	ret = hid_parse(hdev);
+	if (ret)
+		return ret;
+	ret = hid_hw_start(hdev, HID_INPUT_REPORT | HID_OUTPUT_REPORT | HID_FEATURE_REPORT);
+	if (ret)
+		return ret;
+	data->hotkey_input_dev = input_allocate_device();
+	if (!data->hotkey_input_dev) {
+		dev_err(&hdev->dev, "Failed to allocate hotkey input device\n");
+		hid_hw_stop(hdev); return -ENOMEM;
+	}
+	data->hotkey_input_dev->name = "ASUS EC I2C HID hotkeys";
+	data->hotkey_input_dev->phys = "i2c-hid/input1/hotkeys";
+	data->hotkey_input_dev->id.bustype = hdev->bus;
+	data->hotkey_input_dev->id.vendor = hdev->vendor;
+	data->hotkey_input_dev->id.product = hdev->product;
+	data->hotkey_input_dev->dev.parent = &hdev->dev;
+	set_bit(EV_KEY, data->hotkey_input_dev->evbit);
+	set_bit(KEY_FN_ESC, data->hotkey_input_dev->keybit);
+	set_bit(KEY_KBDILLUMUP, data->hotkey_input_dev->keybit);
+	set_bit(KEY_KBDILLUMDOWN, data->hotkey_input_dev->keybit);
+	set_bit(KEY_BRIGHTNESSDOWN, data->hotkey_input_dev->keybit);
+	set_bit(KEY_BRIGHTNESSUP, data->hotkey_input_dev->keybit);
+	set_bit(KEY_SWITCHVIDEOMODE, data->hotkey_input_dev->keybit);
+	set_bit(KEY_EMOJI_PICKER, data->hotkey_input_dev->keybit);
+	set_bit(KEY_MICMUTE, data->hotkey_input_dev->keybit);
+	set_bit(KEY_CAMERA_ACCESS_TOGGLE, data->hotkey_input_dev->keybit);
+	set_bit(KEY_TOUCHPAD_TOGGLE, data->hotkey_input_dev->keybit);
+	set_bit(KEY_PROG1, data->hotkey_input_dev->keybit);
+	set_bit(KEY_PERFORMANCE, data->hotkey_input_dev->keybit);
+
+	ret = input_register_device(data->hotkey_input_dev);
+	if (ret) {
+		dev_err(&hdev->dev, "Failed to register hotkey input device\n");
+		input_free_device(data->hotkey_input_dev); hid_hw_stop(hdev);
+		return ret;
+	}
+	asus_kbd_led_init(hdev);
+	data->kbd_led_cdev.name = "asus::kbd_backlight";
+	data->kbd_led_cdev.brightness_set = asus_kbd_set_brightness;
+	data->kbd_led_cdev.max_brightness = A14_EC_MAX_BACKLIGHT;
+	ret = led_classdev_register(&hdev->dev, &data->kbd_led_cdev);
+	if (ret) {
+		input_unregister_device(data->hotkey_input_dev);
+		hid_hw_stop(hdev);
+		return ret;
+	}
+	dev_info(&hdev->dev,
+		 "ASUS EC HID driver for Zenbook A14 loaded for 0x%04x:0x%04x\n",
+		 ASUS_VENDOR_ID, ASUS_PRODUCT_ID);
+	return 0;
+}
+static void asus_hid_remove(struct hid_device *hdev)
+{
+	struct asus_hid_data *data = hid_get_drvdata(hdev);
+
+	led_classdev_unregister(&data->kbd_led_cdev);
+	input_unregister_device(data->hotkey_input_dev);
+	hid_hw_stop(hdev);
+	asus_data = NULL;
+}
+static struct hid_driver hid_asus_ec_driver = {
+	.name       = "hid-asus-ec",
+	.id_table   = asus_hid_devices,
+	.probe      = asus_hid_probe,
+	.remove     = asus_hid_remove,
+	.raw_event  = asus_raw_event,
+	.suspend    = asus_hid_suspend,
+	.resume     = asus_hid_resume,
+};
+module_hid_driver(hid_asus_ec_driver);
+MODULE_AUTHOR("Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>");
+MODULE_DESCRIPTION("ASUS EC HID driver for Zenbook A14 (UX3407QA)");
+MODULE_LICENSE("GPL");

---
base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
change-id: 20251221-add-support-for-the-asus-hid-ec-a60b9a2d74b3

Best regards,
-- 
Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>



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

* Re: [PATCH] HID: asus: add new Asus EC hid device for keyboard backlight and FN HotKeys
  2025-12-21 11:12 [PATCH] HID: asus: add new Asus EC hid device for keyboard backlight and FN HotKeys Alexandru Marc Serdeliuc via B4 Relay
@ 2026-06-30 16:43 ` Alexandru Serdeliuc
  2026-07-01 14:12   ` Jiri Kosina
  0 siblings, 1 reply; 3+ messages in thread
From: Alexandru Serdeliuc @ 2026-06-30 16:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-kernel, linux-input

Hi Jiri, Benjamin,

Enquiring if anyone has had a chance to look over this patch series from 
December?

To provide a bit of architectural context, this is a 100% pure HID 
client driver.

This laptop (Asus Zenbook A14 UX3407QA) runs on the Qualcomm ARM64 
Snapdragon platform.

Because it is an ARM device, it does not use traditional legacy x86 
ACPI/WMI code.

Instead, the Fn hotkeys and keyboard backlight controls are routed 
completely through standard vendor-specific HID reports (using Report ID 
0x5A) over the existing I2C-HID transport layer.

It requires absolutely zero Device Tree modifications or external 
platform subsystem hooks to bind or function.

The driver matches strictly on Vendor/Product ID (0x0B05 / 0x0220) 
passed directly through the core HID subsystem.

I would highly appreciate any architectural feedback or a quick review!

Best regards,
Alexandru Marc Serdeliuc


On 12/21/25 12:12, Alexandru Marc Serdeliuc via B4 Relay wrote:
> From: Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>
> 
> Add support for Asus Embedded Controlled accessed via HID
> 
> Currently working features:
> - Keyboard Backlight
> - FN HotKeys
> 
> Signed-off-by: Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>
> ---
> Add support for Asus Embedded Controlled accessed via HID
> 
> Currently working features:
> - Keyboard Backlight
> - FN HotKeys
> ---
>   drivers/hid/Kconfig       |  10 ++
>   drivers/hid/Makefile      |   1 +
>   drivers/hid/hid-asus-ec.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 397 insertions(+)
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 920a64b66b25b39e8259105c7c9b975fb77b2725..f0fbc951735eeea39198137294a9429f5b9d34b8 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -202,6 +202,16 @@ config HID_ASUS
>   	- GL553V series
>   	- GL753V series
>   
> +config HID_ASUS_EC
> +	tristate "ASUS EC HID support (Zenbook A14 UX3407QA)"
> +	depends on HID && I2C_HID
> +	help
> +	  Say Y here if you have an ASUS Zenbook A14 (UX3407QA) and want
> +	  support for its EC-controlled keyboard backlight and Fn hotkeys
> +
> +	  This driver is currently only tested on the ASUS Zenbook A14
> +	  UX3407QA; behaviour on other models is unknown.
> +
>   config HID_AUREAL
>   	tristate "Aureal"
>   	help
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 361a7daedeb85454114def8afb5f58caeab58a00..bacccf00482c1b787ce59660e4366f8224aeefee 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -34,6 +34,7 @@ obj-$(CONFIG_HID_APPLETB_BL)	+= hid-appletb-bl.o
>   obj-$(CONFIG_HID_APPLETB_KBD)	+= hid-appletb-kbd.o
>   obj-$(CONFIG_HID_CREATIVE_SB0540)	+= hid-creative-sb0540.o
>   obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
> +obj-$(CONFIG_HID_ASUS_EC)	+= hid-asus-ec.o
>   obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
>   obj-$(CONFIG_HID_BELKIN)	+= hid-belkin.o
>   obj-$(CONFIG_HID_BETOP_FF)	+= hid-betopff.o
> diff --git a/drivers/hid/hid-asus-ec.c b/drivers/hid/hid-asus-ec.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..1cf61fb2468d079827bfdb1db49daca905e53874
> --- /dev/null
> +++ b/drivers/hid/hid-asus-ec.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * ASUS EC HID driver for Zenbook A14 (UX3407QA)
> + *
> + * EC I2C HID driver for keyboard backlight and Fn hotkeys.
> + *
> + * Copyright (c) 2025 Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/leds.h>
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/string.h>
> +#include <linux/delay.h>
> +#include <linux/pm.h>
> +
> +#define ASUS_VENDOR_ID		0x0B05
> +#define ASUS_PRODUCT_ID		0x0220
> +
> +#define A14_EC_REPORT_ID	0x5A
> +#define A14_EC_REPORT_SIZE	64
> +#define A14_EC_MAX_BACKLIGHT	3
> +
> +#define A14_EC_EVT_KEY_FN_ESC	0x4E
> +#define A14_EC_EVT_KEY_FN_F4	0xC7
> +#define A14_EC_EVT_KEY_FN_F5	0x10
> +#define A14_EC_EVT_KEY_FN_F6	0x20
> +#define A14_EC_EVT_KEY_FN_F8	0x7E
> +#define A14_EC_EVT_KEY_FN_F9	0x7C
> +#define A14_EC_EVT_KEY_FN_F10	0x85
> +#define A14_EC_EVT_KEY_FN_F11	0x6B
> +#define A14_EC_EVT_KEY_FN_F12	0x86
> +#define A14_EC_EVT_KEY_FN_F	0x9d
> +
> +
> +struct asus_hid_data {
> +	struct hid_device *hdev;
> +	struct led_classdev kbd_led_cdev;
> +	struct input_dev *hotkey_input_dev;
> +	enum led_brightness saved_brightness;
> +};
> +
> +static struct asus_hid_data *asus_data;
> +
> +static int asus_send_ec_command(struct hid_device *hdev, u8 *cmd_buf)
> +{
> +	int ret;
> +	u8 report_id = cmd_buf[0];
> +
> +	ret = hid_hw_raw_request(hdev, report_id, cmd_buf, A14_EC_REPORT_SIZE,
> +				     HID_FEATURE_REPORT,
> +				     HID_REQ_SET_REPORT);
> +
> +	if (ret < 0)
> +		dev_err(&hdev->dev, "hid_hw_raw_request failed with status: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int asus_kbd_led_init(struct hid_device *hdev)
> +{
> +	u8 ec_init_cmd[A14_EC_REPORT_SIZE] = { A14_EC_REPORT_ID, 0xD0, 0x8F, 0x01 };
> +	int ret;
> +
> +	ret = asus_send_ec_command(hdev, ec_init_cmd);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	u8 brightness_cmd[A14_EC_REPORT_SIZE] = { A14_EC_REPORT_ID, 0xBA, 0xC5,
> +				    0xC4, A14_EC_MAX_BACKLIGHT };
> +
> +	ret = asus_send_ec_command(hdev, brightness_cmd);
> +	if (ret < 0)
> +		dev_warn(&hdev->dev, "Brightness init failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static void asus_kbd_set_brightness(struct led_classdev *led_cdev,
> +				    enum led_brightness brightness)
> +{
> +	struct asus_hid_data *data = container_of(led_cdev, struct asus_hid_data, kbd_led_cdev);
> +
> +	u8 level = (u8)brightness;
> +
> +	if (level > A14_EC_MAX_BACKLIGHT)
> +		level = A14_EC_MAX_BACKLIGHT;
> +
> +	u8 buf[A14_EC_REPORT_SIZE] = { A14_EC_REPORT_ID, 0xBA, 0xC5, 0xC4, level };
> +
> +	asus_send_ec_command(data->hdev, buf);
> +	msleep(20);
> +	data->saved_brightness = (enum led_brightness)level;
> +}
> +
> +static int asus_raw_event(struct hid_device *hdev, struct hid_report *report,
> +			  u8 *raw_data, int size)
> +{
> +	struct asus_hid_data *data = hid_get_drvdata(hdev);
> +	struct input_dev *input_dev = data->hotkey_input_dev;
> +
> +	if (report->id == A14_EC_REPORT_ID && size >= 2) {
> +		u8 usage_code = raw_data[1];
> +
> +		switch (usage_code) {
> +		case A14_EC_EVT_KEY_FN_ESC:
> +			input_event(input_dev, EV_KEY, KEY_FN_ESC, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_FN_ESC, 0);
> +			input_sync(input_dev);
> +			return 1;
> +
> +		/* FN + F1, F2 and F3 are not managed by EC*/
> +
> +		case A14_EC_EVT_KEY_FN_F4:
> +			if (size >= 3) {
> +				u8 current_level = data->saved_brightness;
> +				u8 max_level = A14_EC_MAX_BACKLIGHT;
> +				u8 next_level = (current_level + 1) % (max_level + 1);
> +
> +				asus_kbd_set_brightness(&data->kbd_led_cdev,
> +							(enum led_brightness)next_level);
> +				if (next_level > current_level ||
> +				    (current_level == max_level && next_level == 0)) {
> +					if (next_level == 0) {
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMDOWN, 1);
> +						input_sync(input_dev);
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMDOWN, 0);
> +						input_sync(input_dev);
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMDOWN, 1);
> +						input_sync(input_dev);
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMDOWN, 0);
> +						input_sync(input_dev);
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMDOWN, 1);
> +						input_sync(input_dev);
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMDOWN, 0);
> +						input_sync(input_dev);
> +					} else {
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMUP, 1);
> +						input_sync(input_dev);
> +						input_event(input_dev, EV_KEY,
> +							    KEY_KBDILLUMUP, 0);
> +						input_sync(input_dev);
> +					}
> +				} else if (next_level < current_level) {
> +					input_event(input_dev, EV_KEY,
> +						    KEY_KBDILLUMDOWN, 1);
> +					input_sync(input_dev);
> +					input_event(input_dev, EV_KEY,
> +						    KEY_KBDILLUMDOWN, 0);
> +					input_sync(input_dev);
> +					input_event(input_dev, EV_KEY,
> +						    KEY_KBDILLUMDOWN, 1);
> +					input_sync(input_dev);
> +					input_event(input_dev, EV_KEY,
> +						    KEY_KBDILLUMDOWN, 0);
> +					input_sync(input_dev);
> +					input_event(input_dev, EV_KEY,
> +						    KEY_KBDILLUMDOWN, 1);
> +					input_sync(input_dev);
> +					input_event(input_dev, EV_KEY,
> +						    KEY_KBDILLUMDOWN, 0);
> +					input_sync(input_dev);
> +				}
> +			}
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F5:
> +			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSDOWN, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSDOWN, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F6:
> +			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSUP, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_BRIGHTNESSUP, 0);
> +			input_sync(input_dev);
> +			return 1;
> +
> +		/* FN + F7 is not managed by the EC*/
> +
> +		case A14_EC_EVT_KEY_FN_F8:
> +			input_event(input_dev, EV_KEY, KEY_EMOJI_PICKER, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_EMOJI_PICKER, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F9:
> +			input_event(input_dev, EV_KEY, KEY_MICMUTE, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_MICMUTE, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F10:
> +			input_event(input_dev, EV_KEY, KEY_CAMERA_ACCESS_TOGGLE, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_CAMERA_ACCESS_TOGGLE, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F11:
> +			input_event(input_dev, EV_KEY, KEY_TOUCHPAD_TOGGLE, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_TOUCHPAD_TOGGLE, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F12:
> +			input_event(input_dev, EV_KEY, KEY_PROG1, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_PROG1, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		case A14_EC_EVT_KEY_FN_F:
> +			input_event(input_dev, EV_KEY, KEY_PERFORMANCE, 1);
> +			input_sync(input_dev);
> +			input_event(input_dev, EV_KEY, KEY_PERFORMANCE, 0);
> +			input_sync(input_dev);
> +			return 1;
> +		default:
> +			return 0;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int asus_hid_suspend(struct hid_device *hdev, pm_message_t message)
> +{
> +	struct asus_hid_data *data = hid_get_drvdata(hdev);
> +
> +	if (message.event == PM_EVENT_SUSPEND || message.event == PM_EVENT_HIBERNATE) {
> +		asus_kbd_set_brightness(&data->kbd_led_cdev, LED_OFF);
> +		return 0;
> +	}
> +
> +	dev_dbg(&hdev->dev, "Runtime suspend: turning off keyboard backlight\n");
> +	asus_kbd_set_brightness(&data->kbd_led_cdev, LED_OFF);
> +	return 0;
> +}
> +
> +
> +static int asus_hid_resume(struct hid_device *hdev)
> +{
> +	struct asus_hid_data *data = hid_get_drvdata(hdev);
> +	int ret;
> +	int retry_count;
> +
> +	msleep(40);
> +
> +	dev_dbg(&hdev->dev, "Re-initialising EC backlight communication\n");
> +	retry_count = 0;
> +	do {
> +		ret = asus_kbd_led_init(hdev);
> +		if (ret >= 0) {
> +			dev_dbg(&hdev->dev,
> +				"EC init successful on attempt %d\n",
> +				retry_count + 1);
> +			break;
> +		}
> +		retry_count++;
> +		dev_warn(&hdev->dev,
> +			 "EC init attempt %d failed: %d, retrying...\n",
> +			 retry_count, ret);
> +		msleep(300 * retry_count);
> +	} while (retry_count < 5);
> +
> +	if (ret < 0) {
> +		dev_err(&hdev->dev,
> +			"EC init on resume failed after %d attempts: %d\n",
> +			retry_count, ret);
> +		dev_err(&hdev->dev,
> +			"Keyboard backlight may not work; try reloading the driver\n");
> +	} else {
> +		dev_dbg(&hdev->dev, "EC backlight communication restored\n");
> +	}
> +
> +	asus_kbd_set_brightness(&data->kbd_led_cdev, data->saved_brightness);
> +
> +	dev_info(&hdev->dev, "Resume complete\n");
> +	return 0;
> +}
> +
> +static const struct hid_device_id asus_hid_devices[] = {
> +	/* Tested on ASUS Zenbook A14 (UX3407QA) only. */
> +	{ HID_DEVICE(0x18, 0x00, ASUS_VENDOR_ID, ASUS_PRODUCT_ID) },
> +	{ } /* Terminating entry */
> +};
> +MODULE_DEVICE_TABLE(hid, asus_hid_devices);
> +
> +static int asus_hid_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> +	int ret;
> +	struct asus_hid_data *data;
> +
> +	data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +	data->hdev = hdev;
> +	hid_set_drvdata(hdev, data);
> +	asus_data = data;
> +	data->saved_brightness = A14_EC_MAX_BACKLIGHT;
> +	ret = hid_parse(hdev);
> +	if (ret)
> +		return ret;
> +	ret = hid_hw_start(hdev, HID_INPUT_REPORT | HID_OUTPUT_REPORT | HID_FEATURE_REPORT);
> +	if (ret)
> +		return ret;
> +	data->hotkey_input_dev = input_allocate_device();
> +	if (!data->hotkey_input_dev) {
> +		dev_err(&hdev->dev, "Failed to allocate hotkey input device\n");
> +		hid_hw_stop(hdev); return -ENOMEM;
> +	}
> +	data->hotkey_input_dev->name = "ASUS EC I2C HID hotkeys";
> +	data->hotkey_input_dev->phys = "i2c-hid/input1/hotkeys";
> +	data->hotkey_input_dev->id.bustype = hdev->bus;
> +	data->hotkey_input_dev->id.vendor = hdev->vendor;
> +	data->hotkey_input_dev->id.product = hdev->product;
> +	data->hotkey_input_dev->dev.parent = &hdev->dev;
> +	set_bit(EV_KEY, data->hotkey_input_dev->evbit);
> +	set_bit(KEY_FN_ESC, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_KBDILLUMUP, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_KBDILLUMDOWN, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_BRIGHTNESSDOWN, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_BRIGHTNESSUP, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_SWITCHVIDEOMODE, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_EMOJI_PICKER, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_MICMUTE, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_CAMERA_ACCESS_TOGGLE, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_TOUCHPAD_TOGGLE, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_PROG1, data->hotkey_input_dev->keybit);
> +	set_bit(KEY_PERFORMANCE, data->hotkey_input_dev->keybit);
> +
> +	ret = input_register_device(data->hotkey_input_dev);
> +	if (ret) {
> +		dev_err(&hdev->dev, "Failed to register hotkey input device\n");
> +		input_free_device(data->hotkey_input_dev); hid_hw_stop(hdev);
> +		return ret;
> +	}
> +	asus_kbd_led_init(hdev);
> +	data->kbd_led_cdev.name = "asus::kbd_backlight";
> +	data->kbd_led_cdev.brightness_set = asus_kbd_set_brightness;
> +	data->kbd_led_cdev.max_brightness = A14_EC_MAX_BACKLIGHT;
> +	ret = led_classdev_register(&hdev->dev, &data->kbd_led_cdev);
> +	if (ret) {
> +		input_unregister_device(data->hotkey_input_dev);
> +		hid_hw_stop(hdev);
> +		return ret;
> +	}
> +	dev_info(&hdev->dev,
> +		 "ASUS EC HID driver for Zenbook A14 loaded for 0x%04x:0x%04x\n",
> +		 ASUS_VENDOR_ID, ASUS_PRODUCT_ID);
> +	return 0;
> +}
> +static void asus_hid_remove(struct hid_device *hdev)
> +{
> +	struct asus_hid_data *data = hid_get_drvdata(hdev);
> +
> +	led_classdev_unregister(&data->kbd_led_cdev);
> +	input_unregister_device(data->hotkey_input_dev);
> +	hid_hw_stop(hdev);
> +	asus_data = NULL;
> +}
> +static struct hid_driver hid_asus_ec_driver = {
> +	.name       = "hid-asus-ec",
> +	.id_table   = asus_hid_devices,
> +	.probe      = asus_hid_probe,
> +	.remove     = asus_hid_remove,
> +	.raw_event  = asus_raw_event,
> +	.suspend    = asus_hid_suspend,
> +	.resume     = asus_hid_resume,
> +};
> +module_hid_driver(hid_asus_ec_driver);
> +MODULE_AUTHOR("Alexandru Marc Serdeliuc <serdeliuk@yahoo.com>");
> +MODULE_DESCRIPTION("ASUS EC HID driver for Zenbook A14 (UX3407QA)");
> +MODULE_LICENSE("GPL");
> 
> ---
> base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
> change-id: 20251221-add-support-for-the-asus-hid-ec-a60b9a2d74b3
> 
> Best regards,


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

* Re: [PATCH] HID: asus: add new Asus EC hid device for keyboard backlight and FN HotKeys
  2026-06-30 16:43 ` Alexandru Serdeliuc
@ 2026-07-01 14:12   ` Jiri Kosina
  0 siblings, 0 replies; 3+ messages in thread
From: Jiri Kosina @ 2026-07-01 14:12 UTC (permalink / raw)
  To: Alexandru Serdeliuc; +Cc: Benjamin Tissoires, linux-kernel, linux-input

On Tue, 30 Jun 2026, Alexandru Serdeliuc wrote:

> Hi Jiri, Benjamin,
> 
> Enquiring if anyone has had a chance to look over this patch series from
> December?
> 
> To provide a bit of architectural context, this is a 100% pure HID client
> driver.
> 
> This laptop (Asus Zenbook A14 UX3407QA) runs on the Qualcomm ARM64 Snapdragon
> platform.
> 
> Because it is an ARM device, it does not use traditional legacy x86 ACPI/WMI
> code.
> 
> Instead, the Fn hotkeys and keyboard backlight controls are routed completely
> through standard vendor-specific HID reports (using Report ID 0x5A) over the
> existing I2C-HID transport layer.
> 
> It requires absolutely zero Device Tree modifications or external platform
> subsystem hooks to bind or function.
> 
> The driver matches strictly on Vendor/Product ID (0x0B05 / 0x0220) passed
> directly through the core HID subsystem.
> 
> I would highly appreciate any architectural feedback or a quick review!

Hi Alexandru,

first - sorry for not having handled this, it somehow fell in between 
cracks.

I have just one question -- wouldn't it be better to have this device 
handled as part of generic hid-asus driver? Giving too many Kconfig 
configurations for every specific model feels a little bit too 
overwhelming.

(yes, we'd need to have hid-asus depend on I2C ... I don't think that's a 
big issue).

Thanks, and sorry again,

-- 
Jiri Kosina
SUSE Labs


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

end of thread, other threads:[~2026-07-01 14:12 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-21 11:12 [PATCH] HID: asus: add new Asus EC hid device for keyboard backlight and FN HotKeys Alexandru Marc Serdeliuc via B4 Relay
2026-06-30 16:43 ` Alexandru Serdeliuc
2026-07-01 14:12   ` Jiri Kosina

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