Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH] HID: Add HID_CLAIMED_INPUT guards in raw_event callbacks missing them
From: Greg Kroah-Hartman @ 2026-02-19 14:33 UTC (permalink / raw)
  To: linux-input
  Cc: linux-kernel, Greg Kroah-Hartman, Jiri Kosina, Benjamin Tissoires,
	Bastien Nocera, stable

In commit 2ff5baa9b527 ("HID: appleir: Fix potential NULL dereference at
raw event handle"), we handle the fact that raw event callbacks
can happen even for a HID device that has not been "claimed" causing a
crash if a broken device were attempted to be connected to the system.

Fix up the remaining in-tree HID drivers that forgot to add this same
check to resolve the same issue.

Cc: Jiri Kosina <jikos@kernel.org>
Cc: Benjamin Tissoires <bentiss@kernel.org>
Cc: Bastien Nocera <hadess@hadess.net>
Cc: linux-input@vger.kernel.org
Cc: stable <stable@kernel.org>
Assisted-by: gkh_clanker_2000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
This issue was found by running a tool to compare a past kernel CVE to
try to find any potential places in the existing codebase that was
missed with the original fix.

 drivers/hid/hid-cmedia.c          | 2 +-
 drivers/hid/hid-creative-sb0540.c | 2 +-
 drivers/hid/hid-zydacron.c        | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c
index 528d7f361215..8bf5649b0c79 100644
--- a/drivers/hid/hid-cmedia.c
+++ b/drivers/hid/hid-cmedia.c
@@ -99,7 +99,7 @@ static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report,
 {
 	struct cmhid *cm = hid_get_drvdata(hid);
 
-	if (len != CM6533_JD_RAWEV_LEN)
+	if (len != CM6533_JD_RAWEV_LEN || !(hid->claimed & HID_CLAIMED_INPUT))
 		goto out;
 	if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx)))
 		goto out;
