Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH 11/11] HID: wacom: use 'hid_device_id::driver_data_ptr'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

Remove the casts to (kernel_ulong_t) and (struct wacom_features *)
and use the 'drvier_data_ptr' field instead to improve readability
of the code.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/wacom_sys.c | 14 +++++++++-----
 drivers/hid/wacom_wac.c | 10 +++++-----
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index 0d1c6d90fe21..25408676c5f6 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -2555,8 +2555,9 @@ static void wacom_wireless_work(struct work_struct *work)
 		}
 
 		/* Stylus interface */
-		wacom_wac1->features =
-			*((struct wacom_features *)id->driver_data);
+		const struct wacom_features *features = id->driver_data_ptr;
+
+		wacom_wac1->features = *features;
 
 		wacom_wac1->pid = wacom_wac->pid;
 		hid_hw_stop(hdev1);
@@ -2568,8 +2569,7 @@ static void wacom_wireless_work(struct work_struct *work)
 		if (wacom_wac1->features.touch_max ||
 		    (wacom_wac1->features.type >= INTUOSHT &&
 		    wacom_wac1->features.type <= BAMBOO_PT)) {
-			wacom_wac2->features =
-				*((struct wacom_features *)id->driver_data);
+			wacom_wac2->features = *features;
 			wacom_wac2->pid = wacom_wac->pid;
 			hid_hw_stop(hdev2);
 			error = wacom_parse_and_register(wacom2, true);
@@ -2839,7 +2839,11 @@ static int wacom_probe(struct hid_device *hdev,
 	wacom->hdev = hdev;
 
 	wacom_wac = &wacom->wacom_wac;
-	wacom_wac->features = *((struct wacom_features *)id->driver_data);
+
+	const struct wacom_features *id_features = id->driver_data_ptr;
+
+	wacom_wac->features = *id_features;
+
 	features = &wacom_wac->features;
 
 	if (features->check_for_hid_type && features->hid_type != hdev->type)
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index da1f0ea85625..034bde5af2df 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -4947,23 +4947,23 @@ static const struct wacom_features wacom_features_0x94 =
 
 #define USB_DEVICE_WACOM(prod)						\
 	HID_DEVICE(BUS_USB, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
-	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+	.driver_data_ptr = &wacom_features_##prod
 
 #define BT_DEVICE_WACOM(prod)						\
 	HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
-	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+	.driver_data_ptr = &wacom_features_##prod
 
 #define I2C_DEVICE_WACOM(prod)						\
 	HID_DEVICE(BUS_I2C, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
-	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+	.driver_data_ptr = &wacom_features_##prod
 
 #define PCI_DEVICE_WACOM(prod)						\
 	HID_DEVICE(BUS_PCI, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
-	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+	.driver_data_ptr = &wacom_features_##prod
 
 #define USB_DEVICE_LENOVO(prod)					\
 	HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, prod),			\
-	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+	.driver_data_ptr = &wacom_features_##prod
 
 const struct hid_device_id wacom_ids[] = {
 	{ USB_DEVICE_WACOM(0x00) },

-- 
2.43.0


^ permalink raw reply related

* [PATCH 04/11] HID: i2c-hid-dmi-quirks: use named initializer for 'i2c_hid_elan_flipped_quirks[]'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

This makes the code a little bit more readable and consistent
with other drivers in the HID subsystem that assign the
'driver_data' field in the 'hid_device_id' struct explicitly.

It was confirmed that i2c-hid-dmi-quirks.o pre/post patch are equal
using diffscope.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
index 210f17c3a0be..f3e3cf73641e 100644
--- a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
+++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
@@ -420,7 +420,7 @@ static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = {
 
 static const struct hid_device_id i2c_hid_elan_flipped_quirks = {
 	HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_ELAN, 0x2dcd),
-		HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT
+	.driver_data = HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT,
 };
 
 /*

-- 
2.43.0


^ permalink raw reply related

* [PATCH 09/11] HID: mod_devicetable: 'hid_device_id::driver_data' add union
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

The 'driver_data' field can either be interpreted as an unsigned
numerical number or as a pointer - this depends on the driver within
HID subsystem. Unionise this unsigned value with a 'const void*'
pointer. This change allows to remove explicit casts to pointers
in the pointer-users case and as far as integer-users of the field
are concerned nothing has really changed.

The change doesn't affect the size and layout of struct hid_device_id
which is part of the userspace ABI.

This change aims to improve type-safety, readability - as the
intent is more clear - and portability of the code in the long term.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 include/linux/mod_devicetable.h | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 23ff24080dfd..90b2dae75109 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -175,7 +175,10 @@ struct hid_device_id {
 	__u16 group;
 	__u32 vendor;
 	__u32 product;
-	kernel_ulong_t driver_data;
+	union {
+		kernel_ulong_t driver_data;
+		const void *driver_data_ptr;
+	};
 };
 
 /* s390 CCW devices */

-- 
2.43.0


^ permalink raw reply related

* [PATCH 06/11] HID: hid-cypress: clean up usage of 'driver_data'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

The module is storing an integer inside the drvdata pointer, which is
confusing - furthermore this integer is mutable. When its value is
changed it is set again using the 'hid_set_drvdata' API within
the 'cp_event' function.

Let's fix this, create and allocate the 'cp_device' struct that is then
set as the drvdata and then simply use its integer 'quirks' field for
storing the quirks, which shall make the code cleaner, type-safe,
consistent and more readable.

This makes the cast to (void *) during storage unnecessary and the cast
to (unsigned long) during retrieval is also removed.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/hid-cypress.c | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/drivers/hid/hid-cypress.c b/drivers/hid/hid-cypress.c
index 98548201feec..f18fddc176d0 100644
--- a/drivers/hid/hid-cypress.c
+++ b/drivers/hid/hid-cypress.c
@@ -25,6 +25,10 @@
 
 #define VA_INVAL_LOGICAL_BOUNDARY	0x08
 
+struct cp_device {
+	unsigned long quirks;
+};
+
 /*
  * Some USB barcode readers from cypress have usage min and usage max in
  * the wrong order
@@ -70,7 +74,8 @@ static __u8 *va_logical_boundary_fixup(struct hid_device *hdev, __u8 *rdesc,
 static const __u8 *cp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		unsigned int *rsize)
 {
-	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+	const struct cp_device *cp_device = hid_get_drvdata(hdev);
+	unsigned long quirks = cp_device->quirks;
 
 	if (quirks & CP_RDESC_SWAPPED_MIN_MAX)
 		rdesc = cp_rdesc_fixup(hdev, rdesc, rsize);
@@ -84,7 +89,8 @@ static int cp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
 		struct hid_field *field, struct hid_usage *usage,
 		unsigned long **bit, int *max)
 {
-	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+	const struct cp_device *cp_device = hid_get_drvdata(hdev);
+	unsigned long quirks = cp_device->quirks;
 
 	if (!(quirks & CP_2WHEEL_MOUSE_HACK))
 		return 0;
@@ -100,22 +106,21 @@ static int cp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
 static int cp_event(struct hid_device *hdev, struct hid_field *field,
 		struct hid_usage *usage, __s32 value)
 {
-	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+	struct cp_device *cp_device = hid_get_drvdata(hdev);
 
 	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
-			!usage->type || !(quirks & CP_2WHEEL_MOUSE_HACK))
+			!usage->type || !(cp_device->quirks & CP_2WHEEL_MOUSE_HACK))
 		return 0;
 
 	if (usage->hid == 0x00090005) {
 		if (value)
-			quirks |=  CP_2WHEEL_MOUSE_HACK_ON;
+			cp_device->quirks |= CP_2WHEEL_MOUSE_HACK_ON;
 		else
-			quirks &= ~CP_2WHEEL_MOUSE_HACK_ON;
-		hid_set_drvdata(hdev, (void *)quirks);
+			cp_device->quirks &= ~CP_2WHEEL_MOUSE_HACK_ON;
 		return 1;
 	}
 
-	if (usage->code == REL_WHEEL && (quirks & CP_2WHEEL_MOUSE_HACK_ON)) {
+	if (usage->code == REL_WHEEL && (cp_device->quirks & CP_2WHEEL_MOUSE_HACK_ON)) {
 		struct input_dev *input = field->hidinput->input;
 
 		input_event(input, usage->type, REL_HWHEEL, value);
@@ -127,10 +132,17 @@ static int cp_event(struct hid_device *hdev, struct hid_field *field,
 
 static int cp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
-	unsigned long quirks = id->driver_data;
 	int ret;
+	struct cp_device *cp_device;
 
-	hid_set_drvdata(hdev, (void *)quirks);
+	cp_device = devm_kzalloc(&hdev->dev, sizeof(*cp_device), GFP_KERNEL);
+
+	if (!cp_device)
+		return -ENOMEM;
+
+	cp_device->quirks = id->driver_data;
+
+	hid_set_drvdata(hdev, cp_device);
 
 	ret = hid_parse(hdev);
 	if (ret) {

-- 
2.43.0


^ permalink raw reply related

* [PATCH 05/11] HID: hid-belkin: clean up usage of 'driver_data'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

The module is storing an integer inside the drvdata pointer, which is
confusing, lets fix this and set the whole of 'hid_device_id' struct
as the drvdata and then simply use its integer 'driver_data' field for
quirks, which shall make the code cleaner, type-safe, consistent and
more readable.

This makes the cast to (void *) during storage a bit safer (just to
suppress the const qualifier warning) and the cast to (unsigned long)
during retrieval is removed.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/hid-belkin.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-belkin.c b/drivers/hid/hid-belkin.c
index 75aaed35ee9f..84695115d37b 100644
--- a/drivers/hid/hid-belkin.c
+++ b/drivers/hid/hid-belkin.c
@@ -27,7 +27,8 @@ static int belkin_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 		struct hid_field *field, struct hid_usage *usage,
 		unsigned long **bit, int *max)
 {
-	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+	const struct hid_device_id *id = hid_get_drvdata(hdev);
+	unsigned long quirks = id->driver_data;
 
 	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER ||
 			!(quirks & BELKIN_WKBD))
@@ -48,7 +49,7 @@ static int belkin_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	unsigned long quirks = id->driver_data;
 	int ret;
 
-	hid_set_drvdata(hdev, (void *)quirks);
+	hid_set_drvdata(hdev, (void *)id);
 
 	ret = hid_parse(hdev);
 	if (ret) {

-- 
2.43.0


^ permalink raw reply related

* [PATCH 03/11] HID: hid-asus: use named initializer for 'asus_devices[]'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

This makes the code a little bit more readable and consistent
with other drivers in the HID subsystem that assign the
'driver_data' field in the 'hid_device_id' struct explicitly.

It was confirmed that hid-asus.o pre/post the patch are equal
using diffscope.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/hid-asus.c | 46 ++++++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 3f5e96900b67..ba9a49e9c7f0 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1476,33 +1476,41 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 
 static const struct hid_device_id asus_devices[] = {
 	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
-		USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS},
+		USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD),
+	  .driver_data = I2C_KEYBOARD_QUIRKS },
 	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
-		USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS },
+		USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD),
+	  .driver_data = I2C_TOUCHPAD_QUIRKS },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
-		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1), QUIRK_USE_KBD_BACKLIGHT },
+		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1),
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
-		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT },
+		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2),
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
-		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3), QUIRK_G752_KEYBOARD },
+		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3),
+	  .driver_data = QUIRK_G752_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
-	  QUIRK_USE_KBD_BACKLIGHT },
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD |
+		QUIRK_HID_FN_LOCK },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD},
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD |
+		QUIRK_ROG_ALLY_XPAD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD },
+	  .driver_data = QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD |
+		QUIRK_ROG_ALLY_XPAD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_XGM_2022),
 	},
@@ -1511,29 +1519,31 @@ static const struct hid_device_id asus_devices[] = {
 	},
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
-	  QUIRK_ROG_CLAYMORE_II_KEYBOARD },
+	  .driver_data = QUIRK_ROG_CLAYMORE_II_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
-	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
+	  .driver_data = QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD),
-	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
+	  .driver_data = QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_ASUS_AK1D) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_ASUS_MD_5110) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_ASUS_MD_5112) },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
-		USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD), QUIRK_T100CHI },
+		USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD),
+	  .driver_data = QUIRK_T100CHI },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ITE, USB_DEVICE_ID_ITE_MEDION_E1239T),
-		QUIRK_MEDION_E1239T },
+	  .driver_data = QUIRK_MEDION_E1239T },
 	/*
 	 * 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 },
+	  .driver_data = 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) },
+		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD),
+	  .driver_data = 0 },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, asus_devices);

-- 
2.43.0


^ permalink raw reply related

* [PATCH 02/11] HID: hid-quirks: use named initializer in 'hid_quirks[]'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

This makes the code a little bit more readable and consistent
with other drivers in the HID subsystem that assign the
'driver_data' field in the 'hid_device_id' struct explicitly.

While touching the 'hid_quirks[]', modify the terminator entry to use
a space between the curly braces as opposed to a 0 for consistency.

It was confirmed that hid-quirks.o pre/post the patch are equal
using diffscope.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/hid-quirks.c | 575 +++++++++++++++++++++++++++++++----------------
 1 file changed, 382 insertions(+), 193 deletions(-)

diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 9e88c9d6c6dc..36e1ef536cde 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -25,201 +25,390 @@
  */
 
 static const struct hid_device_id hid_quirks[] = {
-	{ HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE_DONGLE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016), HID_QUIRK_FULLSPEED_INTERVAL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AIREN, USB_DEVICE_ID_AIREN_SLIMPLUS), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI_09E8, USB_DEVICE_ID_AKAI_09E8_MIDIMIX), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI, USB_DEVICE_ID_AKAI_MPKMINI2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_AMI, USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS1758), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_AXIS_295), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_COMBATSTICK), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FIGHTERSTICK), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_PEDALS), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_THROTTLE), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_COOLER_MASTER, USB_DEVICE_ID_COOLER_MASTER_MICE_DONGLE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB_RAPIDFIRE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70R), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K95RGB), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_M65RGB), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_GLAIVE_RGB), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_STRAFE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PRO_WIRELESS_KM5221W), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_REDRAGON_SEYMUR2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE1), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE3), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER), HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_EDIFIER, USB_DEVICE_ID_EDIFIER_QR30), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH_2968), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTABA, USB_DEVICE_ID_LED_DISPLAY), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_SAT_ADAPTOR), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE_DONGLE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016),
+	  .driver_data = HID_QUIRK_FULLSPEED_INTERVAL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIREN, USB_DEVICE_ID_AIREN_SLIMPLUS),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI_09E8, USB_DEVICE_ID_AKAI_09E8_MIDIMIX),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI, USB_DEVICE_ID_AKAI_MPKMINI2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AMI,	USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS1758),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY,	USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY,	USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_AXIS_295),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_COMBATSTICK),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FIGHTERSTICK),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_PEDALS),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_THROTTLE),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_COOLER_MASTER, USB_DEVICE_ID_COOLER_MASTER_MICE_DONGLE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR,	USB_DEVICE_ID_CORSAIR_K70RGB_RAPIDFIRE),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70R),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K95RGB),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_M65RGB),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_GLAIVE_RGB),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR,	USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_STRAFE),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PRO_WIRELESS_KM5221W),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_REDRAGON_SEYMUR2),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE1),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE3),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER),
+	  .driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EDIFIER, USB_DEVICE_ID_EDIFIER_QR30),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH_2968),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTABA, USB_DEVICE_ID_LED_DISPLAY),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_SAT_ADAPTOR),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GAMEVICE, USB_DEVICE_ID_GAMEVICE_GV186),
-		HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMEVICE, USB_DEVICE_ID_GAMEVICE_KISHI),
-		HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING), HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING), HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING), HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A293), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M506), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_I405X), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406W), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_340), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_M508WX), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_M508X), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406XE), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_T609A), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MCS, USB_DEVICE_ID_MCS_GAMEPADBLOCK), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_MOUSE_0783), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PIXART_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE3_COVER), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_PRO_2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TOUCH_COVER_2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_NATSU, USB_DEVICE_ID_NATSU_GAMEPAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_NEC, USB_DEVICE_ID_NEC_USB_GAME_PAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_NEXTWINDOW, USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN), HID_QUIRK_MULTI_INPUT},
-	{ HID_USB_DEVICE(USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK), HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1610), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1640), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PI_ENGINEERING, USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL), HID_QUIRK_HIDINPUT_FORCE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4E2A), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D65), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_REALTEK, USB_DEVICE_ID_REALTEK_READER), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_RETROUSB, USB_DEVICE_ID_RETROUSB_SNES_RETROPAD), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_RETROUSB, USB_DEVICE_ID_RETROUSB_SNES_RETROPORT), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS9200_TOUCH), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS_TS), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_1), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_2), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS2), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DELL_K12A), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DELL_K15A), HID_QUIRK_NO_INIT_REPORTS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD), HID_QUIRK_BADPAD },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_TOUCHPACK, USB_DEVICE_ID_TOUCHPACK_RTS), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8882), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8883), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD), HID_QUIRK_NOGET },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_WP5540), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_R295), HID_QUIRK_ALWAYS_POLL },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE), HID_QUIRK_MULTI_INPUT },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_GROUP_AUDIO), HID_QUIRK_NOGET },
-
-	{ 0 }
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING),
+	  .driver_data = HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING),
+	  .driver_data = HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING),
+	  .driver_data = HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A293),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M506),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_I405X),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406W),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_340),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_M508WX),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_M508X),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406XE),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_T609A),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MCS, USB_DEVICE_ID_MCS_GAMEPADBLOCK),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_MOUSE_0783),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PIXART_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE3_COVER),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_PRO_2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TOUCH_COVER_2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NATSU, USB_DEVICE_ID_NATSU_GAMEPAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NEC, USB_DEVICE_ID_NEC_USB_GAME_PAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NEXTWINDOW, USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN),
+	  .driver_data = HID_QUIRK_MULTI_INPUT},
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK),
+	  .driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1610),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1640),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PI_ENGINEERING,
+		USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL),
+	  .driver_data = HID_QUIRK_HIDINPUT_FORCE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4E2A),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D65),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_REALTEK, USB_DEVICE_ID_REALTEK_READER),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RETROUSB, USB_DEVICE_ID_RETROUSB_SNES_RETROPAD),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RETROUSB, USB_DEVICE_ID_RETROUSB_SNES_RETROPORT),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65),
+	  .driver_data = HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS9200_TOUCH),
+	  .driver_data =  HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS_TS),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_1),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_2),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS2),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DELL_K12A),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DELL_K15A),
+	  .driver_data = HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD),
+	  .driver_data = HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOUCHPACK, USB_DEVICE_ID_TOUCHPACK_RTS),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8882),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8883),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_WP5540),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_R295),
+	  .driver_data = HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS),
+	  .driver_data = HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD),
+	  .driver_data = HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE),
+	  .driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_GROUP_AUDIO),
+	  .driver_data = HID_QUIRK_NOGET },
+	{ }
 };
 
 /*

-- 
2.43.0


^ permalink raw reply related

* [PATCH 00/11] HID: storing pointers in 'hid_device_id::driver_data'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)

The <linux/mod_devicetable.h> has multiple structs that follow
the pattern of having either 'driver_data' or 'driver_info'
fields which are of the 'kernel_ulong_t' type. Then how to 
interpret that field is user defined, some users will treat
the value as an actual integer, others as a valid pointer to
dereference. 

One of instances of the above is the 'hid_device_id::driver_data'
field, for the most part it is used for setting HID quirks and
treated as an integer value for storing metadata in the subsystem
drivers. But in a few instances it is used as a valid pointer to
dereference, namely in:
- hid-tmff
- wacom
 
One of the ways to fixing this duality and improve code readability
and type-safety a bit is to use a '{kernel_ulong_t, const void *}'
union. That way the current drivers that treat 'hid_device_id::driver_data'
as an integer value for storing metadata are unaffected. The drivers
that actually store pointers in there benefit from a removed cast
(and more clear intent) at the cost of using the new 'const void *'
field instead.

With the union in place, some of the existing initializers for static
const data now need a named field for the 'driver_data' - this is
also addressed in the series as part of the pre-clean up in 
patches 1-4. 

It was found that some modules use a bit of a type-unsafe way of storing
integers in the 'void *driver_data' pointer of the 'struct hid_device'
- this required a cast during storage via 'hid_set_drvdata' and a cast
during retrieval when using 'hid_get_drvdata'. I can see why this was 
done - as we potentially save on an allocation - but really code is
more readable and better quality without resorting to this. This issue
is also addressed in this patch series in patches 5-8 as part of the
pre-clean up.

The actual implementation and post-clean up can be found in 
patches 9-11.

The change also makes the code more portable on architecture 
like CHERI [1], where a pointer is replaced with a new primitive
(called the capability) at the architecture level and is as twice as
wide as the greatest representable address, ie. for 64 bit address
space capabilities are 128 bits wide (the other 64 bits are used to
store meta-data relating to the 64 bit address). So you can not store 
valid pointers inside 'unsigned long' as effectively a different set of
instructions is being generated by the compiler based on the data-type
that was used in C (ie. capabilities have their own set of load/store
that also copy over the meta-data which are orthogonal to the load/store
instructions used for plain integers that would invalidate the meta-data).
There is slightly more detail to this, but the above is enough to
explain the motivation - the proposed changes make the code a bit
better even without considering CHERI at all - as it is more readable 
and type-safe.

The series was built and tested under QEMU (boots with relevant 
configs set to Y) on arm64.

This series is part of a larger effort led by Uwe [2]

[1] https://cheri-alliance.org/discover-cheri/
[2] https://lore.kernel.org/all/cover.1776429984.git.u.kleine-koenig@baylibre.com/ 

---
Pawel Zalewski (The Capable Hub) (11):
      HID: hid-input: use named initializer for 'hid_battery_quirks[]'
      HID: hid-quirks: use named initializer in 'hid_quirks[]'
      HID: hid-asus: use named initializer for 'asus_devices[]'
      HID: i2c-hid-dmi-quirks: use named initializer for 'i2c_hid_elan_flipped_quirks[]'
      HID: hid-belkin: clean up usage of 'driver_data'
      HID: hid-cypress: clean up usage of 'driver_data'
      HID: hid-gfrm: clean up usage of 'driver_data'
      HID: hid-ite: clean up usage of 'driver_data'
      HID: mod_devicetable: 'hid_device_id::driver_data' add union
      HID: hid-tmff: use 'hid_device_id::driver_data_ptr'
      HID: wacom: use 'hid_device_id::driver_data_ptr'

 drivers/hid/hid-asus.c                   |  46 ++-
 drivers/hid/hid-belkin.c                 |   5 +-
 drivers/hid/hid-cypress.c                |  32 +-
 drivers/hid/hid-gfrm.c                   |   8 +-
 drivers/hid/hid-input.c                  |  38 +-
 drivers/hid/hid-ite.c                    |   9 +-
 drivers/hid/hid-quirks.c                 | 575 ++++++++++++++++++++-----------
 drivers/hid/hid-tmff.c                   |  22 +-
 drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c |   2 +-
 drivers/hid/wacom_sys.c                  |  14 +-
 drivers/hid/wacom_wac.c                  |  10 +-
 include/linux/mod_devicetable.h          |   5 +-
 12 files changed, 496 insertions(+), 270 deletions(-)
---
base-commit: 25ccf4586bead3fe3cf2c57ff0480f31a0e335ad
change-id: 20260427-mod-devicetable-hid_device_id-7f30d877387c

Best regards,
--  
Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>


^ permalink raw reply

* [PATCH 01/11] HID: hid-input: use named initializer for 'hid_battery_quirks[]'
From: Pawel Zalewski (The Capable Hub) @ 2026-05-18 16:06 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Ping Cheng, Jason Gerecke
  Cc: linux-kernel, linux-input, Christian A. Ehrhardt,
	Christian A. Ehrhardt, Pawel Zalewski (The Capable Hub)
In-Reply-To: <20260518-mod-devicetable-hid_device_id-v1-0-a08e3989c283@thegoodpenguin.co.uk>

This makes the code a little bit more readable and consistent
with other drivers in the HID subsystem that assign the
'driver_data' field in the 'hid_device_id' struct explicitly.

While touching the 'hid_battery_quirks[]', align the list
terminator entry to use a space between the curly braces for
better matching the layout of the other entries.

It was confirmed that hid-input.o pre/post the patch are equal
using diffscope.

Signed-off-by: Pawel Zalewski (The Capable Hub) <pzalewski@thegoodpenguin.co.uk>
---
 drivers/hid/hid-input.c | 38 ++++++++++++++++++++------------------
 1 file changed, 20 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index d73cfa2e73d3..50ee03508806 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -359,49 +359,51 @@ static enum power_supply_property hidinput_battery_props[] = {
 static const struct hid_device_id hid_battery_quirks[] = {
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
-	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	  .driver_data = HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
-	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	  .driver_data = HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI),
-	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	  .driver_data = HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
-			       USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
-	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
+	  .driver_data = HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
-	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	  .driver_data = HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_MAGICTRACKPAD),
-	  HID_BATTERY_QUIRK_IGNORE },
+	  .driver_data = HID_BATTERY_QUIRK_IGNORE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
 		USB_DEVICE_ID_ELECOM_BM084),
-	  HID_BATTERY_QUIRK_IGNORE },
+	  .driver_data = HID_BATTERY_QUIRK_IGNORE },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL,
 		USB_DEVICE_ID_SYMBOL_SCANNER_3),
-	  HID_BATTERY_QUIRK_IGNORE },
+	  .driver_data = HID_BATTERY_QUIRK_IGNORE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
 		USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD),
-	  HID_BATTERY_QUIRK_IGNORE },
+	  .driver_data = HID_BATTERY_QUIRK_IGNORE },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
 		USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD),
-	  HID_BATTERY_QUIRK_IGNORE },
+	  .driver_data = HID_BATTERY_QUIRK_IGNORE },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L),
-	  HID_BATTERY_QUIRK_AVOID_QUERY },
+	  .driver_data = HID_BATTERY_QUIRK_AVOID_QUERY },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW),
-	  HID_BATTERY_QUIRK_AVOID_QUERY },
+	  .driver_data = HID_BATTERY_QUIRK_AVOID_QUERY },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW),
-	  HID_BATTERY_QUIRK_AVOID_QUERY },
+	  .driver_data = HID_BATTERY_QUIRK_AVOID_QUERY },
 	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM),
-	  HID_BATTERY_QUIRK_AVOID_QUERY },
+	  .driver_data = HID_BATTERY_QUIRK_AVOID_QUERY },
 	/*
 	 * Elan HID touchscreens seem to all report a non present battery,
 	 * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices.
 	 */