diff --git a/drivers/hid/hid-creative-sb0540.c b/drivers/hid/hid-creative-sb0540.c
index b4c8e7a5d3e0..dfd6add353d1 100644
--- a/drivers/hid/hid-creative-sb0540.c
+++ b/drivers/hid/hid-creative-sb0540.c
@@ -153,7 +153,7 @@ static int creative_sb0540_raw_event(struct hid_device *hid,
 	u64 code, main_code;
 	int key;
 
-	if (len != 6)
+	if (len != 6 || !(hid->claimed & HID_CLAIMED_INPUT))
 		return 0;
 
 	/* From daemons/hw_hiddev.c sb0540_rec() in lirc */
diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c
index 3bdb26f45592..1aae80f848f5 100644
--- a/drivers/hid/hid-zydacron.c
+++ b/drivers/hid/hid-zydacron.c
@@ -114,7 +114,7 @@ static int zc_raw_event(struct hid_device *hdev, struct hid_report *report,
 	unsigned key;
 	unsigned short index;
 
-	if (report->id == data[0]) {
+	if (report->id == data[0] && (hdev->claimed & HID_CLAIMED_INPUT)) {
 
 		/* break keys */
 		for (index = 0; index < 4; index++) {
-- 
2.53.0


^ permalink raw reply related

* [dtor-input:for-linus] BUILD SUCCESS fc1e8a6f129d87c64ac8e58b50d9dfa66217cfda
From: kernel test robot @ 2026-02-19 13:18 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: fc1e8a6f129d87c64ac8e58b50d9dfa66217cfda  Input: bcm5974 - recover from failed mode switch

elapsed time: 842m

configs tested: 233
configs skipped: 4

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                              allyesconfig    gcc-15.2.0
arc                          axs101_defconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                 nsimosci_hs_smp_defconfig    clang-23
arc                   randconfig-001-20260219    clang-23
arc                   randconfig-002-20260219    clang-23
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                        clps711x_defconfig    clang-23
arm                                 defconfig    gcc-15.2.0
arm                           h3600_defconfig    clang-18
arm                       imx_v4_v5_defconfig    clang-18
arm                            mps2_defconfig    clang-23
arm                        multi_v5_defconfig    gcc-15.2.0
arm                         mv78xx0_defconfig    clang-18
arm                           omap1_defconfig    clang-23
arm                          pxa3xx_defconfig    gcc-15.2.0
arm                   randconfig-001-20260219    clang-23
arm                   randconfig-002-20260219    clang-23
arm                   randconfig-003-20260219    clang-23
arm                   randconfig-004-20260219    clang-23
arm                         s5pv210_defconfig    gcc-15.2.0
arm                         vf610m4_defconfig    clang-23
arm                    vt8500_v6_v7_defconfig    gcc-15.2.0
arm64                            alldefconfig    clang-23
arm64                            allmodconfig    clang-19
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260219    gcc-8.5.0
arm64                 randconfig-002-20260219    gcc-8.5.0
arm64                 randconfig-003-20260219    gcc-8.5.0
arm64                 randconfig-004-20260219    gcc-8.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260219    gcc-8.5.0
csky                  randconfig-002-20260219    gcc-8.5.0
hexagon                          alldefconfig    gcc-15.2.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon               randconfig-001-20260219    clang-17
hexagon               randconfig-002-20260219    clang-17
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386        buildonly-randconfig-001-20260219    gcc-14
i386        buildonly-randconfig-002-20260219    gcc-14
i386        buildonly-randconfig-003-20260219    gcc-14
i386        buildonly-randconfig-004-20260219    gcc-14
i386        buildonly-randconfig-005-20260219    gcc-14
i386        buildonly-randconfig-006-20260219    gcc-14
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260219    gcc-14
i386                  randconfig-002-20260219    gcc-14
i386                  randconfig-003-20260219    gcc-14
i386                  randconfig-004-20260219    gcc-14
i386                  randconfig-005-20260219    gcc-14
i386                  randconfig-006-20260219    gcc-14
i386                  randconfig-007-20260219    gcc-14
i386                  randconfig-011-20260219    clang-20
i386                  randconfig-012-20260219    clang-20
i386                  randconfig-013-20260219    clang-20
i386                  randconfig-014-20260219    clang-20
i386                  randconfig-015-20260219    clang-20
i386                  randconfig-016-20260219    clang-20
i386                  randconfig-017-20260219    clang-20
loongarch                        allmodconfig    clang-19
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260219    clang-17
loongarch             randconfig-002-20260219    clang-17
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                          amiga_defconfig    clang-23
m68k                                defconfig    clang-19
m68k                        m5272c3_defconfig    gcc-15.2.0
m68k                       m5475evb_defconfig    gcc-15.2.0
m68k                          multi_defconfig    clang-23
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                         bigsur_defconfig    gcc-15.2.0
mips                      fuloong2e_defconfig    gcc-15.2.0
mips                           ip22_defconfig    clang-23
mips                          rb532_defconfig    clang-18
nios2                            allmodconfig    clang-23
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260219    clang-17
nios2                 randconfig-002-20260219    clang-17
openrisc                         allmodconfig    clang-23
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260219    clang-23
parisc                randconfig-002-20260219    clang-23
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc                      cm5200_defconfig    clang-23
powerpc                   currituck_defconfig    gcc-15.2.0
powerpc                     ep8248e_defconfig    clang-23
powerpc                      ep88xc_defconfig    clang-18
powerpc                        fsp2_defconfig    gcc-15.2.0
powerpc                  iss476-smp_defconfig    clang-18
powerpc                      katmai_defconfig    gcc-15.2.0
powerpc                    mvme5100_defconfig    gcc-15.2.0
powerpc                      pasemi_defconfig    clang-23
powerpc                      ppc44x_defconfig    gcc-15.2.0
powerpc               randconfig-001-20260219    clang-23
powerpc               randconfig-002-20260219    clang-23
powerpc64             randconfig-001-20260219    clang-23
powerpc64             randconfig-002-20260219    clang-23
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260219    clang-17
riscv                 randconfig-002-20260219    clang-17
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260219    clang-17
s390                  randconfig-002-20260219    clang-17
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                         apsh4a3a_defconfig    gcc-15.2.0
sh                                  defconfig    gcc-14
sh                 kfr2r09-romimage_defconfig    clang-23
sh                          lboxre2_defconfig    gcc-15.2.0
sh                            migor_defconfig    gcc-15.2.0
sh                          r7785rp_defconfig    gcc-15.2.0
sh                    randconfig-001-20260219    clang-17
sh                    randconfig-002-20260219    clang-17
sh                          rsk7264_defconfig    gcc-15.2.0
sh                          rsk7269_defconfig    gcc-15.2.0
sh                           se7343_defconfig    gcc-15.2.0
sh                           se7705_defconfig    clang-18
sh                            titan_defconfig    clang-18
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260219    gcc-8.5.0
sparc                 randconfig-002-20260219    gcc-8.5.0
sparc64                          alldefconfig    clang-23
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64               randconfig-001-20260219    gcc-8.5.0
sparc64               randconfig-002-20260219    gcc-8.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260219    gcc-8.5.0
um                    randconfig-002-20260219    gcc-8.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260219    gcc-14
x86_64      buildonly-randconfig-002-20260219    gcc-14
x86_64      buildonly-randconfig-003-20260219    gcc-14
x86_64      buildonly-randconfig-004-20260219    gcc-14
x86_64      buildonly-randconfig-005-20260219    gcc-14
x86_64      buildonly-randconfig-006-20260219    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260219    clang-20
x86_64                randconfig-002-20260219    clang-20
x86_64                randconfig-003-20260219    clang-20
x86_64                randconfig-004-20260219    clang-20
x86_64                randconfig-005-20260219    clang-20
x86_64                randconfig-006-20260219    clang-20
x86_64                randconfig-011-20260219    gcc-14
x86_64                randconfig-012-20260219    gcc-14
x86_64                randconfig-013-20260219    gcc-14
x86_64                randconfig-014-20260219    gcc-14
x86_64                randconfig-015-20260219    gcc-14
x86_64                randconfig-016-20260219    gcc-14
x86_64                randconfig-071-20260219    clang-20
x86_64                randconfig-071-20260219    gcc-14
x86_64                randconfig-072-20260219    gcc-14
x86_64                randconfig-073-20260219    gcc-14
x86_64                randconfig-074-20260219    gcc-14
x86_64                randconfig-075-20260219    clang-20
x86_64                randconfig-075-20260219    gcc-14
x86_64                randconfig-076-20260219    clang-20
x86_64                randconfig-076-20260219    gcc-14
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                       common_defconfig    gcc-15.2.0
xtensa                randconfig-001-20260219    gcc-8.5.0
xtensa                randconfig-002-20260219    gcc-8.5.0

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH] HID: generic: add LampArray support via hid-lamparray helper
From: Tim Guttzeit @ 2026-02-19 13:02 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: wse, Tim Guttzeit, linux-kernel, linux-input

Add a new hid-lamparray helper module and integrate it with the
hid-generic driver.

The helper provides basic support for devices exposing a
Lighting/LampArray application collection (usage page 0x59) and
registers a single-zone RGB LED representation via the LED
subsystem.

hid-generic now checks for LampArray support after hid_parse() and
optionally registers a lamparray instance. Failures in the helper
do not abort device probe to keep the device functional.

LampArray resources are released on driver remove.

Signed-off-by: Tim Guttzeit <tgu@tuxedocomputers.com>
---
 drivers/hid/Makefile        |   1 +
 drivers/hid/hid-generic.c   |  41 +-
 drivers/hid/hid-lamparray.c | 855 ++++++++++++++++++++++++++++++++++++
 drivers/hid/hid-lamparray.h |  68 +++
 4 files changed, 963 insertions(+), 2 deletions(-)
 create mode 100644 drivers/hid/hid-lamparray.c
 create mode 100644 drivers/hid/hid-lamparray.h

diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daedeb8..e6903be9c4df 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_HID)		+= hid.o
 obj-$(CONFIG_UHID)		+= uhid.o
 
 obj-$(CONFIG_HID_GENERIC)	+= hid-generic.o
+obj-$(CONFIG_HID_GENERIC)	+= hid-lamparray.o
 
 hid-$(CONFIG_HIDRAW)		+= hidraw.o
 
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index c2de916747de..650c2121b403 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -20,6 +20,7 @@
 #include <asm/byteorder.h>
 
 #include <linux/hid.h>
+#include "hid-lamparray.h"
 
 static struct hid_driver hid_generic;
 
@@ -31,7 +32,7 @@ static int __check_hid_generic(struct device_driver *drv, void *data)
 	if (hdrv == &hid_generic)
 		return 0;
 
-	return hid_match_device(hdev, hdrv) != NULL;
+	return !!hid_match_device(hdev, hdrv);
 }
 
 static bool hid_generic_match(struct hid_device *hdev,
@@ -60,6 +61,7 @@ static int hid_generic_probe(struct hid_device *hdev,
 			     const struct hid_device_id *id)
 {
 	int ret;
+	struct lamparray *la = NULL;
 
 	hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
 
@@ -67,7 +69,31 @@ static int hid_generic_probe(struct hid_device *hdev,
 	if (ret)
 		return ret;
 
-	return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret)
+		return ret;
+
+	/*
+	 * Optional: attach LampArray support if present.
+	 * Never fail probe on LampArray errors; keep device functional.
+	 */
+	if (lamparray_is_supported_device(hdev)) {
+		const struct lamparray_init_state init = {
+			.r = 0xff,
+			.g = 0xff,
+			.b = 0xff,
+			.brightness = LED_FULL,
+		};
+
+		la = lamparray_register(hdev, &init);
+		if (IS_ERR(la)) {
+			hid_warn(hdev, "LampArray init failed: %ld\n", PTR_ERR(la));
+			la = NULL;
+		}
+	}
+
+	hid_set_drvdata(hdev, la);
+	return 0;
 }
 
 static int hid_generic_reset_resume(struct hid_device *hdev)
@@ -78,6 +104,16 @@ static int hid_generic_reset_resume(struct hid_device *hdev)
 	return 0;
 }
 
+static void hid_generic_remove(struct hid_device *hdev)
+{
+	struct lamparray *la = hid_get_drvdata(hdev);
+
+	if (la)
+		lamparray_unregister(la);
+
+	hid_hw_stop(hdev);
+}
+
 static const struct hid_device_id hid_table[] = {
 	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
 	{ }
@@ -90,6 +126,7 @@ static struct hid_driver hid_generic = {
 	.match = hid_generic_match,
 	.probe = hid_generic_probe,
 	.reset_resume = hid_generic_reset_resume,
+	.remove = hid_generic_remove,
 };
 module_hid_driver(hid_generic);
 
diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
new file mode 100644
index 000000000000..5be46fff0191
--- /dev/null
+++ b/drivers/hid/hid-lamparray.c
@@ -0,0 +1,855 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * hid-lamparray.c - HID LampArray helper module (single-zone RGB)
+ *
+ * Helper module for HID drivers supporting devices that expose a
+ * Lighting and Illumination (LampArray) application collection
+ * (usage page 0x59).
+ *
+ * The module provides a minimal integration with the LED subsystem
+ * and treats the device as a single zone: all lamps share one RGB
+ * value and a global brightness level. It does not implement multi-
+ * zone layouts or hardware effects.
+ *
+ *
+ * If enabled, one multicolor LED class device is registered under
+ * /sys/class/leds/<HID-ID> to expose the single-zone RGB control.
+ *
+ * The use_leds_uapi sysfs attribute is attached directly to the HID device
+ * under /sys/bus/hid/devices/<HID-ID>/use_leds_uapi.Writing 0 to use_leds_uapi
+ * unregisters the LED class device. The last state is kept cached. Writing 1
+ * registers it again and restores the cached state to hardware.
+ *
+ * State is cached as last known RGB + brightness. Setting sends a HID
+ * SET_REPORT. Getting issues a HID GET_REPORT and updates the cache on
+ * mismatch. Since the device is handled as single-zone, readback only queries
+ * lamp 0 when a lamp range is available.
+ *
+ * The module does not bind to devices on its own. Instead, a HID
+ * driver may query support via lamparray_is_supported_device() after
+ * hid_parse() and create an instance using lamparray_register().
+ *
+ * Copyright (C) 2026 Tim Guttzeit <tgu@tuxedocomputers.com>
+ */
+
+#include "hid-lamparray.h"
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/xarray.h>
+
+/* Constants */
+
+/* HID usages (LampArray, etc.) */
+#define HID_LIGHTING_ILLUMINATION_USAGE_PAGE	0x0059
+
+#define HID_LAIP_LAMP_COUNT			0x0003
+#define HID_LAIP_LAMP_ID			0x0021
+#define HID_LAIP_RED_UPDATE_CHANNEL		0x0051
+#define HID_LAIP_GREEN_UPDATE_CHANNEL		0x0052
+#define HID_LAIP_BLUE_UPDATE_CHANNEL		0x0053
+#define HID_LAIP_INTENSITY_UPDATE_CHANNEL	0x0054
+#define HID_LAIP_LAMP_ID_START			0x0061
+#define HID_LAIP_LAMP_ID_END			0x0062
+#define HID_LAIP_AUTONOMOUS_MODE		0x0071
+
+#define HID_APPLICATION_COLLECTION_USAGE_TYPE	0x0001
+
+/* Device state */
+
+struct lamparray_quirks {
+	unsigned long flags;
+	int fixed_lamp_count;
+};
+
+#define LAMPARRAY_QUIRK_LAMPCOUNT_FIXED BIT(0)
+
+struct lamparray_quirk_entry {
+	u16 vendor;
+	u16 product;
+	unsigned long flags;
+	int fixed_lamp_count;
+};
+
+static const struct lamparray_quirk_entry lamparray_quirk_table[] = {
+	{ 0xcafe, 0x4005, LAMPARRAY_QUIRK_LAMPCOUNT_FIXED, 12 },
+	{}
+};
+
+static const struct lamparray_quirk_entry *
+lamparray_lookup_quirks(struct hid_device *hdev)
+{
+	const struct lamparray_quirk_entry *e;
+
+	for (e = lamparray_quirk_table; e->vendor; e++) {
+		if (hdev->vendor == e->vendor && hdev->product == e->product)
+			return e;
+	}
+	return NULL;
+}
+
+struct lamparray_device {
+	const struct lamparray_quirk_entry *quirks;
+
+	struct hid_device *hdev;
+	struct hid_report *update_report;
+
+	struct hid_field *red_field;
+	int red_index;
+	struct hid_field *green_field;
+	int green_index;
+	struct hid_field *blue_field;
+	int blue_index;
+	struct hid_field *intensity_field;
+	int intensity_index;
+
+	struct hid_report *autonomous_report;
+	struct hid_field *autonomous_field;
+
+	struct hid_field *range_start_field;
+	int range_start_index;
+
+	struct hid_field *range_end_field;
+	int range_end_index;
+
+	struct hid_field *lamp_count_field;
+	int lamp_count;
+	int lamp_count_index;
+
+	struct led_classdev_mc mc_cdev;
+	struct mc_subled subleds[3];
+
+	struct mutex lock; /* protects cached state and HID access */
+
+	u8 last_r;
+	u8 last_g;
+	u8 last_b;
+	enum led_brightness last_brightness;
+
+	bool use_leds_uapi;
+	bool led_registered;
+};
+
+/*
+ * Opaque handle exposed to callers via the header.
+ * Keep the actual state in lamparray_device, but return a stable pointer.
+ */
+struct lamparray {
+	struct lamparray_device ldev;
+};
+
+static DEFINE_XARRAY(lamparray_by_hdev);
+
+/* HID helper functions */
+
+#ifdef DEBUG
+static void lamparray_dump_reports(struct hid_device *hdev)
+{
+	struct hid_report_enum *re;
+	struct hid_report *report;
+	int i, j, report_type;
+
+	for (report_type = 0; report_type < HID_REPORT_TYPES; report_type++) {
+		re = &hdev->report_enum[report_type];
+		hid_dbg(hdev, "Dumping reports for report type %d",
+			report_type);
+		list_for_each_entry(report, &re->report_list, list) {
+			hid_dbg(hdev,
+				"lamparray: report id=%u type=%d maxfield=%u\n",
+				report->id, report->type, report->maxfield);
+
+			for (i = 0; i < report->maxfield; i++) {
+				struct hid_field *field = report->field[i];
+
+				for (j = 0; j < field->maxusage; j++) {
+					u32 usage = field->usage[j].hid;
+					u16 page = usage >> 16;
+					u16 id = usage & 0xFFFF;
+
+					hid_dbg(hdev,
+						"lamparray: report %u field %d usage[%d]: page=0x%04x id=0x%04x\n",
+						report->id, i, j, page, id);
+				}
+			}
+		}
+	}
+}
+#else
+static inline void lamparray_dump_reports(struct hid_device *hdev)
+{}
+#endif
+
+static int lamparray_read_lamp_count(struct lamparray_device *ldev)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report *report;
+
+	if (ldev->quirks &&
+	    (ldev->quirks->flags & LAMPARRAY_QUIRK_LAMPCOUNT_FIXED)) {
+		ldev->lamp_count = ldev->quirks->fixed_lamp_count;
+		hid_dbg(hdev, "LampCount from quirk: %d\n", ldev->lamp_count);
+		return 0;
+	}
+	if (!ldev->lamp_count_field) {
+		hid_warn(hdev, "No LampCount field found\n");
+		return -ENODEV;
+	}
+
+	report = ldev->lamp_count_field->report;
+
+	if (!report) {
+		hid_warn(hdev, "LampCount field has no report\n");
+		return -ENODEV;
+	}
+	hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+	ldev->lamp_count =
+		ldev->lamp_count_field->value[ldev->lamp_count_index];
+
+	hid_dbg(hdev, "LampCount from device: %d\n", ldev->lamp_count);
+
+	if (ldev->lamp_count <= 0) {
+		hid_warn(hdev, "LampCount is %d (invalid)\n", ldev->lamp_count);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int lamparray_parse_update_report(struct lamparray_device *ldev)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report_enum *re;
+	struct hid_report *report;
+	struct hid_field *field;
+	int i, j;
+
+	lamparray_dump_reports(hdev);
+
+	re = &hdev->report_enum[HID_FEATURE_REPORT];
+
+	list_for_each_entry(report, &re->report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			if (!field)
+				continue;
+
+			if (!field->usage || !field->maxusage)
+				continue;
+
+			for (j = 0; j < field->maxusage; j++) {
+				u32 usage = field->usage[j].hid;
+				u16 page = usage >> 16;
+				u16 id = usage & 0xffff;
+
+				if (page !=
+				    HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
+					continue;
+				switch (id) {
+				case HID_LAIP_LAMP_COUNT:
+					ldev->lamp_count_field = field;
+					ldev->lamp_count_index = j;
+					break;
+				case HID_LAIP_RED_UPDATE_CHANNEL:
+					ldev->update_report = report;
+					ldev->red_field = field;
+					ldev->red_index = j;
+					break;
+				case HID_LAIP_GREEN_UPDATE_CHANNEL:
+					ldev->update_report = report;
+					ldev->green_field = field;
+					ldev->green_index = j;
+					break;
+				case HID_LAIP_BLUE_UPDATE_CHANNEL:
+					ldev->update_report = report;
+					ldev->blue_field = field;
+					ldev->blue_index = j;
+					break;
+				case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
+					ldev->update_report = report;
+					ldev->intensity_field = field;
+					ldev->intensity_index = j;
+					break;
+				case HID_LAIP_LAMP_ID_START:
+					ldev->range_start_field = field;
+					ldev->range_start_index = j;
+					break;
+				case HID_LAIP_LAMP_ID_END:
+					ldev->range_end_field = field;
+					ldev->range_end_index = j;
+					break;
+				case HID_LAIP_AUTONOMOUS_MODE:
+					ldev->autonomous_field = field;
+					ldev->autonomous_report = report;
+					break;
+				default:
+					break;
+				}
+			}
+		}
+	}
+
+	if (!ldev->update_report || !ldev->red_field || !ldev->green_field ||
+	    !ldev->blue_field || !ldev->autonomous_report || !ldev->autonomous_field)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int lamparray_hw_set_autonomous(struct lamparray_device *ldev,
+				       bool enable)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_field *field = ldev->autonomous_field;
+	struct hid_report *report = ldev->autonomous_report;
+
+	if (!field || !report)
+		return -ENODEV;
+
+	field->value[0] = enable ? 1 : 0;
+
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+	return 0;
+}
+
+static int lamparray_hw_set_state(struct lamparray_device *ldev, u8 r, u8 g,
+				  u8 b, u8 intensity)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report *report = ldev->update_report;
+	int i, j;
+
+	if (!report || !ldev->red_field || !ldev->green_field ||
+	    !ldev->blue_field || !ldev->intensity_field)
+		return -ENODEV;
+
+	if (ldev->range_start_field && ldev->range_end_field) {
+		ldev->range_start_field->value[ldev->range_start_index] = 0;
+		ldev->range_end_field->value[ldev->range_end_index] = ldev->lamp_count - 1;
+	}
+
+	for (i = 0; i < report->maxfield; i++) {
+		struct hid_field *field = report->field[i];
+
+		if (!field || !field->usage || !field->maxusage)
+			continue;
+
+		for (j = 0; j < field->maxusage; j++) {
+			u32 usage = field->usage[j].hid;
+			u16 page = usage >> 16;
+			u16 id = usage & 0xffff;
+
+			if (page != HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
+				continue;
+
+			switch (id) {
+			case HID_LAIP_RED_UPDATE_CHANNEL:
+				field->value[j] = r;
+				break;
+			case HID_LAIP_GREEN_UPDATE_CHANNEL:
+				field->value[j] = g;
+				break;
+			case HID_LAIP_BLUE_UPDATE_CHANNEL:
+				field->value[j] = b;
+				break;
+			case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
+				field->value[j] = intensity;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+	return 0;
+}
+
+static int lamparray_hw_get_state(struct lamparray_device *ldev, u8 *r, u8 *g,
+				  u8 *b, enum led_brightness *brightness)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report *report = ldev->update_report;
+
+	if (!report || !ldev->red_field || !ldev->green_field ||
+	    !ldev->blue_field || !ldev->intensity_field)
+		return -ENODEV;
+
+	if (!r || !g || !b || !brightness)
+		return -EINVAL;
+
+	/* Single-zone: Reading lamp 0 only suffices */
+	if (ldev->range_start_field && ldev->range_end_field) {
+		ldev->range_start_field->value[ldev->range_start_index] = 0;
+		ldev->range_end_field->value[ldev->range_end_index] = 0;
+	}
+
+	hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+
+	*r = ldev->red_field->value[ldev->red_index];
+	*g = ldev->green_field->value[ldev->green_index];
+	*b = ldev->blue_field->value[ldev->blue_index];
+	*brightness = ldev->intensity_field->value[ldev->intensity_index];
+
+	return 0;
+}
+
+/* Helper functions */
+
+static int lamparray_restore_state(struct lamparray_device *ldev)
+{
+	u8 r, g, b;
+	int ret;
+	enum led_brightness brightness;
+
+	mutex_lock(&ldev->lock);
+
+	if (!ldev->use_leds_uapi) {
+		mutex_unlock(&ldev->lock);
+		return 0;
+	}
+
+	r = ldev->last_r;
+	g = ldev->last_g;
+	b = ldev->last_b;
+	brightness = ldev->last_brightness;
+
+	ldev->mc_cdev.subled_info[0].brightness = r;
+	ldev->mc_cdev.subled_info[1].brightness = g;
+	ldev->mc_cdev.subled_info[2].brightness = b;
+	ldev->mc_cdev.led_cdev.brightness = brightness;
+
+	mutex_unlock(&ldev->lock);
+
+	ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
+	return ret;
+}
+
+/* LEDs API */
+
+static int lamparray_led_brightness_set(struct led_classdev *cdev,
+					enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+	struct lamparray_device *ldev =
+		container_of(mc, struct lamparray_device, mc_cdev);
+	struct lamparray *la = container_of(ldev, struct lamparray, ldev);
+	u8 r, g, b;
+	int ret;
+
+	if (!la)
+		return -ENODEV;
+	ldev = &la->ldev;
+
+	ret = led_mc_calc_color_components(mc, brightness);
+	if (ret)
+		return ret;
+
+	r = mc->subled_info[0].brightness;
+	g = mc->subled_info[1].brightness;
+	b = mc->subled_info[2].brightness;
+
+	ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
+	if (ret)
+		hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
+			ret);
+
+	mutex_lock(&ldev->lock);
+	ldev->last_r = r;
+	ldev->last_g = g;
+	ldev->last_b = b;
+	ldev->last_brightness = brightness;
+	mutex_unlock(&ldev->lock);
+
+	return 0;
+}
+
+static enum led_brightness
+lamparray_led_brightness_get(struct led_classdev *cdev)
+{
+	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+	struct lamparray_device *ldev =
+		container_of(mc, struct lamparray_device, mc_cdev);
+	enum led_brightness brightness;
+	struct lamparray *la = container_of(ldev, struct lamparray, ldev);
+	u8 rr, gg, bb;
+	enum led_brightness br;
+	int ret;
+
+	/* Default: cache (also used while registering LED classdev) */
+	mutex_lock(&ldev->lock);
+	brightness = ldev->last_brightness;
+	mutex_unlock(&ldev->lock);
+
+	/* Only do HID readback after registration completed */
+	if (READ_ONCE(ldev->led_registered)) {
+		if (!la)
+			return brightness;
+		ldev = &la->ldev;
+
+		ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
+		if (ret) {
+			hid_warn(ldev->hdev,
+				 "Failed to read LampArray state (%d), using cached brightness %u\n",
+				 ret, brightness);
+			return brightness;
+		}
+
+		mutex_lock(&ldev->lock);
+		if (ldev->last_r != rr || ldev->last_g != gg ||
+		    ldev->last_b != bb || ldev->last_brightness != br) {
+			ldev->last_r = rr;
+			ldev->last_g = gg;
+			ldev->last_b = bb;
+			ldev->last_brightness = br;
+
+			if (ldev->led_registered && ldev->mc_cdev.subled_info) {
+				ldev->mc_cdev.subled_info[0].brightness = rr;
+				ldev->mc_cdev.subled_info[1].brightness = gg;
+				ldev->mc_cdev.subled_info[2].brightness = bb;
+			}
+		}
+		mutex_unlock(&ldev->lock);
+		return br;
+	}
+	return brightness;
+}
+
+static int lamparray_register_led(struct lamparray_device *ldev)
+{
+	struct device *dev = &ldev->hdev->dev;
+	struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
+	u8 r_i, g_i, b_i;
+	int ret;
+
+	mutex_lock(&ldev->lock);
+
+	if (ldev->led_registered) {
+		mutex_unlock(&ldev->lock);
+		return 0;
+	}
+
+	if (!cdev->name) {
+		cdev->name =
+			devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
+		if (!cdev->name) {
+			mutex_unlock(&ldev->lock);
+			return -ENOMEM;
+		}
+	}
+
+	cdev->max_brightness = 255;
+	cdev->brightness_set_blocking = lamparray_led_brightness_set;
+	cdev->brightness_get = lamparray_led_brightness_get;
+	cdev->brightness = ldev->last_brightness;
+
+	ldev->subleds[0].color_index = LED_COLOR_ID_RED;
+	ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
+	ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
+
+	/*
+	 * Initialize the color mix (multi_intensity) from the last known HW/init
+	 * state so that writing only /brightness scales the expected default color
+	 * instead of white.
+	 *
+	 * If last_brightness is non-zero, treat last_r/g/b as per-channel
+	 * brightness and normalize back to intensities (0..255).
+	 * If last_brightness is zero, keep last_r/g/b as the intended mix.
+	 */
+	if (ldev->last_brightness) {
+		r_i = (u8)min_t(unsigned int, 255,
+				(ldev->last_r * 255u) / ldev->last_brightness);
+		g_i = (u8)min_t(unsigned int, 255,
+				(ldev->last_g * 255u) / ldev->last_brightness);
+		b_i = (u8)min_t(unsigned int, 255,
+				(ldev->last_b * 255u) / ldev->last_brightness);
+	} else {
+		r_i = ldev->last_r;
+		g_i = ldev->last_g;
+		b_i = ldev->last_b;
+	}
+
+	ldev->subleds[0].intensity = r_i;
+	ldev->subleds[1].intensity = g_i;
+	ldev->subleds[2].intensity = b_i;
+
+	ldev->mc_cdev.subled_info = ldev->subleds;
+	ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
+
+	/* Ensure subled_info[].brightness matches intensity + brightness */
+	led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
+
+	ldev->mc_cdev.subled_info = ldev->subleds;
+	ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
+
+	mutex_unlock(&ldev->lock);
+
+	ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
+	if (ret)
+		return ret;
+
+	mutex_lock(&ldev->lock);
+	ldev->led_registered = true;
+	mutex_unlock(&ldev->lock);
+
+	return 0;
+}
+
+static void lamparray_unregister_led(struct lamparray_device *ldev)
+{
+	bool was_registered;
+
+	mutex_lock(&ldev->lock);
+	was_registered = ldev->led_registered;
+	ldev->led_registered = false;
+	mutex_unlock(&ldev->lock);
+
+	if (!was_registered)
+		return;
+
+	led_classdev_multicolor_unregister(&ldev->mc_cdev);
+}
+
+/* Sysfs */
+
+static struct lamparray_device *
+lamparray_ldev_from_sysfs_dev(struct device *dev)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+
+	return xa_load(&lamparray_by_hdev, (unsigned long)hdev);
+}
+
+static ssize_t use_leds_uapi_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+
+	if (!ldev)
+		return -ENODEV;
+
+	return sysfs_emit(buf, "%d\n", ldev->use_leds_uapi);
+}
+
+static ssize_t use_leds_uapi_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+	int val;
+	int old_val;
+	int ret;
+
+	if (!ldev)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val != 0 && val != 1)
+		return -EINVAL;
+
+	mutex_lock(&ldev->lock);
+	old_val = ldev->use_leds_uapi;
+
+	if (val == old_val) {
+		mutex_unlock(&ldev->lock);
+		return count;
+	}
+
+	ldev->use_leds_uapi = val;
+	mutex_unlock(&ldev->lock);
+
+	if (val == 1) {
+		ret = lamparray_register_led(ldev);
+		if (ret) {
+			mutex_lock(&ldev->lock);
+			ldev->use_leds_uapi = old_val;
+			mutex_unlock(&ldev->lock);
+			return ret;
+		}
+		ret = lamparray_restore_state(ldev);
+		if (ret) {
+			hid_err(ldev->hdev, "Could not restore state: %d", ret);
+			return ret;
+		}
+
+	} else {
+		lamparray_unregister_led(ldev);
+	}
+
+	return count;
+}
+static DEVICE_ATTR_RW(use_leds_uapi);
+
+static struct attribute *lamparray_attrs[] = {
+	&dev_attr_use_leds_uapi.attr,
+	NULL,
+};
+
+static const struct attribute_group lamparray_attr_group = {
+	.attrs = lamparray_attrs,
+};
+
+static int lamparray_register_sysfs(struct lamparray_device *ldev)
+{
+	struct device *dev = &ldev->hdev->dev;
+	int ret;
+
+	ret = sysfs_create_group(&dev->kobj, &lamparray_attr_group);
+	if (ret)
+		hid_err(ldev->hdev,
+			"Failed to create lamparray sysfs group: %d\n", ret);
+
+	return ret;
+}
+
+static void lamparray_remove_sysfs(struct lamparray_device *ldev)
+{
+	sysfs_remove_group(&ldev->hdev->dev.kobj, &lamparray_attr_group);
+}
+
+/* Public API */
+
+bool lamparray_is_supported_device(struct hid_device *hdev)
+{
+	unsigned int i;
+
+	hid_dbg(hdev, "lamparray: walking %u collections\n",
+		hdev->maxcollection);
+
+	for (i = 0; i < hdev->maxcollection; i++) {
+		struct hid_collection *col = &hdev->collection[i];
+		u16 page = (col->usage & HID_USAGE_PAGE) >> 16;
+		u16 code = col->usage & HID_USAGE;
+
+		hid_dbg(hdev,
+			"lamparray:  collection[%u]: type=%u level=%u usage=0x%08x page=0x%04x code=0x%04x\n",
+			i, col->type, col->level, col->usage, page, code);
+
+		if (col->type == HID_COLLECTION_APPLICATION &&
+		    page == HID_LIGHTING_ILLUMINATION_USAGE_PAGE &&
+		    code == HID_APPLICATION_COLLECTION_USAGE_TYPE) {
+			return true;
+		}
+	}
+	return false;
+}
+EXPORT_SYMBOL_GPL(lamparray_is_supported_device);
+
+struct lamparray *
+lamparray_register(struct hid_device *hdev,
+		   const struct lamparray_init_state *led_init_state)
+{
+	int ret;
+	struct lamparray *la;
+	struct lamparray_device *ldev;
+
+	if (!hdev)
+		return ERR_PTR(-ENODEV);
+
+	la = kzalloc(sizeof(*la), GFP_KERNEL);
+	if (!la)
+		return ERR_PTR(-ENOMEM);
+
+	ldev = &la->ldev;
+
+	mutex_init(&ldev->lock);
+	ldev->hdev = hdev;
+	ldev->quirks = lamparray_lookup_quirks(hdev);
+	ldev->use_leds_uapi = 1;
+	ldev->led_registered = false;
+	if (!led_init_state) {
+		ldev->last_r = 255;
+		ldev->last_g = 255;
+		ldev->last_b = 255;
+		ldev->last_brightness = LED_OFF;
+	} else {
+		ldev->last_r = led_init_state->r;
+		ldev->last_g = led_init_state->g;
+		ldev->last_b = led_init_state->b;
+		ldev->last_brightness = led_init_state->brightness;
+	}
+	ret = lamparray_parse_update_report(ldev);
+	if (ret) {
+		hid_err(hdev, "No LampArray update report found: %d\n", ret);
+		goto err_free;
+	}
+
+	ret = lamparray_read_lamp_count(ldev);
+	if (ret) {
+		hid_err(hdev,
+			"Could not determine LampCount. This device needs a quirk for a fixed LampCount: %d\n",
+			ret);
+		goto err_unregister_led;
+	}
+
+	ret = lamparray_register_led(ldev);
+	if (ret) {
+		hid_warn(hdev, "Failed to register LED UAPI: %d\n", ret);
+		mutex_lock(&ldev->lock);
+		ldev->use_leds_uapi = 0;
+		mutex_unlock(&ldev->lock);
+	}
+
+	ret = xa_err(xa_store(&lamparray_by_hdev, (unsigned long)hdev, ldev,
+			      GFP_KERNEL));
+	if (ret)
+		goto err_unregister_led;
+
+	ret = lamparray_register_sysfs(ldev);
+	if (ret)
+		goto err_xa_erase;
+
+	ret = lamparray_hw_set_autonomous(ldev, false);
+	if (ret) {
+		hid_err(hdev, "Could not disable autonomous mode: %d", ret);
+		goto err_remove_sysfs;
+	}
+
+	hid_info(hdev, "LampArray device registered\n");
+
+	ret = lamparray_restore_state(ldev);
+	if (ret) {
+		hid_err(hdev, "Failed to set standard state: %d", ret);
+		goto err_remove_sysfs;
+	}
+	return la;
+
+err_remove_sysfs:
+	lamparray_remove_sysfs(ldev);
+err_xa_erase:
+	xa_erase(&lamparray_by_hdev, (unsigned long)hdev);
+err_unregister_led:
+	lamparray_unregister_led(ldev);
+err_free:
+	kfree(la);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(lamparray_register);
+
+void lamparray_unregister(struct lamparray *la)
+{
+	struct lamparray_device *ldev;
+
+	if (!la)
+		return;
+
+	ldev = &la->ldev;
+
+	lamparray_unregister_led(ldev);
+	lamparray_remove_sysfs(ldev);
+	xa_erase(&lamparray_by_hdev, (unsigned long)ldev->hdev);
+
+	kfree(la);
+}
+EXPORT_SYMBOL_GPL(lamparray_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID LampArray helper module (single-zone RGB)");
diff --git a/drivers/hid/hid-lamparray.h b/drivers/hid/hid-lamparray.h
new file mode 100644
index 000000000000..b786ca00c404
--- /dev/null
+++ b/drivers/hid/hid-lamparray.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _HID_LAMPARRAY_H
+#define _HID_LAMPARRAY_H
+
+#include <linux/types.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+
+struct hid_device;
+struct lamparray;
+
+/*
+ * Optional initial LED state for lamparray_register().
+ * Used to define the initial state of a LampArray's LEDs.
+ */
+struct lamparray_init_state {
+	u8 r;
+	u8 g;
+	u8 b;
+	enum led_brightness brightness;
+};
+
+/**
+ * lamparray_is_supported_device() - check whether a HID device supports LampArray
+ * @hdev: HID device to inspect
+ *
+ * Check whether the given HID device exposes a Lighting/LampArray application
+ * collection as defined by the HID Lighting specification.
+ *
+ * This helper can be used by HID drivers to determine whether LampArray
+ * functionality should be enabled for a device.
+ *
+ * Return: %true if LampArray support is detected, %false otherwise.
+ */
+bool lamparray_is_supported_device(struct hid_device *hdev);
+
+/**
+ * lamparray_register() - initialize LampArray support for a HID device
+ * @hdev: HID device
+ * @led_init_state: Optional LED state at init specification
+ *
+ * Allocate and initialize internal LampArray state for the given HID device.
+ * The function parses required HID reports and fields and registers the
+ * associated miscdevice and sysfs attributes.
+ *
+ * If enabled, a multicolor LED class device is also registered to expose the
+ * LampArray functionality via the LED subsystem. If specified, the desired
+ * initial LED state is applied. If led_init_state is NULL, a default state is
+ * applied.
+ *
+ * Return: pointer to a LampArray handle on success, or ERR_PTR() on failure.
+ */
+struct lamparray *lamparray_register(struct hid_device *hdev,
+				     const struct lamparray_init_state *led_init_state);
+
+/**
+ * lamparray_unregister() - tear down LampArray support
+ * @la: LampArray handle returned by lamparray_register()
+ *
+ * Remove all resources associated with a LampArray instance.
+ *
+ * This unregisters the LED class device (if present), removes the miscdevice
+ * and sysfs interfaces and frees all internal state associated with @la.
+ */
+void lamparray_unregister(struct lamparray *la);
+
+#endif
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 3/3] arm64: dts: qcom: monaco-pmics: Add PON power key and reset inputs
From: Rakesh Kota @ 2026-02-19 11:17 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Vinod Koul, Dmitry Torokhov, Courtney Cavin, Bjorn Andersson,
	Konrad Dybcio, linux-pm, devicetree, linux-kernel, linux-arm-msm,
	linux-input, Rakesh Kota
In-Reply-To: <253ca948-07cd-4ded-8e15-619589f2d314@oss.qualcomm.com>

On Tue, Feb 17, 2026 at 01:30:19PM +0100, Konrad Dybcio wrote:
> On 2/9/26 2:23 PM, Rakesh Kota wrote:
> > Add the Power On (PON) peripheral with power key and reset input
> > support for the PMM8654AU PMIC on Monaco platforms.
> > 
> > Signed-off-by: Rakesh Kota <rakesh.kota@oss.qualcomm.com>
> > ---
> >  arch/arm64/boot/dts/qcom/monaco-pmics.dtsi | 20 ++++++++++++++++++++
> >  1 file changed, 20 insertions(+)
> > 
> > diff --git a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi
> > index e990d7367719beaa9e0cea87d9c183ae18c3ebc8..182c2339bb11af40275050a36c4688227e89497a 100644
> > --- a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi
> > +++ b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi
> > @@ -13,6 +13,26 @@ pmm8620au_0: pmic@0 {
> >  		#address-cells = <1>;
> >  		#size-cells = <0>;
> >  
> > +		pmm8654au_0_pon: pon@1200 {
> > +			compatible = "qcom,pmm8654au-pon", "qcom,pmk8350-pon";
> > +			reg = <0x1200>, <0x800>;
> > +			reg-names = "hlos", "pbs";
> > +
> > +			pmm8654au_0_pon_pwrkey: pwrkey {
> > +				compatible = "qcom,pmm8654au-pwrkey", "qcom,pmk8350-pwrkey";
> > +				interrupts-extended = <&spmi_bus 0x0 0x12 0x7 IRQ_TYPE_EDGE_BOTH>;
> > +				linux,code = <KEY_POWER>;
> > +				debounce = <15625>;
> > +			};
> > +
> > +			pmm8654au_0_pon_resin: resin {
> > +				compatible = "qcom,pmm8654au-resin", "qcom,pmk8350-resin";
> > +				interrupts-extended = <&spmi_bus 0x0 0x12 0x6 IRQ_TYPE_EDGE_BOTH>;
> > +				linux,code = <KEY_VOLUMEDOWN>;
> > +				debounce = <15625>;
> 
> FWIW we tend to disable RESIN by default, as it's not as ubiquitous as
> the power key and only assign the keycode in board DT, since it may
> commonly be reused for either of the volume keys (or something else
> entirely)
>
Ok, i will disable the RESIN key by default and remove the assigned
keycode in the next patch revision.

regards
Rakesh Kota

> Konrad

^ permalink raw reply

* Re: [PATCH v2 1/3] dt-bindings: power: reset: qcom-pon: Add new compatible PMM8654AU
From: Rakesh Kota @ 2026-02-19 10:48 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: Dmitry Baryshkov, Krzysztof Kozlowski, Sebastian Reichel,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vinod Koul,
	Dmitry Torokhov, Courtney Cavin, Bjorn Andersson, Konrad Dybcio,
	linux-pm, devicetree, linux-kernel, linux-arm-msm, linux-input,
	Rakesh Kota
In-Reply-To: <bd10782b-444c-417b-bf27-9fc6a2117567@oss.qualcomm.com>

On Tue, Feb 17, 2026 at 01:27:29PM +0100, Konrad Dybcio wrote:
> On 2/13/26 7:17 PM, Dmitry Baryshkov wrote:
> > On Tue, Feb 10, 2026 at 01:56:12PM +0530, Rakesh Kota wrote:
> >> On Mon, Feb 09, 2026 at 02:49:24PM +0100, Krzysztof Kozlowski wrote:
> >>> On 09/02/2026 14:23, Rakesh Kota wrote:
> >>>> Add the compatible string "qcom,pmm8654au-pon" for the PMM8654AU PMIC.
> >>>> The PON peripheral on PMM8654AU is compatible with PMK8350, so it is
> >>>> documented as a fallback to "qcom,pmk8350-pon".
> >>>
> >>> Drop everything after ,. Do not explain WHAT you did. We see it.
> >>>
> >>>>
> >>>> While PMM8654AU supports additional registers compared to the baseline,
> > 
> > I can't find PMM8654AU either on Qualcomm.com or in the catalog. Is it
> > an actual name for the chip?
> 
> Right, I would like to see some clarity on that too.
> 
> I see there's a PMM8650AU and there's two variants of it, perhaps that's
> one of them?
>
To clarify, PMM8654AU is a different PMIC from the PMM8650AU, though
they do share the same PMIC subtype.

We are specifically using the "PMM8654AU" name because that is what has
been defined and used in the upstream pinctrl-spmi-gpio.c driver
compatible. To ensure consistency with the kernel driver
representation, we are maintaining that naming convention here as well.

regards
Rakesh kota
> Konrad

^ permalink raw reply

* Re: [PATCH v2 4/5] input: drv260x: Fix unbalanced regulator_disable() call
From: Yauhen Kharuzhy @ 2026-02-19 10:11 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <aZZ4jqZQZ4vA1SFO@google.com>

On Wed, Feb 18, 2026 at 06:43:30PM -0800, Dmitry Torokhov wrote:
> Hi Yauhen,
> 
> On Sun, Feb 15, 2026 at 04:14:34PM +0200, Yauhen Kharuzhy wrote:
> > +static void drv260x_remove(struct i2c_client *client)
> > +{
> > +	struct drv260x_data *haptics = i2c_get_clientdata(client);
> > +
> > +	regulator_disable(haptics->regulator);
> >  }
> 
> This will result in power being shut off the first thing during
> unbinding, which is too early.
> 
> I switched this to devm_add_action_or_reset() and applied.

Do you mean that, in this case, regulator_disable() may be called before any
devm_* registered actions? Good point, thanks.

-- 
Yauhen Kharuzhy

^ permalink raw reply

* Re: [PATCH v2] bcm5974: recover from failed mode switch
From: Dmitry Torokhov @ 2026-02-19  2:45 UTC (permalink / raw)
  To: Liam Mitchell; +Cc: Henrik Rydberg, linux-input, linux-kernel
In-Reply-To: <20260213-bcm5974-reset-v2-1-1837851336b0@gmail.com>

Hi Liam,

On Fri, Feb 13, 2026 at 10:25:40AM +0100, Liam Mitchell wrote:
> Mode switches sent before control response are ignored.
> On receiving unknown 8-byte packets, assume that mode switch was ignored
> and reset by switching to normal mode, waiting then switching back to
> wellspring mode.
> 
> ---
> This patch addresses an issue where the bcm5974 driver switches modes
> before the device is ready, resulting in an unresponsive trackpad and
> "bcm5974: bad trackpad package, length: 8" repeated in logs.
> 
> Discussion of issue in the thread:
> https://lore.kernel.org/linux-input/CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com/
> 
> This fix is conservative, avoiding changing existing mode-switch
> behavior because I cannot test all variations of hardware.
> 
> On receiving an unknown 8-byte packet, we assume the device is not in
> wellspring mode and schedule an asynchronous mode reset.
> 
> Signed-off-by: Liam Mitchell <mitchell.liam@gmail.com>
> Link: https://lore.kernel.org/linux-input/CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com/
> ---
> Changes in v2:
> - mutex_lock -> guard(mutex)
> - dprintk -> dev_err
> - msleep -> fsleep
> - removed 0 init
> - cancel_work_sync -> disable_delayed_work_sync
> - work_struct -> delayed_work

My apologies, I did not mean to request switch to delayed work, just to
switch to disable_work_sync().

I fixed this up on my side and applied.

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v2 0/5] DRV260x: Support ACPI-enumerated devices
From: Dmitry Torokhov @ 2026-02-19  2:44 UTC (permalink / raw)
  To: Yauhen Kharuzhy; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <20260215141435.727872-1-jekhor@gmail.com>

On Sun, Feb 15, 2026 at 04:14:30PM +0200, Yauhen Kharuzhy wrote:
> Lenovo Yoga Book YB1-X90 and YB1-X91 tablets use haptics controllers
> DRV2604L. The X91 (Windows tablet) uses ACPI to define its configuration,
> such as I2C address and GPIO connections. The X90 (Android tablet)
> doesn't have it in the ACPI, but the device may be defined as an
> i2c_board in the x86-android-tablets driver.
> 
> To support these variants, add an ACPI matching table and add additional
> I2C IDs to the I2C matching table (the driver supports DRV2604(L),
> DRV2605(L) devices).
> 
> Also, implement a timeout for waiting for calibration,
> and fix the non-working suspend due to unbalanced regulator_disable() call.

Applied the series with a small modification in #4.

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v2 4/5] input: drv260x: Fix unbalanced regulator_disable() call
From: Dmitry Torokhov @ 2026-02-19  2:43 UTC (permalink / raw)
  To: Yauhen Kharuzhy; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <20260215141435.727872-5-jekhor@gmail.com>

Hi Yauhen,

On Sun, Feb 15, 2026 at 04:14:34PM +0200, Yauhen Kharuzhy wrote:
> +static void drv260x_remove(struct i2c_client *client)
> +{
> +	struct drv260x_data *haptics = i2c_get_clientdata(client);
> +
> +	regulator_disable(haptics->regulator);
>  }

This will result in power being shut off the first thing during
unbinding, which is too early.

I switched this to devm_add_action_or_reset() and applied.

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 0/2] iio: orientation: hid-sensor-rotation: fix quaternion alignment
From: Jonathan Cameron @ 2026-02-18 19:52 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jiri Kosina,
	Srinivas Pandruvada, linux-iio, linux-kernel, Jonathan Cameron,
	linux-input, Lixu Zhang
In-Reply-To: <aZQijRLVjzuWcU2W@smile.fi.intel.com>

On Tue, 17 Feb 2026 10:10:53 +0200
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:

> On Mon, Feb 16, 2026 at 09:25:58AM -0600, David Lechner wrote:
> > On 2/16/26 1:44 AM, Andy Shevchenko wrote:  
> > > On Sat, Feb 14, 2026 at 03:00:19PM -0600, David Lechner wrote:  
> > >> The main point of this series is to fix a regression reported in
> > >> hid-sensor-rotation where the alignment of the quaternion field in the
> > >> data was inadvertently changed from 16 bytes to 8 bytes. This is an
> > >> unusually case (one of only 2 in the kernel) where the .repeat field of
> > >> struct iio_scan_type is used and we have such a requirement. (The other
> > >> case uses u16 instead of u32, so it wasn't affected.)
> > >>
> > >> To make the reason for the alignment more explicit to future readers,
> > >> we introduce a new macro, IIO_DECLARE_REPEATED_ELEMENT, to declare the
> > >> array with proper allignment. This is meant to follow the pattern of
> > >> the similar IIO_DECLARE_BUFFER_WITH_TS() macro.  
> > > 
> > > In both cases it's quaternion, maybe be more explicit and define
> > > IIO_DECLARE_QUATERNION() ?  

I like this.  It's special and this shouts that nicely.

> > 
> > It is really the fact that the scan_type has .repeat > 1 that requires
> > this, so I was trying to make a name that shows that link.
> > 
> > But right now, quaternion is the only thing that has .repeat > 1, so
> > I guess it would be OK either way. We'll see if Jonathan has an
> > opinion on the naming.  
> 
> I think we should solve the problems when they appear. Naming explicitly
> for quaternion makes it easier to get from the core reading without having
> a variable name to repeat that. Magic 4  might not always be a quaternion.
> 
> Do we have "repeat" to be used in other cases, btw?
> 
Don't think so.

Note there is a second older bug here.
The timestamp is landing at the wrong location :(  See discussion of
original bug.  I suspect that applies to the bno055 as well (that avoids
the bug we are fixing in this patch because helpfully the quaternion is
a total of 8 bytes. 

Short story - it is just another channel, so should be naturally aligned
at first valid location after the previous channel. That's bytes 32-39 not
55-63 which is where iio_push_to_buffers_with_timestamp() puts it.


Jonathan



^ permalink raw reply

* [dtor-input:next] BUILD SUCCESS e7b53288d9ea899abc6d47a7f20065ab511a810c
From: kernel test robot @ 2026-02-18 19:38 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: e7b53288d9ea899abc6d47a7f20065ab511a810c  Input: drv260x - fix unbalanced regulator_disable() call

elapsed time: 720m

configs tested: 269
configs skipped: 3

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                          axs103_defconfig    gcc-15.2.0
arc                      axs103_smp_defconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                     haps_hs_smp_defconfig    clang-23
arc                     haps_hs_smp_defconfig    gcc-15.2.0
arc                        nsim_700_defconfig    clang-23
arc                   randconfig-001-20260218    clang-23
arc                   randconfig-001-20260219    clang-23
arc                   randconfig-002-20260218    clang-23
arc                   randconfig-002-20260219    clang-23
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                                 defconfig    gcc-15.2.0
arm                            dove_defconfig    gcc-15.2.0
arm                          exynos_defconfig    clang-23
arm                            hisi_defconfig    gcc-15.2.0
arm                      jornada720_defconfig    clang-23
arm                            mps2_defconfig    clang-23
arm                        multi_v5_defconfig    gcc-15.2.0
arm                        mvebu_v5_defconfig    clang-23
arm                        neponset_defconfig    gcc-15.2.0
arm                       netwinder_defconfig    clang-23
arm                         orion5x_defconfig    clang-23
arm                             pxa_defconfig    gcc-15.2.0
arm                   randconfig-001-20260218    clang-23
arm                   randconfig-001-20260219    clang-23
arm                   randconfig-002-20260218    clang-23
arm                   randconfig-002-20260219    clang-23
arm                   randconfig-003-20260218    clang-23
arm                   randconfig-003-20260219    clang-23
arm                   randconfig-004-20260218    clang-23
arm                   randconfig-004-20260219    clang-23
arm                         socfpga_defconfig    clang-23
arm                           spitz_defconfig    gcc-15.2.0
arm                           stm32_defconfig    gcc-15.2.0
arm                       versatile_defconfig    gcc-15.2.0
arm                    vt8500_v6_v7_defconfig    gcc-15.2.0
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                 randconfig-001-20260218    clang-23
arm64                 randconfig-002-20260218    clang-23
arm64                 randconfig-003-20260218    clang-23
arm64                 randconfig-004-20260218    clang-23
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                  randconfig-001-20260218    clang-23
csky                  randconfig-002-20260218    clang-23
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon               randconfig-001-20260218    clang-16
hexagon               randconfig-001-20260219    clang-17
hexagon               randconfig-002-20260218    clang-16
hexagon               randconfig-002-20260219    clang-17
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386        buildonly-randconfig-001-20260218    clang-20
i386        buildonly-randconfig-002-20260218    clang-20
i386        buildonly-randconfig-003-20260218    clang-20
i386        buildonly-randconfig-004-20260218    clang-20
i386        buildonly-randconfig-005-20260218    clang-20
i386        buildonly-randconfig-006-20260218    clang-20
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260218    clang-20
i386                  randconfig-002-20260218    clang-20
i386                  randconfig-003-20260218    clang-20
i386                  randconfig-004-20260218    clang-20
i386                  randconfig-005-20260218    clang-20
i386                  randconfig-006-20260218    clang-20
i386                  randconfig-007-20260218    clang-20
i386                  randconfig-011-20260218    gcc-14
i386                  randconfig-012-20260218    gcc-14
i386                  randconfig-013-20260218    gcc-14
i386                  randconfig-014-20260218    gcc-14
i386                  randconfig-015-20260218    gcc-14
i386                  randconfig-016-20260218    gcc-14
i386                  randconfig-017-20260218    gcc-14
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260218    clang-16
loongarch             randconfig-001-20260219    clang-17
loongarch             randconfig-002-20260218    clang-16
loongarch             randconfig-002-20260219    clang-17
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
m68k                       m5249evb_defconfig    gcc-15.2.0
m68k                        mvme16x_defconfig    gcc-15.2.0
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                          ath25_defconfig    clang-23
mips                         bigsur_defconfig    clang-23
mips                         cobalt_defconfig    clang-23
mips                 decstation_r4k_defconfig    clang-23
mips                           ip28_defconfig    gcc-15.2.0
mips                malta_qemu_32r6_defconfig    clang-23
mips                      pic32mzda_defconfig    clang-23
mips                          rm200_defconfig    clang-23
mips                   sb1250_swarm_defconfig    gcc-15.2.0
mips                           xway_defconfig    clang-23
nios2                            allmodconfig    clang-23
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260218    clang-16
nios2                 randconfig-001-20260219    clang-17
nios2                 randconfig-002-20260218    clang-16
nios2                 randconfig-002-20260219    clang-17
openrisc                         allmodconfig    clang-23
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc         de0_nano_multicore_defconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                generic-32bit_defconfig    gcc-15.2.0
parisc                randconfig-001-20260218    gcc-8.5.0
parisc                randconfig-002-20260218    gcc-8.5.0
parisc64                            defconfig    clang-19
powerpc                    adder875_defconfig    gcc-15.2.0
powerpc                     akebono_defconfig    gcc-15.2.0
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc                    amigaone_defconfig    clang-23
powerpc                      chrp32_defconfig    clang-23
powerpc                      cm5200_defconfig    clang-23
powerpc                   currituck_defconfig    gcc-15.2.0
powerpc                      ep88xc_defconfig    clang-23
powerpc                        fsp2_defconfig    gcc-15.2.0
powerpc                        icon_defconfig    gcc-15.2.0
powerpc                      katmai_defconfig    gcc-15.2.0
powerpc                     kmeter1_defconfig    clang-23
powerpc                  mpc885_ads_defconfig    gcc-15.2.0
powerpc                      pcm030_defconfig    clang-23
powerpc                     ppa8548_defconfig    gcc-15.2.0
powerpc               randconfig-001-20260218    gcc-8.5.0
powerpc               randconfig-002-20260218    gcc-8.5.0
powerpc                     redwood_defconfig    gcc-15.2.0
powerpc                  storcenter_defconfig    clang-23
powerpc                     tqm8540_defconfig    clang-23
powerpc                     tqm8555_defconfig    clang-23
powerpc                        warp_defconfig    clang-23
powerpc                 xes_mpc85xx_defconfig    clang-23
powerpc64             randconfig-001-20260218    gcc-8.5.0
powerpc64             randconfig-002-20260218    gcc-8.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260218    clang-23
riscv                 randconfig-002-20260218    clang-23
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260218    clang-23
s390                  randconfig-002-20260218    clang-23
s390                       zfcpdump_defconfig    gcc-15.2.0
sh                               alldefconfig    clang-23
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sh                          kfr2r09_defconfig    gcc-15.2.0
sh                          landisk_defconfig    gcc-15.2.0
sh                          polaris_defconfig    gcc-15.2.0
sh                          r7785rp_defconfig    gcc-15.2.0
sh                    randconfig-001-20260218    clang-23
sh                    randconfig-002-20260218    clang-23
sh                          rsk7269_defconfig    gcc-15.2.0
sh                   sh7770_generic_defconfig    clang-23
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                 randconfig-001-20260218    gcc-10.5.0
sparc                 randconfig-002-20260218    gcc-10.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64               randconfig-001-20260218    gcc-10.5.0
sparc64               randconfig-002-20260218    gcc-10.5.0
um                               alldefconfig    clang-23
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                    randconfig-001-20260218    gcc-10.5.0
um                    randconfig-002-20260218    gcc-10.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64      buildonly-randconfig-001-20260218    gcc-14
x86_64      buildonly-randconfig-001-20260219    gcc-14
x86_64      buildonly-randconfig-002-20260218    gcc-14
x86_64      buildonly-randconfig-002-20260219    gcc-14
x86_64      buildonly-randconfig-003-20260218    gcc-14
x86_64      buildonly-randconfig-003-20260219    gcc-14
x86_64      buildonly-randconfig-004-20260218    gcc-14
x86_64      buildonly-randconfig-004-20260219    gcc-14
x86_64      buildonly-randconfig-005-20260218    gcc-14
x86_64      buildonly-randconfig-005-20260219    gcc-14
x86_64      buildonly-randconfig-006-20260218    gcc-14
x86_64      buildonly-randconfig-006-20260219    gcc-14
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260218    gcc-14
x86_64                randconfig-002-20260218    gcc-14
x86_64                randconfig-003-20260218    gcc-14
x86_64                randconfig-004-20260218    gcc-14
x86_64                randconfig-005-20260218    gcc-14
x86_64                randconfig-006-20260218    gcc-14
x86_64                randconfig-011-20260218    gcc-13
x86_64                randconfig-012-20260218    gcc-13
x86_64                randconfig-013-20260218    gcc-13
x86_64                randconfig-014-20260218    gcc-13
x86_64                randconfig-015-20260218    gcc-13
x86_64                randconfig-016-20260218    gcc-13
x86_64                randconfig-071-20260218    clang-20
x86_64                randconfig-072-20260218    clang-20
x86_64                randconfig-073-20260218    clang-20
x86_64                randconfig-074-20260218    clang-20
x86_64                randconfig-075-20260218    clang-20
x86_64                randconfig-076-20260218    clang-20
x86_64                randconfig-076-20260218    gcc-14
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                           allyesconfig    gcc-15.2.0
xtensa                       common_defconfig    gcc-15.2.0
xtensa                generic_kc705_defconfig    clang-23
xtensa                randconfig-001-20260218    gcc-10.5.0
xtensa                randconfig-002-20260218    gcc-10.5.0
xtensa                    smp_lx200_defconfig    gcc-15.2.0

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
From: Benjamin Tissoires @ 2026-02-18 19:04 UTC (permalink / raw)
  To: Günther Noack; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <aZTEnPEHcWEkoTJR@google.com>

On Feb 17 2026, Günther Noack wrote:
> Hello Benjamin!
> 
> On Tue, Feb 17, 2026 at 07:22:20PM +0100, Benjamin Tissoires wrote:
> > On Feb 17 2026, Günther Noack wrote:
> > > The apple_report_fixup() function was allocating a new buffer with
> > > kmemdup() but never freeing it. Since the caller of report_fixup() already
> > > provides a writable buffer and allows returning a pointer within that
> > > buffer, we can just modify the descriptor in-place and return the adjusted
> > > pointer.
> > > 
> > > Assisted-by: Gemini-CLI:Google Gemini 3
> > > Signed-off-by: Günther Noack <gnoack@google.com>
> > > ---
> > >  drivers/hid/hid-apple.c | 4 +---
> > >  1 file changed, 1 insertion(+), 3 deletions(-)
> > > 
> > > diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> > > index 233e367cce1d..894adc23367b 100644
> > > --- a/drivers/hid/hid-apple.c
> > > +++ b/drivers/hid/hid-apple.c
> > > @@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> > >  		hid_info(hdev,
> > >  			 "fixing up Magic Keyboard battery report descriptor\n");
> > >  		*rsize = *rsize - 1;
> > > -		rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
> > > -		if (!rdesc)
> > > -			return NULL;
> > > +		rdesc = rdesc + 1;
> > 
> > I might be wrong, but later we call free(dev->rdesc) on device removal.
> > AFAICT, incrementing the pointer is undefined behavior.
> > 
> > What we should do instead is probably a krealloc instead of a kmemdup.
> > 
> > Same for all 3 patches.
> 
> Thanks for the review.
> 
> Let me try to address your three responses in one reply.
> 
> I am happy to accept it if I am wrong about this, but I don't
> understand your reasoning.  (This should go without saying, but maybe
> is worth reiterating, I would not have sent this if I had not
> convinced myself independently of LLM-assisted reasoning.)
> 
> Let me explain my reasoning based on the place where .report_fixup()
> is called from, which is hid_open_report() in hid-core.c:
> 
> 
> 	start = device->bpf_rdesc;                         /* (1) */
> 	if (WARN_ON(!start))
> 		return -ENODEV;
> 	size = device->bpf_rsize;
> 
> 	if (device->driver->report_fixup) {
> 		/*
> 		 * device->driver->report_fixup() needs to work
> 		 * on a copy of our report descriptor so it can
> 		 * change it.
> 		 */
> 		__u8 *buf = kmemdup(start, size, GFP_KERNEL);   /* (2) */
> 
> 		if (buf == NULL)
> 			return -ENOMEM;
> 
> 		start = device->driver->report_fixup(device, buf, &size);  /* (3) */
> 
> 		/*
> 		 * The second kmemdup is required in case report_fixup() returns
> 		 * a static read-only memory, but we have no idea if that memory
> 		 * needs to be cleaned up or not at the end.
> 		 */
> 		start = kmemdup(start, size, GFP_KERNEL);  /* (4) */
> 		kfree(buf);                                /* (5) */
> 		if (start == NULL)
> 			return -ENOMEM;
> 	}
> 
> 	device->rdesc = start;
> 	device->rsize = size;
> 
> 
> This function uses a slightly elaborate scheme to call .report_fixup:
> 
> (1) start is assigned to the original device->bpf_rdesc
> (2) buf is assigned to a copy of the 'start' buffer (deallocated in (5)).
>  .  It is done because buf is meant to be mutated by .report_fixup()
>  .  (3) start = ...->report_fixup(..., buf, ...)
>  .  (4) start = kmemdup(start, ...)
> (5) deallocate buf

Ouch. Yeah, sorry. I wrote that code and it seemed I completely paged
it out. Your code is actually correct (all three) but it would be nice
to have a longer commit message explaining this above.

The main point of this alloc before calling fixup is because some
drivers are using a static array as the new report descriptor. So we can
not free it later on. Working on a known copy allows to handle the kfree
correctly.

So yes, sorry, returning rdesc+1 in 1/3 and 2/3 is correct, and using a
devm_kzalloc is too in 3/3.

Cheers,
Benjamin

> 
> Importantly:
> 
> (a) The buffer buf passed to report_fixup() is a copy of the report
>     descriptor whose lifetime spans only from (2) to (5).
> (b) The result of .report_fixup(), start, is immediately discarded in
>     (4) and reassigned to the start variable again.
> 
> From (b), we can see that the result of .report_fixup() does *not* get
> deallocated by the caller, and thus, when the driver wants to return a
> newly allocated array, is must also hold a reference to it so that it
> can deallocate it later.
> 
> From (a), we can see that the report_fixup hook is free to manipulate
> the contents of the buffer that it receives, but if we were to
> *reallocate* it within report_fixup, as you are suggesting above, it
> could become a double free:
> 
> * During realloc, the allocator would potentially have to move the
>   buffer to a place where there is enough space, freeing up the old
>   place and allocating a new place. [1]
> * In (5), we would then pass the original (now deallocated) buf
>   pointer to kfree, leading to a double free.
> 
> If I were to describe the current interface of the hook
> .report_fixup(dev, rdesc, rsize), it would be:
> 
> * report_fixup may modify rdesc[0] to rdesc[rsize-1]
> * report_fixup may *not* deallocate rdesc
>   (ownership of rdesc stays with the caller)
>   * specifically, it may also not reallocate rdesc
>     (because that may imply a deallocation)
> * report_fixup returns a pointer to a buffer for which it can
>   guarantee that it lives long enough for the kmemdup in (4), but
>   which will *not* be deallocated by the caller (see (b) above).  The
>   three techniques I have found for that are:
>   * returning a subsection of the rdesc that it received
>   * returning a pointer into a statically allocated array
>     (e.g. motion_fixup() and ps3remote_fixup() in hid-sony.c)
>   * allocating it with a devm_*() function.  My understanding was
>     that this ties it to the lifetime of the device.  (e.g. the
>     QUIRK_G752_KEYBOARD case in hid-asus.c)
> 
> Honestly, I still think that this reasoning holds, but I am happy to
> be convinced otherwise.  Please let me know what you think.
> 
> —Günther

^ permalink raw reply

* [PATCH] HID: mcp2221: Add module parameter to enfoce GPIO mode
From: Linus Walleij @ 2026-02-18 13:44 UTC (permalink / raw)
  To: Rishi Gupta, Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-gpio, Linus Walleij

Add a module parameter to MCP2221 to enfor GPIO mode on the
general purpose pins GP0 thru GP3.

If I plug a device of this type into my machine, it will use the
GP0, GP1, GP2 and GP3 pins for IIO voltage readings by default
if CONFIG_IIO is set.

However there may be cases where IIO is available but we want to
use the GP0 thru GP3 lines for GPIO anyway.

Example use:

insmode hid-mcp2221.ko gpio_mode_enforce=1

Result in dmesg:
mcp2221 0003:04D8:00DD.0005: GPIO 0 not in gpio mode
mcp2221 0003:04D8:00DD.0005: GPIO 1 not in gpio mode
mcp2221 0003:04D8:00DD.0005: GPIO 2 not in gpio mode
mcp2221 0003:04D8:00DD.0005: GPIO 3 not in gpio mode
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 0!
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 1!
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 2!
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 3!

After this the gpiolib tools such as gpioset can be used to alter
the GPIO line values successfully.

Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/hid/hid-mcp2221.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c
index 33603b019f97..58695f3f9e9d 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -19,8 +19,15 @@
 #include <linux/gpio/driver.h>
 #include <linux/iio/iio.h>
 #include <linux/minmax.h>
+#include <linux/moduleparam.h>
 #include "hid-ids.h"
 
+static bool gpio_mode_enforce;
+
+module_param(gpio_mode_enforce, bool, 0644);
+MODULE_PARM_DESC(gpio_mode_enforce,
+	 "Enfore GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)");
+
 /* Commands codes in a raw output report */
 enum {
 	MCP2221_I2C_WR_DATA = 0x90,
@@ -648,7 +655,7 @@ static int mcp2221_check_gpio_pinfunc(struct mcp2221 *mcp)
 	int needgpiofix = 0;
 	int ret;
 
-	if (IS_ENABLED(CONFIG_IIO))
+	if (IS_ENABLED(CONFIG_IIO) && !gpio_mode_enforce)
 		return 0;
 
 	ret = mcp_gpio_read_sram(mcp);
@@ -1043,7 +1050,8 @@ static void mcp2221_remove(struct hid_device *hdev)
 #if IS_REACHABLE(CONFIG_IIO)
 	struct mcp2221 *mcp = hid_get_drvdata(hdev);
 
-	cancel_delayed_work_sync(&mcp->init_work);
+	if (!gpio_mode_enforce)
+		cancel_delayed_work_sync(&mcp->init_work);
 #endif
 }
 
@@ -1317,8 +1325,10 @@ static int mcp2221_probe(struct hid_device *hdev,
 #endif
 
 #if IS_REACHABLE(CONFIG_IIO)
-	INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
-	schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+	if (!gpio_mode_enforce) {
+		INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
+		schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+	}
 #endif
 
 	return 0;

---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260218-hid-mcp2221-gpio-46f7c1a638c4

Best regards,
-- 
Linus Walleij <linusw@kernel.org>


^ permalink raw reply related

* Re: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: kernel test robot @ 2026-02-18 13:28 UTC (permalink / raw)
  To: Aelin Reidel, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hans de Goede, Neil Armstrong, Henrik Rydberg
  Cc: llvm, oe-kbuild-all, linux-input, devicetree, linux-kernel, linux,
	phone-devel, ~postmarketos/upstreaming, Aelin Reidel,
	Piyush Raj Chouhan
In-Reply-To: <20260218-gtx8-v1-2-0d575b3dedc5@mainlining.org>

Hi Aelin,

kernel test robot noticed the following build errors:

[auto build test ERROR on fe9e3edb6a215515d1148d32a5c445c5bdd7916f]

url:    https://github.com/intel-lab-lkp/linux/commits/Aelin-Reidel/dt-bindings-input-document-Goodix-GTX8-Touchscreen-ICs/20260218-075424
base:   fe9e3edb6a215515d1148d32a5c445c5bdd7916f
patch link:    https://lore.kernel.org/r/20260218-gtx8-v1-2-0d575b3dedc5%40mainlining.org
patch subject: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
config: sparc64-allmodconfig (https://download.01.org/0day-ci/archive/20260218/202602182104.ONBwXzkn-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project e86750b29fa0ff207cd43213d66dabe565417638)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260218/202602182104.ONBwXzkn-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602182104.ONBwXzkn-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/input/touchscreen/goodix_gtx8.c:110:16: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     110 |                         finger_id = FIELD_GET(
         |                                     ^
   drivers/input/touchscreen/goodix_gtx8.c:135:14: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     135 |         touch_num = FIELD_GET(GOODIX_GTX8_TOUCH_COUNT_MASK, touch_num);
         |                     ^
   2 errors generated.


vim +/FIELD_GET +110 drivers/input/touchscreen/goodix_gtx8.c

    87	
    88	static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
    89					     union goodix_gtx8_touch *touch_data)
    90	{
    91		union goodix_gtx8_touch *t;
    92		int i;
    93		u8 finger_id;
    94	
    95		for (i = 0; i < touch_num; i++) {
    96			t = &touch_data[i];
    97	
    98			if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
    99				input_mt_slot(cd->input_dev, t->normandy.finger_id);
   100				input_mt_report_slot_state(cd->input_dev,
   101							   MT_TOOL_FINGER, true);
   102	
   103				touchscreen_report_pos(cd->input_dev, &cd->props,
   104						       __le16_to_cpu(t->normandy.x),
   105						       __le16_to_cpu(t->normandy.y),
   106						       true);
   107				input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
   108						 t->normandy.w);
   109			} else {
 > 110				finger_id = FIELD_GET(
   111					GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
   112					t->yellowstone.finger_id);
   113				input_mt_slot(cd->input_dev, finger_id);
   114				input_mt_report_slot_state(cd->input_dev,
   115							   MT_TOOL_FINGER, true);
   116	
   117				touchscreen_report_pos(cd->input_dev, &cd->props,
   118						       __be16_to_cpu(t->yellowstone.x),
   119						       __be16_to_cpu(t->yellowstone.y),
   120						       true);
   121				input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
   122						 t->yellowstone.w);
   123			}
   124		}
   125	
   126		input_mt_sync_frame(cd->input_dev);
   127		input_sync(cd->input_dev);
   128	}
   129	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: kernel test robot @ 2026-02-18 10:40 UTC (permalink / raw)
  To: Aelin Reidel, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hans de Goede, Neil Armstrong, Henrik Rydberg
  Cc: oe-kbuild-all, linux-input, devicetree, linux-kernel, linux,
	phone-devel, ~postmarketos/upstreaming, Aelin Reidel,
	Piyush Raj Chouhan
In-Reply-To: <20260218-gtx8-v1-2-0d575b3dedc5@mainlining.org>

Hi Aelin,

kernel test robot noticed the following build errors:

[auto build test ERROR on fe9e3edb6a215515d1148d32a5c445c5bdd7916f]

url:    https://github.com/intel-lab-lkp/linux/commits/Aelin-Reidel/dt-bindings-input-document-Goodix-GTX8-Touchscreen-ICs/20260218-075424
base:   fe9e3edb6a215515d1148d32a5c445c5bdd7916f
patch link:    https://lore.kernel.org/r/20260218-gtx8-v1-2-0d575b3dedc5%40mainlining.org
patch subject: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20260218/202602181848.DK5Wc0iI-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260218/202602181848.DK5Wc0iI-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602181848.DK5Wc0iI-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/input/touchscreen/goodix_gtx8.c: In function 'goodix_gtx8_report_state':
>> drivers/input/touchscreen/goodix_gtx8.c:110:37: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration]
     110 |                         finger_id = FIELD_GET(
         |                                     ^~~~~~~~~
   In file included from include/linux/kernel.h:35,
                    from include/linux/random.h:7,
                    from include/linux/nodemask.h:94,
                    from include/linux/numa.h:6,
                    from include/linux/cpumask.h:15,
                    from include/linux/smp.h:13,
                    from include/linux/lockdep.h:14,
                    from include/linux/spinlock.h:63,
                    from include/linux/mmzone.h:8,
                    from include/linux/gfp.h:7,
                    from include/linux/slab.h:17,
                    from include/linux/resource_ext.h:11,
                    from include/linux/acpi.h:14,
                    from include/linux/i2c.h:13,
                    from drivers/input/touchscreen/goodix_gtx8.c:16:
   drivers/input/touchscreen/goodix_gtx8.c: At top level:
>> drivers/input/touchscreen/goodix_gtx8.c:555:37: error: 'goodix_gtx8_pm_ops' undeclared here (not in a function); did you mean 'goodix_gtx8_probe'?
     555 |                 .pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
         |                                     ^~~~~~~~~~~~~~~~~~
   include/linux/util_macros.h:136:44: note: in definition of macro 'PTR_IF'
     136 | #define PTR_IF(cond, ptr)       ((cond) ? (ptr) : NULL)
         |                                            ^~~
   drivers/input/touchscreen/goodix_gtx8.c:555:23: note: in expansion of macro 'pm_sleep_ptr'
     555 |                 .pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
         |                       ^~~~~~~~~~~~


vim +/FIELD_GET +110 drivers/input/touchscreen/goodix_gtx8.c

    87	
    88	static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
    89					     union goodix_gtx8_touch *touch_data)
    90	{
    91		union goodix_gtx8_touch *t;
    92		int i;
    93		u8 finger_id;
    94	
    95		for (i = 0; i < touch_num; i++) {
    96			t = &touch_data[i];
    97	
    98			if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
    99				input_mt_slot(cd->input_dev, t->normandy.finger_id);
   100				input_mt_report_slot_state(cd->input_dev,
   101							   MT_TOOL_FINGER, true);
   102	
   103				touchscreen_report_pos(cd->input_dev, &cd->props,
   104						       __le16_to_cpu(t->normandy.x),
   105						       __le16_to_cpu(t->normandy.y),
   106						       true);
   107				input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
   108						 t->normandy.w);
   109			} else {
 > 110				finger_id = FIELD_GET(
   111					GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
   112					t->yellowstone.finger_id);
   113				input_mt_slot(cd->input_dev, finger_id);
   114				input_mt_report_slot_state(cd->input_dev,
   115							   MT_TOOL_FINGER, true);
   116	
   117				touchscreen_report_pos(cd->input_dev, &cd->props,
   118						       __be16_to_cpu(t->yellowstone.x),
   119						       __be16_to_cpu(t->yellowstone.y),
   120						       true);
   121				input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
   122						 t->yellowstone.w);
   123			}
   124		}
   125	
   126		input_mt_sync_frame(cd->input_dev);
   127		input_sync(cd->input_dev);
   128	}
   129	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH] Input: atkbd: add keymap fixup for notebooks using 0x6e as Fn modifier
From: Dmitry Torokhov @ 2026-02-18  6:26 UTC (permalink / raw)
  To: Mikhail Novosyolov; +Cc: linux-input, mpearson-lenovo
In-Reply-To: <20260218041352.797625-1-m.novosyolov@rosa.ru>

On Wed, Feb 18, 2026 at 07:13:52AM +0300, Mikhail Novosyolov wrote:
> Commit dc8c9c171ef3 ("Input: atkbd - map F23 key to support default
> copilot shortcut") mapped scancode 0x6e to KEY_F23 to support the
> Microsoft Copilot key on Lenovo, HP, and Dell notebooks.
> 
> However, some notebook platforms (including Positron Proxima 15 and
> possibly others based on the same OEM design) use scancode 0x6e for the
> Fn modifier key instead of a dedicated Copilot key. When 0x6e generates
> KEY_F23 events, the Fn key breaks Fn combinations such as Fn+F5
> (touchpad toggle).
> 
> On these platforms, the hardware relies on 0x6e being unmapped to
> properly handle Fn combinations at the firmware level. When the kernel
> maps it to KEY_F23, desktop environments intercept this as a global
> hotkey and toggle the touchpad, but cannot re-enable it because the
> firmware no longer recognizes Fn as a valid modifier.
> 
> Userspace solutions (systemd hwdb) cannot fix this because the keycode
> mapping happens in the atkbd driver before events reach userspace.
> A kernel-level quirk is required.

? That is exactly what udev hwdb is for. Use it. Check 60-keyboard.hwdb
for examples.

Thanks.

-- 
Dmitry

^ permalink raw reply

* [PATCH] Input: atkbd: add keymap fixup for notebooks using 0x6e as Fn modifier
From: Mikhail Novosyolov @ 2026-02-18  4:13 UTC (permalink / raw)
  To: dmitry.torokhov; +Cc: linux-input, mpearson-lenovo, m.novosyolov

Commit dc8c9c171ef3 ("Input: atkbd - map F23 key to support default
copilot shortcut") mapped scancode 0x6e to KEY_F23 to support the
Microsoft Copilot key on Lenovo, HP, and Dell notebooks.

However, some notebook platforms (including Positron Proxima 15 and
possibly others based on the same OEM design) use scancode 0x6e for the
Fn modifier key instead of a dedicated Copilot key. When 0x6e generates
KEY_F23 events, the Fn key breaks Fn combinations such as Fn+F5
(touchpad toggle).

On these platforms, the hardware relies on 0x6e being unmapped to
properly handle Fn combinations at the firmware level. When the kernel
maps it to KEY_F23, desktop environments intercept this as a global
hotkey and toggle the touchpad, but cannot re-enable it because the
firmware no longer recognizes Fn as a valid modifier.

Userspace solutions (systemd hwdb) cannot fix this because the keycode
mapping happens in the atkbd driver before events reach userspace.
A kernel-level quirk is required.

Add a DMI-based keymap fixup to remap scancode 0x6e to 0 for affected
systems. Currently only Positron Proxima 15 is known to be affected,
but other notebooks based on the same OEM platform may exhibit the same
behavior and can be added to the quirk table.

Scancode 0x6e has different meanings on different hardware:
- Lenovo/HP/Dell: F23 (Copilot key) - correctly mapped to KEY_F23
- Some OEM platforms: Fn modifier - must be unmapped

A generic solution is not feasible without breaking Copilot support
on other vendors.

Link: https://linux-hardware.org/?probe=7aca7ed668
Link: https://bugzilla.rosa.ru/show_bug.cgi?id=19950

Fixes: dc8c9c171ef3 ("Input: atkbd - map F23 key to support default copilot shortcut")
Co-developed-by: GLM-4.7 AI <noreply@z.ai>
Signed-off-by: Mikhail Novosyolov <m.novosyolov@rosa.ru>
---
 drivers/input/keyboard/atkbd.c | 51 ++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index 6c999d89ee4b..e4ecdd9fc07b 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -1068,6 +1068,40 @@ static unsigned int atkbd_volume_forced_release_keys[] = {
 	0xae, 0xb0, -1U
 };
 
+/*
+ * Positron notebooks where scancode 0x6e is used by the Fn key and should
+ * not generate KEY_F23 events.
+ *
+ * Commit dc8c9c171ef3 ("Input: atkbd - map F23 key to support default
+ * copilot shortcut") mapped scancode 0x6e to KEY_F23 for Copilot support.
+ * However, on Positron notebooks scancode 0x6e is generated by the Fn
+ * modifier key and should be unmapped to allow firmware/hardware to
+ * process Fn+F5 and other Fn combinations properly.
+ */
+static unsigned int atkbd_positron_fn_keymap_fixup_scancodes[] = {
+	0x6e, -1U
+};
+
+/*
+ * Fixup to remap scancodes to 0 (no keycode) for machines where
+ * the default keymap incorrectly assigns keys that should be unused.
+ * This is needed when BIOS/firmware uses certain scancodes for
+ * internal purposes (e.g., Fn modifier) and they should not generate
+ * keyboard events.
+ */
+static void atkbd_apply_keymap_fixup(struct atkbd *atkbd, const void *data)
+{
+	const unsigned int *scancodes = data;
+	unsigned int i;
+
+	for (i = 0; scancodes[i] != -1U; i++) {
+		unsigned int scancode = scancodes[i];
+
+		if (scancode < ATKBD_KEYMAP_SIZE)
+			atkbd->keycode[scancode] = 0;
+	}
+}
+
 /*
  * OQO 01+ multimedia keys (64--66) generate e0 6x upon release whereas
  * they should be generating e4-e6 (0x80 | code).
@@ -1775,6 +1809,14 @@ static int __init atkbd_setup_forced_release(const struct dmi_system_id *id)
 	return 1;
 }
 
+static int __init atkbd_setup_keymap_fixup(const struct dmi_system_id *id)
+{
+	atkbd_platform_fixup = atkbd_apply_keymap_fixup;
+	atkbd_platform_fixup_data = id->driver_data;
+
+	return 1;
+}
+
 static int __init atkbd_setup_scancode_fixup(const struct dmi_system_id *id)
 {
 	atkbd_platform_scancode_fixup = id->driver_data;
@@ -1937,6 +1979,15 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = {
 		},
 		.callback = atkbd_deactivate_fixup,
 	},
+	{
+		/* Positron Proxima 15 - Fn key (0x6e) should not generate F23 */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LNPO Positron LLC"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "G1569"),
+		},
+		.callback = atkbd_setup_keymap_fixup,
+		.driver_data = atkbd_positron_fn_keymap_fixup_scancodes,
+	},
 	{ }
 };
 
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: Alexander Koskovich @ 2026-02-18  0:19 UTC (permalink / raw)
  To: aelin
  Cc: conor+dt, devicetree, dmitry.torokhov, hansg, krzk+dt,
	linux-input, linux-kernel, linux, neil.armstrong, pc1598,
	phone-devel, robh, rydberg, ~postmarketos/upstreaming,
	Alexander Koskovich