-	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC },
-	{}
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID),
+	  .driver_data = HID_BATTERY_QUIRK_DYNAMIC },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID),
+	  .driver_data = HID_BATTERY_QUIRK_DYNAMIC },
+	{ }
 };
 
 static unsigned find_battery_quirk(struct hid_device *hdev)

-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 4/6] tools/build: Allow versioning LLVM readelf
From: Ian Rogers @ 2026-05-18 16:05 UTC (permalink / raw)
  To: James Clark
  Cc: Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
	Jiri Kosina, Benjamin Tissoires, Shuah Khan, Peter Zijlstra,
	Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Alexander Shishkin, Jiri Olsa, Adrian Hunter, Andrii Nakryiko,
	Eduard Zingerman, Alexei Starovoitov, Daniel Borkmann,
	Martin KaFai Lau, Kumar Kartikeya Dwivedi, Song Liu,
	Yonghong Song, ihor.solodrai, linux-kernel, llvm, linux-input,
	linux-kselftest, bpf, linux-perf-users
In-Reply-To: <20260518-james-perf-llvm-version-v2-4-f12cc4f031d8@linaro.org>

On Mon, May 18, 2026 at 2:04 AM James Clark <james.clark@linaro.org> wrote:
>
> Documentation/kbuild/llvm.rst mentions that readelf is included in the
> LLVM toolchain, but it's not currently included in this block.
>
> Add it so that LLVM=... options also apply to readelf. Users in tools/
> were Perf which was hardcoding it, and another was the BPF makefile.
> Both already include Makefile.include so convert them to use the new
> variable.
>
> It also didn't have the cross compile prefix, so either readelf didn't
> mind opening cross binaries, or it wasn't working for cross builds.
>
> Signed-off-by: James Clark <james.clark@linaro.org>

For the tools/perf/Makefile.perf part of this change.

Reviewed-by: Ian Rogers <irogers@google.com>

Thanks for the cleanup!
Ian