Validated on the ASUS ROG Phone 3 (GT9896).

Tested-by: Alexander Koskovich <AKoskovich@pm.me>


^ permalink raw reply

* [PATCH 1/3] dt-bindings: input: document Goodix GTX8 Touchscreen ICs
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Hans de Goede, Neil Armstrong, Henrik Rydberg
  Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
	~postmarketos/upstreaming, Aelin Reidel
In-Reply-To: <20260218-gtx8-v1-0-0d575b3dedc5@mainlining.org>

Document the Goodix GT9886 and GT9896 which are part of the GTX8 series
of Touchscreen controller ICs from Goodix.

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
 .../bindings/input/touchscreen/goodix,gt9886.yaml  | 71 ++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml b/Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6307495c2746313cfc32cdbb701455d1596be435
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/goodix,gt9886.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Goodix GTX8 series touchscreen controller
+
+maintainers:
+  - Aelin Reidel <aelin@mainlining.org>
+
+allOf:
+  - $ref: touchscreen.yaml#
+
+properties:
+  compatible:
+    enum:
+      - goodix,gt9886
+      - goodix,gt9896
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  avdd-supply:
+    description: Analog power supply regulator on AVDD pin
+
+  vddio-supply:
+    description: power supply regulator on VDDIO pin
+
+  touchscreen-inverted-x: true
+  touchscreen-inverted-y: true
+  touchscreen-size-x: true
+  touchscreen-size-y: true
+  touchscreen-swapped-x-y: true
+
+additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - avdd-supply
+  - touchscreen-size-x
+  - touchscreen-size-y
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/gpio/gpio.h>
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+      touchscreen@5d {
+        compatible = "goodix,gt9886";
+        reg = <0x5d>;
+        interrupt-parent = <&gpio>;
+        interrupts = <9 IRQ_TYPE_LEVEL_LOW>;
+        reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
+        avdd-supply = <&ts_avdd>;
+        touchscreen-size-x = <1080>;
+        touchscreen-size-y = <2340>;
+      };
+    };
+
+...