> ---
>  tools/lib/bpf/Makefile         | 8 ++++----
>  tools/perf/Makefile.perf       | 1 -
>  tools/scripts/Makefile.include | 2 ++
>  3 files changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/tools/lib/bpf/Makefile b/tools/lib/bpf/Makefile
> index 168140f8e646..180dca9c57c8 100644
> --- a/tools/lib/bpf/Makefile
> +++ b/tools/lib/bpf/Makefile
> @@ -114,12 +114,12 @@ PC_FILE           := $(addprefix $(OUTPUT),$(PC_FILE))
>
>  TAGS_PROG := $(if $(shell which etags 2>/dev/null),etags,ctags)
>
> -GLOBAL_SYM_COUNT = $(shell readelf -s --wide $(BPF_IN_SHARED) | \
> +GLOBAL_SYM_COUNT = $(shell $(READELF) -s --wide $(BPF_IN_SHARED) | \
>                            cut -d "@" -f1 | sed 's/_v[0-9]_[0-9]_[0-9].*//' | \
>                            sed 's/\[.*\]//' | \
>                            awk '/GLOBAL/ && /DEFAULT/ && !/UND|ABS/ {print $$NF}' | \
>                            sort -u | wc -l)
> -VERSIONED_SYM_COUNT = $(shell readelf --dyn-syms --wide $(OUTPUT)libbpf.so | \
> +VERSIONED_SYM_COUNT = $(shell $(READELF) --dyn-syms --wide $(OUTPUT)libbpf.so | \
>                               sed 's/\[.*\]//' | \
>                               awk '/GLOBAL/ && /DEFAULT/ && !/UND|ABS/ {print $$NF}' | \
>                               grep -Eo '[^ ]+@LIBBPF_' | cut -d@ -f1 | sort -u | wc -l)
> @@ -182,12 +182,12 @@ check_abi: $(OUTPUT)libbpf.so $(VERSION_SCRIPT)
>                      "versioned symbols in $^ ($(VERSIONED_SYM_COUNT))." \
>                      "Please make sure all LIBBPF_API symbols are"       \
>                      "versioned in $(VERSION_SCRIPT)." >&2;              \
> -               readelf -s --wide $(BPF_IN_SHARED) |                     \
> +               $(READELF) -s --wide $(BPF_IN_SHARED) |                  \
>                     cut -d "@" -f1 | sed 's/_v[0-9]_[0-9]_[0-9].*//' |   \
>                     sed 's/\[.*\]//' |                                   \
>                     awk '/GLOBAL/ && /DEFAULT/ && !/UND/ {print $$NF}'|  \
>                     sort -u > $(OUTPUT)libbpf_global_syms.tmp;           \
> -               readelf --dyn-syms --wide $(OUTPUT)libbpf.so |           \
> +               $(READELF) --dyn-syms --wide $(OUTPUT)libbpf.so |                \
>                     sed 's/\[.*\]//' |                                   \
>                     awk '/GLOBAL/ && /DEFAULT/ && !/UND|ABS/ {print $$NF}'|  \
>                     grep -Eo '[^ ]+@LIBBPF_' | cut -d@ -f1 |             \
> diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
> index 0aba14f22a06..63276bf55856 100644
> --- a/tools/perf/Makefile.perf
> +++ b/tools/perf/Makefile.perf
> @@ -215,7 +215,6 @@ FLEX    ?= flex
>  BISON   ?= bison
>  STRIP   = strip
>  AWK     = awk
> -READELF ?= readelf
>
>  # include Makefile.config by default and rule out
>  # non-config cases
> diff --git a/tools/scripts/Makefile.include b/tools/scripts/Makefile.include
> index e81e5b479c56..380ad84ac51e 100644
> --- a/tools/scripts/Makefile.include
> +++ b/tools/scripts/Makefile.include
> @@ -73,6 +73,7 @@ ifneq ($(LLVM),)
>    $(call allow-override,LLC,$(LLVM_PREFIX)llc$(LLVM_SUFFIX))
>    $(call allow-override,LLVM_CONFIG,$(LLVM_PREFIX)llvm-config$(LLVM_SUFFIX))
>    $(call allow-override,LLVM_OBJCOPY,$(LLVM_PREFIX)llvm-objcopy$(LLVM_SUFFIX))
> +  $(call allow-override,READELF,$(LLVM_PREFIX)llvm-readelf$(LLVM_SUFFIX))
>  else
>    # Allow setting various cross-compile vars or setting CROSS_COMPILE as a prefix.
>    $(call allow-override,CC,$(CROSS_COMPILE)gcc)
> @@ -80,6 +81,7 @@ else
>    $(call allow-override,LD,$(CROSS_COMPILE)ld)
>    $(call allow-override,CXX,$(CROSS_COMPILE)g++)
>    $(call allow-override,STRIP,$(CROSS_COMPILE)strip)
> +  $(call allow-override,READELF,$(CROSS_COMPILE)readelf)
>
>    # Host versions aren't prefixed
>    $(call allow-override,HOSTAR,ar)
>
> --
> 2.34.1
>

^ permalink raw reply

* [PATCH] HID: lg-g15: Add support for the Logitech G710/G710+ gaming keyboards
From: Xavier Bestel @ 2026-05-18 15:35 UTC (permalink / raw)
  To: Hans de Goede, Jiri Kosina, Benjamin Tissoires
  Cc: Xavier Bestel, linux-input, linux-kernel

Add support for the Logitech G710 and G710+ (USB ID 046d:c24d) gaming
keyboards to the hid-lg-g15 driver.

These keyboards have 6 G-keys (G1-G6), M-keys (M1-M3, MR), a game mode
toggle, and two independent backlights: one for the main keyboard and one
for the WASD keys, each with a physical button to cycle brightness through
5 levels (0-4).

Key implementation details:

 - G-keys and M-keys are reported via HID input report 0x03 (4 bytes)
   using KEY_MACRO1-6, KEY_MACRO_PRESET1-3 and KEY_MACRO_RECORD_START.

 - The WASD backlight LED is registered as
   "g15::kbd_zoned_backlight-wasd" rather than with a
   "::kbd_backlight" suffix, because UPower currently only supports a
   single kbd_backlight LED per device. This follows the nomenclature
   from Documentation/leds/leds-class.rst

 - The G710+ firmware GET_REPORT for the backlight feature (0x08)
   always returns the power-on default values, ignoring any changes
   made via SET_REPORT. To work around this, the backlight brightness
   is tracked in the driver cache and brightness_get returns the
   cached value. M-key and game mode LEDs read back correctly.

 - The physical brightness cycle buttons are handled following the
   same pattern as the G15/G15v2: no key events are sent, instead the
   driver cycles the cached brightness and calls
   led_classdev_notify_brightness_hw_changed() from a work function,
   which allows GNOME to show the brightness OSD.

 - The game mode toggle is handled entirely by the firmware (it
   disables the Super key and lights an indicator LED). The driver
   exposes a read-only LED "g15::gamemode" with the
   LED_BRIGHT_HW_CHANGED flag, and notifies userspace of state
   changes via led_classdev_notify_brightness_hw_changed() by reading
   back the actual firmware state on each toggle.

 - Both brightness LEDs are registered with the LED_BRIGHT_HW_CHANGED
   flag to enable the brightness_hw_changed sysfs attribute.

 - HID_QUIRK_NOGET is set because the keyboard has buggy GET_REPORT
   handling that causes timeouts on some feature reports.

 - The G-keys feature report (0x09) uses 2 bytes per key rather than
   1 as on the G510, so the report size calculation is adjusted
   accordingly.

 - Also fix a pre-existing comment typo: "f000.0000" -> "ff00.0000"
   for the application report ID.

 - The loop bounds in lg_g15_led_set() and lg_g510_led_set() are
   tightened from "< LG_G15_LED_MAX" to "<= LG_G15_MACRO_RECORD" to
   avoid iterating over the new LG_G15_GAMEMODE enum value which does
   not apply to those keyboards.

Signed-off-by: Xavier Bestel <xav@bes.tel>
---
 drivers/hid/hid-ids.h    |   1 +
 drivers/hid/hid-lg-g15.c | 383 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 377 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index afcee13bad61..7c0f930bd014 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -904,6 +904,7 @@
 #define USB_DEVICE_ID_LOGITECH_G15_V2_LCD	0xc227
 #define USB_DEVICE_ID_LOGITECH_G510		0xc22d
 #define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO	0xc22e
+#define USB_DEVICE_ID_LOGITECH_G710		0xc24d
 #define USB_DEVICE_ID_LOGITECH_G29_WHEEL	0xc24f
 #define USB_DEVICE_ID_LOGITECH_G920_WHEEL	0xc262
 #define USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL	0xc26e
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
index 1a88bc44ada4..95e89e9aeca6 100644
--- a/drivers/hid/hid-lg-g15.c
+++ b/drivers/hid/hid-lg-g15.c
@@ -29,6 +29,11 @@
 #define LG_G510_INPUT_MACRO_KEYS	0x03
 #define LG_G510_INPUT_KBD_BACKLIGHT	0x04
 
+#define LG_G710_FEATURE_GAMEMODE        0x05
+#define LG_G710_FEATURE_M_KEYS_LEDS     0x06
+#define LG_G710_FEATURE_BACKLIGHT       0x08
+#define LG_G710_FEATURE_EXTRA_KEYS      0x09
+
 #define LG_G13_INPUT_REPORT		0x01
 #define LG_G13_FEATURE_M_KEYS_LEDS	0x05
 #define LG_G13_FEATURE_BACKLIGHT_RGB	0x07
@@ -48,6 +53,7 @@ enum lg_g15_model {
 	LG_G15_V2,
 	LG_G510,
 	LG_G510_USB_AUDIO,
+	LG_G710,
 	LG_Z10,
 };
 
@@ -59,6 +65,7 @@ enum lg_g15_led_type {
 	LG_G15_MACRO_PRESET2,
 	LG_G15_MACRO_PRESET3,
 	LG_G15_MACRO_RECORD,
+	LG_G15_GAMEMODE,
 	LG_G15_LED_MAX
 };
 
@@ -92,6 +99,7 @@ struct lg_g15_data {
 	struct lg_g15_led leds[LG_G15_LED_MAX];
 	bool game_mode_enabled;
 	bool backlight_disabled;	/* true == HW backlight toggled *OFF* */
+	unsigned long brightness_changed;	/* bitmask of LEDs hw-cycled */
 };
 
 /********* G13 LED functions ***********/
@@ -334,7 +342,7 @@ static int lg_g15_led_set(struct led_classdev *led_cdev,
 		g15->transfer_buf[1] = g15_led->led + 1;
 		g15->transfer_buf[2] = brightness << (g15_led->led * 4);
 	} else {
-		for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; i++) {
+		for (i = LG_G15_MACRO_PRESET1; i <= LG_G15_MACRO_RECORD; i++) {
 			if (i == g15_led->led)
 				val = brightness;
 			else
@@ -567,7 +575,7 @@ static int lg_g510_mkey_led_set(struct led_classdev *led_cdev,
 
 	mutex_lock(&g15->mutex);
 
-	for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; i++) {
+	for (i = LG_G15_MACRO_PRESET1; i <= LG_G15_MACRO_RECORD; i++) {
 		if (i == g15_led->led)
 			val = brightness;
 		else
@@ -597,6 +605,234 @@ static int lg_g510_mkey_led_set(struct led_classdev *led_cdev,
 	return ret;
 }
 
+/******** G710 LED functions ********/
+
+static int lg_g710_update_game_led_brightness(struct lg_g15_data *g15)
+{
+	int ret;
+
+	ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_GAMEMODE,
+				 g15->transfer_buf, 8,
+				 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret != 8) {
+		hid_err(g15->hdev, "Error getting gamemode LED brightness: %d\n", ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+
+	g15->leds[LG_G15_GAMEMODE].brightness =
+		!!g15->transfer_buf[1];
+
+	return 0;
+}
+
+static int lg_g710_update_mkey_led_brightness(struct lg_g15_data *g15)
+{
+	int ret;
+
+	ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_M_KEYS_LEDS,
+				 g15->transfer_buf, 2,
+				 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret != 2) {
+		hid_err(g15->hdev, "Error getting macro LED brightness: %d\n", ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+
+	g15->leds[LG_G15_MACRO_PRESET1].brightness =
+		!!(g15->transfer_buf[1] & 0x10);
+	g15->leds[LG_G15_MACRO_PRESET2].brightness =
+		!!(g15->transfer_buf[1] & 0x20);
+	g15->leds[LG_G15_MACRO_PRESET3].brightness =
+		!!(g15->transfer_buf[1] & 0x40);
+	g15->leds[LG_G15_MACRO_RECORD].brightness =
+		!!(g15->transfer_buf[1] & 0x80);
+
+	return 0;
+}
+
+static int lg_g710_update_kbd_led_brightness(struct lg_g15_data *g15)
+{
+	int ret;
+
+	ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_BACKLIGHT,
+				 g15->transfer_buf, 4,
+				 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret != 4) {
+		hid_err(g15->hdev, "Error getting LED brightness: %d\n", ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+
+	g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = 4 - min_t(u8, g15->transfer_buf[2], 4);
+	g15->leds[LG_G15_LCD_BRIGHTNESS].brightness = 4 - min_t(u8, g15->transfer_buf[1], 4);
+
+	return 0;
+}
+
+static enum led_brightness lg_g710_led_get(struct led_classdev *led_cdev)
+{
+	struct lg_g15_led *g15_led =
+		container_of(led_cdev, struct lg_g15_led, cdev);
+	struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
+	enum led_brightness brightness;
+
+	mutex_lock(&g15->mutex);
+	/*
+	 * The G710+ firmware's GET_REPORT for the backlight always returns
+	 * the power-on default values, ignoring any changes made via
+	 * SET_REPORT. Use the cached brightness which is kept in sync by
+	 * the _set callbacks. M-key and gamemode LEDs read back correctly.
+	 */
+	if (g15_led->led >= LG_G15_BRIGHTNESS_MAX && g15_led->led < LG_G15_GAMEMODE)
+		lg_g710_update_mkey_led_brightness(g15);
+	else if (g15_led->led >= LG_G15_GAMEMODE)
+		lg_g710_update_game_led_brightness(g15);
+	brightness = g15->leds[g15_led->led].brightness;
+	mutex_unlock(&g15->mutex);
+
+	return brightness;
+}
+
+static int lg_g710_mkey_led_set(struct led_classdev *led_cdev,
+			  enum led_brightness brightness)
+{
+	struct lg_g15_led *g15_led =
+		container_of(led_cdev, struct lg_g15_led, cdev);
+	struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
+	u8 val, mask = 0;
+	int i, ret;
+
+	/* Ignore LED off on unregister / keyboard unplug */
+	if (led_cdev->flags & LED_UNREGISTERING)
+		return 0;
+
+	mutex_lock(&g15->mutex);
+
+	g15->transfer_buf[0] = LG_G710_FEATURE_M_KEYS_LEDS;
+
+	for (i = LG_G15_MACRO_PRESET1; i <= LG_G15_MACRO_RECORD; i++) {
+		if (i == g15_led->led)
+			val = brightness;
+		else
+			val = g15->leds[i].brightness;
+
+		if (val)
+			mask |= 1 << (i - LG_G15_MACRO_PRESET1 + 4);
+	}
+
+	g15->transfer_buf[1] = mask;
+
+	ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_M_KEYS_LEDS,
+				 g15->transfer_buf, 2,
+				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	if (ret == 2) {
+		/* Success */
+		g15_led->brightness = brightness;
+		ret = 0;
+	} else {
+		if (ret != -EPIPE)
+			hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
+		/* -EPIPE is transient (USB stall), cache is unchanged, retry next time */
+		ret = 0;
+	}
+
+	mutex_unlock(&g15->mutex);
+
+	return ret;
+}
+
+static int lg_g710_kbd_led_set(struct led_classdev *led_cdev,
+			  enum led_brightness brightness)
+{
+	struct lg_g15_led *g15_led =
+		container_of(led_cdev, struct lg_g15_led, cdev);
+	struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
+	int ret;
+
+	/* Ignore LED off on unregister / keyboard unplug */
+	if (led_cdev->flags & LED_UNREGISTERING)
+		return 0;
+
+	mutex_lock(&g15->mutex);
+
+	g15->transfer_buf[0] = LG_G710_FEATURE_BACKLIGHT;
+	g15->transfer_buf[3] = 0;
+
+	if (g15_led->led == LG_G15_KBD_BRIGHTNESS) {
+		g15->transfer_buf[1] = 4 - g15->leds[LG_G15_LCD_BRIGHTNESS].brightness;
+		g15->transfer_buf[2] = 4 - brightness;
+	} else {
+		g15->transfer_buf[1] = 4 - brightness;
+		g15->transfer_buf[2] = 4 - g15->leds[LG_G15_KBD_BRIGHTNESS].brightness;
+	}
+
+	ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_BACKLIGHT,
+				 g15->transfer_buf, 4,
+				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	if (ret == 4) {
+		/* Success */
+		g15_led->brightness = brightness;
+		ret = 0;
+	} else {
+		if (ret != -EPIPE)
+			hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
+		/* -EPIPE is transient (USB stall), cache is unchanged, retry next time */
+		ret = 0;
+	}
+
+	mutex_unlock(&g15->mutex);
+
+	return ret;
+}
+
+/*
+ * The G710+ has separate physical keys for cycling the main keyboard backlight
+ * and the WASD backlight. The firmware handles the actual brightness change
+ * internally, but GET_REPORT always returns the power-on defaults regardless
+ * of any changes. So we must track the brightness in our cache and cycle it
+ * ourselves when a hardware brightness key press is detected.
+ *
+ * The firmware cycles brightness DOWN: 4 → 3 → 2 → 1 → 0 → 4 (in wire
+ * format where 0 = brightest, 4 = off). In user-facing terms (inverted):
+ * 4 → 3 → 2 → 1 → 0 → 4.
+ *
+ * The game mode toggle is also handled here: the firmware toggles game mode
+ * internally and updates the LED, so we read back the actual state via
+ * GET_REPORT and notify userspace of the change.
+ */
+static void lg_g710_leds_changed_work(struct work_struct *work)
+{
+	struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work);
+	bool changed[LG_G15_LED_MAX] = {};
+	int i;
+
+	mutex_lock(&g15->mutex);
+	for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) {
+		if (!test_and_clear_bit(i, &g15->brightness_changed))
+			continue;
+
+		changed[i] = true;
+
+		if (g15->leds[i].brightness > 0)
+			g15->leds[i].brightness--;
+		else
+			g15->leds[i].brightness =
+				g15->leds[i].cdev.max_brightness;
+	}
+
+	if (test_and_clear_bit(LG_G15_GAMEMODE, &g15->brightness_changed)) {
+		changed[LG_G15_GAMEMODE] = true;
+		lg_g710_update_game_led_brightness(g15);
+	}
+
+	for (i = 0; i < LG_G15_LED_MAX; i++) {
+		if (!changed[i])
+			continue;
+
+		led_classdev_notify_brightness_hw_changed(&g15->leds[i].cdev,
+						  g15->leds[i].brightness);
+	}
+	mutex_unlock(&g15->mutex);
+}
+
 /******** Generic LED functions ********/
 static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
 {
@@ -619,6 +855,16 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
 			return ret;
 
 		return lg_g510_update_mkey_led_brightness(g15);
+	case LG_G710:
+		ret = lg_g710_update_game_led_brightness(g15);
+		if (ret)
+			return ret;
+
+		ret = lg_g710_update_mkey_led_brightness(g15);
+		if (ret)
+			return ret;
+
+		return lg_g710_update_kbd_led_brightness(g15);
 	case LG_Z10:
 		/*
 		 * Getting the LCD backlight brightness is not supported.
@@ -890,6 +1136,71 @@ static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data)
 	return 0;
 }
 
+static int lg_g710_event(struct lg_g15_data *g15, u8 *data, int size)
+{
+	/*
+	 * Bits 0-5:  G1-G6 keys
+	 * Bits 6-8:  M1-M3 keys
+	 * Bit 9:     MR key
+	 * Bit 10:    WASD backlight cycle (handled as hw brightness change)
+	 * Bit 11:    Kbd backlight cycle (handled as hw brightness change)
+	 * Bit 12:    Game mode toggle (LED state change, handled by firmware)
+	 */
+	static const u16 keymap[] = {
+		KEY_MACRO1,
+		KEY_MACRO2,
+		KEY_MACRO3,
+		KEY_MACRO4,
+		KEY_MACRO5,
+		KEY_MACRO6,
+		KEY_MACRO_PRESET1,
+		KEY_MACRO_PRESET2,
+		KEY_MACRO_PRESET3,
+		KEY_MACRO_RECORD_START,
+		0,	/* WASD illumination cycle - not a key event */
+		0,	/* Kbd illumination cycle - not a key event */
+		0,	/* Game mode toggle */
+	};
+	u16 pressed_keys;
+	int i;
+
+	if (size != 4 || data[0] != 3)
+		return 1;
+
+	pressed_keys = (data[1] & 0x3f) | ((data[2] & 0xf0) << 2) |
+		       ((data[3] & 0x7) << 10);
+
+	for (i = 0; i < ARRAY_SIZE(keymap); i++) {
+		if (keymap[i])
+			input_report_key(g15->input, keymap[i],
+					pressed_keys & BIT(i));
+	}
+	input_sync(g15->input);
+
+	/*
+	 * Detect brightness key presses and schedule the work function
+	 * to cycle cached brightness and notify userspace.
+	 * Bit 10 = WASD backlight (maps to LG_G15_LCD_BRIGHTNESS slot).
+	 * Bit 11 = Kbd backlight (maps to LG_G15_KBD_BRIGHTNESS slot).
+	 */
+	if (pressed_keys & BIT(10)) {
+		set_bit(LG_G15_LCD_BRIGHTNESS, &g15->brightness_changed);
+		schedule_work(&g15->work);
+	}
+	if (pressed_keys & BIT(11)) {
+		set_bit(LG_G15_KBD_BRIGHTNESS, &g15->brightness_changed);
+		schedule_work(&g15->work);
+	}
+
+	/* Game mode toggle — bit 12 is a state bit, trigger on any change */
+	if (pressed_keys & BIT(12)) {
+		set_bit(LG_G15_GAMEMODE, &g15->brightness_changed);
+		schedule_work(&g15->work);
+	}
+
+	return 0;
+}
+
 static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
 			    u8 *data, int size)
 {
@@ -924,6 +1235,10 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
 		if (data[0] == LG_G510_INPUT_KBD_BACKLIGHT && size == 2)
 			return lg_g510_leds_event(g15, data);
 		break;
+	case LG_G710:
+		if (data[0] == 0x03 && size == 4)
+			return lg_g710_event(g15, data, size);
+		break;
 	}
 
 	return 0;
@@ -1055,6 +1370,37 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
 			ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
 		}
 		break;
+	case LG_G710:
+		switch (i) {
+		case LG_G15_LCD_BRIGHTNESS:
+			/*
+			 * The G710+ does not have a separate LCD brightness,
+			 * but it does have a separate brightness for WASD keys.
+			 * Do not use the ::kbd_backlight suffix here, UPower
+			 * only supports one kbd_backlight LED per device.
+			 */
+			g15->leds[i].cdev.name = "g15::kbd_zoned_backlight-wasd";
+			fallthrough;
+		case LG_G15_KBD_BRIGHTNESS:
+			g15->leds[i].cdev.brightness_set_blocking =
+				lg_g710_kbd_led_set;
+			g15->leds[i].cdev.brightness_get =
+				lg_g710_led_get;
+			g15->leds[i].cdev.max_brightness = 4;
+			g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED;
+			break;
+		default:
+			if (i != LG_G15_GAMEMODE)
+				g15->leds[i].cdev.brightness_set_blocking =
+					lg_g710_mkey_led_set;
+			g15->leds[i].cdev.brightness_get =
+				lg_g710_led_get;
+			g15->leds[i].cdev.max_brightness = 1;
+			if (i == LG_G15_GAMEMODE)
+				g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED;
+		}
+		ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
+		break;
 	}
 
 	return ret;
@@ -1079,10 +1425,14 @@ static void lg_g15_init_input_dev_core(struct hid_device *hdev, struct input_dev
 static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input,
 				  const char *name)
 {
+	struct lg_g15_data *g15 = hid_get_drvdata(hdev);
 	int i;
 
 	lg_g15_init_input_dev_core(hdev, input, name);
 
+	if (g15->model == LG_G710)
+		return;
+
 	/* Keys below the LCD, intended for controlling a menu on the LCD */
 	for (i = 0; i < 5; i++)
 		input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i);
@@ -1137,8 +1487,8 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		return ret;
 
 	/*
-	 * Some models have multiple interfaces, we want the interface with
-	 * the f000.0000 application input report.
+	 * Some models have multiple interfaces, we want the interface
+	 * with the ff00.0000 application input report.
 	 */
 	rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
 	list_for_each_entry(rep, &rep_enum->report_list, list) {
@@ -1212,6 +1562,13 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	case LG_Z10:
 		connect_mask = HID_CONNECT_HIDRAW;
 		break;
+	case LG_G710:
+		INIT_WORK(&g15->work, lg_g710_leds_changed_work);
+		hdev->quirks |= HID_QUIRK_NOGET;
+		connect_mask = HID_CONNECT_DEFAULT;
+		gkeys_settings_feature_report = LG_G710_FEATURE_EXTRA_KEYS;
+		gkeys = 6;
+		break;
 	}
 
 	ret = hid_hw_start(hdev, connect_mask);
@@ -1234,11 +1591,13 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	}
 
 	if (gkeys_settings_feature_report) {
+		int report_size = (g15->model == LG_G710) ? gkeys * 2 : gkeys;
+
 		g15->transfer_buf[0] = gkeys_settings_feature_report;
-		memset(g15->transfer_buf + 1, 0, gkeys);
+		memset(g15->transfer_buf + 1, 0, report_size);
 		ret = hid_hw_raw_request(g15->hdev,
 				gkeys_settings_feature_report,
-				g15->transfer_buf, gkeys + 1,
+				g15->transfer_buf, report_size + 1,
 				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
 	}
 
@@ -1327,12 +1686,18 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		goto error_hw_stop;
 
 	/* Register LED devices */
-	for (i = 0; i < LG_G15_LED_MAX; i++) {
+	for (i = 0; i <= LG_G15_MACRO_RECORD; i++) {
 		ret = lg_g15_register_led(g15, i, led_names[i]);
 		if (ret)
 			goto error_hw_stop;
 	}
 
+	if (g15->model == LG_G710) {
+		ret = lg_g15_register_led(g15, LG_G15_GAMEMODE, "g15::gamemode");
+		if (ret)
+			goto error_hw_stop;
+	}
+
 	return 0;
 
 error_hw_stop:
@@ -1366,6 +1731,10 @@ static const struct hid_device_id lg_g15_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
 			 USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO),
 		.driver_data = LG_G510_USB_AUDIO },
+	/* G710 or G710+ */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+			 USB_DEVICE_ID_LOGITECH_G710),
+		.driver_data = LG_G710 },
 	/* Z-10 speakers */
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
 			 USB_DEVICE_ID_LOGITECH_Z_10_SPK),
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH] HID: lg-g15: Add support for the Logitech G710/G710+ gaming keyboards
From: Xavier Bestel @ 2026-05-18 15:33 UTC (permalink / raw)
  To: Hans de Goede, Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel
In-Reply-To: <3a03ac01-3b82-4ad6-bd35-f91ce738847c@kernel.org>

Le vendredi 15 mai 2026 à 09:52 +0200, Hans de Goede a écrit :
> Hi,
> 
> On 6-Apr-26 20:50, Hans de Goede wrote:
> > Hi Xavier,
> > 
> > Thank you for your patch, it is always nice to have people helping
> > with improving Linux support for devices like this.
> > 
> > On 2-Apr-26 09:52, Xavier Bestel wrote:
> > > Add support for the Logitech G710 and G710+ (USB ID 046d:c24d) gaming
> > > keyboards to the hid-lg-g15 driver.
> > > 
> > > These keyboards have 6 G-keys (G1-G6), M-keys (M1-M3, MR), a game mode
> > > toggle, and two independent backlights: one for the main keyboard and one
> > > for the WASD keys, each with a physical button to cycle brightness through
> > > 5 levels (0-4).
> > > 
> > > Key implementation details:
> > > 
> > >  - G-keys and M-keys are reported via HID input report 0x03 (4 bytes)
> > >    using KEY_MACRO1-6, KEY_MACRO_PRESET1-3 and KEY_MACRO_RECORD_START.
> > > 
> > >  - The WASD backlight LED is registered as
> > >    "g15::kbd_zoned_backlight-wasd" rather than with a
> > >    "::kbd_backlight" suffix, because UPower currently only supports a
> > >    single kbd_backlight LED per device. This follows the nomenclature
> > >    from Documentation/leds/leds-class.rst
> > 
> > Actually that nomenclature is something which was suggested in the past
> > but has never been agreed upon.
> > 
> > Since the discussion about how to name the LED class devices for
> > multi-zone keyboard backlights keeps coming up I've submitted a patch
> > now to document how these should be named:
> > 
> > https://lore.kernel.org/linux-leds/20260406174638.320135-1-johannes.goede@oss.qualcomm.com/
> > 
> > Since the new userspace API for this needs to be agreed upon first, that
> > patch should be accepted upstream first, before we can move forward with
> > the G710 support from this patch.
> 
> My v2 patch with the LED naming scheme has been accepted into the
> leds maintainer git tree now, so this is no longer a blocker for this
> patch:
> 
> https://lore.kernel.org/linux-leds/20260504145434.12746-1-johannes.goede@oss.qualcomm.com/
> 

Ok, I'm sending a new version of the patch.

Thanks,

    Xavier

^ permalink raw reply

* Re: [PATCH v3 0/9] iio: introduce devm_ API for hid sensro setup and cleanup
From: Jonathan Cameron @ 2026-05-18 13:51 UTC (permalink / raw)
  To: Sanjay Chitroda
  Cc: Zhang, Lixu, jikos@kernel.org,
	srinivas.pandruvada@linux.intel.com, Lechner, David,
	nuno.sa@analog.com, andy@kernel.org, sakari.ailus@linux.intel.com,
	linux-input@vger.kernel.org, linux-iio@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <agoi55wCo//eY1YQ@hu-ckantibh-hyd.qualcomm.com>

On Mon, 18 May 2026 01:49:51 +0530
Sanjay Chitroda <sanjayembeddedse@gmail.com> wrote:

> On Thu, May 14, 2026 at 02:47:52AM +0000, Zhang, Lixu wrote:
> > Tested-by: Zhang Lixu <lixu.zhang@intel.com>  
> 
> Hi Zhang,
> 
> Thanks for the testing and validaiton.
> I hope you validated the complete series, including the unbind flow for the
> drivers using devm API.
> 
> Hi Jonathan,
> 
> How should we take this series forward? should I extend the series to
> cover remaining HID IIO drivers for this devm API, or this series can
> applied to IIO tree first and sent followup patches on top of that?

There is just enough minor feedback on various patches that it needs
a v4 I think, or response to those bits of review feedback to argue
against the requested changes.  

I'd like to see the new API applied everywhere unless there is a reason
it's not suitable.  Key aim being that the last patch should remove
the now unused non devm functions or maybe make them private if
they are still useful from a code structure point of view.

We may be waiting on testing though for some of them so make sure
it is clear what Zhang has tested (obviously not the stuff you haven't
sent yet :)

Jonathan




> 
> Thanks, Sanjay
> 
> >   
> > >-----Original Message-----
> > >From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
> > >Sent: Saturday, May 9, 2026 6:11 PM
> > >To: jikos@kernel.org; jic23@kernel.org; srinivas.pandruvada@linux.intel.com
> > >Cc: Lechner, David <dlechner@baylibre.com>; nuno.sa@analog.com;
> > >andy@kernel.org; sanjayembeddedse@gmail.com;
> > >sakari.ailus@linux.intel.com; linux-input@vger.kernel.org; linux-
> > >iio@vger.kernel.org; linux-kernel@vger.kernel.org
> > >Subject: [PATCH v3 0/9] iio: introduce devm_ API for hid sensro setup and
> > >cleanup
> > >
> > >From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
> > >
> > >Key highlights:
> > >- Prepare change as pre-requisite for devm conversion for HID IIO
> > >  drivers by removing redundant argument
> > >- Add devm API to setup trigger and clenaup resource using
> > >  devm_add_action_or_reset()
> > >- few cleanup and prepratory changes before updating driver for devm_
> > >- few sample driver update using devm conversion to auto release resource
> > >
> > >changes in v3:
> > >- Added cleanup and prepratory changes before adding devm_ API
> > >  conversion based on self review: 0002, 0004, 0006, 0007 and 0008
> > >- Address andy's review comment on commit message and coding style
> > >- v2 series -> https://lore.kernel.org/all/20260429175918.2541914-1-
> > >sanjayembedded@gmail.com/
> > >changes in v2:
> > >- Following input from Jonathan and Andy, squash initial patch v1
> > >  series in single change as individual change should not break anything
> > >- Add devm API support and two driver using the same
> > >- v1 series -> https://lore.kernel.org/all/20260428071613.1134053-1-
> > >sanjayembedded@gmail.com/
> > >
> > >Testing:
> > >  - Compiled with W=1
> > >  - Build-tested on QEMU x86_64
> > >
> > >Based on further feedback and reviews, I would extend this series to convert all
> > >HID IIO driver to use devm_* API.
> > >
> > >Thanks,
> > >Sanjay Chitroda
> > >
> > >
> > >Sanjay Chitroda (9):
> > >  iio: hid-sensors: drop redundant iio_dev argument
> > >  iio: hid-sensors: cleanup codestyle warning
> > >  iio: hid-sensors: introduce device managed API
> > >  iio: gyro: hid-sensor-gyro-3d: cleanup codestyle warning
> > >  iio: gyro: hid-sensor-gyro-3d: drop hid_sensor_remove_trigger() using
> > >    devm API
> > >  iio: humidity: hid-sensor-humidity: cleanup codestyle check
> > >  iio: humidity: hid-sensor-humidity: use common device for devres
> > >  iio: humidity: hid-sensor-humidity: use local struct device
> > >  iio: humidity: hid-sensor-humidity: drop hid_sensor_remove_trigger()
> > >    using devm API
> > >
> > > drivers/iio/accel/hid-sensor-accel-3d.c       |  4 +-
> > > .../common/hid-sensors/hid-sensor-trigger.c   | 24 +++++++-
> > > .../common/hid-sensors/hid-sensor-trigger.h   |  5 +-
> > > drivers/iio/gyro/hid-sensor-gyro-3d.c         | 16 ++---
> > > drivers/iio/humidity/hid-sensor-humidity.c    | 61 +++++++++----------
> > > drivers/iio/light/hid-sensor-als.c            |  4 +-
> > > drivers/iio/light/hid-sensor-prox.c           |  4 +-
> > > drivers/iio/magnetometer/hid-sensor-magn-3d.c |  4 +-
> > >drivers/iio/orientation/hid-sensor-incl-3d.c  |  4 +-  drivers/iio/orientation/hid-
> > >sensor-rotation.c |  4 +-  .../position/hid-sensor-custom-intel-hinge.c  |  4 +-
> > > drivers/iio/pressure/hid-sensor-press.c       |  4 +-
> > > .../iio/temperature/hid-sensor-temperature.c  |  4 +-
> > > 13 files changed, 78 insertions(+), 64 deletions(-)
> > >
> > >
> > >base-commit: 39b80c5c9830d12d2d6531059001301c4265322a
> > >--
> > >2.34.1
> > >  
> >   
> 


^ permalink raw reply

* Re: [bug report] HID: intel-ish: enable raw interface to HID devices on ISH
From: Dan Carpenter @ 2026-05-18 13:51 UTC (permalink / raw)
  To: Zhang Lixu, Jiri Kosina; +Cc: linux-input
In-Reply-To: <agsTQoL1WM4l3AZ3@stanley.mountain>

This code is 7 years old now and Hyungwoo Yang's email is dead.  I know
Jiri reads mail to the list but let me add a Zhang Lixu to the CC list.

regards,
dan carpenter

On Mon, May 18, 2026 at 04:25:22PM +0300, Dan Carpenter wrote:
> Hello Hyungwoo Yang,
> 
> Commit e19595fcabb5 ("HID: intel-ish: enable raw interface to HID
> devices on ISH") from Mar 4, 2019 (linux-next), leads to the
> following Smatch static checker warning:
> 
> 	drivers/hid/intel-ish-hid/ishtp-hid-client.c:225 process_recv()
> 	warn: potentially one past the end of array 'client_data->hid_devices[i]'
> 
> drivers/hid/intel-ish-hid/ishtp-hid-client.c
>     66 static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
>     67                          size_t data_len)
>     68 {
>     69         struct hostif_msg *recv_msg;
>     70         unsigned char *payload;
>     71         struct device_info *dev_info;
>     72         int i, j;
>     73         size_t        payload_len, total_len, cur_pos, raw_len, msg_len;
>     74         int report_type;
>     75         struct report_list *reports_list;
>     76         struct report *report;
>     77         size_t report_len;
>     78         struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
>     79         int curr_hid_dev = client_data->cur_hid_dev;
>     80         struct ishtp_hid_data *hid_data = NULL;
>     81         struct hid_device *hid = NULL;
>     82 
>     83         payload = recv_buf + sizeof(struct hostif_msg_hdr);
>     84         total_len = data_len;
>     85         cur_pos = 0;
>     86 
>     87         do {
>     88                 if (cur_pos + sizeof(struct hostif_msg) > total_len) {
>     89                         dev_err(cl_data_to_dev(client_data),
>     90                                 "[hid-ish]: error, received %u which is less than data header %u\n",
>     91                                 (unsigned int)data_len,
>     92                                 (unsigned int)sizeof(struct hostif_msg_hdr));
>     93                         ++client_data->bad_recv_cnt;
>     94                         ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
>     95                         break;
>     96                 }
>     97 
>     98                 recv_msg = (struct hostif_msg *)(recv_buf + cur_pos);
>     99                 payload_len = recv_msg->hdr.size;
>     100 
>     101                 /* Sanity checks */
>     102                 if (cur_pos + payload_len + sizeof(struct hostif_msg) >
>     103                                 total_len) {
>     104                         ++client_data->bad_recv_cnt;
>     105                         report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos,
>     106                                           payload_len);
>     107                         ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
>     108                         break;
>     109                 }
>     110 
>     111                 hid_ishtp_trace(client_data,  "%s %d\n",
>     112                                 __func__, recv_msg->hdr.command & CMD_MASK);
>     113 
>     114                 switch (recv_msg->hdr.command & CMD_MASK) {
>     115                 case HOSTIF_DM_ENUM_DEVICES:
>     116                         if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
>     117                                         client_data->init_done)) {
>     118                                 ++client_data->bad_recv_cnt;
>     119                                 report_bad_packet(hid_ishtp_cl, recv_msg,
>     120                                                   cur_pos,
>     121                                                   payload_len);
>     122                                 ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
>     123                                 break;
>     124                         }
>     125                         client_data->hid_dev_count = (unsigned int)*payload;
>     126                         if (!client_data->hid_devices)
>     127                                 client_data->hid_devices = devm_kcalloc(
>     128                                                 cl_data_to_dev(client_data),
>     129                                                 client_data->hid_dev_count,
>     130                                                 sizeof(struct device_info),
>     131                                                 GFP_KERNEL);
> 
> We only allocate the client_data->hid_devices buffer one time, and
> then re-use it after that.
> 
> 1) However, how do we know that the new value of client_data->hid_dev_count
>    assigned on line 125 is the same or smaller than the previous value?
> 2) I think we need to update client_data->num_hid_devices as well since
>    that has to be == client_data->hid_dev_count.  Also we assume that if
>    client_data->num_hid_devices is non-zero then client_data->hid_devices
>    was allocated successfully.
> 
> Or maybe client_data->hid_devices is always NULL here and the check
> can be removed that way?  In that case we just need to make a cleanup
> and there is no security bug, however there is another security issue
> below.
> 
>     132                         if (!client_data->hid_devices) {
>     133                                 dev_err(cl_data_to_dev(client_data),
>     134                                 "Mem alloc failed for hid device info\n");
>     135                                 wake_up_interruptible(&client_data->init_wait);
>     136                                 break;
>     137                         }
>     138                         for (i = 0; i < client_data->hid_dev_count; ++i) {
>     139                                 if (1 + sizeof(struct device_info) * i >=
>     140                                                 payload_len) {
>     141                                         dev_err(cl_data_to_dev(client_data),
>     142                                                 "[hid-ish]: [ENUM_DEVICES]: content size %zu is bigger than payload_len %zu\n",
>     143                                                 1 + sizeof(struct device_info)
>     144                                                 * i, payload_len);
> 
> 
> 3) This if statement is testing if we are reading beyond the end of the
>    buffer, but it just prints an error message and continues reading.
>    So it is an information leak.  It should error out.
> 
>     145                                 }
>     146 
>     147                                 if (1 + sizeof(struct device_info) * i >=
>     148                                                 data_len)
>     149                                         break;
> 
> 4) It's weird that if we have an overflow in this inner loop we break but
>    potentially still keep iterating in the outer loop.
> 
>     150 
>     151                                 dev_info = (struct device_info *)(payload + 1 +
>     152                                         sizeof(struct device_info) * i);
>     153                                 if (client_data->hid_devices)
>                                             ^^^^^^^^^^^^^^^^^^^^^^^^
> 5) It's not a bug, but this NULL check can be removed.  It triggers a
>    static checker warning about inconsistent NULL checking.
> 
> regards,
> dan carpenter
> 
>     154                                         memcpy(client_data->hid_devices + i,
>     155                                                dev_info,
>     156                                                sizeof(struct device_info));
>     157                         }
>     158 
>     159                         client_data->enum_devices_done = true;
>     160                         wake_up_interruptible(&client_data->init_wait);
>     161 
> 
> This email is a free service from the Smatch-CI project [smatch.sf.net].

^ permalink raw reply

* [bug report] HID: intel-ish: enable raw interface to HID devices on ISH
From: Dan Carpenter @ 2026-05-18 13:25 UTC (permalink / raw)
  To: Hyungwoo Yang; +Cc: linux-input

Hello Hyungwoo Yang,

Commit e19595fcabb5 ("HID: intel-ish: enable raw interface to HID
devices on ISH") from Mar 4, 2019 (linux-next), leads to the
following Smatch static checker warning:

	drivers/hid/intel-ish-hid/ishtp-hid-client.c:225 process_recv()
	warn: potentially one past the end of array 'client_data->hid_devices[i]'

drivers/hid/intel-ish-hid/ishtp-hid-client.c
    66 static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
    67                          size_t data_len)
    68 {
    69         struct hostif_msg *recv_msg;
    70         unsigned char *payload;
    71         struct device_info *dev_info;
    72         int i, j;
    73         size_t        payload_len, total_len, cur_pos, raw_len, msg_len;
    74         int report_type;
    75         struct report_list *reports_list;
    76         struct report *report;
    77         size_t report_len;
    78         struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
    79         int curr_hid_dev = client_data->cur_hid_dev;
    80         struct ishtp_hid_data *hid_data = NULL;
    81         struct hid_device *hid = NULL;
    82 
    83         payload = recv_buf + sizeof(struct hostif_msg_hdr);
    84         total_len = data_len;
    85         cur_pos = 0;
    86 
    87         do {
    88                 if (cur_pos + sizeof(struct hostif_msg) > total_len) {
    89                         dev_err(cl_data_to_dev(client_data),
    90                                 "[hid-ish]: error, received %u which is less than data header %u\n",
    91                                 (unsigned int)data_len,
    92                                 (unsigned int)sizeof(struct hostif_msg_hdr));
    93                         ++client_data->bad_recv_cnt;
    94                         ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
    95                         break;
    96                 }
    97 
    98                 recv_msg = (struct hostif_msg *)(recv_buf + cur_pos);
    99                 payload_len = recv_msg->hdr.size;
    100 
    101                 /* Sanity checks */
    102                 if (cur_pos + payload_len + sizeof(struct hostif_msg) >
    103                                 total_len) {
    104                         ++client_data->bad_recv_cnt;
    105                         report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos,
    106                                           payload_len);
    107                         ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
    108                         break;
    109                 }
    110 
    111                 hid_ishtp_trace(client_data,  "%s %d\n",
    112                                 __func__, recv_msg->hdr.command & CMD_MASK);
    113 
    114                 switch (recv_msg->hdr.command & CMD_MASK) {
    115                 case HOSTIF_DM_ENUM_DEVICES:
    116                         if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
    117                                         client_data->init_done)) {
    118                                 ++client_data->bad_recv_cnt;
    119                                 report_bad_packet(hid_ishtp_cl, recv_msg,
    120                                                   cur_pos,
    121                                                   payload_len);
    122                                 ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
    123                                 break;
    124                         }
    125                         client_data->hid_dev_count = (unsigned int)*payload;
    126                         if (!client_data->hid_devices)
    127                                 client_data->hid_devices = devm_kcalloc(
    128                                                 cl_data_to_dev(client_data),
    129                                                 client_data->hid_dev_count,
    130                                                 sizeof(struct device_info),
    131                                                 GFP_KERNEL);

We only allocate the client_data->hid_devices buffer one time, and
then re-use it after that.

1) However, how do we know that the new value of client_data->hid_dev_count
   assigned on line 125 is the same or smaller than the previous value?
2) I think we need to update client_data->num_hid_devices as well since
   that has to be == client_data->hid_dev_count.  Also we assume that if
   client_data->num_hid_devices is non-zero then client_data->hid_devices
   was allocated successfully.

Or maybe client_data->hid_devices is always NULL here and the check
can be removed that way?  In that case we just need to make a cleanup
and there is no security bug, however there is another security issue
below.

    132                         if (!client_data->hid_devices) {
    133                                 dev_err(cl_data_to_dev(client_data),
    134                                 "Mem alloc failed for hid device info\n");
    135                                 wake_up_interruptible(&client_data->init_wait);
    136                                 break;
    137                         }
    138                         for (i = 0; i < client_data->hid_dev_count; ++i) {
    139                                 if (1 + sizeof(struct device_info) * i >=
    140                                                 payload_len) {
    141                                         dev_err(cl_data_to_dev(client_data),
    142                                                 "[hid-ish]: [ENUM_DEVICES]: content size %zu is bigger than payload_len %zu\n",
    143                                                 1 + sizeof(struct device_info)
    144                                                 * i, payload_len);


3) This if statement is testing if we are reading beyond the end of the
   buffer, but it just prints an error message and continues reading.
   So it is an information leak.  It should error out.

    145                                 }
    146 
    147                                 if (1 + sizeof(struct device_info) * i >=
    148                                                 data_len)
    149                                         break;

4) It's weird that if we have an overflow in this inner loop we break but
   potentially still keep iterating in the outer loop.

    150 
    151                                 dev_info = (struct device_info *)(payload + 1 +
    152                                         sizeof(struct device_info) * i);
    153                                 if (client_data->hid_devices)
                                            ^^^^^^^^^^^^^^^^^^^^^^^^
5) It's not a bug, but this NULL check can be removed.  It triggers a
   static checker warning about inconsistent NULL checking.

regards,
dan carpenter

    154                                         memcpy(client_data->hid_devices + i,
    155                                                dev_info,
    156                                                sizeof(struct device_info));
    157                         }
    158 
    159                         client_data->enum_devices_done = true;
    160                         wake_up_interruptible(&client_data->init_wait);
    161 

This email is a free service from the Smatch-CI project [smatch.sf.net].


^ permalink raw reply

* Re: [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery
From: sashiko-bot @ 2026-05-18 12:37 UTC (permalink / raw)
  To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, linux-input, robh, conor+dt, devicetree
In-Reply-To: <20260518095907.36158-7-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The driver lacks a MODULE_ALIAS declaration, which breaks module autoloading.
- [Low] Missing NULL check for the devm_kasprintf allocation of the power supply name.
--

commit ccc69cbdb16bd3e704318f7d07abbad8dc370844
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>

power: supply: Add driver for ASUS Transformer battery

This commit introduces a new power supply driver for the ASUS Transformer
battery. It communicates with the embedded controller via I2C to read battery
status for both the tablet and mobile dock.

> diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/power/supply/asus-transformer-ec-battery.c
> new file mode 100644
> index 0000000000000..d08d155f72a3d
> --- /dev/null
> +++ b/drivers/power/supply/asus-transformer-ec-battery.c
[ ... ]
> +static int asus_ec_battery_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	cfg.fwnode = dev_fwnode(dev->parent);
> +	cfg.drv_data = priv;
> +
> +	memcpy(&priv->psy_desc, &asus_ec_battery_desc, sizeof(priv->psy_desc));
> +	priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-battery",
> +					     priv->ec->name);
> +
> +	priv->battery = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
> +	if (IS_ERR(priv->battery))
> +		return dev_err_probe(dev, PTR_ERR(priv->battery),
> +				     "Failed to register power supply\n");

Is it possible for devm_kasprintf() to fail and return NULL under memory
pressure?

If so, priv->psy_desc.name would become NULL, and devm_power_supply_register()
would return ERR_PTR(-EINVAL) because it checks for !desc->name. This relies
on downstream validation and results in an incorrect error code being
propagated for a memory allocation failure instead of -ENOMEM.

Should there be an explicit NULL check here?

[ ... ]
> +static struct platform_driver asus_ec_battery_driver = {
> +	.driver = {
> +		.name = "asus-transformer-ec-battery",
> +		.pm = &asus_ec_battery_pm_ops,
> +	},
> +	.probe = asus_ec_battery_probe,
> +};
> +module_platform_driver(asus_ec_battery_driver);
> +
> +MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
> +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
> +MODULE_DESCRIPTION("ASUS Transformer's battery driver");
> +MODULE_LICENSE("GPL");

Since this platform driver matches devices purely by name
("asus-transformer-ec-battery") and lacks an id_table or of_match_table,
will userspace tools like udev be able to automatically load this driver
when it is compiled as a module?

Should a MODULE_ALIAS("platform:asus-transformer-ec-battery"); declaration
be added to ensure module autoloading works correctly?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=6

^ permalink raw reply