-- 
2.53.0


^ permalink raw reply related

* [PATCH 0/3] Input: add initial support for Goodix GTX8 touchscreen ICs
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Hans de Goede, Neil Armstrong, Henrik Rydberg
  Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
	~postmarketos/upstreaming, Aelin Reidel, Piyush Raj Chouhan

These ICs support SPI and I2C interfaces, up to 10 finger touch, stylus
and gesture events.

This driver is derived from the Goodix gtx8_driver_linux available at
[1] and only supports the GT9886 and GT9896 ICs present in the Xiaomi
Mi 9T and Xiaomi Redmi Note 10 Pro smartphones.

The current implementation only supports Normandy and Yellowstone type
ICs, aka only GT9886 and GT9896. It is also limited to I2C only, since I
don't have a device with GTX8 over SPI at hand. Adding support for SPI
should be fairly easy in the future, since the code uses a regmap.

Support for advanced features like:
- Firmware updates
- Stylus events
- Gesture events
- Nanjing IC support
is not included in current version.

The current support requires a previously flashed firmware to be
present.

As I did not have access to datasheets for these ICs, I extracted the
addresses from a couple of config files using a small tool [2]. The
addresses are identical for the same IC families in all configs I
observed, however not all of them make sense and I stubbed out firmware
request support due to this.