* Re: [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs
From: sashiko-bot @ 2026-05-18 12:02 UTC (permalink / raw)
  To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, robh, conor+dt, linux-input, devicetree
In-Reply-To: <20260518095907.36158-6-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Incorrect use of `LED_CORE_SUSPENDRESUME` for charging indicators.
- [Medium] Lack of hardware state synchronization (`brightness_get`) at initialization.
- [Medium] Missing `MODULE_ALIAS` prevents automatic module loading.
- [Low] The driver exposes a physical bi-color LED as two independent single-color LED devices instead of utilizing the LED Multicolor class.
--

commit f02de4369b03c2d6785dea5fb4edfab53036130d
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>

leds: Add driver for ASUS Transformer LEDs

ASUS Transformer tablets have a green and an amber LED on both the Pad
and the Dock. If both LEDs are enabled simultaneously, the emitted light
will be yellow.

> ASUS Transformer tablets have a green and an amber LED on both the Pad
> and the Dock. If both LEDs are enabled simultaneously, the emitted light
> will be yellow.

Since this describes a physically multi-color LED, shouldn't the driver use
the LED Multicolor class API (CONFIG_LEDS_CLASS_MULTICOLOR) instead of exposing
them as two separate single-color LED devices?

> diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
> new file mode 100644
> index 0000000000000..c9bdc46c02e15
> --- /dev/null
> +++ b/drivers/leds/leds-asus-transformer-ec.c
[ ... ]
> +	for (i = 0; i < ASUSEC_LED_MAX; i++) {
> +		const struct asus_ec_led_config *cfg = &asus_ec_leds[i];
> +		struct asus_ec_led *led = &ddata->leds[i];
> +
> +		led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s::%s",
> +						ddata->ec->name, cfg->name);
> +		if (!led->cdev.name)
> +			return -ENOMEM;
> +
> +		led->cdev.max_brightness = 1;
> +		led->cdev.color = cfg->color;
> +		led->cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;

Does LED_CORE_SUSPENDRESUME make sense for a charging indicator?

This flag instructs the LED core to explicitly turn off the LED during suspend,
which turns off the charging indicator while the device is sleeping.

Also, could the Embedded Controller have already turned the LED on before the
driver probes?

Since there is no brightness_get callback to synchronize the initial hardware
state, the LED core will initialize the software brightness to 0. If the device
resumes from suspend, the LED core will restore this saved brightness of 0,
permanently turning off the LED even if it was physically on before.

[ ... ]
> +static struct platform_driver asus_ec_led_driver = {
> +	.driver.name = "asus-transformer-ec-led",
> +	.probe = asus_ec_led_probe,
> +};
> +module_platform_driver(asus_ec_led_driver);
> +
> +MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
> +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
> +MODULE_DESCRIPTION("ASUS Transformer's charging LED driver");
> +MODULE_LICENSE("GPL");

Will this module load automatically when the platform device is registered?

Since there is no MODULE_ALIAS("platform:asus-transformer-ec-led") declaration,
udev might not be able to automatically load the module during boot when the
MFD core registers the corresponding platform device.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=5

^ permalink raw reply

* Re: [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
From: sashiko-bot @ 2026-05-18 11:46 UTC (permalink / raw)
  To: Svyatoslav Ryhel; +Cc: conor+dt, linux-input, devicetree, dmitry.torokhov, robh
In-Reply-To: <20260518095907.36158-5-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Registering a statically allocated `struct input_handler` in the per-device probe function violates the driver model and causes list corruption.
- [High] The input handler permanently opens all `EV_KEY` input devices in the system without declaring itself as a passive observer, preventing runtime suspend.
--

commit 3ac5537da45198a85ba82452de2c64484baeac36
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>

input: keyboard: Add driver for ASUS Transformer dock multimedia keys

This adds support for the multimedia top button row of the ASUS Transformer
Mobile Dock keyboard. It defaults to function keys (F1-F12) and uses an input
handler to monitor system-wide key events to switch to multimedia keys when
ScreenLock + AltGr are pressed.

> diff --git a/drivers/input/keyboard/asus-transformer-ec-keys.c b/drivers/input/keyboard/asus-transformer-ec-keys.c
> new file mode 100644
> index 0000000000000..d30d991773744
> --- /dev/null
> +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
[ ... ]
> +static int asus_ec_input_connect(struct input_handler *handler,
> +				 struct input_dev *dev,
> +				 const struct input_device_id *id)
> +{
> +	struct input_handle *handle;
> +	int error;
[ ... ]
> +	error = input_open_device(handle);
> +	if (error)
> +		goto err_unregister_handle;
> +
> +	return 0;

[ ... ]

> +static struct input_handler asus_ec_input_handler = {
> +	.name =	"asusec-media-handler",
> +	.event = asus_ec_input_event,
> +	.connect = asus_ec_input_connect,
> +	.disconnect = asus_ec_input_disconnect,
> +	.id_table = asus_ec_input_ids,
> +};

Because this handler connects to all EV_KEY devices and calls
input_open_device() without setting .passive_observer = true, will this
increment the dev->users count for every matching input device in the system?

Could this force all EV_KEY input devices to remain open and active
permanently, preventing them from entering runtime suspend?

[ ... ]

> +static int asus_ec_keys_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	asus_ec_input_handler.private = priv;
> +	error = input_register_handler(&asus_ec_input_handler);
> +	if (error)
> +		return error;

Since asus_ec_input_handler is a static global structure, what happens if this
driver is probed multiple times (for instance, via sysfs bind/unbind or
multiple ECs)?

If input_register_handler() is called a second time, it will re-initialize the
list head and add an already linked node. Could this corrupt the
input_handler_list?

Should the input_handler be dynamically allocated per-device or embedded within
struct asus_ec_keys_data?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=4

^ permalink raw reply

* Re: [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
From: sashiko-bot @ 2026-05-18 11:17 UTC (permalink / raw)
  To: Svyatoslav Ryhel; +Cc: linux-input, robh, devicetree, dmitry.torokhov, conor+dt
In-Reply-To: <20260518095907.36158-4-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Sleeping in atomic context due to synchronous I2C transfer in serio->write callback.
- [High] Off-by-one error in packet length calculation causes a buffer over-read and breaks keycode remapping.
- [Medium] In-place modification of a shared notifier data buffer.
--

commit 3c9b2bf0acd20a594f17d65b0d6fd9762329b3bc
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>

input: serio: Add driver for ASUS Transformer dock keyboard and touchpad

This commit adds a serio input driver for the ASUS Transformer dock keyboard
and touchpad. It registers serio ports that act as a bridge over I2C and
remaps specific keycodes according to their position.

> diff --git a/drivers/input/serio/asus-transformer-ec-kbc.c b/drivers/input/serio/asus-transformer-ec-kbc.c
> new file mode 100644
> index 0000000000000..c9707c7fbede5
> --- /dev/null
> +++ b/drivers/input/serio/asus-transformer-ec-kbc.c

[ ... ]

> +static int asus_ec_kbc_notify(struct notifier_block *nb,
> +			      unsigned long action, void *data_)
> +{
> +	struct asus_ec_kbc_data *priv = container_of(nb, struct asus_ec_kbc_data, nb);
> +	unsigned int port_idx, n;
> +	u8 *data = data_;
> +
> +	if (action & (ASUSEC_SMI_MASK | ASUSEC_SCI_MASK))
> +		return NOTIFY_DONE;
> +	else if (action & ASUSEC_AUX_MASK)
> +		port_idx = 1;
> +	else if (action & (ASUSEC_KBC_MASK | ASUSEC_KEY_MASK))
> +		port_idx = 0;
> +	else
> +		return NOTIFY_DONE;
> +
> +	/*
> +	 * The data[0] is the length of the packet including itself. It has to
> +	 * be at least 2 bytes (length + 1 data byte) and must not exceed
> +	 * the EC buffer size.
> +	 */
> +	if (data[0] < 2 || data[0] > DOCKRAM_ENTRY_BUFSIZE)
> +		return NOTIFY_BAD;
> +
> +	n = data[0] - 1;
> +	data += 2;

Is the payload length calculation off by one?

The comment states data[0] is the length of the packet including itself.
The code then skips two bytes (data += 2), which suggests the payload
starts at offset 2. If the payload length is data[0] - 2, will setting
n = data[0] - 1 cause the loop at the end of the function to read one byte
past the actual payload and inject garbage data into the serio port?

If an external device sends a packet with data[0] == DOCKRAM_ENTRY_BUFSIZE,
could this off-by-one error cause an out-of-bounds read past the end of
the EC buffer array?

Additionally, will this artificially inflated n cause the exact-length
validation in the key remapping logic (if (n == 2 ...) and if (n == 3 ...))
to always fail for valid payload sequences, breaking the layout translation?

> +
> +	if (port_idx == 0) {
> +		/*
> +		 * Remap keyboard key codes to match AT layout:
> +		 * SEARCH: RIGHT-META [E0 27] -> LEFT-ALT   [11]
> +		 * MENU:   COMPOSE    [E0 2F] -> RIGHT-META [E0 27]
> +		 */
> +		if ((n == 2 || (n == 3 && data[1] == 0xF0)) && data[0] == 0xE0) {
> +			u8 *keycode = &data[n - 1];
> +
> +			switch (*keycode) {
> +			case 0x27:
> +				*keycode = 0x11;

Is it safe to modify the data_ buffer in place?

Since data_ is passed to this function via the notifier chain, it points to a
shared event buffer that is broadcast to all registered listeners. Will
modifying this payload permanently corrupt the event data for any downstream
notifiers that might inspect the packet subsequently?

> +				++data;
> +				--n;
> +				break;
> +			case 0x2F:
> +				*keycode = 0x27;
> +				break;
> +			}
> +		}
> +	}
> +
> +	while (n--)
> +		serio_interrupt(priv->sdev[port_idx], *data++, 0);
> +
> +	return NOTIFY_OK;
> +}
> +
> +static int asus_ec_serio_write(struct serio *port, unsigned char data)
> +{
> +	struct asus_ec_kbc_data *priv = port->port_data;
> +
> +	return i2c_smbus_write_word_data(priv->parent, ASUSEC_WRITE_BUF,
> +					 (data << 8) | port->id.extra);

Can this sleep while in an atomic context?

i2c_smbus_write_word_data() performs a synchronous I2C transaction which
acquires mutexes and can sleep. However, serio->write callbacks can be
invoked from atomic contexts.

For example, asus_ec_kbc_notify() calls serio_interrupt() which acquires a
spinlock and disables interrupts:

serio_interrupt()
  spin_lock_irqsave(&serio->lock, flags);
  serio->drv->interrupt()
    serio_write()
      asus_ec_serio_write()
        i2c_smbus_write_word_data()

Will this trigger a scheduling-while-atomic panic if the input driver issues
a command back to the port while the spinlock is held?

> +}

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=3

^ permalink raw reply

* Re: [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: sashiko-bot @ 2026-05-18 10:31 UTC (permalink / raw)
  To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, robh, devicetree, linux-input, conor+dt
In-Reply-To: <20260518095907.36158-3-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Unsynchronized reallocation of `ddata->ec.model` causes Use-After-Free during interrupts.
- [High] Stale and uninitialized data is accessed on short I2C reads.
- [Medium] `asus_dockram_access_ctl()` silently ignores I2C write errors.
- [Medium] Memory leak of `ddata->ec.model` upon driver unbind or probe failure.
- [Medium] The original IRQ event payload is corrupted before being passed to child notifiers.
- [Medium] Subsystem Guideline Violation: Mixing `goto` error handling with `__free()` cleanup.
- [Medium] Missing error check for `kstrndup()` allocation failure in `asus_ec_log_info()`.
- [Low] Incorrect length check causes string truncation in `asus_ec_log_info()`.
--

commit d95a94a77b9b37ec021eabccb7ff81e4dd52730e
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>

mfd: Add driver for ASUS Transformer embedded controller
    
Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
and TF600T, P1801-T and TF701T pad. This is a glue driver handling
detection and common operations for EC's functions.

> diff --git a/drivers/mfd/asus-transformer-ec.c b/drivers/mfd/asus-transformer-ec.c
> new file mode 100644
> index 0000000000000..18bb3e5f384d9
> --- /dev/null
> +++ b/drivers/mfd/asus-transformer-ec.c

[ ... ]

> +	if (mask || xor) {
> +		put_unaligned_le64((val & ~mask) ^ xor, buf + 1);
> +		ret = i2c_smbus_write_i2c_block_data(client,
> +						     ASUSEC_DOCKRAM_CONTROL,
> +						     DOCKRAM_ENTRY_SIZE, buf);
> +	}
> +
> +	return 0;
> +}

Does this accidentally hide hardware write failures? Since ret is ignored,
callers might falsely assume the I2C block write succeeded.

[ ... ]

> +static void asus_ec_clear_buffer(struct asus_ec_data *ddata)
> +{
> +	int ret, retry = ASUSEC_RSP_BUFFER_SIZE;
> +
> +	/*
> +	 * Read the buffer till we get valid data by checking ASUSEC_OBF_MASK
> +	 * of the status byte or till we reach end of the 256 byte buffer.
> +	 */
> +	while (retry--) {
> +		ret = i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF,
> +						    sizeof(ddata->ec_buf),
> +						    ddata->ec_buf);
> +		if (ret < 0)
> +			continue;
> +
> +		if (ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK)
> +			continue;

Can a short read leave stale data in ddata->ec_buf? The function
i2c_smbus_read_i2c_block_data() returns the actual number of bytes read. If it
returns a positive value less than ASUSEC_IRQ_STATUS + 1, the buffer will
retain data from previous iterations.

[ ... ]

> +static int asus_ec_log_info(struct asus_ec_data *ddata, unsigned int reg,
> +			    const char *name, char **out)
> +{
> +	struct device *dev = &ddata->client->dev;
> +	u8 buf[DOCKRAM_ENTRY_BUFSIZE];
> +	int ret;
> +
> +	memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
> +	ret = i2c_smbus_read_i2c_block_data(ddata->ec.dockram, reg,
> +					    DOCKRAM_ENTRY_SIZE, buf);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (buf[0] > DOCKRAM_ENTRY_SIZE) {
> +		dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
> +			DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> +		return -EPROTO;
> +	}

Is there a risk of an out-of-bounds read here? The I2C read fills up to 32
bytes (DOCKRAM_ENTRY_SIZE), meaning buf[0] to buf[31] are valid. If buf[0]
equals 32, the kstrndup() below will attempt to copy 32 bytes starting from
buf + 1, reading up to buf[32].

> +
> +	if (!ddata->logging_disabled)
> +		dev_info(dev, "%-14s: %.*s\n", name, buf[0], buf + 1);
> +
> +	if (out)
> +		*out = kstrndup(buf + 1, buf[0], GFP_KERNEL);
> +
> +	return 0;
> +}

If kstrndup() fails and returns NULL, asus_ec_detect() will unknowingly assign
NULL to ddata->ec.model. Should this return -ENOMEM on allocation failure?

[ ... ]

> +static int asus_ec_detect(struct asus_ec_data *ddata)
> +{
> +	char *model __free(kfree) = NULL;
> +	int ret;
> +
> +	ret = asus_ec_reset(ddata);
> +	if (ret)
> +		goto err_exit;

This isn't a bug, but mixing goto error handling with __free() cleanup in the
same function violates the subsystem guidelines. Functions are expected to use
either scope-based cleanup or goto-based cleanup exclusively to prevent
confusing ownership semantics.

[ ... ]

> +	/* Check and inform about EC firmware behavior */
> +	ret = asus_ec_susb_on_status(ddata);
> +	if (ret)
> +		goto err_exit;
> +
> +	kfree(ddata->ec.model);
> +
> +	ddata->ec.model = no_free_ptr(model);

Could this cause a use-after-free or a memory leak?

Since asus_ec_detect() can be called from the threaded interrupt handler at
runtime, ddata->ec.model might be freed and replaced without synchronization
while child drivers or sysfs attributes are concurrently accessing it.

Additionally, ddata->ec.model is never freed on driver unbind or probe
failure, causing a memory leak.

[ ... ]

> +static irqreturn_t asus_ec_interrupt(int irq, void *dev_id)
> +{
> +	struct asus_ec_data *ddata = dev_id;
> +	unsigned long notify_action;
> +	int ret;
> +
> +	ret = i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF,
> +					    sizeof(ddata->ec_buf), ddata->ec_buf);
> +	if (ret <= 0 || !(ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK))
> +		return IRQ_NONE;

Similar to asus_ec_clear_buffer(), if ret is a positive value smaller than
the required length, this could process stale data from previous interrupt
events.

> +
> +	notify_action = ddata->ec_buf[ASUSEC_IRQ_STATUS];
> +	if (notify_action & ASUSEC_SMI_MASK) {
> +		unsigned int code = ddata->ec_buf[ASUSEC_SMI_CODE];
> +
> +		asus_ec_handle_smi(ddata, code);
> +
> +		notify_action |= code << 8;
> +	}
> +
> +	blocking_notifier_call_chain(&ddata->ec.notify_list,
> +				     notify_action, ddata->ec_buf);
> +
> +	return IRQ_HANDLED;
> +}

Does asus_ec_handle_smi() corrupt the event payload before it is passed to
child notifiers?

If asus_ec_handle_smi() triggers a reset, it eventually calls
asus_ec_clear_buffer(), which performs new I2C reads directly into
ddata->ec_buf. This overwrites the original event data, meaning
blocking_notifier_call_chain() will pass a clobbered buffer to child
devices.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=2

^ permalink raw reply

* Re: [PATCH v6 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-05-18 10:27 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Pavel Machek, Sebastian Reichel, Ion Agorria,
	Michał Mirosław, devicetree, linux-kernel, linux-input,
	linux-leds, linux-pm
In-Reply-To: <20260518101328.GS305027@google.com>

пн, 18 трав. 2026 р. о 13:13 Lee Jones <lee@kernel.org> пише:
>
> On Thu, 14 May 2026, Svyatoslav Ryhel wrote:
>
> > чт, 14 трав. 2026 р. о 18:50 Lee Jones <lee@kernel.org> пише:
> > >
> > > On Thu, 14 May 2026, Svyatoslav Ryhel wrote:
> > >
> > > > чт, 14 трав. 2026 р. о 13:02 Lee Jones <lee@kernel.org> пише:
> > > > >
> > > > > On Sat, 02 May 2026, Svyatoslav Ryhel wrote:
> > > > >
> > > > > > From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > > > >
> > > > > > Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
> > > > > > TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
> > > > > > and TF600T, P1801-T and TF701T pad. This is a glue driver handling
> > > > > > detection and common operations for EC's functions.
> > > > > >
> > > > > > Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > > > Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > > > > ---
> > > > > >  drivers/mfd/Kconfig                     |  14 +
> > > > > >  drivers/mfd/Makefile                    |   1 +
> > > > > >  drivers/mfd/asus-transformer-ec.c       | 762 ++++++++++++++++++++++++
> > > > > >  include/linux/mfd/asus-transformer-ec.h | 162 +++++
> > > > > >  4 files changed, 939 insertions(+)
> > > > > >  create mode 100644 drivers/mfd/asus-transformer-ec.c
> > > > > >  create mode 100644 include/linux/mfd/asus-transformer-ec.h
> > >
> > > [...]
> > >
> > > > > > +     unsigned int num_devices;
> > > > > > +     bool clr_fmode; /* clear Factory Mode bit in EC control register */
> > > > > > +};
> > > > > > +
> > > > > > +struct asus_ec_data {
> > > > > > +     struct asusec_info info;
> > > > >
> > > > > You have 'data' and 'info' which a) using non-forthcoming nomenclature
> > > > > and doesn't tell me anything and then you b) put 'info' in the device's
> > > > > driver_data attribute which is very confusing.  driver_data should be
> > > > > for what we call ddata which I assume is expressed as 'data' here.
> > > > >
> > > >
> > > > asusec_info is shared among all child devices and is exposed while
> > > > remaining elements of this struct are for internal use only.
> > >
> > > Our terminology for that is usually ddata, that gets stored in
> > > 'struct devices' device_data attribute.
> > >
> > > > > > +     struct mutex ecreq_lock; /* prevent simultaneous access */
> > > > > > +     struct gpio_desc *ecreq;
> > > > >
> > > > > If I hadn't seen the declaration, I'd have no idea this was a GPIO
> > > > > descriptor.  Please improve the nomenclature throughout.
> > > > >
> > > > > > +     struct i2c_client *self;
> > > > >
> > > > > Again, please use standard naming conventions:
> > > > >
> > > > > % git grep "struct i2c_client" | grep "\*self" | wc -l
> > > > > 0
> > > > >
> > > > > % git grep "struct i2c_client" | grep "\*client" | wc -l
> > > > > 6304
> > > > >
> > > > > % git grep "struct i2c_client" | grep "\*i2c" | wc -l
> > > > > 903
> > > > >
> > > >
> > > > ok, noted.
> > > >
> > > > > > +     const struct asus_ec_chip_data *data;
> > > > >
> > > > > 'data', 'priv' and 'info' should be improved.
> > > > >
> > > > > > +     char ec_data[DOCKRAM_ENTRY_BUFSIZE];
> > > > >
> > > > > An array of chars called 'data'.  This could be anything.
> > > > >
> > > >
> > > > Do you have a comprehensive list of name conventions you find suitable?
> > >
> > > Anything descriptive that alludes to the type of data being held there.
> > >
> > > There are 100's of good examples, but a handful of generic / bad ones.
> > >
> > > > > > +     bool logging_disabled;
> > > > >
> > > > > This debugging tool is probably never going to be used again.
> > > > >
> > > > > Keep it local.
> > > > >
> > > > > > +};
> > > > > > +
> > > > > > +struct dockram_ec_data {
> > > > > > +     struct mutex ctl_lock; /* prevent simultaneous access */
> > > > > > +     char ctl_data[DOCKRAM_ENTRY_BUFSIZE];
> > > > > > +};
> > > > > > +
> > > > > > +#define to_ec_data(ec) \
> > > > > > +     container_of(ec, struct asus_ec_data, info)
> > > > > > +
> > > > > > +/**
> > > > > > + * asus_dockram_read - Read a register from the DockRAM device.
> > > > > > + * @client: Handle to the DockRAM device.
> > > > > > + * @reg: Register to read.
> > > > > > + * @buf: Byte array into which data will be read; must be large enough to
> > > > > > + *    hold the data returned by the DockRAM.
> > > > > > + *
> > > > > > + * This executes the DockRAM read based on the SMBus "block read" protocol
> > > > > > + * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set
> > > > > > + * register address.
> > > > > > + *
> > > > > > + * Returns a negative errno code else zero on success.
> > > > > > + */
> > > > > > +int asus_dockram_read(struct i2c_client *client, int reg, char *buf)
> > > > > > +{
> > > > >
> > > > > Have you considered using Regmap for register access instead of
> > > > > implementing custom functions?  Remaps already deals with caching and
> > > > > locking mechanisms that you'd get for free.
> > > > >
> > > > > This looks like it would be replaced with devm_regmap_init_i2c().
> > > > >
> > > >
> > > > I will consider this, thank you.
> > > >
> >
> > It seems that regmap does not fit for this purpose, but I might switch
> > to plain i2c_smbus_read_i2c_block_data
>
> Can you explain why Regmap doesn't work for you?
>
> Regmap is just a wrapper about i2c_smbus_read_i2c_block_data() and friends.
>

Honestly, it seems that this will cause more hassle. Regmap has max
bit size of reg 64 which will require use of regmap_read/write_raw and
obviously no regmap bit update operations will be possible. If this
chip was a plain 8 bit register i2c device, obviously I would use
regmap.

I have removed most of custom functions in favor for i2c smbus block
helpers and left only asus_dockram_access_ctl since it is used in many
places.

> > > > > > +     struct device *dev = &client->dev;
> > > > > > +     int ret;
> > > > > > +
> > > > > > +     memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
> > > > > > +     ret = i2c_smbus_read_i2c_block_data(client, reg,
> > > > > > +                                         DOCKRAM_ENTRY_BUFSIZE, buf);
> > > > > > +     if (ret < 0)
> > > > > > +             return ret;
> > > > > > +
> > > > > > +     if (buf[0] > DOCKRAM_ENTRY_SIZE) {
> > > > > > +             dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
> > > > > > +                     DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > > > > > +             return -EPROTO;
> > > > > > +     }
> > > > > > +
> > > > > > +     dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n",
> > > > > > +             DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > > > >
> > > > > Please remove all of these debug messages.
> > > > >
> > > >
> > > > Why debug messages cannot be preserved? They are specifically marked as dev_dbg
> > >
> > > It's a general convention.
> > >
> > > After initial development, they tend to just litter the code-base.
> > >
> > > Debug prints can be useful higher up the stack though.
> > >
> >
> > I am fine with removing all debugs and logging but I strongly would
> > like to keep EC model and firmware version along with susb and factory
> > status. That may be quite useful in identifying EC used and its
> > behavior without need in rebuilding the kernel and digging huge piles
> > of downstream code in order to find how to dump these values.
>
> Yes, you can keep this sort of thing as INFO.
>

Noted. I have send v7 with your suggestions applied. Change log is in
the cover commit.

> --
> Lee Jones

^ permalink raw reply

* Re: [PATCH v6 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Lee Jones @ 2026-05-18 10:13 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Pavel Machek, Sebastian Reichel, Ion Agorria,
	Michał Mirosław, devicetree, linux-kernel, linux-input,
	linux-leds, linux-pm
In-Reply-To: <CAPVz0n1drWV6zMzOx93gHNaw+Tt0M9oAF2RyKW6tZcC2gF_HAQ@mail.gmail.com>

On Thu, 14 May 2026, Svyatoslav Ryhel wrote:

> чт, 14 трав. 2026 р. о 18:50 Lee Jones <lee@kernel.org> пише:
> >
> > On Thu, 14 May 2026, Svyatoslav Ryhel wrote:
> >
> > > чт, 14 трав. 2026 р. о 13:02 Lee Jones <lee@kernel.org> пише:
> > > >
> > > > On Sat, 02 May 2026, Svyatoslav Ryhel wrote:
> > > >
> > > > > From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > > >
> > > > > Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
> > > > > TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
> > > > > and TF600T, P1801-T and TF701T pad. This is a glue driver handling
> > > > > detection and common operations for EC's functions.
> > > > >
> > > > > Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > > Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > > > ---
> > > > >  drivers/mfd/Kconfig                     |  14 +
> > > > >  drivers/mfd/Makefile                    |   1 +
> > > > >  drivers/mfd/asus-transformer-ec.c       | 762 ++++++++++++++++++++++++
> > > > >  include/linux/mfd/asus-transformer-ec.h | 162 +++++
> > > > >  4 files changed, 939 insertions(+)
> > > > >  create mode 100644 drivers/mfd/asus-transformer-ec.c
> > > > >  create mode 100644 include/linux/mfd/asus-transformer-ec.h
> >
> > [...]
> >
> > > > > +     unsigned int num_devices;
> > > > > +     bool clr_fmode; /* clear Factory Mode bit in EC control register */
> > > > > +};
> > > > > +
> > > > > +struct asus_ec_data {
> > > > > +     struct asusec_info info;
> > > >
> > > > You have 'data' and 'info' which a) using non-forthcoming nomenclature
> > > > and doesn't tell me anything and then you b) put 'info' in the device's
> > > > driver_data attribute which is very confusing.  driver_data should be
> > > > for what we call ddata which I assume is expressed as 'data' here.
> > > >
> > >
> > > asusec_info is shared among all child devices and is exposed while
> > > remaining elements of this struct are for internal use only.
> >
> > Our terminology for that is usually ddata, that gets stored in
> > 'struct devices' device_data attribute.
> >
> > > > > +     struct mutex ecreq_lock; /* prevent simultaneous access */
> > > > > +     struct gpio_desc *ecreq;
> > > >
> > > > If I hadn't seen the declaration, I'd have no idea this was a GPIO
> > > > descriptor.  Please improve the nomenclature throughout.
> > > >
> > > > > +     struct i2c_client *self;
> > > >
> > > > Again, please use standard naming conventions:
> > > >
> > > > % git grep "struct i2c_client" | grep "\*self" | wc -l
> > > > 0
> > > >
> > > > % git grep "struct i2c_client" | grep "\*client" | wc -l
> > > > 6304
> > > >
> > > > % git grep "struct i2c_client" | grep "\*i2c" | wc -l
> > > > 903
> > > >
> > >
> > > ok, noted.
> > >
> > > > > +     const struct asus_ec_chip_data *data;
> > > >
> > > > 'data', 'priv' and 'info' should be improved.
> > > >
> > > > > +     char ec_data[DOCKRAM_ENTRY_BUFSIZE];
> > > >
> > > > An array of chars called 'data'.  This could be anything.
> > > >
> > >
> > > Do you have a comprehensive list of name conventions you find suitable?
> >
> > Anything descriptive that alludes to the type of data being held there.
> >
> > There are 100's of good examples, but a handful of generic / bad ones.
> >
> > > > > +     bool logging_disabled;
> > > >
> > > > This debugging tool is probably never going to be used again.
> > > >
> > > > Keep it local.
> > > >
> > > > > +};
> > > > > +
> > > > > +struct dockram_ec_data {
> > > > > +     struct mutex ctl_lock; /* prevent simultaneous access */
> > > > > +     char ctl_data[DOCKRAM_ENTRY_BUFSIZE];
> > > > > +};
> > > > > +
> > > > > +#define to_ec_data(ec) \
> > > > > +     container_of(ec, struct asus_ec_data, info)
> > > > > +
> > > > > +/**
> > > > > + * asus_dockram_read - Read a register from the DockRAM device.
> > > > > + * @client: Handle to the DockRAM device.
> > > > > + * @reg: Register to read.
> > > > > + * @buf: Byte array into which data will be read; must be large enough to
> > > > > + *    hold the data returned by the DockRAM.
> > > > > + *
> > > > > + * This executes the DockRAM read based on the SMBus "block read" protocol
> > > > > + * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set
> > > > > + * register address.
> > > > > + *
> > > > > + * Returns a negative errno code else zero on success.
> > > > > + */
> > > > > +int asus_dockram_read(struct i2c_client *client, int reg, char *buf)
> > > > > +{
> > > >
> > > > Have you considered using Regmap for register access instead of
> > > > implementing custom functions?  Remaps already deals with caching and
> > > > locking mechanisms that you'd get for free.
> > > >
> > > > This looks like it would be replaced with devm_regmap_init_i2c().
> > > >
> > >
> > > I will consider this, thank you.
> > >
> 
> It seems that regmap does not fit for this purpose, but I might switch
> to plain i2c_smbus_read_i2c_block_data

Can you explain why Regmap doesn't work for you?

Regmap is just a wrapper about i2c_smbus_read_i2c_block_data() and friends.

> > > > > +     struct device *dev = &client->dev;
> > > > > +     int ret;
> > > > > +
> > > > > +     memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
> > > > > +     ret = i2c_smbus_read_i2c_block_data(client, reg,
> > > > > +                                         DOCKRAM_ENTRY_BUFSIZE, buf);
> > > > > +     if (ret < 0)
> > > > > +             return ret;
> > > > > +
> > > > > +     if (buf[0] > DOCKRAM_ENTRY_SIZE) {
> > > > > +             dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
> > > > > +                     DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > > > > +             return -EPROTO;
> > > > > +     }
> > > > > +
> > > > > +     dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n",
> > > > > +             DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > > >
> > > > Please remove all of these debug messages.
> > > >
> > >
> > > Why debug messages cannot be preserved? They are specifically marked as dev_dbg
> >
> > It's a general convention.
> >
> > After initial development, they tend to just litter the code-base.
> >
> > Debug prints can be useful higher up the stack though.
> >
> 
> I am fine with removing all debugs and logging but I strongly would
> like to keep EC model and firmware version along with susb and factory
> status. That may be quite useful in identifying EC used and its
> behavior without need in rebuilding the kernel and digging huge piles
> of downstream code in order to find how to dump these values.

Yes, you can keep this sort of thing as INFO.

-- 
Lee Jones

^ permalink raw reply

* Re: [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
From: sashiko-bot @ 2026-05-18 10:09 UTC (permalink / raw)
  To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, linux-input, robh, conor+dt, devicetree
In-Reply-To: <20260518095907.36158-2-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `reg` property specifies `minItems: 2` but is missing an explicit `maxItems: 2` constraint or an `items` array definition.
--

commit 1a4f9a19859ef3d1429d7fbcebe0842f0c3684af
Author: Svyatoslav Ryhel <clamor95@gmail.com>

dt-bindings: embedded-controller: document ASUS Transformer EC

This commit adds device tree binding documentation for the embedded
controller used in the ASUS Transformer device series. The controllers
are attached to an I2C bus and run custom ASUS firmware.

> diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
> new file mode 100644
> index 0000000000000..60b6375864aaf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml

[ ... ]

> +  reg:
> +    description:
> +      The ASUS Transformer EC has a main I2C address and an associated
> +      DockRAM device, which provides power-related functions for the
> +      embedded controller. Both addresses are required for operation.
> +    minItems: 2

Does this property need an explicit maxItems: 2 constraint or an items array
definition to properly bound the schema? 

> +
> +  reg-names:
> +    items:
> +      - const: ec
> +      - const: dockram

While the reg-names property correctly defines exactly two expected names,
without an explicit upper bound or items array, the reg property definition
might be incomplete according to standard dt-binding conventions.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=1

^ permalink raw reply

* [PATCH v7 7/7] power: supply: Add charger driver for Asus Transformers
From: Svyatoslav Ryhel @ 2026-05-18  9:59 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
	Ion Agorria, Michał Mirosław
  Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260518095907.36158-1-clamor95@gmail.com>

From: Michał Mirosław <mirq-linux@rere.qmqm.pl>

Add support for charger detection capabilities found in the embedded
controller of ASUS Transformer devices.

Suggested-by: Maxim Schwalm <maxim.schwalm@gmail.com>
Suggested-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/power/supply/Kconfig                  |  11 +
 drivers/power/supply/Makefile                 |   1 +
 .../supply/asus-transformer-ec-charger.c      | 207 ++++++++++++++++++
 3 files changed, 219 insertions(+)
 create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 1dc3d0b2e021..ebc6d5c01330 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -508,6 +508,17 @@ config CHARGER_88PM860X
 	help
 	  Say Y here to enable charger for Marvell 88PM860x chip.
 
+config CHARGER_ASUS_TRANSFORMER_EC
+	tristate "Asus Transformer's charger driver"
+	depends on MFD_ASUS_TRANSFORMER_EC
+	help
+	  Say Y here to enable support AC plug detection on Asus Transformer
+	  Dock.
+
+	  This sub-driver supports charger detection mechanism found in Asus
+	  Transformer tablets and mobile docks and controlled by special
+	  embedded controller.
+
 config CHARGER_PF1550
 	tristate "NXP PF1550 battery charger driver"
 	depends on MFD_PF1550
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 1313f367715c..93d17d28081e 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_CHARGER_RT9471)	+= rt9471.o
 obj-$(CONFIG_CHARGER_RT9756)	+= rt9756.o
 obj-$(CONFIG_BATTERY_TWL4030_MADC)	+= twl4030_madc_battery.o
 obj-$(CONFIG_CHARGER_88PM860X)	+= 88pm860x_charger.o
+obj-$(CONFIG_CHARGER_ASUS_TRANSFORMER_EC)	+= asus-transformer-ec-charger.o
 obj-$(CONFIG_CHARGER_PF1550)	+= pf1550-charger.o
 obj-$(CONFIG_BATTERY_RX51)	+= rx51_battery.o
 obj-$(CONFIG_AB8500_BM)		+= ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
diff --git a/drivers/power/supply/asus-transformer-ec-charger.c b/drivers/power/supply/asus-transformer-ec-charger.c
new file mode 100644
index 000000000000..e33f4389d337
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-charger.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+
+struct asus_ec_charger_data {
+	struct notifier_block nb;
+	struct asusec_core *ec;
+	struct power_supply *psy;
+	struct power_supply_desc psy_desc;
+};
+
+static enum power_supply_property asus_ec_charger_properties[] = {
+	POWER_SUPPLY_PROP_USB_TYPE,
+	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int asus_ec_charger_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+	enum power_supply_usb_type psu;
+	int ret;
+	u64 ctl;
+
+	/* Check if model name is requested first since it needs no hw access */
+	if (psp == POWER_SUPPLY_PROP_MODEL_NAME) {
+		val->strval = priv->ec->model;
+		return 0;
+	}
+
+	ret = asus_dockram_access_ctl(priv->ec->dockram, &ctl, 0, 0);
+	if (ret)
+		return ret;
+
+	switch (ctl & (ASUSEC_CTL_FULL_POWER_SOURCE | ASUSEC_CTL_DIRECT_POWER_SOURCE)) {
+	case ASUSEC_CTL_FULL_POWER_SOURCE:
+		psu = POWER_SUPPLY_USB_TYPE_CDP;	/* DOCK */
+		break;
+	case ASUSEC_CTL_DIRECT_POWER_SOURCE:
+		psu = POWER_SUPPLY_USB_TYPE_SDP;	/* USB */
+		break;
+	case 0:
+		psu = POWER_SUPPLY_USB_TYPE_UNKNOWN;	/* no power source connected */
+		break;
+	default:
+		psu = POWER_SUPPLY_USB_TYPE_ACA;	/* power adapter */
+		break;
+	}
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = psu != POWER_SUPPLY_USB_TYPE_UNKNOWN;
+		return 0;
+
+	case POWER_SUPPLY_PROP_USB_TYPE:
+		val->intval = psu;
+		return 0;
+
+	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+		if (ctl & ASUSEC_CTL_TEST_DISCHARGE)
+			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+		else if (ctl & ASUSEC_CTL_USB_CHARGE)
+			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+		else
+			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int asus_ec_charger_set_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					const union power_supply_propval *val)
+{
+	struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+		switch ((enum power_supply_charge_behaviour)val->intval) {
+		case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+			return asus_dockram_access_ctl(priv->ec->dockram, NULL,
+				ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+				ASUSEC_CTL_USB_CHARGE);
+
+		case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+			return asus_dockram_access_ctl(priv->ec->dockram, NULL,
+				ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE, 0);
+
+		case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+			return asus_dockram_access_ctl(priv->ec->dockram, NULL,
+				ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+				ASUSEC_CTL_TEST_DISCHARGE);
+		default:
+			return -EINVAL;
+		}
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int asus_ec_charger_property_is_writeable(struct power_supply *psy,
+						 enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct power_supply_desc asus_ec_charger_desc = {
+	.name = "asus-ec-charger",
+	.type = POWER_SUPPLY_TYPE_USB,
+	.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |
+			     BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) |
+			     BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
+	.usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+		     BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+		     BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+		     BIT(POWER_SUPPLY_USB_TYPE_ACA),
+	.properties = asus_ec_charger_properties,
+	.num_properties = ARRAY_SIZE(asus_ec_charger_properties),
+	.get_property = asus_ec_charger_get_property,
+	.set_property = asus_ec_charger_set_property,
+	.property_is_writeable = asus_ec_charger_property_is_writeable,
+	.no_thermal = true,
+};
+
+static int asus_ec_charger_notify(struct notifier_block *nb,
+				  unsigned long action, void *data)
+{
+	struct asus_ec_charger_data *priv =
+		container_of(nb, struct asus_ec_charger_data, nb);
+
+	switch (action) {
+	case ASUSEC_SMI_ACTION(POWER_NOTIFY):
+	case ASUSEC_SMI_ACTION(ADAPTER_EVENT):
+		power_supply_changed(priv->psy);
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int asus_ec_charger_probe(struct platform_device *pdev)
+{
+	struct asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
+	struct asus_ec_charger_data *priv;
+	struct device *dev = &pdev->dev;
+	struct power_supply_config cfg = { };
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+	priv->ec = ec;
+
+	cfg.fwnode = dev_fwnode(dev->parent);
+	cfg.drv_data = priv;
+
+	memcpy(&priv->psy_desc, &asus_ec_charger_desc, sizeof(priv->psy_desc));
+	priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-charger",
+					     priv->ec->name);
+	if (!priv->psy_desc.name)
+		return -ENOMEM;
+
+	priv->psy = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+	if (IS_ERR(priv->psy))
+		return dev_err_probe(dev, PTR_ERR(priv->psy),
+				     "Failed to register power supply\n");
+
+	priv->nb.notifier_call = asus_ec_charger_notify;
+
+	return blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
+}
+
+static void asus_ec_charger_remove(struct platform_device *pdev)
+{
+	struct asus_ec_charger_data *priv = platform_get_drvdata(pdev);
+	struct asusec_core *ec = priv->ec;
+
+	blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
+}
+
+static struct platform_driver asus_ec_charger_driver = {
+	.driver.name = "asus-transformer-ec-charger",
+	.probe = asus_ec_charger_probe,
+	.remove = asus_ec_charger_remove,
+};
+module_platform_driver(asus_ec_charger_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver");
+MODULE_LICENSE("GPL");
-- 
2.51.0


^ permalink raw reply related

* [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery
From: Svyatoslav Ryhel @ 2026-05-18  9:59 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
	Ion Agorria, Michał Mirosław
  Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260518095907.36158-1-clamor95@gmail.com>

From: Michał Mirosław <mirq-linux@rere.qmqm.pl>

Driver implements one battery cell per EC controller and supports reading
of battery status for ASUS Transformer's pad and mobile dock.

Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/power/supply/Kconfig                  |  11 +
 drivers/power/supply/Makefile                 |   1 +
 .../supply/asus-transformer-ec-battery.c      | 286 ++++++++++++++++++
 3 files changed, 298 insertions(+)
 create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 83392ed6a8da..1dc3d0b2e021 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -122,6 +122,17 @@ config BATTERY_CHAGALL
 	  This driver can also be built as a module. If so, the module will be
 	  called chagall-battery.
 
+config BATTERY_ASUS_TRANSFORMER_EC
+	tristate "Asus Transformer's battery driver"
+	depends on MFD_ASUS_TRANSFORMER_EC
+	help
+	  Say Y to enable support for battery status access on Tegra based
+	  ASUS Transformer devices.
+
+	  This sub-driver supports battery cells found in Asus Transformer
+	  tablets and mobile docks and controlled by a special embedded
+	  controller.
+
 config BATTERY_CPCAP
 	tristate "Motorola CPCAP PMIC battery driver"
 	depends on MFD_CPCAP && IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 7ee839dca7f3..1313f367715c 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_TEST_POWER)	+= test_power.o
 obj-$(CONFIG_BATTERY_88PM860X)	+= 88pm860x_battery.o
 obj-$(CONFIG_CHARGER_ADP5061)	+= adp5061.o
 obj-$(CONFIG_BATTERY_ACT8945A)	+= act8945a_charger.o
+obj-$(CONFIG_BATTERY_ASUS_TRANSFORMER_EC)	+= asus-transformer-ec-battery.o
 obj-$(CONFIG_BATTERY_AXP20X)	+= axp20x_battery.o
 obj-$(CONFIG_CHARGER_AXP20X)	+= axp20x_ac_power.o
 obj-$(CONFIG_BATTERY_CHAGALL)	+= chagall-battery.o
diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/power/supply/asus-transformer-ec-battery.c
new file mode 100644
index 000000000000..d08d155f72a3
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-battery.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/unaligned.h>
+
+#define ASUSEC_BATTERY_DATA_FRESH_MSEC		5000
+
+#define ASUSEC_BATTERY_DISCHARGING		BIT(6)
+#define ASUSEC_BATTERY_FULL_CHARGED		BIT(5)
+#define ASUSEC_BATTERY_NOT_CHARGING		BIT(4)
+
+#define TEMP_CELSIUS_OFFSET			2731
+
+struct asus_ec_battery_data {
+	struct asusec_core *ec;
+	struct power_supply *battery;
+	struct power_supply_desc psy_desc;
+	struct delayed_work poll_work;
+	struct mutex battery_lock; /* for data refresh */
+	unsigned long batt_data_ts;
+	int last_state;
+	u8 batt_data[DOCKRAM_ENTRY_BUFSIZE];
+};
+
+static int asus_ec_battery_refresh(struct asus_ec_battery_data *priv)
+{
+	struct i2c_client *client = priv->ec->dockram;
+	struct device *dev = &client->dev;
+	int ret = 0;
+
+	if (time_before(jiffies, priv->batt_data_ts))
+		return ret;
+
+	memset(priv->batt_data, 0, DOCKRAM_ENTRY_BUFSIZE);
+	ret = i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_BATT_CTL,
+					    DOCKRAM_ENTRY_SIZE, priv->batt_data);
+	if (ret < 0)
+		return ret;
+
+	if (priv->batt_data[0] > DOCKRAM_ENTRY_SIZE) {
+		dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
+			DOCKRAM_ENTRY_BUFSIZE, priv->batt_data, ret);
+		return -EPROTO;
+	}
+
+	priv->batt_data_ts = jiffies +
+		msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC);
+
+	return ret;
+}
+
+static enum power_supply_property asus_ec_battery_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+	POWER_SUPPLY_PROP_PRESENT,
+};
+
+static const unsigned int asus_ec_battery_prop_offs[] = {
+	[POWER_SUPPLY_PROP_STATUS] = 1,
+	[POWER_SUPPLY_PROP_VOLTAGE_MAX] = 3,
+	[POWER_SUPPLY_PROP_CURRENT_MAX] = 5,
+	[POWER_SUPPLY_PROP_TEMP] = 7,
+	[POWER_SUPPLY_PROP_VOLTAGE_NOW] = 9,
+	[POWER_SUPPLY_PROP_CURRENT_NOW] = 11,
+	[POWER_SUPPLY_PROP_CAPACITY] = 13,
+	[POWER_SUPPLY_PROP_CHARGE_NOW] = 15,
+	[POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW] = 17,
+	[POWER_SUPPLY_PROP_TIME_TO_FULL_NOW] = 19,
+};
+
+static int asus_ec_battery_get_value(struct asus_ec_battery_data *priv,
+				     enum power_supply_property psp)
+{
+	int ret, offs;
+
+	guard(mutex)(&priv->battery_lock);
+
+	if (psp >= ARRAY_SIZE(asus_ec_battery_prop_offs))
+		return -EINVAL;
+
+	offs = asus_ec_battery_prop_offs[psp];
+	if (!offs)
+		return -EINVAL;
+
+	ret = asus_ec_battery_refresh(priv);
+	if (ret < 0)
+		return ret;
+
+	if (offs >= priv->batt_data[0])
+		return -ENODATA;
+
+	return get_unaligned_le16(priv->batt_data + offs);
+}
+
+static int asus_ec_battery_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct asus_ec_battery_data *priv = power_supply_get_drvdata(psy);
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+
+	default:
+		ret = asus_ec_battery_get_value(priv, psp);
+		if (ret < 0)
+			return ret;
+
+		val->intval = (s16)ret;
+
+		switch (psp) {
+		case POWER_SUPPLY_PROP_STATUS:
+			if (ret & ASUSEC_BATTERY_FULL_CHARGED)
+				val->intval = POWER_SUPPLY_STATUS_FULL;
+			else if (ret & ASUSEC_BATTERY_NOT_CHARGING)
+				val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			else if (ret & ASUSEC_BATTERY_DISCHARGING)
+				val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+			else
+				val->intval = POWER_SUPPLY_STATUS_CHARGING;
+			break;
+
+		case POWER_SUPPLY_PROP_TEMP:
+			val->intval -= TEMP_CELSIUS_OFFSET;
+			break;
+
+		case POWER_SUPPLY_PROP_CHARGE_NOW:
+		case POWER_SUPPLY_PROP_CURRENT_NOW:
+		case POWER_SUPPLY_PROP_CURRENT_MAX:
+		case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+			val->intval *= 1000;
+			break;
+
+		case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+		case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+			val->intval *= 60;
+			break;
+
+		default:
+			break;
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+static void asus_ec_battery_poll_work(struct work_struct *work)
+{
+	struct asus_ec_battery_data *priv =
+		container_of(work, struct asus_ec_battery_data, poll_work.work);
+	int state;
+
+	state = asus_ec_battery_get_value(priv, POWER_SUPPLY_PROP_STATUS);
+	if (state < 0)
+		goto reschedule;
+
+	if (state & ASUSEC_BATTERY_FULL_CHARGED)
+		state = POWER_SUPPLY_STATUS_FULL;
+	else if (state & ASUSEC_BATTERY_NOT_CHARGING)
+		state = POWER_SUPPLY_STATUS_NOT_CHARGING;
+	else if (state & ASUSEC_BATTERY_DISCHARGING)
+		state = POWER_SUPPLY_STATUS_DISCHARGING;
+	else
+		state = POWER_SUPPLY_STATUS_CHARGING;
+
+	if (priv->last_state != state) {
+		priv->last_state = state;
+		power_supply_changed(priv->battery);
+	}
+
+reschedule:
+	/* continuously send uevent notification */
+	schedule_delayed_work(&priv->poll_work,
+			      msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+}
+
+static const struct power_supply_desc asus_ec_battery_desc = {
+	.name = "asus-ec-battery",
+	.type = POWER_SUPPLY_TYPE_BATTERY,
+	.properties = asus_ec_battery_properties,
+	.num_properties = ARRAY_SIZE(asus_ec_battery_properties),
+	.get_property = asus_ec_battery_get_property,
+	.external_power_changed = power_supply_changed,
+};
+
+static int asus_ec_battery_probe(struct platform_device *pdev)
+{
+	struct asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
+	struct asus_ec_battery_data *priv;
+	struct device *dev = &pdev->dev;
+	struct power_supply_config cfg = { };
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+
+	mutex_init(&priv->battery_lock);
+
+	priv->ec = ec;
+	priv->batt_data_ts = jiffies - 1;
+	priv->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
+
+	cfg.fwnode = dev_fwnode(dev->parent);
+	cfg.drv_data = priv;
+
+	memcpy(&priv->psy_desc, &asus_ec_battery_desc, sizeof(priv->psy_desc));
+	priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-battery",
+					     priv->ec->name);
+
+	priv->battery = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+	if (IS_ERR(priv->battery))
+		return dev_err_probe(dev, PTR_ERR(priv->battery),
+				     "Failed to register power supply\n");
+
+	ret = devm_delayed_work_autocancel(dev, &priv->poll_work,
+					   asus_ec_battery_poll_work);
+	if (ret)
+		return ret;
+
+	schedule_delayed_work(&priv->poll_work,
+			      msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+
+	return 0;
+}
+
+static int __maybe_unused asus_ec_battery_suspend(struct device *dev)
+{
+	struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
+
+	cancel_delayed_work_sync(&priv->poll_work);
+
+	return 0;
+}
+
+static int __maybe_unused asus_ec_battery_resume(struct device *dev)
+{
+	struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
+
+	schedule_delayed_work(&priv->poll_work,
+			      msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(asus_ec_battery_pm_ops,
+			 asus_ec_battery_suspend, asus_ec_battery_resume);
+
+static struct platform_driver asus_ec_battery_driver = {
+	.driver = {
+		.name = "asus-transformer-ec-battery",
+		.pm = &asus_ec_battery_pm_ops,
+	},
+	.probe = asus_ec_battery_probe,
+};
+module_platform_driver(asus_ec_battery_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's battery driver");
+MODULE_LICENSE("GPL");
-- 
2.51.0


^ permalink raw reply related


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