I've taken a lot of inspiration from the goodix_berlin driver, but the 
Berlin and GTX8 series of touchscreen ICs differ quite a bit. The driver 
architecture is the same overall, i.e. the power-up sequence and general 
concepts are the mostly same, but it is very clear that they are 
different generations when looking at it in more detail.

Some of the differences:
- There is no equivalent to the bootoption reg that I can find in the 
public GTX8 drivers
- Firmware version struct layout is different yet again
- GTX8 does not expose IC information at runtime as far as I can tell
- The checksum method differs yet again
- The vendor driver reads only 1 touch upfront rather than 2
- Register addresses are 16-bit on GTX8 and 32-bit on Berlin
- Firmware requests don't appear to really exist on GTX8

From what I can tell, the evolution seems to be:
Normandy -> Yellowstone -> Berlin
since Normandy and Yellowstone are already quite different (especially 
with the way checksums work) and Yellowstone has a couple of things 
(checksum, fw_version) that appear similar to Berlin series ICs.

I've tried to make the Berlin driver work for GTX8 ICs before, but 
they're so different (and I lack documentation for registers to perhaps 
make some parts work on GTX8) that I'd rather support these ICs in a new 
and tiny driver. I hope that makes sense. I took heavy inspiration from 
the Berlin driver, but the only parts that are really common between 
them are very trivial things like e.g. the input dev config or power on, 
which I don't think are worth putting in a separate header.

[1] https://github.com/goodix/gtx8_driver_linux
[2] https://github.com/sm7150-mainline/goodix-cfg-bin

Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
Changes in v1 (post-RFC):
- Drop RFC prefix, the series has been tested enough and works well
  as-is
- Update my name and email address
- Add some reasoning for a new driver to the cover letter
- Add Rob's R-b on the dt-bindings patch
- Add Piyush's T-b to the driver patch
- Link to RFC: https://lore.kernel.org/r/20250918-gtx8-v1-0-cba879c84775@mainlining.org

---
Aelin Reidel (3):
      dt-bindings: input: document Goodix GTX8 Touchscreen ICs
      Input: add support for Goodix GTX8 Touchscreen ICs
      MAINTAINERS: add an entry for Goodix GTX8 Touchscreen driver

 .../bindings/input/touchscreen/goodix,gt9886.yaml  |  71 +++
 MAINTAINERS                                        |   7 +
 drivers/input/touchscreen/Kconfig                  |  15 +
 drivers/input/touchscreen/Makefile                 |   1 +
 drivers/input/touchscreen/goodix_gtx8.c            | 562 +++++++++++++++++++++
 drivers/input/touchscreen/goodix_gtx8.h            | 137 +++++
 6 files changed, 793 insertions(+)
---
base-commit: fe9e3edb6a215515d1148d32a5c445c5bdd7916f
change-id: 20250918-gtx8-59a50ccd78a5

Best regards,
-- 
Aelin Reidel <aelin@mainlining.org>


^ permalink raw reply

* [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Hans de Goede, Neil Armstrong, Henrik Rydberg
  Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
	~postmarketos/upstreaming, Aelin Reidel, Piyush Raj Chouhan
In-Reply-To: <20260218-gtx8-v1-0-0d575b3dedc5@mainlining.org>

Add initial support for the Goodix GTX8 touchscreen ICs.

These ICs support SPI and I2C interfaces, up to 10 finger touch, stylus
and gesture events.

This driver is derived from the Goodix gtx8_driver_linux available at
[1] and only supports the GT9886 and GT9896 ICs present in the Xiaomi
Mi 9T and Xiaomi Redmi Note 10 Pro smartphones.

The current implementation only supports Normandy and Yellowstone type
ICs, aka only GT9886 and GT9896. It is also limited to I2C only, since I
don't have a device with GTX8 over SPI at hand. Adding support for SPI
should be fairly easy in the future, since the code uses a regmap.

Support for advanced features like:
- Firmware updates
- Stylus events
- Gesture events
- Nanjing IC support
is not included in current version.

The current support requires a previously flashed firmware to be
present.

As I did not have access to datasheets for these ICs, I extracted the
addresses from a couple of config files using a small tool [2]. The
addresses are identical for the same IC families in all configs I
observed, however not all of them make sense and I stubbed out firmware
request support due to this.

[1] https://github.com/goodix/gtx8_driver_linux
[2] https://github.com/sm7150-mainline/goodix-cfg-bin

Tested-by: Piyush Raj Chouhan <pc1598@mainlining.org>
Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
 drivers/input/touchscreen/Kconfig       |  15 +
 drivers/input/touchscreen/Makefile      |   1 +
 drivers/input/touchscreen/goodix_gtx8.c | 562 ++++++++++++++++++++++++++++++++
 drivers/input/touchscreen/goodix_gtx8.h | 137 ++++++++
 4 files changed, 715 insertions(+)

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 7d5b72ee07fa1313da39a625b5129a0459720865..099ccd3679383dcf037bc7c6e6a3dbf0741722b4 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -429,6 +429,21 @@ config TOUCHSCREEN_GOODIX_BERLIN_SPI
 	  To compile this driver as a module, choose M here: the
 	  module will be called goodix_berlin_spi.
 
+config TOUCHSCREEN_GOODIX_GTX8
+	tristate "Goodix GTX8 touchscreen"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Say Y here if you have a Goodix GTX8 IC connected to
+	  your system via I2C. This driver supports Normandy and
+	  Yellowstone ICs like the GT9886 and GT9896.
+	  They are commonly found in mobile phones.
+
+	  if unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called goodix_gtx8.
+
 config TOUCHSCREEN_HIDEEP
 	tristate "HiDeep Touch IC"
 	depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index ab9abd151078831a4b22d6998e00ef74fe01c356..9bcb8f01ea785dcbe2a22bd3293601dd4259ba1d 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix_ts.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE)	+= goodix_berlin_core.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C)	+= goodix_berlin_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI)	+= goodix_berlin_spi.o
+obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8)	+= goodix_gtx8.o
 obj-$(CONFIG_TOUCHSCREEN_HIDEEP)	+= hideep.o
 obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX852X)	+= himax_hx852x.o
 obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX)	+= hynitron_cstxxx.o
diff --git a/drivers/input/touchscreen/goodix_gtx8.c b/drivers/input/touchscreen/goodix_gtx8.c
new file mode 100644
index 0000000000000000000000000000000000000000..b210e866974e423b413ca1741b3b6c7df117b92f
--- /dev/null
+++ b/drivers/input/touchscreen/goodix_gtx8.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Goodix GTX8 Touchscreens
+ *
+ * Copyright (c) 2019 - 2020 Goodix, Inc.
+ * Copyright (C) 2023 Linaro Ltd.
+ * Copyright (c) 2025 Aelin Reidel <aelin@mainlining.org>
+ *
+ * Based on gtx8_driver_linux vendor driver and goodix_berlin kernel driver.
+ *
+ * The driver currently relies on the pre-flashed firmware and only supports
+ * Normandy / Yellowstone ICs.
+ * Pen support is also missing.
+ */
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/unaligned.h>
+
+#include "goodix_gtx8.h"
+
+static const struct regmap_config goodix_gtx8_regmap_conf = {
+	.reg_bits = 16,
+	.val_bits = 8,
+	.max_raw_read = I2C_MAX_TRANSFER_SIZE,
+	.max_raw_write = I2C_MAX_TRANSFER_SIZE,
+};
+
+/* vendor & product left unassigned here, should probably be updated from fw info */
+static const struct input_id goodix_gtx8_input_id = {
+	.bustype = BUS_I2C,
+};
+
+static bool goodix_gtx8_checksum_valid_normandy(const u8 *data, int size)
+{
+	u8 cal_checksum = 0;
+	int i;
+
+	if (size < GOODIX_GTX8_CHECKSUM_SIZE)
+		return false;
+
+	for (i = 0; i < size; i++)
+		cal_checksum += data[i];
+
+	return cal_checksum == 0;
+}
+
+static bool goodix_gtx8_checksum_valid_yellowstone(const u8 *data, int size)
+{
+	u16 cal_checksum = 0;
+	u16 r_checksum;
+	int i;
+
+	if (size < GOODIX_GTX8_CHECKSUM_SIZE)
+		return false;
+
+	for (i = 0; i < size - GOODIX_GTX8_CHECKSUM_SIZE; i++)
+		cal_checksum += data[i];
+
+	r_checksum = get_unaligned_be16(&data[i]);
+
+	return cal_checksum == r_checksum;
+}
+
+static int goodix_gtx8_get_remaining_contacts(struct goodix_gtx8_core *cd,
+					      int n)
+{
+	size_t offset = cd->ic_data->header_size + GOODIX_GTX8_TOUCH_SIZE +
+			GOODIX_GTX8_CHECKSUM_SIZE;
+	u32 addr = cd->ic_data->touch_data_addr + offset;
+	int error;
+
+	error = regmap_raw_read(cd->regmap, addr, &cd->event_buffer[offset],
+				(n - 1) * GOODIX_GTX8_TOUCH_SIZE);
+	if (error) {
+		dev_err_ratelimited(cd->dev, "failed to get touch data, %d\n",
+				    error);
+		return error;
+	}
+
+	return 0;
+}
+
+static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
+				     union goodix_gtx8_touch *touch_data)
+{
+	union goodix_gtx8_touch *t;
+	int i;
+	u8 finger_id;
+
+	for (i = 0; i < touch_num; i++) {
+		t = &touch_data[i];
+
+		if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
+			input_mt_slot(cd->input_dev, t->normandy.finger_id);
+			input_mt_report_slot_state(cd->input_dev,
+						   MT_TOOL_FINGER, true);
+
+			touchscreen_report_pos(cd->input_dev, &cd->props,
+					       __le16_to_cpu(t->normandy.x),
+					       __le16_to_cpu(t->normandy.y),
+					       true);
+			input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
+					 t->normandy.w);
+		} else {
+			finger_id = FIELD_GET(
+				GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
+				t->yellowstone.finger_id);
+			input_mt_slot(cd->input_dev, finger_id);
+			input_mt_report_slot_state(cd->input_dev,
+						   MT_TOOL_FINGER, true);
+
+			touchscreen_report_pos(cd->input_dev, &cd->props,
+					       __be16_to_cpu(t->yellowstone.x),
+					       __be16_to_cpu(t->yellowstone.y),
+					       true);
+			input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
+					 t->yellowstone.w);
+		}
+	}
+
+	input_mt_sync_frame(cd->input_dev);
+	input_sync(cd->input_dev);
+}
+
+static void goodix_gtx8_touch_handler(struct goodix_gtx8_core *cd, u8 touch_num,
+				      union goodix_gtx8_touch *touch_data)
+{
+	int error;
+
+	touch_num = FIELD_GET(GOODIX_GTX8_TOUCH_COUNT_MASK, touch_num);
+
+	if (touch_num > GOODIX_GTX8_MAX_TOUCH) {
+		dev_warn(cd->dev, "invalid touch num %d\n", touch_num);
+		return;
+	}
+
+	if (touch_num > 1) {
+		/* read additional contact data if more than 1 touch event */
+		error = goodix_gtx8_get_remaining_contacts(cd, touch_num);
+		if (error)
+			return;
+	}
+
+	if (touch_num) {
+		/*
+		 * Normandy checksum is for the entire read buffer,
+		 * Yellowstone is only for the touch data (since header
+		 * has a separate checksum)
+		 */
+		if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
+			int len = GOODIX_GTX8_HEADER_SIZE_NORMANDY +
+				  touch_num * GOODIX_GTX8_TOUCH_SIZE +
+				  GOODIX_GTX8_CHECKSUM_SIZE;
+			if (!goodix_gtx8_checksum_valid_normandy(
+				    cd->event_buffer, len)) {
+				dev_err(cd->dev,
+					"touch data checksum error: %*ph\n",
+					len, cd->event_buffer);
+				return;
+			}
+		} else {
+			int len = touch_num * GOODIX_GTX8_TOUCH_SIZE +
+				  GOODIX_GTX8_CHECKSUM_SIZE;
+			if (!goodix_gtx8_checksum_valid_yellowstone(
+				    (u8 *)touch_data, len)) {
+				dev_err(cd->dev,
+					"touch data checksum error: %*ph\n",
+					len, (u8 *)touch_data);
+				return;
+			}
+		}
+	}
+
+	goodix_gtx8_report_state(cd, touch_num, touch_data);
+}
+
+static irqreturn_t goodix_gtx8_irq(int irq, void *data)
+{
+	struct goodix_gtx8_core *cd = data;
+	struct goodix_gtx8_event_normandy *ev_normandy;
+	struct goodix_gtx8_event_yellowstone *ev_yellowstone;
+	union goodix_gtx8_touch *touch_data;
+	int error;
+	u8 status, touch_num;
+
+	error = regmap_raw_read(
+		cd->regmap, cd->ic_data->touch_data_addr, cd->event_buffer,
+		cd->ic_data->header_size + GOODIX_GTX8_TOUCH_SIZE +
+			GOODIX_GTX8_CHECKSUM_SIZE);
+	if (error) {
+		dev_warn_ratelimited(
+			cd->dev, "failed to get event head data: %d\n", error);
+		goto out;
+	}
+
+	/*
+	 * Both IC types have the same data in the header, just at different
+	 * offsets
+	 */
+	if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
+		ev_normandy =
+			(struct goodix_gtx8_event_normandy *)cd->event_buffer;
+		status = ev_normandy->hdr.status;
+		touch_num = ev_normandy->hdr.touch_num;
+		touch_data = (union goodix_gtx8_touch *)ev_normandy->data;
+	} else {
+		ev_yellowstone = (struct goodix_gtx8_event_yellowstone *)
+					 cd->event_buffer;
+		status = ev_yellowstone->hdr.status;
+		touch_num = ev_yellowstone->hdr.touch_num;
+		touch_data = (union goodix_gtx8_touch *)ev_yellowstone->data;
+	}
+
+	if (status == 0)
+		goto out;
+
+	/* Yellowstone ICs have a checksum for the header */
+	if (cd->ic_data->ic_type == IC_TYPE_YELLOWSTONE &&
+	    !goodix_gtx8_checksum_valid_yellowstone(
+		    cd->event_buffer, GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE)) {
+		dev_warn_ratelimited(cd->dev,
+				     "touch head checksum error: %*ph\n",
+				     (int)GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE,
+				     cd->event_buffer);
+		goto out_clear;
+	}
+
+	if (status & GOODIX_GTX8_TOUCH_EVENT)
+		goodix_gtx8_touch_handler(cd, touch_num, touch_data);
+
+	if (status & GOODIX_GTX8_REQUEST_EVENT) {
+		/*
+		 * All configs seen so far either set the firmware request
+		 * address to 0 (Normandy) or have it equal the touch data
+		 * address (Yellowstone). Neither seems correct, and this
+		 * is not testable. Therefore it is currently omitted.
+		 */
+		dev_dbg(cd->dev, "received request event, ignoring\n");
+	}
+
+out_clear:
+	/* Clear up status field */
+	regmap_write(cd->regmap, cd->ic_data->touch_data_addr, 0);
+
+out:
+	return IRQ_HANDLED;
+}
+
+static int goodix_gtx8_input_dev_config(struct goodix_gtx8_core *cd)
+{
+	struct input_dev *input_dev;
+	int error;
+
+	input_dev = devm_input_allocate_device(cd->dev);
+	if (!input_dev)
+		return -ENOMEM;
+
+	cd->input_dev = input_dev;
+	input_set_drvdata(input_dev, cd);
+
+	input_dev->name = "Goodix GTX8 Capacitive TouchScreen";
+	input_dev->phys = "input/ts";
+
+	input_dev->id = goodix_gtx8_input_id;
+
+	input_set_abs_params(cd->input_dev, ABS_MT_POSITION_X, 0, SZ_64K - 1, 0,
+			     0);
+	input_set_abs_params(cd->input_dev, ABS_MT_POSITION_Y, 0, SZ_64K - 1, 0,
+			     0);
+	input_set_abs_params(cd->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+
+	touchscreen_parse_properties(cd->input_dev, true, &cd->props);
+
+	error = input_mt_init_slots(cd->input_dev, GOODIX_GTX8_MAX_TOUCH,
+				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (error)
+		return error;
+
+	error = input_register_device(cd->input_dev);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static int goodix_gtx8_read_version(struct goodix_gtx8_core *cd)
+{
+	int error;
+
+	/*
+	 * The vendor driver reads a whole lot more data to calculate and
+	 * verify a checksum. Without documentation, we don't know what
+	 * most of that data is, so we only read the parts we know about
+	 * and instead ensure their values are as expected
+	 */
+	error = regmap_raw_read(cd->regmap, cd->ic_data->fw_version_addr,
+				&cd->fw_version, sizeof(cd->fw_version));
+	if (error) {
+		dev_err(cd->dev, "error reading fw version, %d\n", error);
+		return error;
+	}
+
+	/*
+	 * Since we don't verify the checksum, do a basic check that the
+	 * product ID meets expectations
+	 */
+	if (memcmp(cd->fw_version.product_id, cd->ic_data->product_id,
+		   sizeof(cd->fw_version.product_id))) {
+		dev_err(cd->dev, "unexpected product ID, got: %c%c%c%c\n",
+			cd->fw_version.product_id[0],
+			cd->fw_version.product_id[1],
+			cd->fw_version.product_id[2],
+			cd->fw_version.product_id[3]);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int goodix_gtx8_dev_confirm(struct goodix_gtx8_core *cd)
+{
+	u8 rx_buf[1];
+	int retry = 3;
+	int error;
+
+	while (retry--) {
+		/*
+		 * test_addr appears to always be the touch_data_addr for
+		 * Normandy, but it doesn't really matter since all we
+		 * need is a valid address
+		 */
+		error = regmap_raw_read(cd->regmap,
+					cd->ic_data->touch_data_addr, rx_buf,
+					sizeof(rx_buf));
+
+		if (!error)
+			return 0;
+
+		usleep_range(5000, 5100);
+	}
+
+	dev_err(cd->dev, "device confirm failed\n");
+
+	return -EINVAL;
+}
+
+static int goodix_gtx8_power_on(struct goodix_gtx8_core *cd)
+{
+	int error;
+
+	error = regulator_enable(cd->vddio);
+	if (error) {
+		dev_err(cd->dev, "Failed to enable VDDIO: %d\n", error);
+		return error;
+	}
+
+	error = regulator_enable(cd->avdd);
+	if (error) {
+		dev_err(cd->dev, "Failed to enable AVDD: %d\n", error);
+		goto err_vddio_disable;
+	}
+
+	/* Vendors usually configure the power on delay as 300ms */
+	msleep(GOODIX_GTX8_POWER_ON_DELAY_MS);
+
+	gpiod_set_value_cansleep(cd->reset_gpio, 0);
+
+	/* Vendor waits 5ms for firmware to initialize */
+	usleep_range(5000, 5100);
+
+	error = goodix_gtx8_dev_confirm(cd);
+	if (error)
+		goto err_dev_reset;
+
+	/* Vendor waits 100ms for firmware to fully boot */
+	msleep(GOODIX_GTX8_NORMAL_RESET_DELAY_MS);
+
+	return 0;
+
+err_dev_reset:
+	gpiod_set_value_cansleep(cd->reset_gpio, 1);
+	regulator_disable(cd->avdd);
+err_vddio_disable:
+	regulator_disable(cd->vddio);
+	return error;
+}
+
+static void goodix_gtx8_power_off(struct goodix_gtx8_core *cd)
+{
+	gpiod_set_value_cansleep(cd->reset_gpio, 1);
+	regulator_disable(cd->avdd);
+	regulator_disable(cd->vddio);
+}
+
+static int goodix_gtx8_suspend(struct device *dev)
+{
+	struct goodix_gtx8_core *cd = dev_get_drvdata(dev);
+
+	disable_irq(cd->irq);
+	goodix_gtx8_power_off(cd);
+
+	return 0;
+}
+
+static int goodix_gtx8_resume(struct device *dev)
+{
+	struct goodix_gtx8_core *cd = dev_get_drvdata(dev);
+	int error;
+
+	error = goodix_gtx8_power_on(cd);
+	if (error)
+		return error;
+
+	enable_irq(cd->irq);
+
+	return 0;
+}
+
+EXPORT_GPL_SIMPLE_DEV_PM_OPS(goodix_gtx8_pm_ops, goodix_gtx8_suspend,
+			     goodix_gtx8_resume);
+
+static void goodix_gtx8_power_off_act(void *data)
+{
+	struct goodix_gtx8_core *cd = data;
+
+	goodix_gtx8_power_off(cd);
+}
+
+static int goodix_gtx8_probe(struct i2c_client *client)
+{
+	struct goodix_gtx8_core *cd;
+	struct regmap *regmap;
+	int error;
+
+	cd = devm_kzalloc(&client->dev, sizeof(*cd), GFP_KERNEL);
+	if (!cd)
+		return -ENOMEM;
+
+	regmap = devm_regmap_init_i2c(client, &goodix_gtx8_regmap_conf);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	cd->dev = &client->dev;
+	cd->irq = client->irq;
+	cd->regmap = regmap;
+	cd->ic_data = i2c_get_match_data(client);
+
+	cd->event_buffer =
+		devm_kzalloc(cd->dev, cd->ic_data->event_size, GFP_KERNEL);
+	if (!cd->event_buffer)
+		return -ENOMEM;
+
+	cd->reset_gpio =
+		devm_gpiod_get_optional(cd->dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(cd->reset_gpio))
+		return dev_err_probe(cd->dev, PTR_ERR(cd->reset_gpio),
+				     "Failed to request reset GPIO\n");
+
+	cd->avdd = devm_regulator_get(cd->dev, "avdd");
+	if (IS_ERR(cd->avdd))
+		return dev_err_probe(cd->dev, PTR_ERR(cd->avdd),
+				     "Failed to request AVDD regulator\n");
+
+	cd->vddio = devm_regulator_get(cd->dev, "vddio");
+	if (IS_ERR(cd->vddio))
+		return dev_err_probe(cd->dev, PTR_ERR(cd->vddio),
+				     "Failed to request VDDIO regulator\n");
+
+	error = goodix_gtx8_power_on(cd);
+	if (error) {
+		dev_err(cd->dev, "failed power on");
+		return error;
+	}
+
+	error = devm_add_action_or_reset(cd->dev, goodix_gtx8_power_off_act,
+					 cd);
+	if (error)
+		return error;
+
+	error = goodix_gtx8_read_version(cd);
+	if (error) {
+		dev_err(cd->dev, "failed to get version info");
+		return error;
+	}
+
+	error = goodix_gtx8_input_dev_config(cd);
+	if (error) {
+		dev_err(cd->dev, "failed to set input device");
+		return error;
+	}
+
+	error = devm_request_threaded_irq(cd->dev, cd->irq, NULL,
+					  goodix_gtx8_irq, IRQF_ONESHOT,
+					  "goodix-gtx8", cd);
+	if (error) {
+		dev_err(cd->dev, "request threaded IRQ failed: %d\n", error);
+		return error;
+	}
+
+	dev_set_drvdata(cd->dev, cd);
+
+	dev_dbg(cd->dev,
+		"Goodix GT%c%c%c%c Touchscreen Controller, Version %d.%d.%d.%d\n",
+		cd->fw_version.product_id[0], cd->fw_version.product_id[1],
+		cd->fw_version.product_id[2], cd->fw_version.product_id[3],
+		cd->fw_version.fw_version[0], cd->fw_version.fw_version[1],
+		cd->fw_version.fw_version[2], cd->fw_version.fw_version[3]);
+
+	return 0;
+}
+
+static const struct goodix_gtx8_ic_data gt9886_data = {
+	.event_size = GOODIX_GTX8_EVENT_SIZE_NORMANDY,
+	.fw_version_addr = GOODIX_GTX8_FW_VERSION_ADDR_NORMANDY,
+	.header_size = GOODIX_GTX8_HEADER_SIZE_NORMANDY,
+	.ic_type = IC_TYPE_NORMANDY,
+	.product_id = { '9', '8', '8', '6' },
+	.touch_data_addr = GOODIX_GTX8_TOUCH_DATA_ADDR_NORMANDY,
+};
+
+static const struct goodix_gtx8_ic_data gt9896_data = {
+	.event_size = GOODIX_GTX8_EVENT_SIZE_YELLOWSTONE,
+	.fw_version_addr = GOODIX_GTX8_FW_VERSION_ADDR_YELLOWSTONE,
+	.header_size = GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE,
+	.ic_type = IC_TYPE_YELLOWSTONE,
+	.product_id = { '9', '8', '9', '6' },
+	.touch_data_addr = GOODIX_GTX8_TOUCH_DATA_ADDR_YELLOWSTONE,
+};
+
+static const struct i2c_device_id goodix_gtx8_i2c_id[] = {
+	{ .name = "gt9886", .driver_data = (long)&gt9886_data },
+	{ .name = "gt9896", .driver_data = (long)&gt9896_data },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, goodix_gtx8_i2c_id);
+
+static const struct of_device_id goodix_gtx8_of_match[] = {
+	{ .compatible = "goodix,gt9886", .data = &gt9886_data },
+	{ .compatible = "goodix,gt9896", .data = &gt9896_data },
+	{},
+};
+MODULE_DEVICE_TABLE(of, goodix_gtx8_of_match);
+
+static struct i2c_driver goodix_gtx8_driver = {
+	.probe = goodix_gtx8_probe,
+	.id_table = goodix_gtx8_i2c_id,
+	.driver = {
+		.name = "goodix-gtx8",
+		.of_match_table = of_match_ptr(goodix_gtx8_of_match),
+		.pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
+	},
+};
+module_i2c_driver(goodix_gtx8_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Goodix GTX8 Touchscreen driver");
+MODULE_AUTHOR("Aelin Reidel <aelin@mainlining.org>");
diff --git a/drivers/input/touchscreen/goodix_gtx8.h b/drivers/input/touchscreen/goodix_gtx8.h
new file mode 100644
index 0000000000000000000000000000000000000000..79e79988869b46a3fc70fa64b4698cdb1d7a0394
--- /dev/null
+++ b/drivers/input/touchscreen/goodix_gtx8.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __GOODIX_GTX8_H__
+#define __GOODIX_GTX8_H__
+
+#define GOODIX_GTX8_NORMAL_RESET_DELAY_MS	100
+#define GOODIX_GTX8_POWER_ON_DELAY_MS		300
+
+#define GOODIX_GTX8_TOUCH_EVENT			BIT(7)
+#define GOODIX_GTX8_REQUEST_EVENT		BIT(6)
+#define GOODIX_GTX8_TOUCH_COUNT_MASK		GENMASK(3, 0)
+#define GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE	GENMASK(7, 4)
+
+#define GOODIX_GTX8_MAX_TOUCH			10
+#define GOODIX_GTX8_CHECKSUM_SIZE		sizeof(u16)
+
+#define GOODIX_GTX8_FW_VERSION_ADDR_NORMANDY	0x4535
+#define GOODIX_GTX8_FW_VERSION_ADDR_YELLOWSTONE	0x4022
+#define GOODIX_GTX8_TOUCH_DATA_ADDR_NORMANDY	0x4100
+#define GOODIX_GTX8_TOUCH_DATA_ADDR_YELLOWSTONE	0x4180
+
+#define I2C_MAX_TRANSFER_SIZE			256
+
+enum goodix_gtx8_ic_type {
+	IC_TYPE_NORMANDY,
+	IC_TYPE_YELLOWSTONE,
+};
+
+struct goodix_gtx8_ic_data {
+	size_t event_size;
+	/*
+	 * This is technically not the firmware version address
+	 * referenced in the vendor driver, but rather the
+	 * address of the product ID part. The meaning of the
+	 * other parts is unknown and they are therefore omitted
+	 * for now.
+	 */
+	int fw_version_addr;
+	size_t header_size;
+	enum goodix_gtx8_ic_type ic_type;
+	char product_id[4];
+	int touch_data_addr;
+};
+
+struct goodix_gtx8_header_normandy {
+	u8 status;
+	/* Only the lower 4 bits are actually used */
+	u8 touch_num;
+};
+#define GOODIX_GTX8_HEADER_SIZE_NORMANDY \
+	sizeof(struct goodix_gtx8_header_normandy)
+
+struct goodix_gtx8_header_yellowstone {
+	u8 status;
+	/* Most likely unused */
+	u8 __unknown1;
+	/* Only the lower 4 bits are actually used */
+	u8 touch_num;
+	/* Most likely unused */
+	u8 __unknown2[3];
+	__le16 checksum;
+} __packed __aligned(1);
+#define GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE \
+	sizeof(struct goodix_gtx8_header_yellowstone)
+
+struct goodix_gtx8_touch_normandy {
+	u8 finger_id;
+	__le16 x;
+	__le16 y;
+	u8 w;
+	u8 __unknown[2];
+} __packed __aligned(1);
+
+struct goodix_gtx8_touch_yellowstone {
+	/*
+	 * Only the upper 4 bits are used, lower 4 bits are
+	 * probably the sensor ID.
+	 */
+	u8 finger_id;
+	u8 __unknown1;
+	__be16 x;
+	__be16 y;
+	/*
+	 * Vendor driver claims that this is a single __be16,
+	 * but testing shows that it likely isn't.
+	 */
+	u8 __unknown2;
+	u8 w;
+} __packed __aligned(1);
+
+union goodix_gtx8_touch {
+	struct goodix_gtx8_touch_normandy normandy;
+	struct goodix_gtx8_touch_yellowstone yellowstone;
+};
+#define GOODIX_GTX8_TOUCH_SIZE		sizeof(union goodix_gtx8_touch)
+
+struct goodix_gtx8_event_normandy {
+	struct goodix_gtx8_header_normandy hdr;
+	/* The data below is u16 aligned */
+	u8 data[GOODIX_GTX8_TOUCH_SIZE * GOODIX_GTX8_MAX_TOUCH +
+		GOODIX_GTX8_CHECKSUM_SIZE];
+};
+#define GOODIX_GTX8_EVENT_SIZE_NORMANDY \
+	sizeof(struct goodix_gtx8_event_normandy)
+
+struct goodix_gtx8_event_yellowstone {
+	struct goodix_gtx8_header_yellowstone hdr;
+	/* The data below is u16 aligned */
+	u8 data[GOODIX_GTX8_TOUCH_SIZE * GOODIX_GTX8_MAX_TOUCH +
+		GOODIX_GTX8_CHECKSUM_SIZE];
+};
+#define GOODIX_GTX8_EVENT_SIZE_YELLOWSTONE \
+	sizeof(struct goodix_gtx8_event_yellowstone)
+
+struct goodix_gtx8_fw_version {
+	/* 4 digits IC number */
+	char product_id[4];
+	/* Most likely unused */
+	u8 __unknown[4];
+	/* Four components version number */
+	u8 fw_version[4];
+};
+
+struct goodix_gtx8_core {
+	struct device *dev;
+	struct regmap *regmap;
+	struct regulator *avdd;
+	struct regulator *vddio;
+	struct gpio_desc *reset_gpio;
+	struct touchscreen_properties props;
+	struct goodix_gtx8_fw_version fw_version;
+	struct input_dev *input_dev;
+	int irq;
+	const struct goodix_gtx8_ic_data *ic_data;
+	u8 *event_buffer;
+};
+
+#endif

-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/3] MAINTAINERS: add an entry for Goodix GTX8 Touchscreen driver
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Hans de Goede, Neil Armstrong, Henrik Rydberg
  Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
	~postmarketos/upstreaming, Aelin Reidel
In-Reply-To: <20260218-gtx8-v1-0-0d575b3dedc5@mainlining.org>

Add MAINTAINERS entry for the Goodix GTX8 Touchscreen IC driver.

Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index afbba2fdc0f49abb6d0d1877a5e161266715f275..cb0f19d622e25cd9a5ceb8fc1e781a1f2232c64a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10831,6 +10831,13 @@ M:	Maud Spierings <maudspierings@gocontroll.com>
 S:	Maintained
 F:	Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml
 
+GOODIX GTX8 TOUCHSCREEN
+M:	Aelin Reidel <aelin@mainlining.org>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml
+F:	drivers/input/touchscreen/goodix_gtx8*
+
 GOODIX TOUCHSCREEN
 M:	Hans de Goede <hansg@kernel.org>
 L:	linux-input@vger.kernel.org

-- 
2.53.0


^ permalink raw reply related

* [syzbot] Monthly input report (Feb 2026)
From: syzbot @ 2026-02-17 22:04 UTC (permalink / raw)
  To: linux-input, linux-kernel, syzkaller-bugs

Hello input maintainers/developers,

This is a 31-day syzbot report for the input subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/input

During the period, 0 new issues were detected and 0 were fixed.
In total, 22 issues are still open and 63 have already been fixed.

Some of the still happening issues:

Ref Crashes Repro Title
<1> 3448    Yes   WARNING in cm109_urb_irq_callback/usb_submit_urb
                  https://syzkaller.appspot.com/bug?extid=2d6d691af5ab4b7e66df
<2> 1677    No    possible deadlock in evdev_pass_values (2)
                  https://syzkaller.appspot.com/bug?extid=13d3cb2a3dc61e6092f5
<3> 430     Yes   KASAN: slab-out-of-bounds Read in mcp2221_raw_event (2)
                  https://syzkaller.appspot.com/bug?extid=1018672fe70298606e5f
<4> 116     Yes   WARNING in cm109_input_open/usb_submit_urb (3)
                  https://syzkaller.appspot.com/bug?extid=ac0f9c4cc1e034160492
<5> 91      Yes   possible deadlock in uinput_request_submit
                  https://syzkaller.appspot.com/bug?extid=159077b1355b8cd72757
<6> 59      No    KASAN: slab-use-after-free Read in report_descriptor_read
                  https://syzkaller.appspot.com/bug?extid=bc537ca7a0efe33988eb
<7> 20      Yes   INFO: task hung in __input_unregister_device (5)
                  https://syzkaller.appspot.com/bug?extid=78e2288f58b881ed3c45

---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders

To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem

You may send multiple commands in a single email message.

^ permalink raw reply

* Re: [PATCH 0/3] HID: Fix some memory leaks in drivers/hid
From: Günther Noack @ 2026-02-17 20:08 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <aZS0OAaSPhX2pJ6l@plouf>

Hello!

On Tue, Feb 17, 2026 at 07:36:46PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > These patches fix a few memory leaks in HID report descriptor fixups.
> > 
> > FWIW, a good ad-hoc way to look for usages of allocation functions in
> > these is:
> > 
> >   awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c \
> >     | grep -E '(malloc|kzalloc|kcalloc|kmemdup)'
> > 
> > The devm_* variants are safe in this context, because they tie the
> > allocated memory to the lifetime of the driver.
> 
> No. Look at hid_close_report() in drivers/hid/hid-core.c.
> 
> HID still hasn't fully migrated to devm, so as a rule of thumb, if you
> change a kzalloc into a devm_kzalloc, you are getting into troubles
> unless you fix the all the kfree path.

OK, I have not verified where the devm-allocated objects get freed up.
If devm_*() is not possible here, then the drivers hid-asus.c and
hid-gembird.c have two additional memory leaks, because they do that.

$ awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c | grep -E 'alloc'
drivers/hid/hid-asus.c          new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
drivers/hid/hid-gembird.c               new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);

(That is without my threee patches)


> > For transparency, I generated these commits with Gemini-CLI,
> > starting with this prompt:
> > 
> >     We are working in the Linux kernel. In the HID drivers in
> >     `drivers/hid/hid-*.c`, the `report_fixup` driver hook is a function
> >     that gets a byte buffer (with size) as input and that may modify that
> >     byte buffer, and optionally return a pointer to a new byte buffer and
> >     update the size.  The returned value is *not* memory-managed by the
> >     caller though and will not be freed subsequently.  When the
> 
> If the memory is *not* managed, why would gemini converts kzalloc into
> devm variants without changing the kfree paths????

I'm not sure I understand the question, it's not clear to me what you
mean by the "kfree paths".

I have seen usages of devm in other HID drivers and I was under the
impression that devm_* allocations would work in the HID subsystem to
allocate objects which are then freed automatically at a later point
when the device gets removed.  Is that inaccurate?


> >     `report_fixup` implementation allocates a new buffer and returns that,
> >     that will not get freed by the caller.  
> 
> This is wrong. See hid_close_report(): if the new rdesc (after fixup)
> differs from the one initially set, there is an explicit call to
> kfree().
> 
> -> there is no memleak AFAICT, and your prompt is wrong.

See my discussion in [1].  The pointer returned by report_fixup() is
immediately discarded in the position marked with (4).  This is still
in the hid_open_report() function where the leak happens.

Let me know whether this makes sense.  I'm happy to be corrected, but
so far, I still have the feeling that my reasoning is sound.

—Günther

[1] https://lore.kernel.org/all/aZTEnPEHcWEkoTJR@google.com/

^ permalink raw reply

* Re: [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup()
From: Günther Noack @ 2026-02-17 19:51 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <aZSzASB_TC2RyQsR@plouf>

Hello!

On Tue, Feb 17, 2026 at 07:31:23PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > The asus_report_fixup() function was allocating a new buffer with kmemdup()
> > when growing the report descriptor but never freeing it.  Switch to
> > devm_kzalloc() to ensure the memory is managed and freed automatically when
> > the device is removed.
> 
> Actually this one is even worse: you can't use devm_kzalloc because
> hid-core.c will later call kfree(dev->rdesc) if dev->rdesc is different
> from the one provided by the low level driver. So we are going to have
> a double free.

The buffer returned by report_fixup() is duplicated first before
hid-core stores it in dev->rdesc.  The pointer that report_fixup()
returns is not managed by the caller.

I elaborated in the response to the other patch in [1].  You can see
it in the source code in the position marked with (4).

[1] https://lore.kernel.org/all/aZTEnPEHcWEkoTJR@google.com/


> I really wonder if this was ever tested.

I only convinced myself by staring at the code, because I do not
happen to have the matching USB devices here.  What it your usual
approach to verifying such changes?  raw-gadget?

—Günther

^ permalink raw reply


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