Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [REGRESSSION] on linux-next (next-20250509)
From: Rodrigo Vivi @ 2025-08-21 19:26 UTC (permalink / raw)
  To: Lucas De Marchi
  Cc: Luke Jones, Borah, Chaitanya Kumar,
	intel-xe@lists.freedesktop.org, intel-gfx@lists.freedesktop.org,
	Saarinen, Jani, Kurmi, Suresh Kumar, Nikula, Jani,
	linux-input@vger.kernel.org, platform-driver-x86@vger.kernel.org,
	Corentin Chary, Hans de Goede, Ilpo Järvinen
In-Reply-To: <iwo3m4fclstsm4lqmomdmlvk7gwtxjupbczbcw6i2ib5cqv2na@nsu3y5kzqzsp>

On Thu, Jul 03, 2025 at 09:43:41AM -0500, Lucas De Marchi wrote:
> Hi,
> 
> On Wed, May 28, 2025 at 03:07:51PM +0200, Luke Jones wrote:
> > On Wed, 28 May 2025, at 12:08 PM, Borah, Chaitanya Kumar wrote:
> > > Hello Luke,
> > > 
> > > Hope you are doing well. I am Chaitanya from the linux graphics team in Intel.
> > > 
> > > This mail is regarding a regression we are seeing in our CI runs[1] on
> > > linux-next repository.
> > 
> > Can you tell me if the fix here was included?
> > https://lkml.org/lkml/2025/5/24/152
> > 
> > I could change to:
> > static void asus_s2idle_check_register(void)
> > {
> >    // Only register for Ally devices
> >    if (dmi_check_system(asus_rog_ally_device)) {
> >        if (acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops))
> >            pr_warn("failed to register LPS0 sleep handler in asus-wmi\n");
> >    }
> > }
> > 
> > but I don't really understand what is happening here. The inner lps0 functions won't run unless use_ally_mcu_hack is set.
> > 
> > I will do my best to fix but I need to understand what happened a bit better.

Hi Luke, is there anything we could do to help here? Any log or info that
could help from this machine?

This bug is blocking some of our CI runs here.

Thanks,
Rodrigo.

> 
> Any updates here? This is basically killing our tests for drm-xe-fixes
> we are submitting to 6.16 since it taints the kernel. If we can't fix,
> maybe it's already late enough in RCs that we should need a revert?
> 
> FWIW, for 6.17 we have a branch on the side we also merge before testing
> and we've been including the change above to stop it from killing the
> rest of our CI:
> https://gitlab.freedesktop.org/drm/i915/kernel/-/commit/e9d0926aa1c6afcc920013c39d5bd6dd85f581fb
> 
> Lucas De Marchi

^ permalink raw reply

* [BUG] Kernel panic in amd_sfh on Lenovo Legion Go
From: Matthew Schwartz @ 2025-08-21 18:58 UTC (permalink / raw)
  To: Basavaraj.Natikar, Shyam-sundar.S-k; +Cc: mario.limonciello, linux-input

Hello,

While using my Lenovo Legion Go handheld device on SteamOS, I have had many instances of warnings and occasional kernel panics in amd_sfh_work on their 6.15.8 kernel. Using kdumpst, I was able to get some logs for the most recent kernel panic:

<4>[  662.713733] ------------[ cut here ]------------
<4>[  662.713738] list_del corruption. prev->next should be ffff8ccc66a51e88, but was ffff8ccc5fc5d0e8. (prev=ffff8ccc41425280)
<4>[  662.713754] WARNING: CPU: 11 PID: 164 at lib/list_debug.c:62 __list_del_entry_valid_or_report+0xfa/0x10a
<4>[  662.714204] CPU: 11 UID: 0 PID: 164 Comm: kworker/11:1 Tainted: G        W           6.15.8-valve1-2-neptune-615-g49248f4e2ad1 #1 PREEMPT(full)  64605a591817db76b46b95d73e86c4364b25c841
<4>[  662.714214] Tainted: [W]=WARN
<4>[  662.714218] Hardware name: LENOVO 83E1/LNVNB161216, BIOS N3CN37WW 12/06/2024
<4>[  662.714223] Workqueue: events amd_sfh_work [amd_sfh]
<4>[  662.714236] RIP: 0010:__list_del_entry_valid_or_report+0xfa/0x10a
<4>[  662.714245] Code: e8 cb 4a 0f 00 0f 0b e9 77 ff ff ff 4c 89 e7 e8 dc 0c 3b 00 49 8b 14 24 4c 89 e1 48 89 de 48 c7 c7 28 c7 13 b0 e8 a6 4a 0f 00 <0f> 0b e9 52 ff ff ff 5b 5d 41 5c e9 91 c6 eb ff 48 89 df e8 1e fe
<4>[  662.714250] RSP: 0018:ffffd12c806e3df8 EFLAGS: 00010246
<4>[  662.714257] RAX: 0000000000000000 RBX: ffff8ccc66a51e88 RCX: 0000000000000027
<4>[  662.714262] RDX: ffff8ccf0fedbd08 RSI: 0000000000000001 RDI: ffff8ccf0fedbd00
<4>[  662.714266] RBP: ffff8ccc41425280 R08: 0000000000000000 R09: 00000000fffdffff
<4>[  662.714270] R10: ffffffffb16d0120 R11: ffffd12c806e3c88 R12: ffff8ccc41425280
<4>[  662.714274] R13: ffff8ccc66a51e88 R14: 0000000000000000 R15: ffff8ccc66a51e80
<4>[  662.714278] FS:  0000000000000000(0000) GS:ffff8ccf5e88a000(0000) knlGS:0000000000000000
<4>[  662.714283] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
<4>[  662.714287] CR2: 00000000c4f52010 CR3: 0000000164de1000 CR4: 0000000000f50ef0
<4>[  662.714292] PKRU: 55555554
<4>[  662.714296] Call Trace:
<4>[  662.714301]  <TASK>
<4>[  662.714310]  amd_sfh_work.cold+0x1c/0x21 [amd_sfh 590e26be9da743ed9c42d4473ff11e552461cd54]
<4>[  662.714323]  ? srso_alias_return_thunk+0x5/0xfbef5
<4>[  662.714333]  process_one_work+0x190/0x350
<4>[  662.714344]  worker_thread+0x2d7/0x410
<4>[  662.714353]  ? __pfx_worker_thread+0x10/0x10
<4>[  662.714360]  kthread+0xf9/0x240
<4>[  662.714370]  ? __pfx_kthread+0x10/0x10
<4>[  662.714378]  ret_from_fork+0x31/0x50
<4>[  662.714388]  ? __pfx_kthread+0x10/0x10
<4>[  662.714396]  ret_from_fork_asm+0x1a/0x30
<4>[  662.714411]  </TASK>
<4>[  662.714415] ---[ end trace 0000000000000000 ]---
<4>[  662.714577] Oops: general protection fault, probably for non-canonical address 0x32e31c55d8aa0687: 0000 [#1] SMP NOPTI
<4>[  662.714590] CPU: 4 UID: 0 PID: 162 Comm: kworker/4:1 Tainted: G        W           6.15.8-valve1-2-neptune-615-g49248f4e2ad1 #1 PREEMPT(full)  64605a591817db76b46b95d73e86c4364b25c841
<4>[  662.714601] Tainted: [W]=WARN
<4>[  662.714605] Hardware name: LENOVO 83E1/LNVNB161216, BIOS N3CN37WW 12/06/2024
<4>[  662.714609] Workqueue: events amd_sfh_work [amd_sfh]
<4>[  662.714622] RIP: 0010:amd_sfh_work+0x31/0x150 [amd_sfh]
<4>[  662.714631] Code: 00 00 41 57 41 56 41 55 41 54 55 53 48 89 fb 48 83 ec 08 4c 8b af c0 00 00 00 4c 8b 67 f8 49 8b 45 08 49 8b 4d 00 4d 8d 7d f8 <4c> 3b 28 0f 85 2b 3b 00 00 4c 3b 69 08 0f 85 21 3b 00 00 48 89 41
<4>[  662.714636] RSP: 0018:ffffd12c8068fe18 EFLAGS: 00010296
<4>[  662.714643] RAX: 32e31c55d8aa0687 RBX: ffff8ccc414251c8 RCX: dead000000000100
<4>[  662.714648] RDX: 0000000000000001 RSI: ffff8ccc42240ec6 RDI: ffff8ccc414251c8
<4>[  662.714652] RBP: ffff8ccc400b7800 R08: 8080808080808080 R09: 0000000000000000
<4>[  662.714656] R10: ffff8ccc400508c0 R11: fefefefefefefeff R12: ffff8ccc43ac4e58
<4>[  662.714659] R13: ffff8ccc66a51e88 R14: 0000000000000000 R15: ffff8ccc66a51e80
<4>[  662.714664] FS:  0000000000000000(0000) GS:ffff8ccf5e6ca000(0000) knlGS:0000000000000000
<4>[  662.714669] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
<4>[  662.714673] CR2: 00007f7ccc5f9000 CR3: 0000000164de1000 CR4: 0000000000f50ef0
<4>[  662.714678] PKRU: 55555554
<4>[  662.714681] Call Trace:
<4>[  662.714687]  <TASK>
<4>[  662.714690]  ? srso_alias_return_thunk+0x5/0xfbef5
<4>[  662.714704]  process_one_work+0x190/0x350
<4>[  662.714715]  worker_thread+0x2d7/0x410
<4>[  662.714728]  ? __pfx_worker_thread+0x10/0x10
<4>[  662.714736]  kthread+0xf9/0x240
<4>[  662.714746]  ? __pfx_kthread+0x10/0x10
<4>[  662.714755]  ret_from_fork+0x31/0x50
<4>[  662.714763]  ? __pfx_kthread+0x10/0x10
<4>[  662.714772]  ret_from_fork_asm+0x1a/0x30
<4>[  662.714787]  </TASK>
<4>[  662.715218] ---[ end trace 0000000000000000 ]---

Full dmesg from pstore:

dmesg-pstore.202508210732-0: https://gist.github.com/matte-schwartz/f7d5fbc9eb6b47051fb5c3ea4d6ae32f
dmesg-pstore-202508210732-1: https://gist.github.com/matte-schwartz/a473354010a23ecb0b119024c960166d

Similar warnings in amd-sfh appeared intermittently earlier in my journal until eventually one triggered the kernel panic above.

The warnings appeared while I was leaving the device idle on Steam's GamepadUI home page while using gamescope. Because this bug is intermittent, I don't have a more reliable way to reproduce the issue other than leaving the device idle for extended periods of time.

Thanks,
Matthew

^ permalink raw reply

* [PATCH v3 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support
From: Marco Felsch @ 2025-08-21 17:26 UTC (permalink / raw)
  To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
	Rafael J. Wysocki, Andrew Morton, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
	Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
  Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch
In-Reply-To: <20250821-v6-10-topic-touchscreen-axiom-v3-0-940ccee6dba3@pengutronix.de>

This adds the initial support for the TouchNetix AX54A touchcontroller
which is part of TouchNetix's aXiom touchscreen controller family.

The TouchNetix aXiom family provides two physical interfaces: SPI and
I2C. This patch covers only the I2C interface.

Apart the input event handling the driver supports firmware updates too.
One firmware interface handles the touchcontroller firmware (AXFW and
ALC) update the other handles the touchcontroller configuration
(TH2CFGBIN) update.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 .../testing/sysfs-driver-input-touchnetix-axiom    |   74 +
 drivers/input/touchscreen/Kconfig                  |   17 +
 drivers/input/touchscreen/Makefile                 |    1 +
 drivers/input/touchscreen/touchnetix_axiom.c       | 2974 ++++++++++++++++++++
 4 files changed, 3066 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
new file mode 100644
index 0000000000000000000000000000000000000000..d8d9305bb483b5c53aa009a0c6e306cb6d6b0aab
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
@@ -0,0 +1,74 @@
+What:		/sys/bus/i2c/devices/xxx/fw_major
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the firmware major version provided by the touchscreen.
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/fw_minor
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the firmware minor version provided by the touchscreen.
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/fw_rc
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the firmware release canidate version provided by the touchscreen.
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/fw_status
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the firmware status provided by the touchscreen. It may
+		be either "release" or "engineering".
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/fw_variant
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the firmware variant provided by the touchscreen. It may
+		be either: "3d", "2d", "force" or "unknown".
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/device_id
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the touchscreen device id, for example: "54" for the AX54A.
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/device_state
+Date:		May 2025
+Contact:	linux-input@vger.kernel.org
+Description:    Reports the touchscreen device current runtime state. The
+		following values are reported:
+
+		discovery: Device is in discovery mode.
+		tcp:  Device is in touch-control-protocol (tcp) mode. This is
+		      the normal working mode.
+		th2cfg-update: Device is in configuration update mode.
+		bootloader-pre: Device bootloader mode enter was triggered
+		bootloader: Device is in bootloader mode, used for firmware
+			    updates.
+		unknown: Device mode is unknown.
+
+		Access: Read
+
+		Valid values: Represented as string
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 196905162945d59e775c3e0bff6540a82842229a..9263dd79dab7e518e27af35364fcebbff0ba706e 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -806,6 +806,23 @@ config TOUCHSCREEN_MIGOR
 	  To compile this driver as a module, choose M here: the
 	  module will be called migor_ts.
 
+config TOUCHSCREEN_TOUCHNETIX_AXIOM
+	tristate "TouchNetix aXiom based touchscreen controllers"
+	# We need to call into panel code so if DRM=m, this can't be 'y'
+	depends on DRM || !DRM
+	depends on I2C
+	select CRC16
+	select CRC32
+	select REGMAP_I2C
+	help
+	  Say Y here if you have a axiom touchscreen connected to
+	  your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called touchnetix_axiom.
+
 config TOUCHSCREEN_TOUCHRIGHT
 	tristate "Touchright serial touchscreen"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 97a025c6a3770fb80255246eb63c11688ebd79eb..0591cb304784699bf2a8bda204461ac5f4532bb1 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -88,6 +88,7 @@ obj-$(CONFIG_TOUCHSCREEN_SUR40)		+= sur40.o
 obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI)	+= surface3_spi.o
 obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC)	+= ti_am335x_tsc.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
+obj-$(CONFIG_TOUCHSCREEN_TOUCHNETIX_AXIOM)	+= touchnetix_axiom.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_TS4800)	+= ts4800-ts.o
diff --git a/drivers/input/touchscreen/touchnetix_axiom.c b/drivers/input/touchscreen/touchnetix_axiom.c
new file mode 100644
index 0000000000000000000000000000000000000000..e8f56a8f7e8a83361b04bb858caadf9658fb7e05
--- /dev/null
+++ b/drivers/input/touchscreen/touchnetix_axiom.c
@@ -0,0 +1,2974 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TouchNetix aXiom Touchscreen Driver
+ *
+ * Copyright (C) 2024 Pengutronix
+ *
+ * Marco Felsch <kernel@pengutronix.de>
+ */
+
+#include <drm/drm_panel.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/crc16.h>
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#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/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/time.h>
+#include <linux/unaligned.h>
+
+/*
+ * Short introduction for developers:
+ *  The programming manual is written based on u(sages):
+ *   - Max. 0xff usages possible
+ *   - A usage is a group of registers (0x00 ... 0xff)
+ *   - The usage base address must be discovered (FW dependent)
+ *   - Partial RW usage access is allowed
+ *   - Each usage has a revision (FW dependent)
+ *   - Only u31 is always at address 0x0 (used for discovery)
+ *
+ *  E.x. Reading register 0x01 for usage u03 with baseaddr 0x20 results in the
+ *  following physical 16bit I2C address: 0x2001.
+ *
+ * Note the datasheet specifies the usage numbers in hex and the internal
+ * offsets in decimal. Keep it that way to make it more developer friendly.
+ */
+#define AXIOM_U01				0x01
+#define AXIOM_U01_REV1_REPORTTYPE_REG		0
+#define   AXIOM_U01_REV1_REPORTTYPE_HELLO	0
+#define   AXIOM_U01_REV1_REPORTTYPE_HEARTBEAT	1
+#define   AXIOM_U01_REV1_REPORTTYPE_OPCOMPLETE	3
+
+#define AXIOM_U02					0x02
+#define AXIOM_U02_REV1_COMMAND_REG			0
+#define   AXIOM_U02_REV1_CMD_HARDRESET			0x0001
+#define   AXIOM_U02_REV1_CMD_SOFTRESET			0x0002
+#define   AXIOM_U02_REV1_CMD_STOP			0x0005
+#define   AXIOM_U02_REV1_CMD_SAVEVLTLCFG2NVM		0x0007
+#define   AXIOM_U02_REV1_PARAM1_SAVEVLTLCFG2NVM		0xb10c
+#define   AXIOM_U02_REV1_PARAM2_SAVEVLTLCFG2NVM		0xc0de
+#define   AXIOM_U02_REV1_CMD_HANDSHAKENVM		0x0008
+#define   AXIOM_U02_REV1_CMD_COMPUTECRCS		0x0009
+#define   AXIOM_U02_REV1_CMD_FILLCONFIG			0x000a
+#define   AXIOM_U02_REV1_PARAM0_FILLCONFIG		0x5555
+#define   AXIOM_U02_REV1_PARAM1_FILLCONFIG		0xaaaa
+#define   AXIOM_U02_REV1_PARAM2_FILLCONFIG_ZERO		0xa55a
+#define   AXIOM_U02_REV1_CMD_ENTERBOOTLOADER		0x000b
+#define   AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1	0x5555
+#define   AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2	0xaaaa
+#define   AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3	0xa55a
+#define   AXIOM_U02_REV1_RESP_SUCCESS			0x0000
+
+struct axiom_u02_rev1_system_manager_msg {
+	union {
+		__le16 command;
+		__le16 response;
+	};
+	__le16 parameters[3];
+};
+
+#define AXIOM_U04				0x04
+#define   AXIOM_U04_REV1_SIZE_BYTES		128
+
+#define AXIOM_U05				0x05	/* CDU */
+
+#define AXIOM_U22				0x22	/* CDU */
+
+#define AXIOM_U31				0x31
+#define AXIOM_U31_REV1_PAGE0			0x0000
+#define AXIOM_U31_REV1_DEVICE_ID_LOW_REG	(AXIOM_U31_REV1_PAGE0 + 0)
+#define AXIOM_U31_REV1_DEVICE_ID_HIGH_REG	(AXIOM_U31_REV1_PAGE0 + 1)
+#define   AXIOM_U31_REV1_MODE_MASK		BIT(7)
+#define   AXIOM_U31_REV1_MODE_BLP		1
+#define   AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK	GENMASK(6, 0)
+#define AXIOM_U31_REV1_RUNTIME_FW_MIN_REG	(AXIOM_U31_REV1_PAGE0 + 2)
+#define AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG	(AXIOM_U31_REV1_PAGE0 + 3)
+#define AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG	(AXIOM_U31_REV1_PAGE0 + 4)
+#define   AXIOM_U31_REV1_RUNTIME_FW_STATUS	BIT(7)
+#define   AXIOM_U31_REV1_RUNTIME_FW_VARIANT	GENMASK(6, 0)
+#define AXIOM_U31_REV1_JEDEC_ID_LOW_REG		(AXIOM_U31_REV1_PAGE0 + 8)
+#define AXIOM_U31_REV1_JEDEC_ID_HIGH_REG	(AXIOM_U31_REV1_PAGE0 + 9)
+#define AXIOM_U31_REV1_NUM_USAGES_REG		(AXIOM_U31_REV1_PAGE0 + 10)
+#define AXIOM_U31_REV1_RUNTIME_FW_RC_REG	(AXIOM_U31_REV1_PAGE0 + 11)
+#define   AXIOM_U31_REV1_RUNTIME_FW_RC_MASK	GENMASK(7, 4)
+#define   AXIOM_U31_REV1_SILICON_REV_MASK	GENMASK(3, 0)
+
+#define AXIOM_U31_REV1_PAGE1			0x0100
+#define   AXIOM_U31_REV1_OFFSET_TYPE_MASK	BIT(7)
+#define   AXIOM_U31_REV1_MAX_OFFSET_MASK	GENMASK(6, 0)
+
+#define AXIOM_U32				0x32
+
+struct axiom_u31_usage_table_entry {
+	u8 usage_num;
+	u8 start_page;
+	u8 num_pages;
+	u8 max_offset;
+	u8 uifrevision;
+	u8 reserved;
+} __packed;
+
+#define AXIOM_U33				0x33
+
+struct axiom_u33_rev2 {
+	__le32 runtime_crc;
+	__le32 runtime_nvm_crc;
+	__le32 bootloader_crc;
+	__le32 nvltlusageconfig_crc;
+	__le32 vltusageconfig_crc;
+	__le32 u22_sequencedata_crc;
+	__le32 u43_hotspots_crc;
+	__le32 u93_profiles_crc;
+	__le32 u94_deltascalemap_crc;
+	__le32 runtimehash_crc;
+};
+
+struct axiom_u33_rev3 {
+	__le32 runtime_crc;
+	__le32 runtime_nvm_crc;
+	__le32 bootloader_crc;
+	__le32 nvltlusageconfig_crc;
+	__le32 vltusageconfig_crc;
+	__le32 u22_sequencedata_crc;
+	__le32 u43_hotspots_crc;
+	__le32 u77_dod_data_crc;
+	__le32 u93_profiles_crc;
+	__le32 u94_deltascalemap_crc;
+	__le32 runtimehash_crc;
+};
+
+#define AXIOM_U34				0x34
+#define   AXIOM_U34_REV1_OVERFLOW_MASK		BIT(7)
+#define   AXIOM_U34_REV1_REPORTLENGTH_MASK	GENMASK(6, 0)
+#define   AXIOM_U34_REV1_PREAMBLE_BYTES		2
+#define   AXIOM_U34_REV1_POSTAMBLE_BYTES	4
+
+#define AXIOM_U36				0x36
+
+#define AXIOM_U41				0x41
+#define AXIOM_U41_REV2_TARGETSTATUS_REG		0
+#define AXIOM_U41_REV2_X_REG(id)		((4 * (id)) + 2)
+#define AXIOM_U41_REV2_Y_REG(id)		((4 * (id)) + 4)
+#define AXIOM_U41_REV2_Z_REG(id)		((id) + 42)
+
+#define AXIOM_U42				0x42
+#define AXIOM_U42_REV1_REPORT_ID_CONTAINS(id)	((id) + 2)
+#define   AXIOM_U42_REV1_REPORT_ID_TOUCH	1	/* Touch, Proximity, Hover */
+
+#define AXIOM_U42_REV4_REPORT_ID_CONTAINS(id)   ((id) + 8)
+#define   AXIOM_U42_REV4_REPORT_ID_TOUCH	1	/* Touch, Proximity, Hover */
+
+#define AXIOM_U43				0x43	/* CDU */
+
+#define AXIOM_U64					0x64
+#define   AXIOM_U64_REV2_ENABLECDSPROCESSING_REG	0
+#define   AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK	BIT(0)
+
+#define AXIOM_U77				0x77	/* CDU */
+#define AXIOM_U82				0x82
+#define AXIOM_U93				0x93	/* CDU */
+#define AXIOM_U94				0x94	/* CDU */
+
+/*
+ * Axiom CDU usage structure copied from downstream CDU_Common.py. Downstream
+ * doesn't mention any revision. According downstream all CDU register windows
+ * are 56 byte wide (8 byte header + 48 byte data).
+ */
+#define AXIOM_CDU_CMD_STORE			0x0002
+#define AXIOM_CDU_CMD_COMMIT			0x0003
+#define AXIOM_CDU_PARAM0_COMMIT			0xb10c
+#define AXIOM_CDU_PARAM1_COMMIT			0xc0de
+
+#define AXIOM_CDU_RESP_SUCCESS			0x0000
+#define AXIOM_CDU_MAX_DATA_BYTES		48
+
+struct axiom_cdu_usage {
+	union {
+		__le16 command;
+		__le16 response;
+	};
+	__le16 parameters[3];
+	u8 data[AXIOM_CDU_MAX_DATA_BYTES];
+};
+
+/*
+ * u01 for the bootloader protocol (BLP)
+ *
+ * Values taken from Bootloader.py [1] which had a comment that documentation
+ * values are out dated. The BLP does not have different versions according the
+ * documentation python helper.
+ *
+ * [1] https://github.com/TouchNetix/axiom_pylib
+ */
+#define AXIOM_U01_BLP_COMMAND_REG		0x0100
+#define   AXIOM_U01_BLP_COMMAND_RESET		BIT(1)
+#define AXIOM_U01_BLP_SATUS_REG			0x0100
+#define   AXIOM_U01_BLP_STATUS_BUSY		BIT(0)
+#define AXIOM_U01_BLP_FIFO_REG			0x0102
+#define   AXIOM_U01_BLP_FIFO_CHK_SIZE_BYTES	255
+
+#define AXIOM_PROX_LEVEL			-128
+#define AXIOM_STARTUP_TIME_MS			110
+
+#define AXIOM_USAGE_BASEADDR_MASK		GENMASK(15, 8)
+#define AXIOM_MAX_USAGES			256	/* u00 - uFF */
+/*
+ * The devices have a 16bit ADC but Touchnetix used the lower two bits for other
+ * information.
+ */
+#define AXIOM_MAX_XY				(65535 - 3)
+#define AXIOM_DEFAULT_POLL_INTERVAL_MS		10
+#define AXIOM_PAGE_BYTE_LEN			256
+#define AXIOM_MAX_XFERLEN			0x7fff
+#define AXIOM_MAX_TOUCHSLOTS			10
+#define AXIOM_MAX_TOUCHSLOTS_MASK		GENMASK(9, 0)
+
+/* aXiom firmware (.axfw) */
+#define AXIOM_FW_AXFW_SIGNATURE			"AXFW"
+#define AXIOM_FW_AXFW_FILE_FMT_VER		0x0200
+
+struct axiom_fw_axfw_hdr {
+	u8 signature[4];
+	__le32 file_crc32;
+	__le16 file_format_ver;
+	__le16 device_id;
+	u8 variant;
+	u8 minor_ver;
+	u8 major_ver;
+	u8 rc_ver;
+	u8 status;
+	__le16 silicon_ver;
+	u8 silicon_rev;
+	__le32 fw_crc32;
+} __packed;
+
+struct axiom_fw_axfw_chunk_hdr {
+	u8 internal[6]; /* no description */
+	__be16 payload_length;
+};
+
+/* aXiom config (.th2cfgbin) */
+#define AXIOM_FW_CFG_SIGNATURE			0x20071969
+
+struct axiom_fw_cfg_hdr {
+	__be32 signature;
+	__le16 file_format_ver;
+	__le16 tcp_file_rev_major;
+	__le16 tcp_file_rev_minor;
+	__le16 tcp_file_rev_patch;
+	u8 tcp_version;
+} __packed;
+
+struct axiom_fw_cfg_chunk_hdr {
+	u8 usage_num;
+	u8 usage_rev;
+	u8 reserved;
+	__le16 usage_length;
+} __packed;
+
+struct axiom_fw_cfg_chunk {
+	u8 usage_num;
+	u8 usage_rev;
+	u16 usage_length;
+	const u8 *usage_content;
+};
+
+enum axiom_fw_type {
+	AXIOM_FW_AXFW,
+	AXIOM_FW_CFG,
+	AXIOM_FW_NUM
+};
+
+enum axiom_crc_type {
+	AXIOM_CRC_CUR,
+	AXIOM_CRC_NEW,
+	AXIOM_CRC_NUM
+};
+
+struct axiom_data;
+
+struct axiom_usage_info {
+	unsigned char usage_num;	/* uXX number (XX in hex) */
+	unsigned int rev_num;		/* rev.X (X in dec) */
+	bool is_cdu;
+	bool is_ro;
+
+	/* Optional hooks */
+	int (*process_report)(struct axiom_data *ts, const u8 *buf, size_t bufsize);
+};
+
+enum axiom_runmode {
+	AXIOM_DISCOVERY_MODE,
+	AXIOM_TCP_MODE,
+	AXIOM_TCP_CFG_UPDATE_MODE,
+	AXIOM_BLP_PRE_MODE,
+	AXIOM_BLP_MODE,
+};
+
+struct axiom_data {
+	struct input_dev *input;
+	struct device *dev;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[2];
+	unsigned int num_supplies;
+
+	struct regmap *regmap;
+	struct touchscreen_properties prop;
+	bool irq_setup_done;
+	u32 poll_interval;
+
+	struct drm_panel_follower panel_follower;
+	bool is_panel_follower;
+
+	enum axiom_runmode mode;
+	/*
+	 * Two completion types to support firmware updates
+	 * in irq and poll mode.
+	 */
+	struct axiom_completion {
+		struct completion completion;
+		bool poll_done;
+	} nvm_write, boot_complete;
+
+	/* Lock to protect both firmware interfaces */
+	struct mutex fwupdate_lock;
+	struct axiom_firmware {
+		/* Lock to protect cancel */
+		struct mutex lock;
+		bool cancel;
+		struct fw_upload *fwl;
+	} fw[AXIOM_FW_NUM];
+
+	unsigned int fw_major;
+	unsigned int fw_minor;
+	unsigned int fw_rc;
+	unsigned int fw_status;
+	unsigned int fw_variant;
+	u16 device_id;
+	u16 jedec_id;
+	u8 silicon_rev;
+
+	/* CRCs we need to check during a config update */
+	struct axiom_crc {
+		u32 runtime;
+		u32 vltusageconfig;
+		u32 nvltlusageconfig;
+		u32 u22_sequencedata;
+		u32 u43_hotspots;
+		u32 u77_dod_data;
+		u32 u93_profiles;
+		u32 u94_deltascalemap;
+	} crc[AXIOM_CRC_NUM];
+
+	bool cds_enabled;
+	unsigned long enabled_slots;
+	unsigned int num_slots;
+
+	unsigned int max_report_byte_len;
+	struct axiom_usage_table_entry {
+		bool populated;
+		unsigned int baseaddr;
+		unsigned int size_bytes;
+		const struct axiom_usage_info *info;
+	} usage_table[AXIOM_MAX_USAGES];
+};
+
+static int axiom_u01_rev1_process_report(struct axiom_data *ts, const u8 *buf,
+					 size_t bufsize);
+static int axiom_u34_rev1_process_report(struct axiom_data *ts, const u8 *_buf,
+					 size_t bufsize);
+static int axiom_u41_rev2_process_report(struct axiom_data *ts, const u8 *buf,
+					 size_t bufsize);
+
+#define AXIOM_USAGE(num, rev)		\
+	{				\
+		.usage_num = num,	\
+		.rev_num = rev,		\
+	}
+
+#define AXIOM_RO_USAGE(num, rev)	\
+	{				\
+		.usage_num = num,	\
+		.rev_num = rev,		\
+		.is_ro = true,		\
+	}
+
+#define AXIOM_CDU_USAGE(num, rev)	\
+	{				\
+		.usage_num = num,	\
+		.rev_num = rev,		\
+		.is_cdu = true,		\
+	}
+
+#define AXIOM_REPORT_USAGE(num, rev, func)	\
+	{					\
+		.usage_num = num,		\
+		.rev_num = rev,			\
+		.process_report = func,		\
+	}
+
+#define AXIOM_USAGE_REV_UNUSED	(-1)
+
+/*
+ * All usages used by driver must be added to this list to ensure the correct
+ * communictation with the devices. The list can contain multiple entries of the
+ * same usage to handle different usage revisions.
+ *
+ * Note:
+ * During a th2cfgbin update the driver may use usages not listed here.
+ * Therefore the th2cfgbin update compares the current running FW again the
+ * th2cfgbin targets FW.
+ */
+static const struct axiom_usage_info driver_required_usages[] = {
+	AXIOM_REPORT_USAGE(AXIOM_U01, 1, axiom_u01_rev1_process_report),
+	AXIOM_REPORT_USAGE(AXIOM_U01, 3, axiom_u01_rev1_process_report),
+	AXIOM_USAGE(AXIOM_U02, 1),
+	AXIOM_USAGE(AXIOM_U02, 2),
+	AXIOM_USAGE(AXIOM_U04, 1),
+	AXIOM_RO_USAGE(AXIOM_U33, 2),
+	AXIOM_RO_USAGE(AXIOM_U33, 3),
+	AXIOM_REPORT_USAGE(AXIOM_U34, 1, axiom_u34_rev1_process_report),
+	AXIOM_REPORT_USAGE(AXIOM_U41, 2, axiom_u41_rev2_process_report),
+	AXIOM_REPORT_USAGE(AXIOM_U41, 4, axiom_u41_rev2_process_report),
+	AXIOM_USAGE(AXIOM_U42, 1),
+	AXIOM_USAGE(AXIOM_U42, 4),
+	AXIOM_USAGE(AXIOM_U64, 2),
+	AXIOM_USAGE(AXIOM_U64, 4),
+	{ /* sentinel */ }
+};
+
+/*
+ * All usages below are unused but the driver needs to know the type (ro, cdu)
+ * to handle them correctly. Unfortunately the type is not discoverable. Once
+ * a usage is actually used, it must be shifted to driver_required_usages and
+ * the revision must be set accordingly.
+ */
+static const struct axiom_usage_info driver_additional_usages[] = {
+	AXIOM_CDU_USAGE(AXIOM_U05, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_CDU_USAGE(AXIOM_U22, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_RO_USAGE(AXIOM_U31, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_RO_USAGE(AXIOM_U32, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_RO_USAGE(AXIOM_U36, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_CDU_USAGE(AXIOM_U43, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_CDU_USAGE(AXIOM_U77, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_RO_USAGE(AXIOM_U82, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_CDU_USAGE(AXIOM_U93, AXIOM_USAGE_REV_UNUSED),
+	AXIOM_CDU_USAGE(AXIOM_U94, AXIOM_USAGE_REV_UNUSED),
+	{ /* sentinel */ }
+};
+
+/************************ Common helpers **************************************/
+
+static void axiom_set_runmode(struct axiom_data *ts, enum axiom_runmode mode)
+{
+	ts->mode = mode;
+}
+
+static enum axiom_runmode axiom_get_runmode(struct axiom_data *ts)
+{
+	return ts->mode;
+}
+
+static const char *axiom_runmode_to_string(struct axiom_data *ts)
+{
+	switch (ts->mode) {
+	case AXIOM_DISCOVERY_MODE:	return "discovery";
+	case AXIOM_TCP_MODE:		return "tcp";
+	case AXIOM_TCP_CFG_UPDATE_MODE:	return "th2cfg-update";
+	case AXIOM_BLP_PRE_MODE:	return "bootloader-pre";
+	case AXIOM_BLP_MODE:		return "bootlaoder";
+	default:			return "unknown";
+	}
+}
+
+static bool axiom_skip_usage_check(struct axiom_data *ts)
+{
+	switch (ts->mode) {
+	case AXIOM_TCP_CFG_UPDATE_MODE:
+	case AXIOM_DISCOVERY_MODE:
+	case AXIOM_BLP_MODE:
+		return true;
+	case AXIOM_BLP_PRE_MODE:
+	case AXIOM_TCP_MODE:
+	default:
+		return false;
+	}
+}
+
+static unsigned int
+axiom_usage_baseaddr(struct axiom_data *ts, unsigned char usage_num)
+{
+	return ts->usage_table[usage_num].baseaddr;
+}
+
+static unsigned int
+axiom_usage_size(struct axiom_data *ts, unsigned char usage_num)
+{
+	return ts->usage_table[usage_num].size_bytes;
+}
+
+static int
+axiom_usage_rev(struct axiom_data *ts, unsigned char usage_num)
+{
+	struct axiom_usage_table_entry *entry = &ts->usage_table[usage_num];
+
+	if (!entry->info)
+		return -EINVAL;
+
+	return entry->info->rev_num;
+}
+
+static bool
+axiom_driver_supports_usage(struct axiom_data *ts, unsigned char usage_num)
+{
+	const struct axiom_usage_info *iter = driver_required_usages;
+	struct device *dev = ts->dev;
+	int rev;
+
+	/*
+	 * Some features depend on the current running firmware. Don't print an
+	 * error if the usage for an optional feature is missing.
+	 */
+	if (!ts->usage_table[usage_num].populated) {
+		dev_dbg(dev, "u%02X is not supported by the current firmware\n",
+			usage_num);
+		return false;
+	}
+
+	rev = axiom_usage_rev(ts, usage_num);
+	if (rev < 0) {
+		dev_warn(dev, "Driver doesn't support u%02X yet\n", usage_num);
+		return false;
+	}
+
+	for (; iter; iter++) {
+		if (iter->usage_num != usage_num)
+			continue;
+
+		if (iter->rev_num == rev)
+			return true;
+	}
+
+	dev_warn(dev, "Driver doesn't support u%02X rev.%d yet\n",
+		 usage_num, rev);
+
+	return false;
+}
+
+static bool
+axiom_usage_entry_is_report(struct axiom_u31_usage_table_entry *entry)
+{
+	return entry->num_pages == 0;
+}
+
+static unsigned int
+axiom_get_usage_size_bytes(struct axiom_u31_usage_table_entry *entry)
+{
+	unsigned char max_offset;
+
+	max_offset = FIELD_GET(AXIOM_U31_REV1_MAX_OFFSET_MASK,
+			       entry->max_offset) + 1;
+	max_offset *= 2;
+
+	if (axiom_usage_entry_is_report(entry))
+		return max_offset;
+
+	if (FIELD_GET(AXIOM_U31_REV1_OFFSET_TYPE_MASK, entry->max_offset))
+		return (entry->num_pages - 1) * AXIOM_PAGE_BYTE_LEN + max_offset;
+
+	return max_offset;
+}
+
+static void axiom_dump_usage_entry(struct device *dev,
+				   struct axiom_u31_usage_table_entry *entry)
+{
+	unsigned int page_len, total_len;
+
+	total_len = axiom_get_usage_size_bytes(entry);
+
+	if (total_len > AXIOM_PAGE_BYTE_LEN)
+		page_len = AXIOM_PAGE_BYTE_LEN;
+	else
+		page_len = total_len;
+
+	if (axiom_usage_entry_is_report(entry))
+		dev_dbg(dev,
+			"u%02X rev.%d total-len:%u [REPORT]\n",
+			entry->usage_num, entry->uifrevision, total_len);
+	else
+		dev_dbg(dev,
+			"u%02X rev.%d first-page:%#02x page-len:%u num-pages:%u total-len:%u\n",
+			entry->usage_num, entry->uifrevision, entry->start_page, page_len,
+			entry->num_pages, total_len);
+}
+
+static const struct axiom_usage_info *
+axiom_get_usage_info(struct axiom_u31_usage_table_entry *query)
+{
+	const struct axiom_usage_info *info = driver_required_usages;
+	bool required = false;
+	bool found = false;
+
+	for (; info->usage_num; info++) {
+		/* Skip all usages not used by the driver */
+		if (query->usage_num != info->usage_num)
+			continue;
+
+		/* The usage is used so we need to mark it as required */
+		required = true;
+
+		/* Continue with the next usage if the revision doesn't match */
+		if (query->uifrevision != info->rev_num)
+			continue;
+
+		found = true;
+		break;
+	}
+
+	if (found)
+		return info;
+
+	/* Return an error if not found but required */
+	if (required)
+		return ERR_PTR(-EINVAL);
+
+	info = driver_additional_usages;
+	for (; info->usage_num; info++) {
+		if (query->usage_num != info->usage_num)
+			continue;
+
+		/*
+		 * No need to check the revision since these usages are not
+		 * used actually but the driver needs the type information.
+		 */
+		return info;
+	}
+
+	/* No info found */
+	return NULL;
+}
+
+static bool axiom_usage_supported(struct axiom_data *ts, unsigned int baseaddr)
+{
+	struct axiom_usage_table_entry *entry;
+	unsigned int i;
+
+	if (axiom_skip_usage_check(ts))
+		return true;
+
+	dev_dbg(ts->dev, "Checking support for baseaddr: %#x\n", baseaddr);
+
+	for (i = 0; i < ARRAY_SIZE(ts->usage_table); i++) {
+		entry = &ts->usage_table[i];
+
+		if (!entry->populated)
+			continue;
+
+		if (entry->baseaddr != baseaddr)
+			continue;
+
+		break;
+	}
+
+	if (i == ARRAY_SIZE(ts->usage_table)) {
+		dev_warn(ts->dev, "Usage not found\n");
+		return false;
+	}
+
+	if (!entry->info)
+		dev_warn(ts->dev, "Unsupported usage u%02X used, driver bug!", i);
+
+	return !!entry->info;
+}
+
+static void axiom_poll(struct input_dev *input);
+
+static unsigned long
+axiom_wait_for_completion_timeout(struct axiom_data *ts, struct axiom_completion *x,
+				  long timeout)
+{
+	struct i2c_client *client = to_i2c_client(ts->dev);
+	unsigned long poll_timeout;
+
+	if (client->irq)
+		return wait_for_completion_timeout(&x->completion, timeout);
+
+	/*
+	 * Only firmware update cases do wait for completion. Since they require
+	 * the input device to be closed, the poller is not running. So we need
+	 * to do the polling manually.
+	 */
+	poll_timeout = timeout / 10;
+
+	/*
+	 * Very basic and not very accurate but it does the job because there
+	 * are no known timeout constraints.
+	 */
+	do {
+		axiom_poll(ts->input);
+		fsleep(jiffies_to_usecs(poll_timeout));
+		if (x->poll_done)
+			break;
+		timeout -= poll_timeout;
+	} while (timeout > 0);
+
+	x->poll_done = false;
+
+	return timeout > 0 ? timeout : 0;
+}
+
+static void axiom_complete(struct axiom_data *ts, struct axiom_completion *x)
+{
+	struct i2c_client *client = to_i2c_client(ts->dev);
+
+	if (client->irq)
+		complete(&x->completion);
+	else
+		x->poll_done = true;
+}
+
+/*************************** Usage handling ***********************************/
+/*
+ * Wrapper functions to handle the usage access. Wrappers are used to add
+ * different revision handling later on more easily.
+ */
+static int axiom_u02_wait_idle(struct axiom_data *ts)
+{
+	unsigned int reg;
+	int ret, _ret;
+	u16 cmd;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U02);
+	reg += AXIOM_U02_REV1_COMMAND_REG;
+
+	/*
+	 * Missing regmap_raw_read_poll_timeout for now. RESP_SUCCESS means that
+	 * the last command successfully completed and the device is idle.
+	 */
+	ret = read_poll_timeout(regmap_raw_read, _ret,
+				_ret || cmd == AXIOM_U02_REV1_RESP_SUCCESS,
+				10 * USEC_PER_MSEC, 1 * USEC_PER_SEC, false,
+				ts->regmap, reg, &cmd, 2);
+	if (ret)
+		dev_err(ts->dev, "Poll u02 timedout with: %#x\n", cmd);
+
+	return ret;
+}
+
+static int
+axiom_u02_send_msg(struct axiom_data *ts,
+		   const struct axiom_u02_rev1_system_manager_msg *msg,
+		   bool validate_response)
+{
+	unsigned int reg;
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U02);
+	reg += AXIOM_U02_REV1_COMMAND_REG;
+
+	ret = regmap_raw_write(ts->regmap, reg, msg, sizeof(*msg));
+	if (ret)
+		return ret;
+
+	if (!validate_response)
+		return 0;
+
+	return axiom_u02_wait_idle(ts);
+}
+
+static int
+axiom_u02_rev1_send_single_cmd(struct axiom_data *ts, u16 cmd)
+{
+	struct axiom_u02_rev1_system_manager_msg msg = {
+		.command = cpu_to_le16(cmd)
+	};
+
+	return axiom_u02_send_msg(ts, &msg, true);
+}
+
+static int axiom_u02_handshakenvm(struct axiom_data *ts)
+{
+	return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_HANDSHAKENVM);
+}
+
+static int axiom_u02_computecrc(struct axiom_data *ts)
+{
+	return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_COMPUTECRCS);
+}
+
+static int axiom_u02_stop(struct axiom_data *ts)
+{
+	return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_STOP);
+}
+
+static int axiom_u02_save_config(struct axiom_data *ts)
+{
+	struct axiom_u02_rev1_system_manager_msg msg;
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SAVEVLTLCFG2NVM);
+	msg.parameters[0] = 0; /* Don't care */
+	msg.parameters[1] = cpu_to_le16(AXIOM_U02_REV1_PARAM1_SAVEVLTLCFG2NVM);
+	msg.parameters[2] = cpu_to_le16(AXIOM_U02_REV1_PARAM2_SAVEVLTLCFG2NVM);
+
+	ret = axiom_u02_send_msg(ts, &msg, false);
+	if (ret)
+		return ret;
+
+	/* Downstream axcfg.py waits for 2sec without checking U01 response */
+	ret = axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
+					msecs_to_jiffies(2 * MSEC_PER_SEC));
+	if (!ret)
+		dev_err(ts->dev, "Error save volatile config timedout\n");
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
+static int axiom_u02_swreset(struct axiom_data *ts)
+{
+	struct axiom_u02_rev1_system_manager_msg msg = { };
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SOFTRESET);
+	ret = axiom_u02_send_msg(ts, &msg, false);
+	if (ret)
+		return ret;
+
+	/*
+	 * Downstream axcfg.py waits for 1sec without checking U01 hello. Tests
+	 * showed that waiting for the hello message isn't enough therefore we
+	 * need both to make it robuster.
+	 */
+	ret = axiom_wait_for_completion_timeout(ts, &ts->boot_complete,
+					msecs_to_jiffies(1 * MSEC_PER_SEC));
+	if (!ret)
+		dev_err(ts->dev, "Error swreset timedout\n");
+
+	fsleep(USEC_PER_SEC);
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
+static int axiom_u02_fillconfig(struct axiom_data *ts)
+{
+	struct axiom_u02_rev1_system_manager_msg msg;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_FILLCONFIG);
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_FILLCONFIG);
+	msg.parameters[1] = cpu_to_le16(AXIOM_U02_REV1_PARAM1_FILLCONFIG);
+	msg.parameters[2] = cpu_to_le16(AXIOM_U02_REV1_PARAM2_FILLCONFIG_ZERO);
+
+	return axiom_u02_send_msg(ts, &msg, true);
+}
+
+static int axiom_u02_enter_bootloader(struct axiom_data *ts)
+{
+	struct axiom_u02_rev1_system_manager_msg msg = { };
+	struct device *dev = ts->dev;
+	unsigned int val;
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	/*
+	 * Enter the bootloader mode requires 3 consecutive messages so we can't
+	 * check for the response.
+	 */
+	msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_ENTERBOOTLOADER);
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1);
+	ret = axiom_u02_send_msg(ts, &msg, false);
+	if (ret) {
+		dev_err(dev, "Failed to send bootloader-key1: %d\n", ret);
+		return ret;
+	}
+
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2);
+	ret = axiom_u02_send_msg(ts, &msg, false);
+	if (ret) {
+		dev_err(dev, "Failed to send bootloader-key2: %d\n", ret);
+		return ret;
+	}
+
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3);
+	ret = axiom_u02_send_msg(ts, &msg, false);
+	if (ret) {
+		dev_err(dev, "Failed to send bootloader-key3: %d\n", ret);
+		return ret;
+	}
+
+	/* Sleep before the first read to give the device time */
+	fsleep(250 * USEC_PER_MSEC);
+
+	/* Wait till the device reports it is in bootloader mode */
+	return regmap_read_poll_timeout(ts->regmap,
+			AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, val,
+			FIELD_GET(AXIOM_U31_REV1_MODE_MASK, val) ==
+			AXIOM_U31_REV1_MODE_BLP, 250 * USEC_PER_MSEC,
+			USEC_PER_SEC);
+}
+
+static int axiom_u04_get(struct axiom_data *ts, u8 **_buf)
+{
+	u8 buf[AXIOM_U04_REV1_SIZE_BYTES];
+	unsigned int reg;
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U04))
+		return -EINVAL;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U04);
+	ret = regmap_raw_read(ts->regmap, reg, buf, sizeof(buf));
+	if (ret)
+		return ret;
+
+	*_buf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
+
+	return sizeof(buf);
+}
+
+static int axiom_u04_set(struct axiom_data *ts, u8 *buf, unsigned int bufsize)
+{
+	unsigned int reg;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U04))
+		return -EINVAL;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U04);
+	return regmap_raw_write(ts->regmap, reg, buf, bufsize);
+}
+
+/*
+ * U31 revision must be always rev.1 else the whole self discovery mechanism
+ * fall apart.
+ */
+static int axiom_u31_parse_device_info(struct axiom_data *ts)
+{
+	struct regmap *regmap = ts->regmap;
+	unsigned int id_low, id_high, val;
+	int ret;
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &id_high);
+	if (ret)
+		return ret;
+	id_high = FIELD_GET(AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK, id_high);
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_LOW_REG, &id_low);
+	if (ret)
+		return ret;
+	ts->device_id = id_high << 8 | id_low;
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG, &val);
+	if (ret)
+		return ret;
+	ts->fw_major = val;
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MIN_REG, &val);
+	if (ret)
+		return ret;
+	ts->fw_minor = val;
+
+	/* All other fields are not allowed to be read in BLP mode */
+	if (axiom_get_runmode(ts) == AXIOM_BLP_MODE)
+		return 0;
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_RC_REG, &val);
+	if (ret)
+		return ret;
+	ts->fw_rc = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_RC_MASK, val);
+	ts->silicon_rev = FIELD_GET(AXIOM_U31_REV1_SILICON_REV_MASK, val);
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG, &val);
+	if (ret)
+		return ret;
+	ts->fw_status = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_STATUS, val);
+	ts->fw_variant = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_VARIANT, val);
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_HIGH_REG, &val);
+	if (ret)
+		return ret;
+	ts->jedec_id = val << 8;
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_LOW_REG, &val);
+	if (ret)
+		return ret;
+	ts->jedec_id |= val;
+
+	return 0;
+}
+
+static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc);
+
+static int axiom_u31_device_discover(struct axiom_data *ts)
+{
+	struct axiom_u31_usage_table_entry *u31_usage_table __free(kfree) = NULL;
+	struct axiom_u31_usage_table_entry *entry;
+	struct regmap *regmap = ts->regmap;
+	unsigned int mode, num_usages;
+	struct device *dev = ts->dev;
+	unsigned int i;
+	int ret;
+
+	axiom_set_runmode(ts, AXIOM_DISCOVERY_MODE);
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &mode);
+	if (ret) {
+		dev_err(dev, "Failed to read MODE\n");
+		return ret;
+	}
+
+	/* Abort if the device is in bootloader protocol mode */
+	mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
+	if (mode == AXIOM_U31_REV1_MODE_BLP)
+		axiom_set_runmode(ts, AXIOM_BLP_MODE);
+
+	/* Since we are not in bootloader mode we can parse the device info */
+	ret = axiom_u31_parse_device_info(ts);
+	if (ret) {
+		dev_err(dev, "Failed to parse device info\n");
+		return ret;
+	}
+
+	/* All other fields are not allowed to be read in BLP mode */
+	if (axiom_get_runmode(ts) == AXIOM_BLP_MODE) {
+		dev_info(dev, "Device in Bootloader mode, firmware upload required\n");
+		return -EACCES;
+	}
+
+	ret = regmap_read(regmap, AXIOM_U31_REV1_NUM_USAGES_REG, &num_usages);
+	if (ret) {
+		dev_err(dev, "Failed to read NUM_USAGES\n");
+		return ret;
+	}
+
+	u31_usage_table = kcalloc(num_usages, sizeof(*u31_usage_table),
+				  GFP_KERNEL);
+	if (!u31_usage_table)
+		return -ENOMEM;
+
+	ret = regmap_raw_read(regmap, AXIOM_U31_REV1_PAGE1, u31_usage_table,
+			      array_size(num_usages, sizeof(*u31_usage_table)));
+	if (ret) {
+		dev_err(dev, "Failed to read NUM_USAGES\n");
+		return ret;
+	}
+
+	/*
+	 * axiom_u31_device_discover() is call after fw update too, so ensure
+	 * that the usage_table is cleared.
+	 */
+	memset(ts->usage_table, 0, sizeof(ts->usage_table));
+
+	for (i = 0, entry = u31_usage_table; i < num_usages; i++, entry++) {
+		unsigned char idx = entry->usage_num;
+		const struct axiom_usage_info *info;
+		unsigned int size_bytes;
+
+		axiom_dump_usage_entry(dev, entry);
+
+		/*
+		 * Verify that the driver used usages are supported. Don't abort
+		 * yet if a usage isn't supported to allow the user to dump the
+		 * actual usage table.
+		 */
+		info = axiom_get_usage_info(entry);
+		if (IS_ERR(info)) {
+			dev_info(dev, "Required usage u%02X isn't supported for rev.%u\n",
+				 entry->usage_num, entry->uifrevision);
+			ret = -EACCES;
+		}
+
+		size_bytes = axiom_get_usage_size_bytes(entry);
+
+		ts->usage_table[idx].baseaddr = entry->start_page << 8;
+		ts->usage_table[idx].size_bytes = size_bytes;
+		ts->usage_table[idx].populated = true;
+		ts->usage_table[idx].info = info;
+
+		if (axiom_usage_entry_is_report(entry) &&
+		    ts->max_report_byte_len < size_bytes)
+			ts->max_report_byte_len = size_bytes;
+	}
+
+	if (ret)
+		return ret;
+
+	/* From now on we are in TCP mode to include usage revision checks */
+	axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+	return axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+}
+
+static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc)
+{
+	struct device *dev = ts->dev;
+	unsigned int reg;
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U33))
+		return -EINVAL;
+
+	if (axiom_usage_rev(ts, AXIOM_U33) == 2) {
+		struct axiom_u33_rev2 val;
+
+		reg = axiom_usage_baseaddr(ts, AXIOM_U33);
+		ret = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+		if (ret) {
+			dev_err(dev, "Failed to read u33\n");
+			return ret;
+		}
+
+		crc->runtime = le32_to_cpu(val.runtime_crc);
+		crc->vltusageconfig = le32_to_cpu(val.vltusageconfig_crc);
+		crc->nvltlusageconfig = le32_to_cpu(val.nvltlusageconfig_crc);
+		crc->u22_sequencedata = le32_to_cpu(val.u22_sequencedata_crc);
+		crc->u43_hotspots = le32_to_cpu(val.u43_hotspots_crc);
+		crc->u93_profiles = le32_to_cpu(val.u93_profiles_crc);
+		crc->u94_deltascalemap = le32_to_cpu(val.u94_deltascalemap_crc);
+	} else if (axiom_usage_rev(ts, AXIOM_U33) == 3) {
+		struct axiom_u33_rev3 val;
+
+		reg = axiom_usage_baseaddr(ts, AXIOM_U33);
+		ret = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+		if (ret) {
+			dev_err(dev, "Failed to read u33\n");
+			return ret;
+		}
+
+		crc->runtime = le32_to_cpu(val.runtime_crc);
+		crc->vltusageconfig = le32_to_cpu(val.vltusageconfig_crc);
+		crc->nvltlusageconfig = le32_to_cpu(val.nvltlusageconfig_crc);
+		crc->u22_sequencedata = le32_to_cpu(val.u22_sequencedata_crc);
+		crc->u43_hotspots = le32_to_cpu(val.u43_hotspots_crc);
+		crc->u77_dod_data = le32_to_cpu(val.u77_dod_data_crc);
+		crc->u93_profiles = le32_to_cpu(val.u93_profiles_crc);
+		crc->u94_deltascalemap = le32_to_cpu(val.u94_deltascalemap_crc);
+	}
+
+	return 0;
+}
+
+static bool axiom_u42_touch_enabled(struct axiom_data *ts, const u8 *buf,
+				    unsigned int touch_num)
+{
+	switch (axiom_usage_rev(ts, AXIOM_U42)) {
+	case 1:
+		return buf[AXIOM_U42_REV1_REPORT_ID_CONTAINS(touch_num)] ==
+		       AXIOM_U42_REV1_REPORT_ID_TOUCH;
+	case 4:
+		return buf[AXIOM_U42_REV4_REPORT_ID_CONTAINS(touch_num)] ==
+		       AXIOM_U42_REV4_REPORT_ID_TOUCH;
+	default:
+		/* Should never happen */
+		return false;
+	}
+}
+
+static void axiom_u42_get_touchslots(struct axiom_data *ts)
+{
+	u8 *buf __free(kfree) = NULL;
+	struct device *dev = ts->dev;
+	unsigned int bufsize;
+	unsigned int reg;
+	int ret, i;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U42)) {
+		dev_warn(dev, "Use default touchslots num\n");
+		goto fallback;
+	}
+
+	bufsize = axiom_usage_size(ts, AXIOM_U42);
+	buf = kzalloc(bufsize, GFP_KERNEL);
+	if (!buf) {
+		dev_warn(dev, "Failed to alloc u42 read buffer, use default value\n");
+		goto fallback;
+	}
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U42);
+	ret = regmap_raw_read(ts->regmap, reg, buf, bufsize);
+	if (ret) {
+		dev_warn(dev, "Failed to read u42, use default value\n");
+		goto fallback;
+	}
+
+	ts->enabled_slots = 0;
+	ts->num_slots = 0;
+
+	for (i = 0; i < AXIOM_MAX_TOUCHSLOTS; i++) {
+		if (axiom_u42_touch_enabled(ts, buf, i)) {
+			ts->enabled_slots |= BIT(i);
+			ts->num_slots++;
+		}
+	}
+
+	return;
+
+fallback:
+	ts->enabled_slots = AXIOM_MAX_TOUCHSLOTS_MASK;
+	ts->num_slots = AXIOM_MAX_TOUCHSLOTS;
+}
+
+static void axiom_u64_cds_enabled(struct axiom_data *ts)
+{
+	unsigned int reg, val;
+	int ret;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U64))
+		goto fallback_out;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U64);
+	reg += AXIOM_U64_REV2_ENABLECDSPROCESSING_REG;
+
+	ret = regmap_read(ts->regmap, reg, &val);
+	if (ret)
+		goto fallback_out;
+
+	val = FIELD_GET(AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK, val);
+	ts->cds_enabled = val ? true : false;
+
+	return;
+
+fallback_out:
+	ts->cds_enabled = false;
+}
+
+static int axiom_cdu_wait_idle(struct axiom_data *ts, u8 cdu_usage_num)
+{
+	unsigned int reg;
+	int ret, _ret;
+	u16 cmd;
+
+	reg = axiom_usage_baseaddr(ts, cdu_usage_num);
+
+	/*
+	 * Missing regmap_raw_read_poll_timeout for now. RESP_SUCCESS means that
+	 * the last command successfully completed and the device is idle.
+	 */
+	ret = read_poll_timeout(regmap_raw_read, _ret,
+				_ret || cmd == AXIOM_CDU_RESP_SUCCESS,
+				10 * USEC_PER_MSEC, 1 * USEC_PER_SEC, false,
+				ts->regmap, reg, &cmd, 2);
+	if (ret)
+		dev_err(ts->dev, "Poll CDU u%02X timedout with: %#x\n",
+			cdu_usage_num, cmd);
+
+	return ret;
+}
+
+/*********************** Report usage handling ********************************/
+
+static int axiom_process_report(struct axiom_data *ts, unsigned char usage_num,
+				const u8 *buf, size_t buflen)
+{
+	struct axiom_usage_table_entry *entry = &ts->usage_table[usage_num];
+
+	/* Skip processing if not in TCP mode */
+	if ((axiom_get_runmode(ts) != AXIOM_TCP_MODE) &&
+	    (axiom_get_runmode(ts) != AXIOM_TCP_CFG_UPDATE_MODE))
+		return 0;
+
+	/* May happen if an unsupported usage was requested */
+	if (!entry) {
+		dev_info(ts->dev, "Unsupported usage U%x request\n", usage_num);
+		return 0;
+	}
+
+	/* Supported report usages need to have a process_report hook */
+	if (!entry->info || !entry->info->process_report)
+		return -EINVAL;
+
+	return entry->info->process_report(ts, buf, buflen);
+}
+
+/* Make use of datasheet method 1 - single transfer read */
+static int
+axiom_u34_rev1_process_report(struct axiom_data *ts, const u8 *_buf, size_t bufsize)
+{
+	unsigned int reg = axiom_usage_baseaddr(ts, AXIOM_U34);
+	struct regmap *regmap = ts->regmap;
+	u8 buf[AXIOM_PAGE_BYTE_LEN] = { };
+	struct device *dev = ts->dev;
+	unsigned char report_usage;
+	u16 crc_report, crc_calc;
+	unsigned int len;
+	u8 *payload;
+	int ret;
+
+	ret = regmap_raw_read(regmap, reg, buf, ts->max_report_byte_len);
+	if (ret)
+		return ret;
+
+	/* TODO: Add overflow statistics */
+
+	/* REPORTLENGTH is in uint16 */
+	len = FIELD_GET(AXIOM_U34_REV1_REPORTLENGTH_MASK, buf[0]);
+	len *= 2;
+
+	/*
+	 * Downstream ignores zero length reports, extend the check to validate
+	 * the upper bound too.
+	 */
+	if (len == 0 || len > AXIOM_PAGE_BYTE_LEN) {
+		dev_dbg_ratelimited(dev, "Invalid report length: %u\n", len);
+		return -EINVAL;
+	}
+
+	/*
+	 * The CRC16 value can be queried at the last two bytes of the report.
+	 * The value itself is covering the complete report excluding the CRC16
+	 * value at the end.
+	 */
+	crc_report = get_unaligned_le16(&buf[len - 2]);
+	crc_calc = crc16(0, buf, (len - 2));
+
+	if (crc_calc != crc_report) {
+		dev_err_ratelimited(dev, "CRC16 mismatch!\n");
+		return -EINVAL;
+	}
+
+	report_usage = buf[1];
+	payload = &buf[AXIOM_U34_REV1_PREAMBLE_BYTES];
+	len -= AXIOM_U34_REV1_PREAMBLE_BYTES - AXIOM_U34_REV1_POSTAMBLE_BYTES;
+
+	switch (report_usage) {
+	case AXIOM_U01:
+	case AXIOM_U41:
+		return axiom_process_report(ts, report_usage, payload, len);
+	default:
+		dev_dbg(dev, "Unsupported report u%02X received\n",
+			report_usage);
+	}
+
+	return 0;
+}
+
+static void
+axiom_u41_rev2_decode_target(const u8 *buf, u8 id, u16 *x, u16 *y, s8 *z)
+{
+	u16 val;
+
+	val = get_unaligned_le16(&buf[AXIOM_U41_REV2_X_REG(id)]);
+	val &= AXIOM_MAX_XY;
+	*x = val;
+
+	val = get_unaligned_le16(&buf[AXIOM_U41_REV2_Y_REG(id)]);
+	val &= AXIOM_MAX_XY;
+	*y = val;
+
+	*z = buf[AXIOM_U41_REV2_Z_REG(id)];
+}
+
+static int
+axiom_u41_rev2_process_report(struct axiom_data *ts, const u8 *buf, size_t bufsize)
+{
+	struct input_dev *input = ts->input;
+	unsigned char id;
+	u16 targets;
+
+	/*
+	 * The input registration can be postponed but the touchscreen FW is
+	 * sending u41 reports regardless.
+	 */
+	if (!input)
+		return 0;
+
+	targets = get_unaligned_le16(&buf[AXIOM_U41_REV2_TARGETSTATUS_REG]);
+
+	for_each_set_bit(id, &ts->enabled_slots, AXIOM_MAX_TOUCHSLOTS) {
+		bool present;
+		u16 x, y;
+		s8 z;
+
+		axiom_u41_rev2_decode_target(buf, id, &x, &y, &z);
+
+		present = targets & BIT(id);
+		/* Ignore possible jitters */
+		if (z == AXIOM_PROX_LEVEL)
+			present = false;
+
+		dev_dbg(ts->dev, "id:%u x:%u y:%u z:%d present:%u",
+			id, x, y, z, present);
+
+		input_mt_slot(input, id);
+		if (input_mt_report_slot_state(input, MT_TOOL_FINGER, present))
+			touchscreen_report_pos(input, &ts->prop, x, y, true);
+
+		if (!present)
+			continue;
+
+		input_report_abs(input, ABS_MT_DISTANCE, z < 0 ? -z : 0);
+		if (ts->cds_enabled)
+			input_report_abs(input, ABS_MT_PRESSURE, z >= 0 ? z : 0);
+	}
+
+	input_sync(input);
+
+	return 0;
+}
+
+static int
+axiom_u01_rev1_process_report(struct axiom_data *ts, const u8 *buf, size_t bufsize)
+{
+	switch (buf[AXIOM_U01_REV1_REPORTTYPE_REG]) {
+	case AXIOM_U01_REV1_REPORTTYPE_HELLO:
+		dev_dbg(ts->dev, "u01 HELLO received\n");
+		axiom_complete(ts, &ts->boot_complete);
+		return 0;
+	case AXIOM_U01_REV1_REPORTTYPE_HEARTBEAT:
+		dev_dbg_ratelimited(ts->dev, "u01 HEARTBEAT received\n");
+		return 0;
+	case AXIOM_U01_REV1_REPORTTYPE_OPCOMPLETE:
+		dev_dbg(ts->dev, "u01 OPCOMPLETE received\n");
+		axiom_u02_handshakenvm(ts);
+		axiom_complete(ts, &ts->nvm_write);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**************************** Regmap handling *********************************/
+
+#define AXIOM_CMD_HDR_DIR_MASK	BIT(15)
+#define   AXIOM_CMD_HDR_READ	1
+#define	  AXIOM_CMD_HDR_WRITE	0
+#define AXIOM_CMD_HDR_LEN_MASK	GENMASK(14, 0)
+
+struct axiom_cmd_header {
+	__le16 target_address;
+	__le16 xferlen;
+};
+
+/* Custom regmap read/write handling is required due to the aXiom protocol */
+static int axiom_regmap_read(void *context, const void *reg_buf, size_t reg_size,
+			     void *val_buf, size_t val_size)
+{
+	struct device *dev = context;
+	struct i2c_client *i2c = to_i2c_client(dev);
+	struct axiom_data *ts = i2c_get_clientdata(i2c);
+	struct axiom_cmd_header hdr;
+	u16 xferlen, addr, baseaddr;
+	struct i2c_msg xfer[2];
+	int ret;
+
+	if (val_size > AXIOM_MAX_XFERLEN) {
+		dev_err(ts->dev, "Exceed max xferlen: %zu > %u\n",
+			val_size, AXIOM_MAX_XFERLEN);
+		return -EINVAL;
+	}
+
+	addr = *((u16 *)reg_buf);
+	hdr.target_address = cpu_to_le16(addr);
+	xferlen = FIELD_PREP(AXIOM_CMD_HDR_DIR_MASK, AXIOM_CMD_HDR_READ) |
+		  FIELD_PREP(AXIOM_CMD_HDR_LEN_MASK, val_size);
+	hdr.xferlen = cpu_to_le16(xferlen);
+
+	/* Verify that usage including the usage rev is supported */
+	baseaddr = addr & AXIOM_USAGE_BASEADDR_MASK;
+	if (!axiom_usage_supported(ts, baseaddr))
+		return -EINVAL;
+
+	xfer[0].addr = i2c->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = sizeof(hdr);
+	xfer[0].buf = (u8 *)&hdr;
+
+	xfer[1].addr = i2c->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = val_size;
+	xfer[1].buf = val_buf;
+
+	ret = i2c_transfer(i2c->adapter, xfer, 2);
+	if (ret == 2)
+		return 0;
+	else if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+static int axiom_regmap_write(void *context, const void *data, size_t count)
+{
+	struct device *dev = context;
+	struct i2c_client *i2c = to_i2c_client(dev);
+	struct axiom_data *ts = i2c_get_clientdata(i2c);
+	char *buf __free(kfree) = NULL;
+	struct axiom_cmd_header hdr;
+	u16 xferlen, addr, baseaddr;
+	size_t val_size, msg_size;
+	int ret;
+
+	val_size = count - sizeof(addr);
+	if (val_size > AXIOM_MAX_XFERLEN) {
+		dev_err(ts->dev, "Exceed max xferlen: %zu > %u\n",
+			val_size, AXIOM_MAX_XFERLEN);
+		return -EINVAL;
+	}
+
+	addr = *((u16 *)data);
+	hdr.target_address = cpu_to_le16(addr);
+	xferlen = FIELD_PREP(AXIOM_CMD_HDR_DIR_MASK, AXIOM_CMD_HDR_WRITE) |
+		  FIELD_PREP(AXIOM_CMD_HDR_LEN_MASK, val_size);
+	hdr.xferlen = cpu_to_le16(xferlen);
+
+	/* Verify that usage including the usage rev is supported */
+	baseaddr = addr & AXIOM_USAGE_BASEADDR_MASK;
+	if (!axiom_usage_supported(ts, baseaddr))
+		return -EINVAL;
+
+	msg_size = sizeof(hdr) + val_size;
+	buf = kzalloc(msg_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	memcpy(buf, &hdr, sizeof(hdr));
+	memcpy(&buf[sizeof(hdr)], &((char *)data)[2], val_size);
+
+	ret = i2c_master_send(i2c, buf, msg_size);
+
+	return ret == msg_size ? 0 : ret;
+}
+
+static const struct regmap_config axiom_i2c_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 8,
+	.read = axiom_regmap_read,
+	.write = axiom_regmap_write,
+};
+
+/************************ FW update handling **********************************/
+
+static int axiom_update_input_dev(struct axiom_data *ts);
+
+static enum fw_upload_err
+axiom_axfw_fw_prepare(struct fw_upload *fw_upload, const u8 *data, u32 size)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+	struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
+	u8 major_ver, minor_ver, rc_ver, status, variant;
+	u32 fw_file_crc32, crc32_calc;
+	struct device *dev = ts->dev;
+	unsigned int signature_len;
+	enum fw_upload_err ret;
+	u16 fw_file_format_ver;
+	u16 fw_file_device_id;
+
+	mutex_lock(&afw->lock);
+	afw->cancel = false;
+	mutex_unlock(&afw->lock);
+
+	mutex_lock(&ts->fwupdate_lock);
+
+	if (size < sizeof(struct axiom_fw_axfw_hdr)) {
+		dev_err(dev, "Invalid AXFW file size\n");
+		ret = FW_UPLOAD_ERR_INVALID_SIZE;
+		goto out;
+	}
+
+	signature_len = strlen(AXIOM_FW_AXFW_SIGNATURE);
+	if (strncmp(data, AXIOM_FW_AXFW_SIGNATURE, signature_len)) {
+		/*
+		 * AXFW has a header which can be used to perform validations,
+		 * ALC don't. Therefore the AXFW format is preferred.
+		 */
+		dev_warn(dev, "No AXFW signature, assume ALC firmware\n");
+		ret = FW_UPLOAD_ERR_NONE;
+		goto out;
+	}
+
+	fw_file_crc32 = get_unaligned_le32(&data[signature_len]);
+	crc32_calc = crc32(~0, &data[8], size - 8) ^ 0xffffffff;
+	if (fw_file_crc32 != crc32_calc) {
+		dev_err(dev, "AXFW CRC32 doesn't match (fw:%#x calc:%#x)\n",
+			fw_file_crc32, crc32_calc);
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	data += signature_len + sizeof(fw_file_crc32);
+	fw_file_format_ver = get_unaligned_le16(data);
+	if (fw_file_format_ver != AXIOM_FW_AXFW_FILE_FMT_VER) {
+		dev_err(dev, "Invalid AXFW file format version: %04x",
+			fw_file_format_ver);
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	data += sizeof(fw_file_format_ver);
+	fw_file_device_id = get_unaligned_le16(data);
+	if (fw_file_device_id != ts->device_id) {
+		dev_err(dev, "Invalid AXFW target device (fw:%#04x dev:%#04x)\n",
+			fw_file_device_id, ts->device_id);
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	/*
+	 * This can happen if:
+	 *  * the device came up in bootloader mode, or
+	 *  * downloading the firmware failed in between, or
+	 *  * the following usage discovery failed.
+	 *
+	 *  All cases are crcitical and we need to use any firmware to
+	 *  bring the device back into a working state which is supported by the
+	 *  host.
+	 */
+	if (axiom_get_runmode(ts) != AXIOM_TCP_MODE)
+		return FW_UPLOAD_ERR_NONE;
+
+	data += sizeof(fw_file_device_id);
+	variant = *data++;
+	minor_ver = *data++;
+	major_ver = *data++;
+	rc_ver = *data++;
+	status = *data++;
+
+	if (major_ver == ts->fw_major && minor_ver == ts->fw_minor &&
+	    rc_ver == ts->fw_rc && status == ts->fw_status &&
+	    variant == ts->fw_variant) {
+		ret = FW_UPLOAD_ERR_DUPLICATE;
+		goto out;
+	}
+
+	dev_info(dev, "Detected AXFW %02u.%02u.%02u (%s)\n",
+		 major_ver, minor_ver, rc_ver,
+		 status ? "production" : "engineering");
+
+	mutex_lock(&afw->lock);
+	ret = afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
+	mutex_unlock(&afw->lock);
+
+out:
+	/*
+	 * In FW_UPLOAD_ERR_NONE case the complete handler will release the
+	 * lock.
+	 */
+	if (ret != FW_UPLOAD_ERR_NONE)
+		mutex_unlock(&ts->fwupdate_lock);
+
+	return ret;
+}
+
+static int axiom_enter_bootloader_mode(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+	int ret;
+
+	axiom_set_runmode(ts, AXIOM_BLP_PRE_MODE);
+
+	ret = axiom_u02_wait_idle(ts);
+	if (ret)
+		goto err_out;
+
+	ret = axiom_u02_enter_bootloader(ts);
+	if (ret) {
+		dev_err(dev, "Failed to enter bootloader mode\n");
+		goto err_out;
+	}
+
+	axiom_set_runmode(ts, AXIOM_BLP_MODE);
+
+	return 0;
+
+err_out:
+	axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+	return ret;
+}
+
+static int axoim_blp_wait_ready(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+	unsigned int reg;
+	int tmp, ret;
+	u8 buf[4];
+
+	reg = AXIOM_U01_BLP_SATUS_REG;
+
+	/* BLP busy poll requires to read 4 bytes! */
+	ret = read_poll_timeout(regmap_raw_read, tmp,
+				tmp || !(buf[2] & AXIOM_U01_BLP_STATUS_BUSY),
+				10 * USEC_PER_MSEC, 5 * USEC_PER_SEC, false,
+				ts->regmap, reg, &buf, 4);
+	if (ret)
+		dev_err(dev, "Bootloader wait processing packets failed %d\n", ret);
+
+	return ret;
+}
+
+static int
+axiom_blp_write_chunk(struct axiom_data *ts, const u8 *data, u16 length)
+{
+	unsigned int chunk_size = AXIOM_U01_BLP_FIFO_CHK_SIZE_BYTES;
+	unsigned int reg = AXIOM_U01_BLP_FIFO_REG;
+	struct device *dev = ts->dev;
+	unsigned int pos = 0;
+	int ret;
+
+	ret = axoim_blp_wait_ready(ts);
+	if (ret)
+		return ret;
+
+	/*
+	 * TODO: Downstream does this chunk transfers. Verify if this is
+	 * required if one fw-chunk <= AXIOM_MAX_XFERLEN
+	 */
+	while (pos < length) {
+		u16 len;
+
+		len = chunk_size;
+		if ((pos + chunk_size) > length)
+			len = length - pos;
+
+		ret = regmap_raw_write(ts->regmap, reg, &data[pos], len);
+		if (ret) {
+			dev_err(dev, "Bootloader download AXFW chunk failed %d\n", ret);
+			return ret;
+		}
+
+		pos += len;
+		ret = axoim_blp_wait_ready(ts);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int axiom_blp_reset(struct axiom_data *ts)
+{
+	__le16 reset_cmd = cpu_to_le16(AXIOM_U01_BLP_COMMAND_RESET);
+	unsigned int reg = AXIOM_U01_BLP_COMMAND_REG;
+	struct device *dev = ts->dev;
+	unsigned int attempts = 20;
+	unsigned int mode;
+	int ret;
+
+	ret = axoim_blp_wait_ready(ts);
+	if (ret)
+		return ret;
+
+	/*
+	 * For some reason this write fail with -ENXIO. Skip checking the return
+	 * code (which is also done by the downstream axfw.py tool and poll u31
+	 * instead.
+	 */
+	regmap_raw_write(ts->regmap, reg, &reset_cmd, sizeof(reset_cmd));
+
+	do {
+		ret = regmap_read(ts->regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG,
+				  &mode);
+		if (!ret)
+			break;
+
+		fsleep(250 * USEC_PER_MSEC);
+	} while (attempts--);
+
+	if (ret) {
+		dev_err(dev, "Failed to read MODE after BLP reset: %d\n", ret);
+		return ret;
+	}
+
+	mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
+	if (mode == AXIOM_U31_REV1_MODE_BLP) {
+		dev_err(dev, "Device still in BLP mode, abort\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void axiom_lock_input_device(struct axiom_data *ts)
+{
+	if (!ts->input)
+		return;
+
+	mutex_lock(&ts->input->mutex);
+}
+
+static void axiom_unlock_input_device(struct axiom_data *ts)
+{
+	if (!ts->input)
+		return;
+
+	mutex_unlock(&ts->input->mutex);
+}
+
+static void axiom_unregister_input_dev(struct axiom_data *ts)
+{
+	if (ts->input)
+		input_unregister_device(ts->input);
+
+	ts->input = NULL;
+}
+
+static enum fw_upload_err
+axiom_axfw_fw_write(struct fw_upload *fw_upload, const u8 *data, u32 offset,
+		    u32 size, u32 *written)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+	struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
+	struct device *dev = ts->dev;
+	bool cancel;
+	int ret;
+
+	/* Done before cancel check due to cleanup based put */
+	ret = pm_runtime_resume_and_get(ts->dev);
+	if (ret)
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	mutex_lock(&afw->lock);
+	cancel = afw->cancel;
+	mutex_unlock(&afw->lock);
+
+	if (cancel)
+		return FW_UPLOAD_ERR_CANCELED;
+
+	axiom_lock_input_device(ts);
+
+	if (ts->input && input_device_enabled(ts->input)) {
+		dev_err(dev, "Input device not idle, abort AXFW/ALC update\n");
+		goto err;
+	}
+
+	if (!strncmp(data, AXIOM_FW_AXFW_SIGNATURE,
+		     strlen(AXIOM_FW_AXFW_SIGNATURE))) {
+		/* Set the pointer to the first fw chunk */
+		data += sizeof(struct axiom_fw_axfw_hdr);
+		size -= sizeof(struct axiom_fw_axfw_hdr);
+		*written += sizeof(struct axiom_fw_axfw_hdr);
+	}
+
+	if (axiom_enter_bootloader_mode(ts))
+		goto err;
+
+	while (size) {
+		u16 chunk_len, len;
+
+		chunk_len = get_unaligned_be16(&data[6]);
+		len = chunk_len + sizeof(struct axiom_fw_axfw_chunk_hdr);
+
+		/*
+		 * The bootlaoder FW can handle the complete chunk incl. the
+		 * header.
+		 */
+		ret = axiom_blp_write_chunk(ts, data, len);
+		if (ret)
+			goto err;
+
+		size -= len;
+		*written += len;
+		data += len;
+	}
+
+	ret = axiom_blp_reset(ts);
+	if (ret)
+		dev_warn(dev, "BLP reset failed\n");
+
+	ret = axiom_u31_device_discover(ts);
+	if (ret) {
+		/*
+		 * This is critical and we need to avoid that the user-space can
+		 * still use the input-dev.
+		 */
+		axiom_unlock_input_device(ts);
+		axiom_unregister_input_dev(ts);
+		dev_err(dev, "Device discovery failed after AXFW/ALC firmware update\n");
+		goto err;
+	}
+
+	/* Unlock before the input device gets unregistered */
+	axiom_unlock_input_device(ts);
+
+	ret = axiom_update_input_dev(ts);
+	if (ret) {
+		dev_err(dev, "Input device update failed after AXFW/ALC firmware update\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	dev_info(dev, "AXFW update successful\n");
+
+	return FW_UPLOAD_ERR_NONE;
+
+err:
+	axiom_unlock_input_device(ts);
+	return FW_UPLOAD_ERR_HW_ERROR;
+}
+
+static enum fw_upload_err axiom_fw_poll_complete(struct fw_upload *fw_upload)
+{
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static void axiom_axfw_fw_cancel(struct fw_upload *fw_upload)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+	struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
+
+	mutex_lock(&afw->lock);
+	afw->cancel = true;
+	mutex_unlock(&afw->lock);
+}
+
+static void axiom_axfw_fw_cleanup(struct fw_upload *fw_upload)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+
+	mutex_unlock(&ts->fwupdate_lock);
+	pm_runtime_mark_last_busy(ts->dev);
+	pm_runtime_put_sync_autosuspend(ts->dev);
+}
+
+static const struct fw_upload_ops axiom_axfw_fw_upload_ops = {
+	.prepare = axiom_axfw_fw_prepare,
+	.write = axiom_axfw_fw_write,
+	.poll_complete = axiom_fw_poll_complete,
+	.cancel = axiom_axfw_fw_cancel,
+	.cleanup = axiom_axfw_fw_cleanup,
+};
+
+static int
+axiom_set_new_crcs(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *cfg)
+{
+	struct axiom_crc *crc = &ts->crc[AXIOM_CRC_NEW];
+	const u32 *u33_data = (const u32 *)cfg->usage_content;
+
+	if (cfg->usage_rev != 2 && cfg->usage_rev != 3) {
+		dev_err(ts->dev, "The driver doesn't support u33 revision %u\n",
+			cfg->usage_rev);
+		return -EINVAL;
+	}
+
+	crc->runtime = get_unaligned_le32(u33_data);
+	crc->nvltlusageconfig = get_unaligned_le32(&u33_data[3]);
+	crc->vltusageconfig = get_unaligned_le32(&u33_data[4]);
+	crc->u22_sequencedata = get_unaligned_le32(&u33_data[5]);
+	crc->u43_hotspots = get_unaligned_le32(&u33_data[6]);
+	if (cfg->usage_rev == 2) {
+		crc->u93_profiles = get_unaligned_le32(&u33_data[7]);
+		crc->u94_deltascalemap = get_unaligned_le32(&u33_data[8]);
+	} else if (cfg->usage_rev == 3) {
+		crc->u77_dod_data = get_unaligned_le32(&u33_data[7]);
+		crc->u93_profiles = get_unaligned_le32(&u33_data[8]);
+		crc->u94_deltascalemap = get_unaligned_le32(&u33_data[9]);
+	}
+
+	return 0;
+}
+
+static unsigned int
+axiom_cfg_fw_prepare_chunk(struct axiom_fw_cfg_chunk *chunk, const u8 *data)
+{
+	chunk->usage_num = data[0];
+	chunk->usage_rev = data[1];
+	chunk->usage_length = get_unaligned_le16(&data[3]);
+	chunk->usage_content = &data[5];
+
+	return chunk->usage_length + sizeof(struct axiom_fw_cfg_chunk_hdr);
+}
+
+/*
+ * To overcome buggy firmware we need to check if a given usage is used by the
+ * current running firmware. Return true if the usage is unused/not populated
+ * by the firmware since we can't perform the actual check.
+ */
+#define axiom_usage_crc_match(_ts, _usage_num, _cur, _new, _field) \
+	(!_ts->usage_table[_usage_num].populated || (_cur->_field == _new->_field))
+
+static bool axiom_cfg_fw_update_required(struct axiom_data *ts)
+{
+	struct axiom_crc *cur, *new;
+
+	cur = &ts->crc[AXIOM_CRC_CUR];
+	new = &ts->crc[AXIOM_CRC_NEW];
+
+	if (cur->nvltlusageconfig != new->nvltlusageconfig ||
+	    !axiom_usage_crc_match(ts, AXIOM_U22, cur, new, u22_sequencedata) ||
+	    !axiom_usage_crc_match(ts, AXIOM_U43, cur, new, u43_hotspots) ||
+	    !axiom_usage_crc_match(ts, AXIOM_U93, cur, new, u93_profiles) ||
+	    !axiom_usage_crc_match(ts, AXIOM_U94, cur, new, u94_deltascalemap))
+		return true;
+
+	return false;
+}
+
+static enum fw_upload_err
+axiom_cfg_fw_prepare(struct fw_upload *fw_upload, const u8 *data, u32 size)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+	struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
+	u32 cur_runtime_crc, fw_runtime_crc;
+	struct axiom_fw_cfg_chunk chunk;
+	struct device *dev = ts->dev;
+	enum fw_upload_err ret;
+	u32 signature;
+
+	mutex_lock(&afw->lock);
+	afw->cancel = false;
+	mutex_unlock(&afw->lock);
+
+	mutex_lock(&ts->fwupdate_lock);
+
+	if (axiom_get_runmode(ts) != AXIOM_TCP_MODE) {
+		dev_err(dev, "Device not in TCP mode, abort TH2CFG update\n");
+		ret = FW_UPLOAD_ERR_HW_ERROR;
+		goto out;
+	}
+
+	if (size < sizeof(struct axiom_fw_cfg_hdr)) {
+		dev_err(dev, "Invalid TH2CFG file size\n");
+		ret = FW_UPLOAD_ERR_INVALID_SIZE;
+		goto out;
+	}
+
+	signature = get_unaligned_be32(data);
+	if (signature != AXIOM_FW_CFG_SIGNATURE) {
+		dev_err(dev, "Invalid TH2CFG signature\n");
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	/* Skip to the first fw chunk */
+	data += sizeof(struct axiom_fw_cfg_hdr);
+	size -= sizeof(struct axiom_fw_cfg_hdr);
+
+	/*
+	 * Search for u33 which contains the CRC information and perform only
+	 * the runtime-crc check.
+	 */
+	while (size) {
+		unsigned int chunk_len;
+
+		chunk_len = axiom_cfg_fw_prepare_chunk(&chunk, data);
+		if (chunk.usage_num == AXIOM_U33)
+			break;
+
+		data += chunk_len;
+		size -= chunk_len;
+	}
+
+	if (size == 0) {
+		dev_err(dev, "Failed to find the u33 entry in TH2CFG\n");
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	ret = axiom_set_new_crcs(ts, &chunk);
+	if (ret) {
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	/*
+	 * Nothing to do if the CRCs are the same. TODO: Must be extended once
+	 * the CDU update is added.
+	 */
+	if (!axiom_cfg_fw_update_required(ts)) {
+		ret = FW_UPLOAD_ERR_DUPLICATE;
+		goto out;
+	}
+
+	cur_runtime_crc = ts->crc[AXIOM_CRC_CUR].runtime;
+	fw_runtime_crc = ts->crc[AXIOM_CRC_NEW].runtime;
+	if (cur_runtime_crc != fw_runtime_crc) {
+		dev_err(dev, "TH2CFG and device runtime CRC doesn't match: %#x != %#x\n",
+			fw_runtime_crc, cur_runtime_crc);
+		ret = FW_UPLOAD_ERR_FW_INVALID;
+		goto out;
+	}
+
+	mutex_lock(&afw->lock);
+	ret = afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
+	mutex_unlock(&afw->lock);
+
+out:
+	/*
+	 * In FW_UPLOAD_ERR_NONE case the complete handler will release the
+	 * lock.
+	 */
+	if (ret != FW_UPLOAD_ERR_NONE)
+		mutex_unlock(&ts->fwupdate_lock);
+
+	return ret;
+}
+
+static int axiom_zero_volatile_mem(struct axiom_data *ts)
+{
+	int ret, size;
+	u8 *buf;
+
+	/* Zero out the volatile memory except for the user content in u04 */
+	ret = axiom_u04_get(ts, &buf);
+	if (ret < 0)
+		return ret;
+	size = ret;
+
+	ret = axiom_u02_fillconfig(ts);
+	if (ret)
+		goto out;
+
+	ret = axiom_u04_set(ts, buf, size);
+out:
+	kfree(buf);
+	return ret;
+}
+
+static bool
+axiom_skip_cfg_chunk(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
+{
+	u8 usage_num = chunk->usage_num;
+
+	if (!ts->usage_table[usage_num].populated) {
+		dev_warn(ts->dev, "Unknown usage chunk for u%02X\n", usage_num);
+		return true;
+	}
+
+	/* Skip read-only usages */
+	if (ts->usage_table[usage_num].info &&
+	    ts->usage_table[usage_num].info->is_ro)
+		return true;
+
+	return false;
+}
+
+static int
+axiom_write_cdu_usage(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
+{
+	struct axiom_cdu_usage cdu = { };
+	struct device *dev = ts->dev;
+	unsigned int remaining;
+	unsigned int reg;
+	unsigned int pos;
+	int ret;
+
+	pos = 0;
+	remaining = chunk->usage_length;
+	cdu.command = cpu_to_le16(AXIOM_CDU_CMD_STORE);
+	reg = axiom_usage_baseaddr(ts, chunk->usage_num);
+
+	while (remaining) {
+		unsigned int size;
+
+		cdu.parameters[1] = cpu_to_le16(pos);
+
+		size = remaining;
+		if (size > AXIOM_CDU_MAX_DATA_BYTES)
+			size = AXIOM_CDU_MAX_DATA_BYTES;
+
+		memset(cdu.data, 0, sizeof(cdu.data));
+		memcpy(cdu.data, &chunk->usage_content[pos], size);
+
+		ret = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
+		if (ret) {
+			dev_err(dev, "Failed to write CDU u%02X\n",
+				chunk->usage_num);
+			return ret;
+		}
+
+		ret = axiom_cdu_wait_idle(ts, chunk->usage_num);
+		if (ret) {
+			dev_err(dev, "CDU write wait-idle failed\n");
+			return ret;
+		}
+
+		remaining -= size;
+		pos += size;
+	}
+
+	/*
+	 * TODO: Check if we really need to send 48 zero bytes of data like
+	 * downstream does.
+	 */
+	memset(&cdu, 0, sizeof(cdu));
+	cdu.command = cpu_to_le16(AXIOM_CDU_CMD_COMMIT);
+	cdu.parameters[0] = cpu_to_le16(AXIOM_CDU_PARAM0_COMMIT);
+	cdu.parameters[1] = cpu_to_le16(AXIOM_CDU_PARAM1_COMMIT);
+
+	ret = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
+	if (ret) {
+		dev_err(dev, "Failed to commit CDU u%02X to NVM\n",
+			chunk->usage_num);
+		return ret;
+	}
+
+	ret = axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
+					msecs_to_jiffies(5 * MSEC_PER_SEC));
+	if (!ret) {
+		dev_err(ts->dev, "Error CDU u%02X commit timedout\n",
+			chunk->usage_num);
+		return -ETIMEDOUT;
+	}
+
+	return axiom_cdu_wait_idle(ts, chunk->usage_num);
+}
+
+static int
+axiom_write_cfg_chunk(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
+{
+	unsigned int reg;
+	int ret;
+
+	if (ts->usage_table[chunk->usage_num].info &&
+	    ts->usage_table[chunk->usage_num].info->is_cdu) {
+		ret = axiom_write_cdu_usage(ts, chunk);
+		if (ret)
+			return ret;
+		goto out;
+	}
+
+	reg = axiom_usage_baseaddr(ts, chunk->usage_num);
+	ret = regmap_raw_write(ts->regmap, reg, chunk->usage_content, chunk->usage_length);
+	if (ret)
+		return ret;
+
+out:
+	return axiom_u02_wait_idle(ts);
+}
+
+static int axiom_verify_volatile_mem(struct axiom_data *ts)
+{
+	int ret;
+
+	ret = axiom_u02_computecrc(ts);
+	if (ret)
+		return ret;
+
+	/* Query the new CRCs after they are re-computed */
+	ret = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+	if (ret)
+		return ret;
+
+	return ts->crc[AXIOM_CRC_CUR].vltusageconfig ==
+	       ts->crc[AXIOM_CRC_NEW].vltusageconfig ? 0 : -EINVAL;
+}
+
+static int axiom_verify_crcs(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+	struct axiom_crc *cur, *new;
+
+	cur = &ts->crc[AXIOM_CRC_CUR];
+	new = &ts->crc[AXIOM_CRC_NEW];
+
+	if (new->vltusageconfig != cur->vltusageconfig) {
+		dev_err(dev, "VLTUSAGECONFIG CRC32 mismatch (dev:%#x != fw:%#x)\n",
+			cur->vltusageconfig, new->vltusageconfig);
+		return -EINVAL;
+	} else if (new->nvltlusageconfig != cur->nvltlusageconfig) {
+		dev_err(dev, "NVLTUSAGECONFIG CRC32 mismatch (dev:%#x != fw:%#x)\n",
+			cur->nvltlusageconfig, new->nvltlusageconfig);
+		return -EINVAL;
+	} else if (!axiom_usage_crc_match(ts, AXIOM_U22, cur, new, u22_sequencedata)) {
+		dev_err(dev, "U22_SEQUENCEDATA CRC32 mismatch (dev:%#x != fw:%#x)\n",
+			cur->u22_sequencedata, new->u22_sequencedata);
+		return -EINVAL;
+	} else if (!axiom_usage_crc_match(ts, AXIOM_U43, cur, new, u43_hotspots)) {
+		dev_err(dev, "U43_HOTSPOTS CRC32 mismatch (dev:%#x != fw:%#x)\n",
+			cur->u43_hotspots, new->u43_hotspots);
+		return -EINVAL;
+	} else if (!axiom_usage_crc_match(ts, AXIOM_U93, cur, new, u93_profiles)) {
+		dev_err(dev, "U93_PROFILES CRC32 mismatch (dev:%#x != fw:%#x)\n",
+			cur->u93_profiles, new->u93_profiles);
+		return -EINVAL;
+	} else if (!axiom_usage_crc_match(ts, AXIOM_U94, cur, new, u94_deltascalemap)) {
+		dev_err(dev, "U94_DELTASCALEMAP CRC32 mismatch (dev:%#x != fw:%#x)\n",
+			cur->u94_deltascalemap, new->u94_deltascalemap);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static enum fw_upload_err
+axiom_cfg_fw_write(struct fw_upload *fw_upload, const u8 *data, u32 offset,
+		   u32 size, u32 *written)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+	struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
+	struct device *dev = ts->dev;
+	bool cancel;
+	int ret;
+
+	/* Done before cancel check due to cleanup based put */
+	ret = pm_runtime_resume_and_get(ts->dev);
+	if (ret)
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	mutex_lock(&afw->lock);
+	cancel = afw->cancel;
+	mutex_unlock(&afw->lock);
+
+	if (cancel)
+		return FW_UPLOAD_ERR_CANCELED;
+
+	axiom_lock_input_device(ts);
+
+	if (ts->input && input_device_enabled(ts->input)) {
+		dev_err(dev, "Input device not idle, abort TH2CFG update\n");
+		axiom_unlock_input_device(ts);
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	ret = axiom_u02_stop(ts);
+	if (ret)
+		goto err_swreset;
+
+	ret = axiom_zero_volatile_mem(ts);
+	if (ret)
+		goto err_swreset;
+
+	/* Skip to the first fw chunk */
+	data += sizeof(struct axiom_fw_cfg_hdr);
+	size -= sizeof(struct axiom_fw_cfg_hdr);
+	*written += sizeof(struct axiom_fw_cfg_hdr);
+
+	axiom_set_runmode(ts, AXIOM_TCP_CFG_UPDATE_MODE);
+
+	while (size) {
+		struct axiom_fw_cfg_chunk chunk;
+		unsigned int chunk_len;
+
+		chunk_len = axiom_cfg_fw_prepare_chunk(&chunk, data);
+		if (axiom_skip_cfg_chunk(ts, &chunk)) {
+			dev_dbg(dev, "Skip TH2CFG usage u%02X\n", chunk.usage_num);
+			goto next_chunk;
+		}
+
+		ret = axiom_write_cfg_chunk(ts, &chunk);
+		if (ret) {
+			axiom_set_runmode(ts, AXIOM_TCP_MODE);
+			goto err_swreset;
+		}
+
+next_chunk:
+		data += chunk_len;
+		size -= chunk_len;
+		*written += chunk_len;
+	}
+
+	axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+	/* Ensure that the chunks are written correctly */
+	ret = axiom_verify_volatile_mem(ts);
+	if (ret) {
+		dev_err(dev, "Failed to verify written config, abort\n");
+		goto err_swreset;
+	}
+
+	ret = axiom_u02_save_config(ts);
+	if (ret)
+		goto err_swreset;
+
+	/*
+	 * TODO: Check if u02 start would be sufficient to load the new config
+	 * values
+	 */
+	ret = axiom_u02_swreset(ts);
+	if (ret) {
+		dev_err(dev, "Soft reset failed\n");
+		goto err_unlock;
+	}
+
+	ret = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+	if (ret)
+		goto err_unlock;
+
+	if (axiom_verify_crcs(ts))
+		goto err_unlock;
+
+	/* Unlock before the input device gets unregistered */
+	axiom_unlock_input_device(ts);
+
+	ret = axiom_update_input_dev(ts);
+	if (ret) {
+		dev_err(dev, "Input device update failed after TH2CFG firmware update\n");
+		goto err_out;
+	}
+
+	dev_info(dev, "TH2CFG update successful\n");
+
+	return FW_UPLOAD_ERR_NONE;
+
+err_swreset:
+	axiom_u02_swreset(ts);
+err_unlock:
+	axiom_unlock_input_device(ts);
+err_out:
+	return ret == -ETIMEDOUT ? FW_UPLOAD_ERR_TIMEOUT : FW_UPLOAD_ERR_HW_ERROR;
+}
+
+static void axiom_cfg_fw_cancel(struct fw_upload *fw_upload)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+	struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
+
+	mutex_lock(&afw->lock);
+	afw->cancel = true;
+	mutex_unlock(&afw->lock);
+}
+
+static void axiom_cfg_fw_cleanup(struct fw_upload *fw_upload)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+
+	mutex_unlock(&ts->fwupdate_lock);
+	pm_runtime_mark_last_busy(ts->dev);
+	pm_runtime_put_sync_autosuspend(ts->dev);
+}
+
+static const struct fw_upload_ops axiom_cfg_fw_upload_ops = {
+	.prepare = axiom_cfg_fw_prepare,
+	.write = axiom_cfg_fw_write,
+	.poll_complete = axiom_fw_poll_complete,
+	.cancel = axiom_cfg_fw_cancel,
+	.cleanup = axiom_cfg_fw_cleanup,
+};
+
+static void axiom_remove_axfw_fwl_action(void *data)
+{
+	struct axiom_data *ts = data;
+
+	firmware_upload_unregister(ts->fw[AXIOM_FW_AXFW].fwl);
+}
+
+static void axiom_remove_cfg_fwl_action(void *data)
+{
+	struct axiom_data *ts = data;
+
+	firmware_upload_unregister(ts->fw[AXIOM_FW_CFG].fwl);
+}
+
+static int axiom_register_fwl(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+	struct fw_upload *fwl;
+	char *fw_name;
+	int ret;
+
+	if (!IS_ENABLED(CONFIG_FW_UPLOAD)) {
+		dev_dbg(dev, "axfw and th2cfgbin update disabled\n");
+		return 0;
+	}
+
+	mutex_init(&ts->fw[AXIOM_FW_AXFW].lock);
+	fw_name = kasprintf(GFP_KERNEL, "i2c:%s.axfw", dev_name(dev));
+	fwl = firmware_upload_register(THIS_MODULE, ts->dev, fw_name,
+				       &axiom_axfw_fw_upload_ops, ts);
+	kfree(fw_name);
+	if (IS_ERR(fwl))
+		return dev_err_probe(dev, PTR_ERR(fwl),
+				     "Failed to register firmware upload\n");
+
+	ret = devm_add_action_or_reset(dev, axiom_remove_axfw_fwl_action, ts);
+	if (ret)
+		return ret;
+
+	ts->fw[AXIOM_FW_AXFW].fwl = fwl;
+
+	mutex_init(&ts->fw[AXIOM_FW_CFG].lock);
+	fw_name = kasprintf(GFP_KERNEL, "i2c:%s.th2cfgbin", dev_name(dev));
+	fwl = firmware_upload_register(THIS_MODULE, ts->dev, fw_name,
+				       &axiom_cfg_fw_upload_ops, ts);
+	kfree(fw_name);
+	if (IS_ERR(fwl))
+		return dev_err_probe(dev, PTR_ERR(fwl),
+				     "Failed to register cfg firmware upload\n");
+
+	ret = devm_add_action_or_reset(dev, axiom_remove_cfg_fwl_action, ts);
+	if (ret)
+		return ret;
+
+	ts->fw[AXIOM_FW_CFG].fwl = fwl;
+
+	return 0;
+}
+
+/************************* Device handlig *************************************/
+
+#define AXIOM_SIMPLE_FW_DEVICE_ATTR(attr)					\
+	static ssize_t								\
+	fw_ ## attr ## _show(struct device *dev,				\
+			     struct device_attribute *_attr, char *buf)		\
+	{									\
+		struct i2c_client *i2c = to_i2c_client(dev);			\
+		struct axiom_data *ts = i2c_get_clientdata(i2c);		\
+										\
+		return sysfs_emit(buf, "%u\n", ts->fw_##attr);			\
+	}									\
+	static DEVICE_ATTR_RO(fw_##attr)
+
+AXIOM_SIMPLE_FW_DEVICE_ATTR(major);
+AXIOM_SIMPLE_FW_DEVICE_ATTR(minor);
+AXIOM_SIMPLE_FW_DEVICE_ATTR(rc);
+
+static ssize_t fw_status_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *i2c = to_i2c_client(dev);
+	struct axiom_data *ts = i2c_get_clientdata(i2c);
+	const char *val;
+
+	if (ts->fw_status)
+		val = "production";
+	else
+		val = "engineering";
+
+	return sysfs_emit(buf, "%s\n", val);
+}
+static DEVICE_ATTR_RO(fw_status);
+
+static ssize_t fw_variant_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *i2c = to_i2c_client(dev);
+	struct axiom_data *ts = i2c_get_clientdata(i2c);
+	const char *val;
+
+	switch (ts->fw_variant) {
+	case 0:
+		val = "3d";
+		break;
+	case 1:
+		val = "2d";
+		break;
+	case 3:
+		val = "force";
+		break;
+	default:
+		val = "unknown";
+		break;
+	}
+
+	return sysfs_emit(buf, "%s\n", val);
+}
+static DEVICE_ATTR_RO(fw_variant);
+
+static ssize_t device_id_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *i2c = to_i2c_client(dev);
+	struct axiom_data *ts = i2c_get_clientdata(i2c);
+
+	return sysfs_emit(buf, "%u\n", ts->device_id);
+}
+static DEVICE_ATTR_RO(device_id);
+
+static ssize_t device_state_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *i2c = to_i2c_client(dev);
+	struct axiom_data *ts = i2c_get_clientdata(i2c);
+
+	return sysfs_emit(buf, "%s\n", axiom_runmode_to_string(ts));
+}
+static DEVICE_ATTR_RO(device_state);
+
+static struct attribute *axiom_attrs[] = {
+	&dev_attr_fw_major.attr,
+	&dev_attr_fw_minor.attr,
+	&dev_attr_fw_rc.attr,
+	&dev_attr_fw_status.attr,
+	&dev_attr_fw_variant.attr,
+	&dev_attr_device_id.attr,
+	&dev_attr_device_state.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(axiom);
+
+static void axiom_poll(struct input_dev *input)
+{
+	struct axiom_data *ts = input_get_drvdata(input);
+
+	axiom_process_report(ts, AXIOM_U34, NULL, 0);
+}
+
+static irqreturn_t axiom_irq(int irq, void *dev_id)
+{
+	struct axiom_data *ts = dev_id;
+
+	axiom_process_report(ts, AXIOM_U34, NULL, 0);
+
+	return IRQ_HANDLED;
+}
+
+static int axiom_input_open(struct input_dev *dev)
+{
+	struct axiom_data *ts = input_get_drvdata(dev);
+
+	return pm_runtime_resume_and_get(ts->dev);
+}
+
+static void axiom_input_close(struct input_dev *dev)
+{
+	struct axiom_data *ts = input_get_drvdata(dev);
+
+	pm_runtime_mark_last_busy(ts->dev);
+	pm_runtime_put_sync_autosuspend(ts->dev);
+}
+
+static int axiom_register_input_dev(struct axiom_data *ts,
+				    bool update_in_process)
+{
+	struct device *dev = ts->dev;
+	struct i2c_client *client = to_i2c_client(dev);
+	struct input_dev *input;
+	int ret;
+
+	input = input_allocate_device();
+	if (!input) {
+		dev_err(dev, "Failed to allocate input driver data\n");
+		return -ENOMEM;
+	}
+
+	input->dev.parent = dev;
+	input->name = "TouchNetix aXiom Touchscreen";
+	input->id.bustype = BUS_I2C;
+	input->id.vendor = ts->jedec_id;
+	input->id.product = ts->device_id;
+	input->id.version = ts->silicon_rev;
+
+	/* Either follow the panel or the open user count, not both */
+	if (!ts->is_panel_follower) {
+		input->open = axiom_input_open;
+		input->close = axiom_input_close;
+	}
+
+	axiom_u64_cds_enabled(ts);
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, AXIOM_MAX_XY - 1, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, AXIOM_MAX_XY - 1, 0, 0);
+	input_set_abs_params(input, ABS_MT_DISTANCE, 0, 127, 0, 0);
+	if (ts->cds_enabled)
+		input_set_abs_params(input, ABS_MT_PRESSURE, 0, 127, 0, 0);
+
+	touchscreen_parse_properties(input, true, &ts->prop);
+
+	axiom_u42_get_touchslots(ts);
+	if (!ts->num_slots && update_in_process) {
+		input_free_device(input);
+		/*
+		 * Skip input device registration but don't throw an error to
+		 * not abort the update since some FW updates require a
+		 * following CFG update to re-initialize the touchslot handling.
+		 */
+		if (update_in_process) {
+			dev_info(dev, "No touchslots found after FW or CFG update, skip registering input device\n");
+			return 0;
+		}
+
+		dev_err(dev, "Error firmware has no touchslots enabled\n");
+		return -EINVAL;
+	}
+
+	ret = input_mt_init_slots(input, ts->num_slots, INPUT_MT_DIRECT);
+	if (ret) {
+		input_free_device(input);
+		dev_err(dev, "Failed to init mt slots\n");
+		return ret;
+	}
+
+	/*
+	 * Ensure that the IRQ setup is done only once since the handler belong
+	 * to the i2c-dev whereas the input-poller belong to the input-dev. The
+	 * input-dev can get unregistered during a firmware update to reflect
+	 * the new firmware state. Therefore the input-poller setup must be done
+	 * always.
+	 */
+	if (!ts->irq_setup_done && client->irq) {
+		ret = devm_request_threaded_irq(dev, client->irq, NULL, axiom_irq,
+						IRQF_ONESHOT, dev_name(dev), ts);
+		if (ret) {
+			dev_err(dev, "Failed to request IRQ\n");
+			return ret;
+		}
+		ts->irq_setup_done = true;
+	} else {
+		ret = input_setup_polling(input, axiom_poll);
+		if (ret) {
+			input_free_device(input);
+			dev_err(dev, "Setup polling mode failed\n");
+			return ret;
+		}
+
+		input_set_poll_interval(input, ts->poll_interval);
+	}
+
+	input_set_drvdata(input, ts);
+	ts->input = input;
+
+	ret = input_register_device(input);
+	if (ret) {
+		input_free_device(input);
+		ts->input = NULL;
+		dev_err(dev, "Failed to register input device\n");
+	};
+
+	return ret;
+}
+
+static int axiom_update_input_dev(struct axiom_data *ts)
+{
+	axiom_unregister_input_dev(ts);
+
+	return axiom_register_input_dev(ts, true);
+}
+
+static int axiom_parse_firmware(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+	struct gpio_desc *gpio;
+	int ret;
+
+	ts->supplies[0].supply = "vddi";
+	ts->supplies[1].supply = "vdda";
+	ts->num_supplies = ARRAY_SIZE(ts->supplies);
+
+	ret = devm_regulator_bulk_get(dev, ts->num_supplies, ts->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to get power supplies\n");
+
+	gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(gpio))
+		return dev_err_probe(dev, PTR_ERR(gpio),
+				     "Failed to get reset GPIO\n");
+	ts->reset_gpio = gpio;
+
+	ts->poll_interval = AXIOM_DEFAULT_POLL_INTERVAL_MS;
+	device_property_read_u32(dev, "poll-interval", &ts->poll_interval);
+
+	return 0;
+}
+
+static int axiom_power_device(struct axiom_data *ts, unsigned int enable)
+{
+	struct device *dev = ts->dev;
+	int ret;
+
+	if (!enable) {
+		regulator_bulk_disable(ts->num_supplies, ts->supplies);
+		return 0;
+	}
+
+	ret = regulator_bulk_enable(ts->num_supplies, ts->supplies);
+	if (ret) {
+		dev_err(dev, "Failed to enable power supplies\n");
+		return ret;
+	}
+
+	gpiod_set_value_cansleep(ts->reset_gpio, 1);
+	fsleep(2000);
+	gpiod_set_value_cansleep(ts->reset_gpio, 0);
+
+	fsleep(AXIOM_STARTUP_TIME_MS);
+
+	return 0;
+}
+
+static int axiom_panel_prepared(struct drm_panel_follower *follower)
+{
+	struct axiom_data *ts = container_of(follower, struct axiom_data,
+					     panel_follower);
+
+	return pm_runtime_resume_and_get(ts->dev);
+}
+
+static int axiom_panel_unpreparing(struct drm_panel_follower *follower)
+{
+	struct axiom_data *ts = container_of(follower, struct axiom_data,
+					     panel_follower);
+
+	return pm_runtime_put_sync_suspend(ts->dev);
+}
+
+static const struct drm_panel_follower_funcs axiom_panel_follower_funcs = {
+	.panel_prepared = axiom_panel_prepared,
+	.panel_unpreparing = axiom_panel_unpreparing,
+};
+
+static int axiom_register_panel_follower(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+
+	if (!drm_is_panel_follower(dev))
+		return 0;
+
+	if (device_can_wakeup(dev)) {
+		dev_warn(dev, "Can't follow panel if marked as wakup device\n");
+		return 0;
+	}
+
+	ts->panel_follower.funcs = &axiom_panel_follower_funcs;
+	ts->is_panel_follower = true;
+
+	return devm_drm_panel_add_follower(dev, &ts->panel_follower);
+}
+
+static int axiom_i2c_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct axiom_data *ts;
+	int ret;
+
+	ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		return dev_err_probe(dev, -ENOMEM,
+				     "Failed to allocate driver data\n");
+
+	ts->regmap = devm_regmap_init_i2c(client, &axiom_i2c_regmap_config);
+	if (IS_ERR(ts->regmap))
+		return dev_err_probe(dev, PTR_ERR(ts->regmap),
+				     "Failed to initialize regmap\n");
+
+	i2c_set_clientdata(client, ts);
+	ts->dev = dev;
+
+	init_completion(&ts->boot_complete.completion);
+	init_completion(&ts->nvm_write.completion);
+	mutex_init(&ts->fwupdate_lock);
+
+	ret = axiom_register_fwl(ts);
+	if (ret)
+		return ret;
+
+	ret = axiom_parse_firmware(ts);
+	if (ret)
+		return ret;
+
+	ret = axiom_power_device(ts, 1);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to power-on device\n");
+
+	pm_runtime_set_autosuspend_delay(dev, 10 * MSEC_PER_SEC);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable pm-runtime\n");
+
+	ret = axiom_register_panel_follower(ts);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to register panel follower\n");
+
+	ret = axiom_u31_device_discover(ts);
+	/*
+	 * Register the device to allow FW updates in case that the current FW
+	 * doesn't support the required driver usages or if the device is in
+	 * bootloader mode.
+	 */
+	if (ret && ret == -EACCES && IS_ENABLED(CONFIG_FW_UPLOAD)) {
+		dev_warn(dev, "Device discovery failed, wait for user fw update\n");
+		pm_runtime_mark_last_busy(dev);
+		pm_runtime_put_sync_autosuspend(dev);
+		return 0;
+	} else if (ret) {
+		pm_runtime_put_sync(dev);
+		return dev_err_probe(dev, ret, "Device discovery failed\n");
+	}
+
+	ret = axiom_register_input_dev(ts, false);
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_sync_autosuspend(dev);
+	if (ret && IS_ENABLED(CONFIG_FW_UPLOAD))
+		dev_warn(dev, "Failed to register the input device, wait for user fw update\n");
+	else if (ret)
+		return dev_err_probe(dev, ret, "Failed to register input device\n");
+
+	return 0;
+}
+
+static void axiom_i2c_remove(struct i2c_client *client)
+{
+	struct axiom_data *ts = i2c_get_clientdata(client);
+
+	axiom_unregister_input_dev(ts);
+}
+
+static int axiom_runtime_suspend(struct device *dev)
+{
+	struct axiom_data *ts = dev_get_drvdata(dev);
+	struct i2c_client *client = to_i2c_client(dev);
+
+	if (client->irq && ts->irq_setup_done)
+		disable_irq(client->irq);
+
+	return axiom_power_device(ts, 0);
+}
+
+static int axiom_runtime_resume(struct device *dev)
+{
+	struct axiom_data *ts = dev_get_drvdata(dev);
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret;
+
+	ret = axiom_power_device(ts, 1);
+	if (ret)
+		return ret;
+
+	if (client->irq && ts->irq_setup_done)
+		enable_irq(client->irq);
+
+	return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(axiom_pm_ops, axiom_runtime_suspend,
+				 axiom_runtime_resume, NULL);
+
+static const struct i2c_device_id axiom_i2c_id_table[] = {
+	{ "ax54a" },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, axiom_i2c_id_table);
+
+static const struct of_device_id axiom_of_match[] = {
+	{ .compatible = "touchnetix,ax54a", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, axiom_of_match);
+
+static struct i2c_driver axiom_i2c_driver = {
+	.driver = {
+		   .name = KBUILD_MODNAME,
+		   .dev_groups = axiom_groups,
+		   .pm = pm_ptr(&axiom_pm_ops),
+		   .of_match_table = axiom_of_match,
+	},
+	.id_table = axiom_i2c_id_table,
+	.probe = axiom_i2c_probe,
+	.remove = axiom_i2c_remove,
+};
+module_i2c_driver(axiom_i2c_driver);
+
+MODULE_DESCRIPTION("TouchNetix aXiom touchscreen I2C bus driver");
+MODULE_LICENSE("GPL");

-- 
2.39.5


^ permalink raw reply related

* [PATCH v3 3/4] dt-bindings: input: Add TouchNetix axiom touchscreen
From: Marco Felsch @ 2025-08-21 17:26 UTC (permalink / raw)
  To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
	Rafael J. Wysocki, Andrew Morton, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
	Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
  Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch
In-Reply-To: <20250821-v6-10-topic-touchscreen-axiom-v3-0-940ccee6dba3@pengutronix.de>

From: Kamel Bouhara <kamel.bouhara@bootlin.com>

Add the TouchNetix axiom I2C touchscreen device tree bindings
documentation.

Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 .../input/touchscreen/touchnetix,ax54a.yaml        | 62 ++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml b/Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d388c41a75dd4d6d6d0e6de0eaef4d493d439a90
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/touchnetix,ax54a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TouchNetix Axiom series touchscreen controller
+
+maintainers:
+  - Marco Felsch <kernel@pengutronix.de>
+
+allOf:
+  - $ref: /schemas/input/touchscreen/touchscreen.yaml#
+  - $ref: /schemas/input/input.yaml#
+
+properties:
+  compatible:
+    const: touchnetix,ax54a
+
+  reg:
+    enum: [ 0x66, 0x67 ]
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  vdda-supply:
+    description: Analog power supply regulator on VDDA pin
+
+  vddi-supply:
+    description: I/O power supply regulator on VDDI pin
+
+required:
+  - compatible
+  - reg
+  - vdda-supply
+  - vddi-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      touchscreen@66 {
+        compatible = "touchnetix,ax54a";
+        reg = <0x66>;
+        interrupt-parent = <&gpio2>;
+        interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
+        reset-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>;
+        vdda-supply = <&vdda_reg>;
+        vddi-supply = <&vddi_reg>;
+        poll-interval = <20>;
+      };
+    };
+...

-- 
2.39.5


^ permalink raw reply related

* [PATCH v3 1/4] firmware_loader: expand firmware error codes with up-to-date error
From: Marco Felsch @ 2025-08-21 17:26 UTC (permalink / raw)
  To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
	Rafael J. Wysocki, Andrew Morton, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
	Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
  Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch
In-Reply-To: <20250821-v6-10-topic-touchscreen-axiom-v3-0-940ccee6dba3@pengutronix.de>

Add FW_UPLOAD_ERR_DUPLICATE to allow drivers to inform the firmware_loader
framework that the update is not required. This can be the case if the
user provided firmware matches the current running firmware.

Sync lib/test_firmware.c accordingly.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/base/firmware_loader/sysfs_upload.c | 1 +
 include/linux/firmware.h                    | 2 ++
 lib/test_firmware.c                         | 1 +
 3 files changed, 4 insertions(+)

diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c
index 829270067d1632f92656859fb9143e3fa9635670..0a583a1b3f4fde563257566426d523fbf839b13f 100644
--- a/drivers/base/firmware_loader/sysfs_upload.c
+++ b/drivers/base/firmware_loader/sysfs_upload.c
@@ -28,6 +28,7 @@ static const char * const fw_upload_err_str[] = {
 	[FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
 	[FW_UPLOAD_ERR_WEAROUT]	     = "flash-wearout",
 	[FW_UPLOAD_ERR_FW_INVALID]   = "firmware-invalid",
+	[FW_UPLOAD_ERR_DUPLICATE]    = "firmware-duplicate",
 };
 
 static const char *fw_upload_progress(struct device *dev,
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index aae1b85ffc10e20e9c3c9b6009d26b83efd8cb24..fe7797be4c08cd62cdad9617b8f70095d5e0af2f 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -29,6 +29,7 @@ struct firmware {
  * @FW_UPLOAD_ERR_RW_ERROR: read or write to HW failed, see kernel log
  * @FW_UPLOAD_ERR_WEAROUT: FLASH device is approaching wear-out, wait & retry
  * @FW_UPLOAD_ERR_FW_INVALID: invalid firmware file
+ * @FW_UPLOAD_ERR_DUPLICATE: firmware is already up to date (duplicate)
  * @FW_UPLOAD_ERR_MAX: Maximum error code marker
  */
 enum fw_upload_err {
@@ -41,6 +42,7 @@ enum fw_upload_err {
 	FW_UPLOAD_ERR_RW_ERROR,
 	FW_UPLOAD_ERR_WEAROUT,
 	FW_UPLOAD_ERR_FW_INVALID,
+	FW_UPLOAD_ERR_DUPLICATE,
 	FW_UPLOAD_ERR_MAX
 };
 
diff --git a/lib/test_firmware.c b/lib/test_firmware.c
index 211222e63328f970228920f5662ee80cc7f51215..603c3a4b385c849944a695849a1894693234b5eb 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -1133,6 +1133,7 @@ static const char * const fw_upload_err_str[] = {
 	[FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
 	[FW_UPLOAD_ERR_WEAROUT]	     = "flash-wearout",
 	[FW_UPLOAD_ERR_FW_INVALID]   = "firmware-invalid",
+	[FW_UPLOAD_ERR_DUPLICATE]    = "firmware-duplicate",
 };
 
 static void upload_err_inject_error(struct test_firmware_upload *tst,

-- 
2.39.5


^ permalink raw reply related

* [PATCH v3 0/4] Input: Add support for TouchNetix aXiom touchscreen
From: Marco Felsch @ 2025-08-21 17:26 UTC (permalink / raw)
  To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
	Rafael J. Wysocki, Andrew Morton, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
	Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
  Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch,
	Krzysztof Kozlowski

Hi,

this adds the support for the TouchNetix aXiom touchcontroller family.

The following features are added:
 - I2C communication
 - Input event handling
 - Touchcontroller firmware (AXFW or ALC) updates
 - Touchcontroller config (TH2CFGBIN) updates

Regards,
  Marco

Changes in v3:
- Link to v2: https://lore.kernel.org/r/20250529-v6-10-topic-touchscreen-axiom-v2-0-a5edb105a600@pengutronix.de
- firmware: fix commit message (Russ)
- dt-bindings: Add ack from Krzysztof
- dt-bindings: make use of GPIO_ACTIVE_LOW (Krzysztof)
- dt-bindings: drop 'panel: true' property (Krzysztof)
- driver: make use of sysfs_emit (Greg)
- driver: s/WARN()/dev_warn()/ to not take down the system (Greg)
- driver: fix build dependency error by adding "depends on DRM || !DRM"
- driver: harmonize usage printing to u%02X

Changes in v2:
- Link to v1: https://lore.kernel.org/r/20241119-v6-10-topic-touchscreen-axiom-v1-0-6124925b9718@pengutronix.de
- Rework the firmware-duplicate handling -> expose the error to the
  userspace
- Drop Krzysztof Kozlowski ACK and RB
- Add panel-follower support
- Add sysfs-driver-input-touchnetix-axiom documentation
- Add support for new firmware 4.8.9
- Add support to handle 2D and 3D firmware

---
Kamel Bouhara (2):
      dt-bindings: vendor-prefixes: Add TouchNetix AS
      dt-bindings: input: Add TouchNetix axiom touchscreen

Marco Felsch (2):
      firmware_loader: expand firmware error codes with up-to-date error
      Input: Add TouchNetix aXiom I2C Touchscreen support

 .../testing/sysfs-driver-input-touchnetix-axiom    |   74 +
 .../input/touchscreen/touchnetix,ax54a.yaml        |   62 +
 .../devicetree/bindings/vendor-prefixes.yaml       |    2 +
 drivers/base/firmware_loader/sysfs_upload.c        |    1 +
 drivers/input/touchscreen/Kconfig                  |   17 +
 drivers/input/touchscreen/Makefile                 |    1 +
 drivers/input/touchscreen/touchnetix_axiom.c       | 2974 ++++++++++++++++++++
 include/linux/firmware.h                           |    2 +
 lib/test_firmware.c                                |    1 +
 9 files changed, 3134 insertions(+)
---
base-commit: 038d61fd642278bab63ee8ef722c50d10ab01e8f
change-id: 20240704-v6-10-topic-touchscreen-axiom-105761e81011

Best regards,
-- 
Marco Felsch <m.felsch@pengutronix.de>


^ permalink raw reply

* [PATCH v3 2/4] dt-bindings: vendor-prefixes: Add TouchNetix AS
From: Marco Felsch @ 2025-08-21 17:26 UTC (permalink / raw)
  To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
	Rafael J. Wysocki, Andrew Morton, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
	Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
  Cc: linux-kernel, devicetree, linux-input, kernel,
	Krzysztof Kozlowski, Marco Felsch
In-Reply-To: <20250821-v6-10-topic-touchscreen-axiom-v3-0-940ccee6dba3@pengutronix.de>

From: Kamel Bouhara <kamel.bouhara@bootlin.com>

Add vendor prefix for TouchNetix AS (https://www.touchnetix.com/products/).

Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 5d2a7a8d3ac6c666c8b557c2ef385918e5e97bf9..a45b3091f50041cb79a99631c52a1b0c5d6b5f2b 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1567,6 +1567,8 @@ patternProperties:
     description: Toradex AG
   "^toshiba,.*":
     description: Toshiba Corporation
+  "^touchnetix,.*":
+    description: TouchNetix AS
   "^toumaz,.*":
     description: Toumaz
   "^tpk,.*":

-- 
2.39.5


^ permalink raw reply related

* [HID][asus] Fn+Esc not handled on ASUS ProArt P16
From: Massimo Giambona @ 2025-08-21 16:06 UTC (permalink / raw)
  To: jikos, bentiss; +Cc: linux-input, linux-kernel

Hello,

on ASUS ProArt P16 laptops the Fn+Esc key is not handled by the hid-asus
driver.

When pressing Fn+Esc, dmesg shows:

   [21348.413951] asus 0003:0B05:19B6.0002: Unmapped Asus vendor 
usagepage code 0x4e
   [21348.498405] asus 0003:0B05:19B6.0002: Unmapped Asus vendor 
usagepage code 0x4e

One message is for press, the other for release.

According to USB PCap traces (see 
https://gitlab.com/asus-linux/asusctl/-/issues/585),
Fn+Esc sends:

   - 0x5ad04e01 (enable)
   - 0x5ad04e00 (disable)

It would be useful to map this to KEY_FN_ESC (or another appropriate 
keycode)
in drivers/hid/hid-asus.c so that it becomes available to userspace.

System information:
- Model: ASUS ProArt P16 H7606 (BIOS 319)
- Kernel version: Linux 6.15.10-200.fc42.x86_64
- evtest: no event generated
- dmesg: "Unmapped Asus vendor usagepage code 0x4e"
- Distribution: Fedora 42 Workstation

Additional notes:
- I tested a local modification of hid-asus.c:

     case 0x4e:
         asus_map_key_clear(KEY_FN_ESC);

   With this, Fn+Esc is mapped correctly.
- I also tried sending the raw reports to enable/disable the FnLock feature
   from the driver, but hit an error:
     "BUG: scheduling while atomic"
- I wrote a small tool: https://github.com/m4ss1m0g/proart_p16_fnlock

Could you please consider adding support for this key to the driver?

Thanks for your work,

Massimo

P.S. Other unmapped vendor codes were also observed on this machine:
- Fn+F8  (emoji): dmesg code 0x7e
- Fn+F12 (ProArt): dmesg code 0x8b
- Fn+F10 (Camera toggle): dmesg codes 0x85, 0x01 (but work)
These may also deserve mapping, but Fn+Esc is the most important one.

^ permalink raw reply

* Re: [PATCH 2/8] dt-bindings: hwmon: add Apple System Management Controller hwmon schema
From: Sven Peter @ 2025-08-21 15:25 UTC (permalink / raw)
  To: James Calligeros, Rob Herring
  Cc: Janne Grunau, Alyssa Rosenzweig, Neal Gompa, Lee Jones,
	Krzysztof Kozlowski, Conor Dooley, Alexandre Belloni,
	Jean Delvare, Guenter Roeck, Dmitry Torokhov, asahi,
	linux-arm-kernel, devicetree, linux-kernel, linux-rtc,
	linux-hwmon, linux-input
In-Reply-To: <CAHgNfTw+wetmZzvPgkANpmSD4b6k0785QZLpBVD9FMqNDnq2EQ@mail.gmail.com>

On 20.08.25 01:22, James Calligeros wrote:
> Hi Rob,
> 
> On Wed, Aug 20, 2025 at 6:15 AM Rob Herring <robh@kernel.org> wrote:
>>

[...]

>>> +          apple,fan-maximum:
>>> +            $ref: /schemas/types.yaml#/definitions/string
>>> +            pattern: "^[A-Za-z0-9]{4}"
>>> +            description: The maximum speed the current fan can run at
>>> +          apple,fan-target:
>>> +            $ref: /schemas/types.yaml#/definitions/string
>>> +            pattern: "^[A-Za-z0-9]{4}"
>>> +            description: Writeable endpoint for setting desired fan speed
>>> +          apple,fan-mode:
>>> +            $ref: /schemas/types.yaml#/definitions/string
>>> +            pattern: "^[A-Za-z0-9]{4}"
>>> +            description: Writeable endpoint to enable/disable manual fan
>>> +              control
>>> +          label:
>>> +            $ref: /schemas/types.yaml#/definitions/string
>>> +            description: Human-readable name for the sensor
>>
>> Surely more than apple,key-id is required? How would it be useful with
>> only that? You can know how many fans you have, but have no info or
>> control over them?
> 
> The key specified in apple,key-id is the fan's current speed, which is the
> only key strictly required to enumerate the presence of a fan in the system.
> All of the other keys are optional information that are only really useful
> when implementing manual fan control, which is itself optional as the platform
> really expects the SMC firmware to have control over fan speeds at all times.


Can we at least also require the label? Then we have the SMC key and a 
human readable representation which is already useful.


Sven



^ permalink raw reply

* [PATCH 2/2] HID: bpf: rescan the device for the group after a load/unload
From: Benjamin Tissoires @ 2025-08-21 14:38 UTC (permalink / raw)
  To: Jiri Kosina, Peter Hutterer; +Cc: linux-input, linux-kernel, Benjamin Tissoires
In-Reply-To: <20250821-bpf-rescan-v1-0-08f9e2bc01bb@kernel.org>

When a BPF gets loaded, it was previously not possible to bind a
hid-generic device to hid-multitouch because the group was never
updated.

This change forces a rescan of the report descriptor after a bpf is
loaded/unloaded so we set up the proper group.

This was detected while Peter was trying to fix a Viewsonic device: the
HID device sending multiotuch data through a proprietary collection was
handled by hid-generic, and we don't have any way of attaching it to
hid-multitouch because the pre-scanning wasn't able to see the Contact
ID HID usage.

Suggested-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
 drivers/hid/hid-core.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index cf68fdffe0581eefb29a9d691f4acfc8f0d175d5..a5b3a8ca2fcbc868470dd50ec14e4c7829dd863b 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -943,6 +943,15 @@ static int hid_scan_report(struct hid_device *hid)
 	parser->device = hid;
 	hid->group = HID_GROUP_GENERIC;
 
+	/*
+	 * In case we are re-scanning after a BPF has been loaded,
+	 * we need to use the bpf report descriptor, not the original one.
+	 */
+	if (hid->bpf_rdesc && hid->bpf_rsize) {
+		start = hid->bpf_rdesc;
+		end = start + hid->bpf_rsize;
+	}
+
 	/*
 	 * The parsing is simpler than the one in hid_open_report() as we should
 	 * be robust against hid errors. Those errors will be raised by
@@ -2728,6 +2737,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
 	int ret;
 
 	if (!hdev->bpf_rsize) {
+		/* we keep a reference to the currently scanned report descriptor */
+		const __u8  *original_rdesc = hdev->bpf_rdesc;
+
+		if (!original_rdesc)
+			original_rdesc = hdev->dev_rdesc;
+
 		/* in case a bpf program gets detached, we need to free the old one */
 		hid_free_bpf_rdesc(hdev);
 
@@ -2737,6 +2752,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
 		/* call_hid_bpf_rdesc_fixup will always return a valid pointer */
 		hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc,
 							   &hdev->bpf_rsize);
+
+		/* the report descriptor changed, we need to re-scan it */
+		if (original_rdesc != hdev->bpf_rdesc) {
+			hdev->group = 0;
+			hid_set_group(hdev);
+		}
 	}
 
 	if (!hid_check_device_match(hdev, hdrv, &id))

-- 
2.50.1


^ permalink raw reply related

* [PATCH 1/2] HID: core: factor out hid_set_group()
From: Benjamin Tissoires @ 2025-08-21 14:38 UTC (permalink / raw)
  To: Jiri Kosina, Peter Hutterer; +Cc: linux-input, linux-kernel, Benjamin Tissoires
In-Reply-To: <20250821-bpf-rescan-v1-0-08f9e2bc01bb@kernel.org>

When we load a bpf, we can change the report descriptor. However, the
current implementation doesn't change the group meaning that we can not
rebind a device from hid-generic to hid-multitouch.

This is a preparatory patch to force a rescan of the device after the
bpf has been loaded.

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
 drivers/hid/hid-core.c | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 5419a6c10907e307df08c3b44e2b3a147f1b154a..cf68fdffe0581eefb29a9d691f4acfc8f0d175d5 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2708,6 +2708,20 @@ static bool hid_check_device_match(struct hid_device *hdev,
 	return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
 }
 
+static void hid_set_group(struct hid_device *hdev)
+{
+	int ret;
+
+	if (hid_ignore_special_drivers) {
+		hdev->group = HID_GROUP_GENERIC;
+	} else if (!hdev->group &&
+		   !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
+		ret = hid_scan_report(hdev);
+		if (ret)
+			hid_warn(hdev, "bad device descriptor (%d)\n", ret);
+	}
+}
+
 static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
 {
 	const struct hid_device_id *id;
@@ -2903,14 +2917,7 @@ int hid_add_device(struct hid_device *hdev)
 	/*
 	 * Scan generic devices for group information
 	 */
-	if (hid_ignore_special_drivers) {
-		hdev->group = HID_GROUP_GENERIC;
-	} else if (!hdev->group &&
-		   !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
-		ret = hid_scan_report(hdev);
-		if (ret)
-			hid_warn(hdev, "bad device descriptor (%d)\n", ret);
-	}
+	hid_set_group(hdev);
 
 	hdev->id = atomic_inc_return(&id);
 

-- 
2.50.1


^ permalink raw reply related

* [PATCH 0/2] HID: bpf: allow bpf to rebind a driver to hid-mutltiouch
From: Benjamin Tissoires @ 2025-08-21 14:38 UTC (permalink / raw)
  To: Jiri Kosina, Peter Hutterer; +Cc: linux-input, linux-kernel, Benjamin Tissoires

This happened while Peter was trying to fix a Viewsonic device: the HID
device sending multiotuch data through a proprietary collection was
handled by hid-generic, and we don't have any way of attaching it to
hid-multitouch because the pre-scanning wasn't able to see the Contact
ID HID usage.

After a little of back and forth, it turns out that the best solution is
to re-scan the device when a report descriptor is changed from the BPF
point of view.

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
Benjamin Tissoires (2):
      HID: core: factor out hid_set_group()
      HID: bpf: rescan the device for the group after a load/unload

 drivers/hid/hid-core.c | 44 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 36 insertions(+), 8 deletions(-)
---
base-commit: f55f91622e6f10884d30049f6748588b3718eecd
change-id: 20250821-bpf-rescan-d4764865c67f

Best regards,
-- 
Benjamin Tissoires <bentiss@kernel.org>


^ permalink raw reply

* [PATCH] selftests/hid: update vmtest.sh for virtme-ng
From: Benjamin Tissoires @ 2025-08-21 14:29 UTC (permalink / raw)
  To: Jiri Kosina, Shuah Khan
  Cc: linux-input, linux-kselftest, linux-kernel, Benjamin Tissoires

This commit is a rewrite almost from scratch of vmtest.sh.

By relying on virtme-ng, we get rid of boot2container, reducing the
total bootup time (and network requirements). That means that we are
relying on the programs being installed on the host, but that shouldn't
be an issue. The generation of the kconfig is also now handled by
virtme-ng, so that's one less thing to worry.

I used tools/testing/selftests/vsock/vmtest.sh as a base and modified it
to look mostly like my previous script:
- removed the custom ssh handling
- make use of vng for compiling, which allows to bring remote
  compilation (and potentially remote compilation on a remote container)
- change the verbosity logic by having 2 levels:
  - first one shows the tests outputs
  - second level also shows the VM logs
- instead of only running the compiled kernel when it is built, if we
  are in the kernel tree, use the kernel artifacts there (and complain
  if they are not built)
- adapted the tests list to match the HID subsystem tests

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
I have switched my workflow to make use of virtme-ng for a few months.
Now it's time to automate the manual commands I've been running in
vmtest.sh.
---
 tools/testing/selftests/hid/vmtest.sh | 668 +++++++++++++++++++++-------------
 1 file changed, 423 insertions(+), 245 deletions(-)

diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh
index db534e9099a8a4684346eed0067d397ffa6f80cf..ecbd57f775a044b4d076b4800ca0068f9533056c 100755
--- a/tools/testing/selftests/hid/vmtest.sh
+++ b/tools/testing/selftests/hid/vmtest.sh
@@ -1,296 +1,474 @@
 #!/bin/bash
 # SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Red Hat
+# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
+#
+# Dependencies:
+#		* virtme-ng
+#		* busybox-static (used by virtme-ng)
+#		* qemu	(used by virtme-ng)
+
+readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
+readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
+
+source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
+
+readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf
+readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw
+readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs"
+readonly SSH_GUEST_PORT=22
+readonly WAIT_PERIOD=3
+readonly WAIT_PERIOD_MAX=60
+readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
+readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid)
+
+readonly QEMU_OPTS="\
+	 --pidfile ${QEMU_PIDFILE} \
+"
+readonly KERNEL_CMDLINE=""
+readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log)
+readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest)
+readonly TEST_DESCS=(
+	"Run hid_bpf tests in the VM."
+	"Run hidraw tests in the VM."
+	"Run the hid-tools test-suite in the VM."
+)
+
+VERBOSE=0
+SHELL_MODE=0
+BUILD_HOST=""
+BUILD_HOST_PODMAN_CONTAINER_NAME=""
+
+usage() {
+	local name
+	local desc
+	local i
+
+	echo
+	echo "$0 [OPTIONS] [TEST]... [-- tests-args]"
+	echo "If no TEST argument is given, all tests will be run."
+	echo
+	echo "Options"
+	echo "  -b: build the kernel from the current source tree and use it for guest VMs"
+	echo "  -H: hostname for remote build host (used with -b)"
+	echo "  -p: podman container name for remote build host (used with -b)"
+	echo "      Example: -H beefyserver -p vng"
+	echo "  -q: set the path to or name of qemu binary"
+	echo "  -s: start a shell in the VM instead of running tests"
+	echo "  -v: more verbose output (can be repeated multiple times)"
+	echo
+	echo "Available tests"
+
+	for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
+		name=${TEST_NAMES[${i}]}
+		desc=${TEST_DESCS[${i}]}
+		printf "\t%-35s%-35s\n" "${name}" "${desc}"
+	done
+	echo
 
-set -u
-set -e
-
-# This script currently only works for x86_64
-ARCH="$(uname -m)"
-case "${ARCH}" in
-x86_64)
-	QEMU_BINARY=qemu-system-x86_64
-	BZIMAGE="arch/x86/boot/bzImage"
-	;;
-*)
-	echo "Unsupported architecture"
 	exit 1
-	;;
-esac
-SCRIPT_DIR="$(dirname $(realpath $0))"
-OUTPUT_DIR="$SCRIPT_DIR/results"
-KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
-B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py"
-NUM_COMPILE_JOBS="$(nproc)"
-LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
-LOG_FILE="${LOG_FILE_BASE}.log"
-EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
-CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1"
-
-TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
-DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
-
-usage()
-{
-	cat <<EOF
-Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>]
-
-<command> is the command you would normally run when you are in
-the source kernel direcory. e.g:
-
-	$0 -- ./tools/testing/selftests/hid/hid_bpf
-
-If no command is specified and a debug shell (-s) is not requested,
-"${DEFAULT_COMMAND}" will be run by default.
-
-If you build your kernel using KBUILD_OUTPUT= or O= options, these
-can be passed as environment variables to the script:
-
-  O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
-
-or
-
-  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
-
-Options:
-
-	-u)		Update the boot2container script to a newer version.
-	-d)		Update the output directory (default: ${OUTPUT_DIR})
-	-b)		Run only the build steps for the kernel and the selftests
-	-j)		Number of jobs for compilation, similar to -j in make
-			(default: ${NUM_COMPILE_JOBS})
-	-s)		Instead of powering off the VM, start an interactive
-			shell. If <command> is specified, the shell runs after
-			the command finishes executing
-EOF
 }
 
-download()
-{
-	local file="$1"
+die() {
+	echo "$*" >&2
+	exit "${KSFT_FAIL}"
+}
 
-	echo "Downloading $file..." >&2
-	curl -Lsf "$file" -o "${@:2}"
+vm_ssh() {
+	# vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22'
+	# (ED25519) to the list of known hosts.",
+	# So replace the command with what's actually called and add the "-q" option
+	stdbuf -oL ssh -q \
+		       -F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \
+		       -l root virtme-ng%${SSH_GUEST_PORT} \
+		       "$@"
+	return $?
 }
 
-recompile_kernel()
-{
-	local kernel_checkout="$1"
-	local make_command="$2"
+cleanup() {
+	if [[ -s "${QEMU_PIDFILE}" ]]; then
+		pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
+	fi
 
-	cd "${kernel_checkout}"
+	# If failure occurred during or before qemu start up, then we need
+	# to clean this up ourselves.
+	if [[ -e "${QEMU_PIDFILE}" ]]; then
+		rm "${QEMU_PIDFILE}"
+	fi
+}
+
+check_args() {
+	local found
 
-	${make_command} olddefconfig
-	${make_command} headers
-	${make_command}
+	for arg in "$@"; do
+		found=0
+		for name in "${TEST_NAMES[@]}"; do
+			if [[ "${name}" = "${arg}" ]]; then
+				found=1
+				break
+			fi
+		done
+
+		if [[ "${found}" -eq 0 ]]; then
+			echo "${arg} is not an available test" >&2
+			usage
+		fi
+	done
+
+	for arg in "$@"; do
+		if ! command -v > /dev/null "test_${arg}"; then
+			echo "Test ${arg} not found" >&2
+			usage
+		fi
+	done
+}
+
+check_deps() {
+	for dep in vng ${QEMU} busybox pkill ssh pytest; do
+		if [[ ! -x $(command -v "${dep}") ]]; then
+			echo -e "skip:    dependency ${dep} not found!\n"
+			exit "${KSFT_SKIP}"
+		fi
+	done
+
+	if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then
+		printf "skip:    %s not found!" "${HID_BPF_TEST}"
+		printf " Please build the kselftest hid_bpf target.\n"
+		exit "${KSFT_SKIP}"
+	fi
+
+	if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then
+		printf "skip:    %s not found!" "${HIDRAW_TEST}"
+		printf " Please build the kselftest hidraw target.\n"
+		exit "${KSFT_SKIP}"
+	fi
 }
 
-update_selftests()
-{
-	local kernel_checkout="$1"
-	local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"
+check_vng() {
+	local tested_versions
+	local version
+	local ok
 
-	cd "${selftests_dir}"
-	${make_command}
+	tested_versions=("1.36" "1.37")
+	version="$(vng --version)"
+
+	ok=0
+	for tv in "${tested_versions[@]}"; do
+		if [[ "${version}" == *"${tv}"* ]]; then
+			ok=1
+			break
+		fi
+	done
+
+	if [[ ! "${ok}" -eq 1 ]]; then
+		printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
+		printf "not function properly.\n\tThe following versions have been tested: " >&2
+		echo "${tested_versions[@]}" >&2
+	fi
 }
 
-run_vm()
-{
-	local run_dir="$1"
-	local b2c="$2"
-	local kernel_bzimage="$3"
-	local command="$4"
-	local post_command=""
-
-	cd "${run_dir}"
-
-	if ! which "${QEMU_BINARY}" &> /dev/null; then
-		cat <<EOF
-Could not find ${QEMU_BINARY}
-Please install qemu or set the QEMU_BINARY environment variable.
-EOF
+handle_build() {
+	if [[ ! "${BUILD}" -eq 1 ]]; then
+		return
+	fi
+
+	if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
+		echo "-b requires vmtest.sh called from the kernel source tree" >&2
 		exit 1
 	fi
 
-	# alpine (used in post-container requires the PATH to have /bin
-	export PATH=$PATH:/bin
+	pushd "${KERNEL_CHECKOUT}" &>/dev/null
 
-	if [[ "${debug_shell}" != "yes" ]]
-	then
-		touch ${OUTPUT_DIR}/${LOG_FILE}
-		command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
-		post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
-	else
-		command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
+	if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
+		die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
 	fi
 
-	set +e
-	$b2c --command "${command}" \
-	     --kernel ${kernel_bzimage} \
-	     --workdir ${OUTPUT_DIR} \
-	     --image ${CONTAINER_IMAGE}
+	local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build")
 
-	echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
+	if [[ -n "${BUILD_HOST}" ]]; then
+		vng_args+=("--build-host" "${BUILD_HOST}")
+	fi
 
-	set -e
+	if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then
+		vng_args+=("--build-host-exec-prefix" \
+			   "podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}")
+	fi
 
-	${post_command}
-}
+	if ! vng "${vng_args[@]}"; then
+		die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
+	fi
 
-is_rel_path()
-{
-	local path="$1"
+	if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then
+		die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})"
+	fi
 
-	[[ ${path:0:1} != "/" ]]
+	if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then
+		die "failed to build HID selftests from source tree (${SCRIPT_DIR})"
+	fi
+
+	popd &>/dev/null
 }
 
-do_update_kconfig()
-{
-	local kernel_checkout="$1"
-	local kconfig_file="$2"
+vm_start() {
+	local logfile=/dev/null
+	local verbose_opt=""
+	local kernel_opt=""
+	local qemu
 
-	rm -f "$kconfig_file" 2> /dev/null
+	qemu=$(command -v "${QEMU}")
 
-	for config in "${KCONFIG_REL_PATHS[@]}"; do
-		local kconfig_src="${config}"
-		cat "$kconfig_src" >> "$kconfig_file"
-	done
-}
+	if [[ "${VERBOSE}" -eq 2 ]]; then
+		verbose_opt="--verbose"
+		logfile=/dev/stdout
+	fi
 
-update_kconfig()
-{
-	local kernel_checkout="$1"
-	local kconfig_file="$2"
-
-	if [[ -f "${kconfig_file}" ]]; then
-		local local_modified="$(stat -c %Y "${kconfig_file}")"
-
-		for config in "${KCONFIG_REL_PATHS[@]}"; do
-			local kconfig_src="${config}"
-			local src_modified="$(stat -c %Y "${kconfig_src}")"
-			# Only update the config if it has been updated after the
-			# previously cached config was created. This avoids
-			# unnecessarily compiling the kernel and selftests.
-			if [[ "${src_modified}" -gt "${local_modified}" ]]; then
-				do_update_kconfig "$kernel_checkout" "$kconfig_file"
-				# Once we have found one outdated configuration
-				# there is no need to check other ones.
-				break
-			fi
-		done
-	else
-		do_update_kconfig "$kernel_checkout" "$kconfig_file"
+	# If we are running from within the kernel source tree, use the kernel source tree
+	# as the kernel to boot, otherwise use the currently running kernel.
+	if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then
+		kernel_opt="${KERNEL_CHECKOUT}"
 	fi
-}
 
-main()
-{
-	local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
-	local kernel_checkout=$(realpath "${script_dir}"/../../../../)
-	# By default the script searches for the kernel in the checkout directory but
-	# it also obeys environment variables O= and KBUILD_OUTPUT=
-	local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
-	local command="${DEFAULT_COMMAND}"
-	local update_b2c="no"
-	local debug_shell="no"
-	local build_only="no"
-
-	while getopts ':hsud:j:b' opt; do
-		case ${opt} in
-		u)
-			update_b2c="yes"
-			;;
-		d)
-			OUTPUT_DIR="$OPTARG"
-			;;
-		j)
-			NUM_COMPILE_JOBS="$OPTARG"
-			;;
-		s)
-			command="/bin/sh"
-			debug_shell="yes"
-			;;
-		b)
-			build_only="yes"
-			;;
-		h)
-			usage
-			exit 0
-			;;
-		\? )
-			echo "Invalid Option: -$OPTARG"
-			usage
-			exit 1
-			;;
-		: )
-			echo "Invalid Option: -$OPTARG requires an argument"
-			usage
-			exit 1
-			;;
-		esac
-	done
-	shift $((OPTIND -1))
-
-	# trap 'catch "$?"' EXIT
-	if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then
-		if [[ $# -eq 0 ]]; then
-			echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
-		else
-			command="$@"
-
-			if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
-			then
-				debug_shell="yes"
-			fi
+	vng \
+		--run \
+		${kernel_opt} \
+		${verbose_opt} \
+		--qemu-opts="${QEMU_OPTS}" \
+		--qemu="${qemu}" \
+		--user root \
+		--append "${KERNEL_CMDLINE}" \
+		--ssh "${SSH_GUEST_PORT}" \
+		--rw  &> ${logfile} &
+
+	local vng_pid=$!
+	local elapsed=0
+
+	while [[ ! -s "${QEMU_PIDFILE}" ]]; do
+		if ! kill -0 "${vng_pid}" 2>/dev/null; then
+			echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2
+			die "failed to boot VM"
 		fi
-	fi
 
-	local kconfig_file="${OUTPUT_DIR}/latest.config"
-	local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
+		if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then
+			echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2
+			die "failed to boot VM"
+		fi
 
-	# Figure out where the kernel is being built.
-	# O takes precedence over KBUILD_OUTPUT.
-	if [[ "${O:=""}" != "" ]]; then
-		if is_rel_path "${O}"; then
-			O="$(realpath "${PWD}/${O}")"
+		sleep 1
+		elapsed=$((elapsed + 1))
+	done
+}
+
+vm_wait_for_ssh() {
+	local i
+
+	i=0
+	while true; do
+		if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
+			die "Timed out waiting for guest ssh"
 		fi
-		kernel_bzimage="${O}/${BZIMAGE}"
-		make_command="${make_command} O=${O}"
-	elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
-		if is_rel_path "${KBUILD_OUTPUT}"; then
-			KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
+		if vm_ssh -- true; then
+			break
 		fi
-		kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
-		make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
+		i=$(( i + 1 ))
+		sleep ${WAIT_PERIOD}
+	done
+}
+
+vm_mount_bpffs() {
+	vm_ssh -- mount bpffs -t bpf /sys/fs/bpf
+}
+
+__log_stdin() {
+	stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }'
+}
+
+__log_args() {
+	echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
+}
+
+log() {
+	local verbose="$1"
+	shift
+
+	local prefix="$1"
+
+	shift
+	local redirect=
+	if [[ ${verbose} -le 0 ]]; then
+		redirect=/dev/null
+	else
+		redirect=/dev/stdout
+	fi
+
+	if [[ "$#" -eq 0 ]]; then
+		__log_stdin | tee -a "${LOG}" > ${redirect}
+	else
+		__log_args "$@" | tee -a "${LOG}" > ${redirect}
 	fi
+}
 
-	local b2c="${OUTPUT_DIR}/vm2c.py"
+log_setup() {
+	log $((VERBOSE-1)) "setup" "$@"
+}
 
-	echo "Output directory: ${OUTPUT_DIR}"
+log_host() {
+	local testname=$1
 
-	mkdir -p "${OUTPUT_DIR}"
-	update_kconfig "${kernel_checkout}" "${kconfig_file}"
+	shift
+	log $((VERBOSE-1)) "test:${testname}:host" "$@"
+}
 
-	recompile_kernel "${kernel_checkout}" "${make_command}"
-	update_selftests "${kernel_checkout}" "${make_command}"
+log_guest() {
+	local testname=$1
 
-	if [[ "${build_only}" == "no" ]]; then
-		if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
-			echo "vm2c script not found in ${b2c}"
-			update_b2c="yes"
-		fi
+	shift
+	log ${VERBOSE} "# test:${testname}" "$@"
+}
 
-		if [[ "${update_b2c}" == "yes" ]]; then
-			download $B2C_URL $b2c
-			chmod +x $b2c
-		fi
+test_vm_hid_bpf() {
+	local testname="${FUNCNAME[0]#test_}"
 
-		run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
-		if [[ "${debug_shell}" != "yes" ]]; then
-			echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
-		fi
+	vm_ssh -- "${HID_BPF_TEST}" \
+		2>&1 | log_guest "${testname}"
+
+	return ${PIPESTATUS[0]}
+}
+
+test_vm_hidraw() {
+	local testname="${FUNCNAME[0]#test_}"
+
+	vm_ssh -- "${HIDRAW_TEST}" \
+		2>&1 | log_guest "${testname}"
+
+	return ${PIPESTATUS[0]}
+}
+
+test_vm_pytest() {
+	local testname="${FUNCNAME[0]#test_}"
 
-		exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
+	shift
+
+	vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \
+		2>&1 | log_guest "${testname}"
+
+	return ${PIPESTATUS[0]}
+}
+
+run_test() {
+	local vm_oops_cnt_before
+	local vm_warn_cnt_before
+	local vm_oops_cnt_after
+	local vm_warn_cnt_after
+	local name
+	local rc
+
+	vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
+	vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l)
+
+	name=$(echo "${1}" | awk '{ print $1 }')
+	eval test_"${name}" "$@"
+	rc=$?
+
+	vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
+	if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
+		echo "FAIL: kernel oops detected on vm" | log_host "${name}"
+		rc=$KSFT_FAIL
+	fi
+
+	vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l)
+	if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then
+		echo "FAIL: kernel error detected on vm" | log_host "${name}"
+		vm_ssh -- dmesg --level=err | log_host "${name}"
+		rc=$KSFT_FAIL
 	fi
+
+	return "${rc}"
 }
 
-main "$@"
+QEMU="qemu-system-$(uname -m)"
+
+while getopts :hvsbq:H:p: o
+do
+	case $o in
+	v) VERBOSE=$((VERBOSE+1));;
+	s) SHELL_MODE=1;;
+	b) BUILD=1;;
+	q) QEMU=$OPTARG;;
+	H) BUILD_HOST=$OPTARG;;
+	p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;;
+	h|*) usage;;
+	esac
+done
+shift $((OPTIND-1))
+
+trap cleanup EXIT
+
+PARAMS=""
+
+if [[ ${#} -eq 0 ]]; then
+	ARGS=("${TEST_NAMES[@]}")
+else
+	ARGS=()
+	COUNT=0
+	for arg in $@; do
+		COUNT=$((COUNT+1))
+		if [[ x"$arg" == x"--" ]]; then
+			break
+		fi
+		ARGS+=($arg)
+	done
+	shift $COUNT
+	PARAMS="$@"
+fi
+
+if [[ "${SHELL_MODE}" -eq 0 ]]; then
+	check_args "${ARGS[@]}"
+	echo "1..${#ARGS[@]}"
+fi
+check_deps
+check_vng
+handle_build
+
+log_setup "Booting up VM"
+vm_start
+vm_wait_for_ssh
+vm_mount_bpffs
+log_setup "VM booted up"
+
+if [[ "${SHELL_MODE}" -eq 1 ]]; then
+	log_setup "Starting interactive shell in VM"
+	echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM."
+	CURRENT_DIR="$(pwd)"
+	vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l"
+	exit "$KSFT_PASS"
+fi
+
+cnt_pass=0
+cnt_fail=0
+cnt_skip=0
+cnt_total=0
+for arg in "${ARGS[@]}"; do
+	run_test "${arg}" "${PARAMS}"
+	rc=$?
+	if [[ ${rc} -eq $KSFT_PASS ]]; then
+		cnt_pass=$(( cnt_pass + 1 ))
+		echo "ok ${cnt_total} ${arg}"
+	elif [[ ${rc} -eq $KSFT_SKIP ]]; then
+		cnt_skip=$(( cnt_skip + 1 ))
+		echo "ok ${cnt_total} ${arg} # SKIP"
+	elif [[ ${rc} -eq $KSFT_FAIL ]]; then
+		cnt_fail=$(( cnt_fail + 1 ))
+		echo "not ok ${cnt_total} ${arg} # exit=$rc"
+	fi
+	cnt_total=$(( cnt_total + 1 ))
+done
+
+echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
+echo "Log: ${LOG}"
+
+if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
+	exit "$KSFT_PASS"
+else
+	exit "$KSFT_FAIL"
+fi

---
base-commit: b80a75cf6999fb79971b41eaec7af2bb4b514714
change-id: 20250818-virtme-ng-f73db7e61235

Best regards,
-- 
Benjamin Tissoires <bentiss@kernel.org>


^ permalink raw reply related

* Re: [PATCH v2 2/2] HID: i2c-hid: elan: Add parade-tc3408 timing
From: Doug Anderson @ 2025-08-21 14:29 UTC (permalink / raw)
  To: Langyan Ye
  Cc: dmitry.torokhov, robh, krzk+dt, conor+dt, jikos, bentiss,
	linux-input, devicetree, linux-kernel
In-Reply-To: <20250821031514.49497-3-yelangyan@huaqin.corp-partner.google.com>

Hi,

On Wed, Aug 20, 2025 at 8:15 PM Langyan Ye
<yelangyan@huaqin.corp-partner.google.com> wrote:
>
> Parade-tc3408 requires reset to pull down time greater than 10ms,
> so the configuration post_power_delay_ms is 10, and the chipset
> initial time is required to be greater than 300ms,
> so the post_gpio_reset_on_delay_ms is set to 300.
>
> Signed-off-by: Langyan Ye <yelangyan@huaqin.corp-partner.google.com>
> ---
>  drivers/hid/i2c-hid/i2c-hid-of-elan.c | 8 ++++++++
>  1 file changed, 8 insertions(+)

Whoops, I guess I should have realized that the commit description
(300ms) didn't match the contents of the patch (100ms) in your
previous version. Sorry about that. This version looks fine.

Reviewed-by: Douglas Anderson <dianders@chromium.org>

^ permalink raw reply

* Re: [PATCH v1 04/14] net: dt-bindings: Convert Marvell 8897/8997 bindings to YAML
From: Rob Herring @ 2025-08-21 14:28 UTC (permalink / raw)
  To: Ariel D'Alessandro
  Cc: airlied, amergnat, andrew+netdev, andrew-ct.chen,
	angelogioacchino.delregno, broonie, chunkuang.hu, ck.hu, conor+dt,
	davem, dmitry.torokhov, edumazet, flora.fu, houlong.wei, jeesw,
	jmassot, kernel, krzk+dt, kuba, kyrie.wu, lgirdwood,
	linus.walleij, louisalexis.eyraud, maarten.lankhorst,
	matthias.bgg, mchehab, minghsiu.tsai, mripard, p.zabel, pabeni,
	sean.wang, simona, support.opensource, tiffany.lin, tzimmermann,
	yunfei.dong, devicetree, dri-devel, linux-arm-kernel, linux-clk,
	linux-gpio, linux-input, linux-kernel, linux-media,
	linux-mediatek, linux-sound, netdev
In-Reply-To: <20250820171302.324142-5-ariel.dalessandro@collabora.com>

On Wed, Aug 20, 2025 at 12:15 PM Ariel D'Alessandro
<ariel.dalessandro@collabora.com> wrote:
>
> Convert the existing text-based DT bindings for Marvell 8897/8997
> (sd8897/sd8997) bluetooth devices controller to a YAML schema.
>
> While here, bindings for "usb1286,204e" (USB interface) are dropped from
> the YAML definition as these are currently documented in file:
>
> - Documentation/devicetree/bindings/net/btusb.txt
>
> Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>
> ---
>  .../bindings/net/marvell,sd8897-bt.yaml       | 91 +++++++++++++++++++

This needs to move to net/bluetooth/

>  .../bindings/net/marvell-bt-8xxx.txt          | 83 -----------------
>  2 files changed, 91 insertions(+), 83 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/net/marvell,sd8897-bt.yaml
>  delete mode 100644 Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt
>
> diff --git a/Documentation/devicetree/bindings/net/marvell,sd8897-bt.yaml b/Documentation/devicetree/bindings/net/marvell,sd8897-bt.yaml
> new file mode 100644
> index 0000000000000..6539868c08b8a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/marvell,sd8897-bt.yaml
> @@ -0,0 +1,91 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/marvell,sd8897-bt.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Marvell 8897/8997 (sd8897/sd8997) bluetooth devices (SDIO)
> +
> +maintainers:
> +  - Ariel D'Alessandro <ariel.dalessandro@collabora.com>
> +

Needs a $ref to bluetooth-controller.yaml

> +properties:
> +  compatible:
> +    enum:
> +      - marvell,sd8897-bt
> +      - marvell,sd8997-bt
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  marvell,cal-data:
> +    $ref: /schemas/types.yaml#/definitions/uint8-array
> +    description:
> +      Calibration data downloaded to the device during initialization.
> +    minItems: 28

Just: maxItems: 28

> +
> +  marvell,wakeup-pin:
> +    $ref: /schemas/types.yaml#/definitions/uint16
> +    description:
> +      Wakeup pin number of the bluetooth chip. Used by firmware to wakeup host
> +      system.
> +
> +  marvell,wakeup-gap-ms:

This unfortunately needs a uint16 type. That will cause a warning
which has to be fixed on the dtschema side.

> +    description:
> +      Wakeup latency of the host platform. Required by the chip sleep feature.
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/pinctrl/rockchip.h>

Please drop this and just use a number below.

> +
> +    sdio0 {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        btmrvl: btmrvl@2 {
> +            compatible = "marvell,sd8897-bt";
> +            reg = <2>;
> +            interrupt-parent = <&gpio4>;
> +            interrupts = <RK_PD7 IRQ_TYPE_LEVEL_LOW>;
> +            marvell,wakeup-pin = /bits/ 16 <13>;
> +            pinctrl-names = "default";
> +            pinctrl-0 = <&bt_host_wake_l>;
> +        };
> +    };

I would drop this example.

> +
> +    mmc3 {

mmc {

> +        vmmc-supply = <&wlan_en_reg>;
> +        bus-width = <4>;
> +        cap-power-off-card;
> +        keep-power-in-suspend;
> +
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        bluetooth: bluetooth@2 {

Drop the label.

> +            compatible = "marvell,sd8897-bt";
> +            reg = <2>;
> +            interrupt-parent = <&pio>;
> +            interrupts = <119 IRQ_TYPE_LEVEL_LOW>;
> +
> +            marvell,cal-data = /bits/ 8 <
> +                0x37 0x01 0x1c 0x00 0xff 0xff 0xff 0xff 0x01 0x7f 0x04 0x02
> +                0x00 0x00 0xba 0xce 0xc0 0xc6 0x2d 0x00 0x00 0x00 0x00 0x00
> +                0x00 0x00 0xf0 0x00>;
> +            marvell,wakeup-pin = /bits/ 16 <0x0d>;
> +            marvell,wakeup-gap-ms = /bits/ 16 <0x64>;
> +        };
> +    };
> +
> +...
> diff --git a/Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt b/Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt
> deleted file mode 100644
> index 957e5e5c2927c..0000000000000
> --- a/Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt
> +++ /dev/null
> @@ -1,83 +0,0 @@
> -Marvell 8897/8997 (sd8897/sd8997) bluetooth devices (SDIO or USB based)
> -------
> -The 8997 devices supports multiple interfaces. When used on SDIO interfaces,
> -the btmrvl driver is used and when used on USB interface, the btusb driver is
> -used.
> -
> -Required properties:
> -
> -  - compatible : should be one of the following:
> -       * "marvell,sd8897-bt" (for SDIO)
> -       * "marvell,sd8997-bt" (for SDIO)
> -       * "usb1286,204e"      (for USB)
> -
> -Optional properties:
> -
> -  - marvell,cal-data: Calibration data downloaded to the device during
> -                     initialization. This is an array of 28 values(u8).
> -                     This is only applicable to SDIO devices.
> -
> -  - marvell,wakeup-pin: It represents wakeup pin number of the bluetooth chip.
> -                       firmware will use the pin to wakeup host system (u16).
> -  - marvell,wakeup-gap-ms: wakeup gap represents wakeup latency of the host
> -                     platform. The value will be configured to firmware. This
> -                     is needed to work chip's sleep feature as expected (u16).
> -  - interrupt-names: Used only for USB based devices (See below)
> -  - interrupts : specifies the interrupt pin number to the cpu. For SDIO, the
> -                driver will use the first interrupt specified in the interrupt
> -                array. For USB based devices, the driver will use the interrupt
> -                named "wakeup" from the interrupt-names and interrupt arrays.
> -                The driver will request an irq based on this interrupt number.
> -                During system suspend, the irq will be enabled so that the
> -                bluetooth chip can wakeup host platform under certain
> -                conditions. During system resume, the irq will be disabled
> -                to make sure unnecessary interrupt is not received.
> -
> -Example:
> -
> -IRQ pin 119 is used as system wakeup source interrupt.
> -wakeup pin 13 and gap 100ms are configured so that firmware can wakeup host
> -using this device side pin and wakeup latency.
> -
> -Example for SDIO device follows (calibration data is also available in
> -below example).
> -
> -&mmc3 {
> -       vmmc-supply = <&wlan_en_reg>;
> -       bus-width = <4>;
> -       cap-power-off-card;
> -       keep-power-in-suspend;
> -
> -       #address-cells = <1>;
> -       #size-cells = <0>;
> -       btmrvl: bluetooth@2 {
> -               compatible = "marvell,sd8897-bt";
> -               reg = <2>;
> -               interrupt-parent = <&pio>;
> -               interrupts = <119 IRQ_TYPE_LEVEL_LOW>;
> -
> -               marvell,cal-data = /bits/ 8 <
> -                       0x37 0x01 0x1c 0x00 0xff 0xff 0xff 0xff 0x01 0x7f 0x04 0x02
> -                       0x00 0x00 0xba 0xce 0xc0 0xc6 0x2d 0x00 0x00 0x00 0x00 0x00
> -                       0x00 0x00 0xf0 0x00>;
> -               marvell,wakeup-pin = /bits/ 16 <0x0d>;
> -               marvell,wakeup-gap-ms = /bits/ 16 <0x64>;
> -       };
> -};
> -
> -Example for USB device:
> -
> -&usb_host1_ohci {
> -    #address-cells = <1>;
> -    #size-cells = <0>;
> -
> -    mvl_bt1: bt@1 {
> -       compatible = "usb1286,204e";
> -       reg = <1>;
> -       interrupt-parent = <&gpio0>;
> -       interrupt-names = "wakeup";
> -       interrupts = <119 IRQ_TYPE_LEVEL_LOW>;
> -       marvell,wakeup-pin = /bits/ 16 <0x0d>;
> -       marvell,wakeup-gap-ms = /bits/ 16 <0x64>;
> -    };
> -};
> --
> 2.50.1
>

^ permalink raw reply

* Re: [PATCH v1 04/14] net: dt-bindings: Convert Marvell 8897/8997 bindings to YAML
From: Rob Herring (Arm) @ 2025-08-21 13:24 UTC (permalink / raw)
  To: Ariel D'Alessandro
  Cc: flora.fu, chunkuang.hu, broonie, linux-gpio, mripard,
	louisalexis.eyraud, mchehab, kyrie.wu, lgirdwood, linux-kernel,
	dmitry.torokhov, conor+dt, kernel, linux-arm-kernel, amergnat,
	netdev, tiffany.lin, linux-input, airlied, linux-clk, jeesw,
	yunfei.dong, linus.walleij, sean.wang, linux-media, linux-sound,
	tzimmermann, andrew-ct.chen, minghsiu.tsai, simona, kuba, jmassot,
	devicetree, linux-mediatek, houlong.wei,
	angelogioacchino.delregno, andrew+netdev, support.opensource,
	maarten.lankhorst, ck.hu, matthias.bgg, pabeni, p.zabel, edumazet,
	krzk+dt, davem, dri-devel
In-Reply-To: <20250820171302.324142-5-ariel.dalessandro@collabora.com>


On Wed, 20 Aug 2025 14:12:52 -0300, Ariel D'Alessandro wrote:
> Convert the existing text-based DT bindings for Marvell 8897/8997
> (sd8897/sd8997) bluetooth devices controller to a YAML schema.
> 
> While here, bindings for "usb1286,204e" (USB interface) are dropped from
> the YAML definition as these are currently documented in file:
> 
> - Documentation/devicetree/bindings/net/btusb.txt
> 
> Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>
> ---
>  .../bindings/net/marvell,sd8897-bt.yaml       | 91 +++++++++++++++++++
>  .../bindings/net/marvell-bt-8xxx.txt          | 83 -----------------
>  2 files changed, 91 insertions(+), 83 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/net/marvell,sd8897-bt.yaml
>  delete mode 100644 Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/marvell,sd8897-bt.yaml: marvell,wakeup-gap-ms: missing type definition
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/marvell,sd8897-bt.example.dtb: bluetooth@2 (marvell,sd8897-bt): marvell,wakeup-gap-ms: b'\x00d' is not of type 'object', 'integer', 'array', 'boolean', 'null'
	from schema $id: http://devicetree.org/schemas/dt-core.yaml#

doc reference errors (make refcheckdocs):
Warning: Documentation/devicetree/bindings/net/btusb.txt references a file that doesn't exist: Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt
Documentation/devicetree/bindings/net/btusb.txt: Documentation/devicetree/bindings/net/marvell-bt-8xxx.txt

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250820171302.324142-5-ariel.dalessandro@collabora.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply

* Re: [PATCH] Input: Remove dev_err_probe() if error is -ENOMEM
From: Heiko Stübner @ 2025-08-21 12:54 UTC (permalink / raw)
  To: dmitry.torokhov, Xichao Zhao; +Cc: linux-input, linux-kernel, Xichao Zhao
In-Reply-To: <20250821094751.573411-1-zhao.xichao@vivo.com>

Am Donnerstag, 21. August 2025, 11:47:51 Mitteleuropäische Sommerzeit schrieb Xichao Zhao:
> The dev_err_probe() doesn't do anything when error is '-ENOMEM'.
> Therefore, remove the useless call to dev_err_probe(), and just
> return the value instead.
> 
> Signed-off-by: Xichao Zhao <zhao.xichao@vivo.com>

Acked-by: Heiko Stuebner <heiko@sntech.de>

Funnily enough, it seems I "messed up" in both drivers - just with a
decade in between :-)


Heiko

> ---
>  drivers/input/misc/qnap-mcu-input.c   | 2 +-
>  drivers/input/touchscreen/zforce_ts.c | 3 +--
>  2 files changed, 2 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/input/misc/qnap-mcu-input.c b/drivers/input/misc/qnap-mcu-input.c
> index 76e62f0816c1..3be899bfc114 100644
> --- a/drivers/input/misc/qnap-mcu-input.c
> +++ b/drivers/input/misc/qnap-mcu-input.c
> @@ -103,7 +103,7 @@ static int qnap_mcu_input_probe(struct platform_device *pdev)
>  
>  	input = devm_input_allocate_device(dev);
>  	if (!input)
> -		return dev_err_probe(dev, -ENOMEM, "no memory for input device\n");
> +		return -ENOMEM;
>  
>  	idev->input = input;
>  	idev->dev = dev;
> diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c
> index df42fdf36ae3..a360749fa076 100644
> --- a/drivers/input/touchscreen/zforce_ts.c
> +++ b/drivers/input/touchscreen/zforce_ts.c
> @@ -747,8 +747,7 @@ static int zforce_probe(struct i2c_client *client)
>  
>  	input_dev = devm_input_allocate_device(&client->dev);
>  	if (!input_dev)
> -		return dev_err_probe(&client->dev, -ENOMEM,
> -				     "could not allocate input device\n");
> +		return -ENOMEM;
>  
>  	ts->client = client;
>  	ts->input = input_dev;
> 





^ permalink raw reply

* Re: [PATCH v1 13/14] dt-bindings: input/touchscreen: Convert MELFAS MIP4 Touchscreen to YAML
From: Linus Walleij @ 2025-08-21 11:56 UTC (permalink / raw)
  To: Ariel D'Alessandro
  Cc: airlied, amergnat, andrew+netdev, andrew-ct.chen,
	angelogioacchino.delregno, broonie, chunkuang.hu, ck.hu, conor+dt,
	davem, dmitry.torokhov, edumazet, flora.fu, houlong.wei, jeesw,
	jmassot, kernel, krzk+dt, kuba, kyrie.wu, lgirdwood,
	louisalexis.eyraud, maarten.lankhorst, matthias.bgg, mchehab,
	minghsiu.tsai, mripard, p.zabel, pabeni, robh, sean.wang, simona,
	support.opensource, tiffany.lin, tzimmermann, yunfei.dong,
	devicetree, dri-devel, linux-arm-kernel, linux-clk, linux-gpio,
	linux-input, linux-kernel, linux-media, linux-mediatek,
	linux-sound, netdev
In-Reply-To: <20250820171302.324142-14-ariel.dalessandro@collabora.com>

Hi Ariel,

thanks for your patch!

On Wed, Aug 20, 2025 at 7:17 PM Ariel D'Alessandro
<ariel.dalessandro@collabora.com> wrote:

> +  ce-gpios:
> +    description: GPIO connected to the CE (chip enable) pin of the chip
> +    maxItems: 1

Mention that this should always have the flag GPIO_ACTIVE_HIGH
as this is required by the hardware.

Unfortunately we have no YAML syntax for enforcing flags :/

With that fix:
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

^ permalink raw reply

* Re: [PATCH v1 09/14] dt-bindings: pinctrl: mediatek,mt65xx-pinctrl: Allow gpio-line-names
From: Linus Walleij @ 2025-08-21 11:53 UTC (permalink / raw)
  To: Ariel D'Alessandro
  Cc: airlied, amergnat, andrew+netdev, andrew-ct.chen,
	angelogioacchino.delregno, broonie, chunkuang.hu, ck.hu, conor+dt,
	davem, dmitry.torokhov, edumazet, flora.fu, houlong.wei, jeesw,
	jmassot, kernel, krzk+dt, kuba, kyrie.wu, lgirdwood,
	louisalexis.eyraud, maarten.lankhorst, matthias.bgg, mchehab,
	minghsiu.tsai, mripard, p.zabel, pabeni, robh, sean.wang, simona,
	support.opensource, tiffany.lin, tzimmermann, yunfei.dong,
	devicetree, dri-devel, linux-arm-kernel, linux-clk, linux-gpio,
	linux-input, linux-kernel, linux-media, linux-mediatek,
	linux-sound, netdev
In-Reply-To: <20250820171302.324142-10-ariel.dalessandro@collabora.com>

On Wed, Aug 20, 2025 at 7:16 PM Ariel D'Alessandro
<ariel.dalessandro@collabora.com> wrote:

> Current, the DT bindings for MediaTek's MT65xx Pin controller is missing
> the gpio-line-names property, add it to the associated schema.
>
> Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH] Input: Remove dev_err_probe() if error is -ENOMEM
From: Xichao Zhao @ 2025-08-21  9:47 UTC (permalink / raw)
  To: heiko, dmitry.torokhov; +Cc: linux-input, linux-kernel, Xichao Zhao

The dev_err_probe() doesn't do anything when error is '-ENOMEM'.
Therefore, remove the useless call to dev_err_probe(), and just
return the value instead.

Signed-off-by: Xichao Zhao <zhao.xichao@vivo.com>
---
 drivers/input/misc/qnap-mcu-input.c   | 2 +-
 drivers/input/touchscreen/zforce_ts.c | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/input/misc/qnap-mcu-input.c b/drivers/input/misc/qnap-mcu-input.c
index 76e62f0816c1..3be899bfc114 100644
--- a/drivers/input/misc/qnap-mcu-input.c
+++ b/drivers/input/misc/qnap-mcu-input.c
@@ -103,7 +103,7 @@ static int qnap_mcu_input_probe(struct platform_device *pdev)
 
 	input = devm_input_allocate_device(dev);
 	if (!input)
-		return dev_err_probe(dev, -ENOMEM, "no memory for input device\n");
+		return -ENOMEM;
 
 	idev->input = input;
 	idev->dev = dev;
diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c
index df42fdf36ae3..a360749fa076 100644
--- a/drivers/input/touchscreen/zforce_ts.c
+++ b/drivers/input/touchscreen/zforce_ts.c
@@ -747,8 +747,7 @@ static int zforce_probe(struct i2c_client *client)
 
 	input_dev = devm_input_allocate_device(&client->dev);
 	if (!input_dev)
-		return dev_err_probe(&client->dev, -ENOMEM,
-				     "could not allocate input device\n");
+		return -ENOMEM;
 
 	ts->client = client;
 	ts->input = input_dev;
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH] HID: tighten ioctl command parsing
From: Benjamin Tissoires @ 2025-08-21  8:16 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Arnd Bergmann, Jiri Kosina, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <6432a849-512d-4abb-b15a-c2f1b7dd6533@app.fastmail.com>

On Jul 11 2025, Arnd Bergmann wrote:
> On Fri, Jul 11, 2025, at 11:40, Benjamin Tissoires wrote:
> >
> > On Jul 11 2025, Arnd Bergmann wrote:
> >> From: Arnd Bergmann <arnd@arndb.de>
> >> 
> >> The handling for variable-length ioctl commands in hidraw_ioctl() is
> >> rather complex and the check for the data direction is incomplete.
> >> 
> >> Simplify this code using a switch() statement with the size masked
> >> out, to ensure the rest of the command is correctly matched.
> >
> > How much "urgent" you believe this patch is. I would say 6.17 material,
> > but I'm not sure if your analysis regarding "the check for the data
> > direction is incomplete." would justify a 6.16 late fix.
> 
> I'm not aware of anything being actively broken without my patch,
> the driver just accepts extra commands that it should reject instead.
> 
> My feeling is that we still want the change backported to stable
> kernels as it does address incorrect behavior, and based on that
> it should be in a fixes branch for the current release rather than
> wait for the merge window.
> 
> On the other hand, it needs to be properly tested to ensure
> I'm not breaking things.
> 
> > Also, lately I added a new tools/testing/selftests/hid/hidraw.c to test
> > the latest HIDIOCREVOKE addition. I wonder if we should not add a couple
> > of checks there to ensure we get the different kind of other ioctls
> > tested before and after this patch.

FWIW, I was told to use more AI in my company, and I was too lazy to
write a full coverage. So I asked claude to help me out on that and the
result is pasted down below. Feel free to include them in v2 (note:
checkpatch complains about the co-developed-by tag, but not sure we have
a concensus for that ATM).

> 
> Yes, makes sense, e.g. testing HIDIOCGFEATURE with incorrect _IOC_DIR
> bits would be a useful addition.

The current behaviour for HIDIOCGRAWNAME with a wrong _IOC_DIR bit is to
return -EINVAL, while with your patch applied it's changed to -ENOTTY.
Is that expected?

---

From 71110ae5e5f30e0bfe529faf8987d676410f053f Mon Sep 17 00:00:00 2001
From: Benjamin Tissoires <bentiss@kernel.org>
Date: Thu, 21 Aug 2025 09:42:34 +0200
Subject: [PATCH 2/3] selftests/hid: hidraw: add more coverage for hidraw
 ioctls

Try to ensure all ioctls are having at least one test.

Co-developed-by: claude-4-sonnet
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
 tools/testing/selftests/hid/hid_common.h |   6 +
 tools/testing/selftests/hid/hidraw.c     | 346 +++++++++++++++++++++++
 2 files changed, 352 insertions(+)

diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h
index f77f69c6657d..8085519c47cb 100644
--- a/tools/testing/selftests/hid/hid_common.h
+++ b/tools/testing/selftests/hid/hid_common.h
@@ -230,6 +230,12 @@ static int uhid_event(struct __test_metadata *_metadata, int fd)
 		break;
 	case UHID_SET_REPORT:
 		UHID_LOG("UHID_SET_REPORT from uhid-dev");
+
+		answer.type = UHID_SET_REPORT_REPLY;
+		answer.u.set_report_reply.id = ev.u.set_report.id;
+		answer.u.set_report_reply.err = 0; /* success */
+
+		uhid_write(_metadata, fd, &answer);
 		break;
 	default:
 		TH_LOG("Invalid event from uhid-dev: %u", ev.type);
diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests/hid/hidraw.c
index 821db37ba4bb..6d61d03e2ef0 100644
--- a/tools/testing/selftests/hid/hidraw.c
+++ b/tools/testing/selftests/hid/hidraw.c
@@ -2,6 +2,9 @@
 /* Copyright (c) 2022-2024 Red Hat */
 
 #include "hid_common.h"
+#include <linux/input.h>
+#include <string.h>
+#include <sys/ioctl.h>
 
 /* for older kernels */
 #ifndef HIDIOCREVOKE
@@ -215,6 +218,349 @@ TEST_F(hidraw, write_event_revoked)
 	pthread_mutex_unlock(&uhid_output_mtx);
 }
 
+/*
+ * Test HIDIOCGRDESCSIZE ioctl to get report descriptor size
+ */
+TEST_F(hidraw, ioctl_rdescsize)
+{
+	int desc_size = 0;
+	int err;
+
+	/* call HIDIOCGRDESCSIZE ioctl */
+	err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
+	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed");
+
+	/* verify the size matches our test report descriptor */
+	ASSERT_EQ(desc_size, sizeof(rdesc))
+		TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size);
+}
+
+/*
+ * Test HIDIOCGRDESC ioctl to get report descriptor data
+ */
+TEST_F(hidraw, ioctl_rdesc)
+{
+	struct hidraw_report_descriptor desc;
+	int err;
+
+	/* get the full report descriptor */
+	desc.size = sizeof(rdesc);
+	err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
+	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed");
+
+	/* verify the descriptor data matches our test descriptor */
+	ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0)
+		TH_LOG("report descriptor data mismatch");
+}
+
+/*
+ * Test HIDIOCGRDESC ioctl with smaller buffer size
+ */
+TEST_F(hidraw, ioctl_rdesc_small_buffer)
+{
+	struct hidraw_report_descriptor desc;
+	int err;
+	size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */
+
+	/* get partial report descriptor */
+	desc.size = small_size;
+	err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
+	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer");
+
+	/* verify we got the first part of the descriptor */
+	ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0)
+		TH_LOG("partial report descriptor data mismatch");
+}
+
+/*
+ * Test HIDIOCGRAWINFO ioctl to get device information
+ */
+TEST_F(hidraw, ioctl_rawinfo)
+{
+	struct hidraw_devinfo devinfo;
+	int err;
+
+	/* get device info */
+	err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo);
+	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed");
+
+	/* verify device info matches our test setup */
+	ASSERT_EQ(devinfo.bustype, BUS_USB)
+		TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype);
+	ASSERT_EQ(devinfo.vendor, 0x0001)
+		TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor);
+	ASSERT_EQ(devinfo.product, 0x0a37)
+		TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product);
+}
+
+/*
+ * Test HIDIOCGFEATURE ioctl to get feature report
+ */
+TEST_F(hidraw, ioctl_gfeature)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* set report ID 1 in first byte */
+	buf[0] = 1;
+
+	/* get feature report */
+	err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
+	ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err);
+
+	/* verify we got the expected feature data */
+	ASSERT_EQ(buf[0], feature_data[0])
+		TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
+	ASSERT_EQ(buf[1], feature_data[1])
+		TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
+}
+
+/*
+ * Test HIDIOCGFEATURE ioctl with invalid report ID
+ */
+TEST_F(hidraw, ioctl_gfeature_invalid)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* set invalid report ID (not 1) */
+	buf[0] = 2;
+
+	/* try to get feature report */
+	err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID");
+	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
+}
+
+/*
+ * Test HIDIOCSFEATURE ioctl to set feature report
+ */
+TEST_F(hidraw, ioctl_sfeature)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* prepare feature report data */
+	buf[0] = 1; /* report ID */
+	buf[1] = 0x42;
+	buf[2] = 0x24;
+
+	/* set feature report */
+	err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf);
+	ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err);
+
+	/*
+	 * Note: The uhid mock doesn't validate the set report data,
+	 * so we just verify the ioctl succeeds
+	 */
+}
+
+/*
+ * Test HIDIOCGINPUT ioctl to get input report
+ */
+TEST_F(hidraw, ioctl_ginput)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* set report ID 1 in first byte */
+	buf[0] = 1;
+
+	/* get input report */
+	err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
+	ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err);
+
+	/* verify we got the expected input data */
+	ASSERT_EQ(buf[0], feature_data[0])
+		TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
+	ASSERT_EQ(buf[1], feature_data[1])
+		TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
+}
+
+/*
+ * Test HIDIOCGINPUT ioctl with invalid report ID
+ */
+TEST_F(hidraw, ioctl_ginput_invalid)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* set invalid report ID (not 1) */
+	buf[0] = 2;
+
+	/* try to get input report */
+	err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID");
+	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
+}
+
+/*
+ * Test HIDIOCSINPUT ioctl to set input report
+ */
+TEST_F(hidraw, ioctl_sinput)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* prepare input report data */
+	buf[0] = 1; /* report ID */
+	buf[1] = 0x55;
+	buf[2] = 0xAA;
+
+	/* set input report */
+	err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf);
+	ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err);
+
+	/*
+	 * Note: The uhid mock doesn't validate the set report data,
+	 * so we just verify the ioctl succeeds
+	 */
+}
+
+/*
+ * Test HIDIOCGOUTPUT ioctl to get output report
+ */
+TEST_F(hidraw, ioctl_goutput)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* set report ID 1 in first byte */
+	buf[0] = 1;
+
+	/* get output report */
+	err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
+	ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err);
+
+	/* verify we got the expected output data */
+	ASSERT_EQ(buf[0], feature_data[0])
+		TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
+	ASSERT_EQ(buf[1], feature_data[1])
+		TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
+}
+
+/*
+ * Test HIDIOCGOUTPUT ioctl with invalid report ID
+ */
+TEST_F(hidraw, ioctl_goutput_invalid)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* set invalid report ID (not 1) */
+	buf[0] = 2;
+
+	/* try to get output report */
+	err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID");
+	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
+}
+
+/*
+ * Test HIDIOCSOUTPUT ioctl to set output report
+ */
+TEST_F(hidraw, ioctl_soutput)
+{
+	__u8 buf[10] = {0};
+	int err;
+
+	/* prepare output report data */
+	buf[0] = 1; /* report ID */
+	buf[1] = 0x33;
+	buf[2] = 0xCC;
+
+	/* set output report */
+	err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf);
+	ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err);
+
+	/*
+	 * Note: The uhid mock doesn't validate the set report data,
+	 * so we just verify the ioctl succeeds
+	 */
+}
+
+/*
+ * Test HIDIOCGRAWNAME ioctl to get device name string
+ */
+TEST_F(hidraw, ioctl_rawname)
+{
+	char name[256] = {0};
+	char expected_name[64];
+	int err;
+
+	/* get device name */
+	err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name);
+	ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err);
+
+	/* construct expected name based on device id */
+	snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
+
+	/* verify the name matches expected pattern */
+	ASSERT_EQ(strcmp(name, expected_name), 0)
+		TH_LOG("expected name '%s', got '%s'", expected_name, name);
+}
+
+/*
+ * Test HIDIOCGRAWPHYS ioctl to get device physical address string
+ */
+TEST_F(hidraw, ioctl_rawphys)
+{
+	char phys[256] = {0};
+	char expected_phys[64];
+	int err;
+
+	/* get device physical address */
+	err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys);
+	ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err);
+
+	/* construct expected phys based on device id */
+	snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id);
+
+	/* verify the phys matches expected value */
+	ASSERT_EQ(strcmp(phys, expected_phys), 0)
+		TH_LOG("expected phys '%s', got '%s'", expected_phys, phys);
+}
+
+/*
+ * Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string
+ */
+TEST_F(hidraw, ioctl_rawuniq)
+{
+	char uniq[256] = {0};
+	int err;
+
+	/* get device unique identifier */
+	err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq);
+	ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err);
+
+	/* uniq is typically empty in our test setup */
+	ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq);
+}
+
+/*
+ * Test device string ioctls with small buffer sizes
+ */
+TEST_F(hidraw, ioctl_strings_small_buffer)
+{
+	char small_buf[8] = {0};
+	char expected_name[64];
+	int err;
+
+	/* test HIDIOCGRAWNAME with small buffer */
+	err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf);
+	ASSERT_EQ(err, sizeof(small_buf))
+		TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err);
+
+	/* construct expected truncated name */
+	snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
+
+	/* verify we got truncated name (first 8 chars, no null terminator guaranteed) */
+	ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0)
+		TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf));
+
+	/* Note: hidraw driver doesn't guarantee null termination when buffer is too small */
+}
+
 int main(int argc, char **argv)
 {
 	return test_harness_run(argc, argv);
-- 
2.50.1


From 62cbd33ca66b578c3e1ec715cfa308ea48b4dc15 Mon Sep 17 00:00:00 2001
From: Benjamin Tissoires <bentiss@kernel.org>
Date: Thu, 21 Aug 2025 09:44:11 +0200
Subject: [PATCH 3/3] selftests/hid: hidraw: forge wrong ioctls and tests them

We also need coverage for when the malicious user is not using the
proper ioctls definitions and tries to work around the driver.

Suggested-by: Arnd Bergmann <arnd@kernel.org>
Co-developed-by: claude-4-sonnet
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
 tools/testing/selftests/hid/hidraw.c | 61 ++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests/hid/hidraw.c
index 6d61d03e2ef0..2bbe0d69bd8a 100644
--- a/tools/testing/selftests/hid/hidraw.c
+++ b/tools/testing/selftests/hid/hidraw.c
@@ -332,6 +332,67 @@ TEST_F(hidraw, ioctl_gfeature_invalid)
 	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
 }
 
+/*
+ * Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits
+ * NOTE: This test currently fails because the driver does not validate _IOC_DIR
+ * bits for feature/input/output report ioctls. This should be fixed in the driver.
+ */
+TEST_F(hidraw, ioctl_gfeature_invalid_dir)
+{
+	__u8 buf[10] = {0};
+	int err;
+	unsigned int bad_cmd;
+
+	/* set report ID 1 in first byte */
+	buf[0] = 1;
+
+	/*
+	 * craft an ioctl command with wrong _IOC_DIR bits
+	 * HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE
+	 */
+	bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
+
+	/* try to get feature report with wrong direction bits */
+	err = ioctl(self->hidraw_fd, bad_cmd, buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
+	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
+
+	/* also test with only _IOC_READ */
+	bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
+
+	err = ioctl(self->hidraw_fd, bad_cmd, buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
+	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
+}
+
+/*
+ * Test read-only ioctl with incorrect _IOC_DIR bits
+ */
+TEST_F(hidraw, ioctl_readonly_invalid_dir)
+{
+	char buf[256] = {0};
+	int err;
+	unsigned int bad_cmd;
+
+	/*
+	 * craft an ioctl command with wrong _IOC_DIR bits
+	 * HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE
+	 */
+	bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */
+
+	/* try to get device name with wrong direction bits */
+	err = ioctl(self->hidraw_fd, bad_cmd, buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
+	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
+
+	/* also test with _IOC_WRITE|_IOC_READ */
+	bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */
+
+	err = ioctl(self->hidraw_fd, bad_cmd, buf);
+	ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
+	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
+}
+
 /*
  * Test HIDIOCSFEATURE ioctl to set feature report
  */
-- 
2.50.1

---

Cheers,
Benjamin

^ permalink raw reply related

* Re: [PATCH 4/6] Input: mc13783-pwrbutton: convert members to array
From: Dan Carpenter @ 2025-08-21  7:08 UTC (permalink / raw)
  To: oe-kbuild, Alexander Kurz, Lee Jones, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Dzmitry Sankouski, Dr. David Alan Gilbert, Heiko Stuebner,
	Uwe Kleine-König, devicetree, linux-input
  Cc: lkp, oe-kbuild-all, linux-kernel, Alexander Kurz
In-Reply-To: <20250817102751.29709-5-akurz@blala.de>

Hi Alexander,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Alexander-Kurz/Input-mc13783-pwrbutton-fix-irq-mixup/20250817-182649
base:   https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
patch link:    https://lore.kernel.org/r/20250817102751.29709-5-akurz%40blala.de
patch subject: [PATCH 4/6] Input: mc13783-pwrbutton: convert members to array
config: i386-randconfig-141-20250820 (https://download.01.org/0day-ci/archive/20250821/202508210551.VzAtE5re-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0

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>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202508210551.VzAtE5re-lkp@intel.com/

New smatch warnings:
drivers/input/misc/mc13783-pwrbutton.c:185 mc13783_pwrbutton_probe() error: buffer overflow 'pdata->b_on_key' 3 <= 3

vim +185 drivers/input/misc/mc13783-pwrbutton.c

5298cc4cc753bb Bill Pemberton    2012-11-23  108  static int mc13783_pwrbutton_probe(struct platform_device *pdev)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  109  {
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  110  	const struct mc13xxx_buttons_platform_data *pdata;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  111  	struct mc13xxx *mc13783 = dev_get_drvdata(pdev->dev.parent);
739ba46c8b2ddb Alexander Kurz    2025-08-17  112  	enum mc13xxx_chip_type chip = platform_get_device_id(pdev)->driver_data;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  113  	struct input_dev *pwr;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  114  	struct mc13783_pwrb *priv;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  115  	int err = 0;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  116  	int reg = 0;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  117  
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  118  	pdata = dev_get_platdata(&pdev->dev);
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  119  	if (!pdata) {
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  120  		dev_err(&pdev->dev, "missing platform data\n");
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  121  		return -ENODEV;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  122  	}
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  123  
173a22b931fb1b Alexander Kurz    2025-08-17  124  	pwr = devm_input_allocate_device(&pdev->dev);
173a22b931fb1b Alexander Kurz    2025-08-17  125  	if (!pwr)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  126  		return -ENOMEM;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  127  
173a22b931fb1b Alexander Kurz    2025-08-17  128  	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
173a22b931fb1b Alexander Kurz    2025-08-17  129  	if (!priv)
173a22b931fb1b Alexander Kurz    2025-08-17  130  		return -ENOMEM;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  131  
739ba46c8b2ddb Alexander Kurz    2025-08-17  132  	/* ONOFD3 is only supported for MC13783. */
fc0cc88dc308bc Alexander Kurz    2025-08-17  133  	if (pdata->b_on_flags[2] & MC13783_BUTTON_ENABLE &&
739ba46c8b2ddb Alexander Kurz    2025-08-17  134  		chip != MC13XXX_CHIP_TYPE_MC13783)
739ba46c8b2ddb Alexander Kurz    2025-08-17  135  		return -ENODEV;
739ba46c8b2ddb Alexander Kurz    2025-08-17  136  
fc0cc88dc308bc Alexander Kurz    2025-08-17  137  	reg |= (pdata->b_on_flags[0] & 0x3) << MC13783_POWER_CONTROL_2_ON1BDBNC;
fc0cc88dc308bc Alexander Kurz    2025-08-17  138  	reg |= (pdata->b_on_flags[1] & 0x3) << MC13783_POWER_CONTROL_2_ON2BDBNC;
fc0cc88dc308bc Alexander Kurz    2025-08-17  139  	reg |= (pdata->b_on_flags[2] & 0x3) << MC13783_POWER_CONTROL_2_ON3BDBNC;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  140  
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  141  	priv->pwr = pwr;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  142  	priv->mc13783 = mc13783;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  143  
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  144  	mc13xxx_lock(mc13783);
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  145  
fc0cc88dc308bc Alexander Kurz    2025-08-17  146  	if (pdata->b_on_flags[0] & MC13783_BUTTON_ENABLE) {
fc0cc88dc308bc Alexander Kurz    2025-08-17  147  		priv->keymap[0] = pdata->b_on_key[0];
fc0cc88dc308bc Alexander Kurz    2025-08-17  148  		if (pdata->b_on_key[0] != KEY_RESERVED)
fc0cc88dc308bc Alexander Kurz    2025-08-17  149  			__set_bit(pdata->b_on_key[0], pwr->keybit);
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  150  
fc0cc88dc308bc Alexander Kurz    2025-08-17  151  		if (pdata->b_on_flags[0] & MC13783_BUTTON_POL_INVERT)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  152  			priv->flags |= MC13783_PWRB_B1_POL_INVERT;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  153  
fc0cc88dc308bc Alexander Kurz    2025-08-17  154  		if (pdata->b_on_flags[0] & MC13783_BUTTON_RESET_EN)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  155  			reg |= MC13783_POWER_CONTROL_2_ON1BRSTEN;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  156  
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  157  		err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD1,
4eeb8abb43c2b7 Alexander Kurz    2025-08-17  158  					  button1_irq, "b1on", priv);
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  159  		if (err) {
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  160  			dev_dbg(&pdev->dev, "Can't request irq\n");
173a22b931fb1b Alexander Kurz    2025-08-17  161  			goto free_mc13xxx_lock;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  162  		}
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  163  	}
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  164  
fc0cc88dc308bc Alexander Kurz    2025-08-17  165  	if (pdata->b_on_flags[1] & MC13783_BUTTON_ENABLE) {
fc0cc88dc308bc Alexander Kurz    2025-08-17  166  		priv->keymap[1] = pdata->b_on_key[1];
fc0cc88dc308bc Alexander Kurz    2025-08-17  167  		if (pdata->b_on_key[1] != KEY_RESERVED)
fc0cc88dc308bc Alexander Kurz    2025-08-17  168  			__set_bit(pdata->b_on_key[1], pwr->keybit);
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  169  
fc0cc88dc308bc Alexander Kurz    2025-08-17  170  		if (pdata->b_on_flags[1] & MC13783_BUTTON_POL_INVERT)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  171  			priv->flags |= MC13783_PWRB_B2_POL_INVERT;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  172  
fc0cc88dc308bc Alexander Kurz    2025-08-17  173  		if (pdata->b_on_flags[1] & MC13783_BUTTON_RESET_EN)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  174  			reg |= MC13783_POWER_CONTROL_2_ON2BRSTEN;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  175  
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  176  		err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD2,
4eeb8abb43c2b7 Alexander Kurz    2025-08-17  177  					  button2_irq, "b2on", priv);
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  178  		if (err) {
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  179  			dev_dbg(&pdev->dev, "Can't request irq\n");
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  180  			goto free_irq_b1;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  181  		}
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  182  	}
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  183  
fc0cc88dc308bc Alexander Kurz    2025-08-17  184  	if (pdata->b_on_flags[2] & MC13783_BUTTON_ENABLE) {
fc0cc88dc308bc Alexander Kurz    2025-08-17 @185  		priv->keymap[2] = pdata->b_on_key[3];
                                                                                                  ^
fc0cc88dc308bc Alexander Kurz    2025-08-17  186  		if (pdata->b_on_key[3] != KEY_RESERVED)
                                                                                    ^
fc0cc88dc308bc Alexander Kurz    2025-08-17  187  			__set_bit(pdata->b_on_key[3], pwr->keybit);
                                                                                  ^^^^^^^^^^^^^^^^^^
Smatch thinks there are only 3 elements in this array so it's off by one.

30fc7ac3f62945 Philippe Rétornaz 2011-09-18  188  
fc0cc88dc308bc Alexander Kurz    2025-08-17  189  		if (pdata->b_on_flags[2] & MC13783_BUTTON_POL_INVERT)
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  190  			priv->flags |= MC13783_PWRB_B3_POL_INVERT;
30fc7ac3f62945 Philippe Rétornaz 2011-09-18  191  
fc0cc88dc308bc Alexander Kurz    2025-08-17  192  		if (pdata->b_on_flags[2] & MC13783_BUTTON_RESET_EN)

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


^ permalink raw reply

* Re: [PATCH] HID: tighten ioctl command parsing
From: Benjamin Tissoires @ 2025-08-21  6:56 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Jiri Kosina, Arnd Bergmann, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <20250711072847.2836962-1-arnd@kernel.org>

On Jul 11 2025, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
> 
> The handling for variable-length ioctl commands in hidraw_ioctl() is
> rather complex and the check for the data direction is incomplete.
> 
> Simplify this code using a switch() statement with the size masked
> out, to ensure the rest of the command is correctly matched.
> 
> Fixes: 9188e79ec3fd ("HID: add phys and name ioctls to hidraw")
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
>  drivers/hid/hidraw.c | 123 ++++++++++++++++++-------------------------
>  1 file changed, 50 insertions(+), 73 deletions(-)
> 
> diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
> index c887f48756f4..cc657d60d689 100644
> --- a/drivers/hid/hidraw.c
> +++ b/drivers/hid/hidraw.c
> @@ -403,6 +403,8 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
>  	struct hidraw *dev;
>  	struct hidraw_list *list = file->private_data;
>  	void __user *user_arg = (void __user*) arg;
> +	struct hid_device *hid;
> +	int len;
>  
>  	down_read(&minors_rwsem);
>  	dev = hidraw_table[minor];
> @@ -453,81 +455,56 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
>  				break;
>  			}
>  		default:
> -			{
> -				struct hid_device *hid = dev->hid;
> -				if (_IOC_TYPE(cmd) != 'H') {
> -					ret = -EINVAL;
> -					break;
> -				}
> -
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
> -					int len = _IOC_SIZE(cmd);
> -					ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
> -					break;
> -				}
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
> -					int len = _IOC_SIZE(cmd);
> -					ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
> -					break;
> -				}
> -
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
> -					int len = _IOC_SIZE(cmd);
> -					ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
> -					break;
> -				}
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
> -					int len = _IOC_SIZE(cmd);
> -					ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
> -					break;
> -				}
> -
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
> -					int len = _IOC_SIZE(cmd);
> -					ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
> -					break;
> -				}
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
> -					int len = _IOC_SIZE(cmd);
> -					ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
> -					break;
> -				}
> -
> -				/* Begin Read-only ioctls. */
> -				if (_IOC_DIR(cmd) != _IOC_READ) {
> -					ret = -EINVAL;
> -					break;
> -				}
> -
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
> -					int len = strlen(hid->name) + 1;
> -					if (len > _IOC_SIZE(cmd))
> -						len = _IOC_SIZE(cmd);
> -					ret = copy_to_user(user_arg, hid->name, len) ?
> -						-EFAULT : len;
> -					break;
> -				}
> -
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
> -					int len = strlen(hid->phys) + 1;
> -					if (len > _IOC_SIZE(cmd))
> -						len = _IOC_SIZE(cmd);
> -					ret = copy_to_user(user_arg, hid->phys, len) ?
> -						-EFAULT : len;
> -					break;
> -				}
> -
> -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
> -					int len = strlen(hid->uniq) + 1;
> -					if (len > _IOC_SIZE(cmd))
> -						len = _IOC_SIZE(cmd);
> -					ret = copy_to_user(user_arg, hid->uniq, len) ?
> -						-EFAULT : len;
> -					break;
> -				}
> -			}
> +			break;
> +	}
>  
> +	hid = dev->hid;
> +	switch (cmd & ~IOCSIZE_MASK) {

Jiri pinged me about this one, and I gave it a go with the existing
tests I have in selftests... and turns out that this changes the logic
of the ioctl processing.

The removed block was in the default section of the switch/case
statement. Now it's added *after*, meaning that any ioctl that was
normally processed before are now caught in the default of the switch
statement below and return -ENOTTY.

Running tools/testing/selftests/hid/hid_bpf showed that.

Cheers,
Benjamin

> +	case HIDIOCSFEATURE(0):
> +		len = _IOC_SIZE(cmd);
> +		ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
> +		break;
> +	case HIDIOCGFEATURE(0):
> +		len = _IOC_SIZE(cmd);
> +		ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
> +		break;
> +	case HIDIOCSINPUT(0):
> +		len = _IOC_SIZE(cmd);
> +		ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
> +		break;
> +	case HIDIOCGINPUT(0):
> +		len = _IOC_SIZE(cmd);
> +		ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
> +		break;
> +	case HIDIOCSOUTPUT(0):
> +		len = _IOC_SIZE(cmd);
> +		ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
> +		break;
> +	case HIDIOCGOUTPUT(0):
> +		len = _IOC_SIZE(cmd);
> +		ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
> +		break;
> +	case HIDIOCGRAWNAME(0):
> +		len = strlen(hid->name) + 1;
> +		if (len > _IOC_SIZE(cmd))
> +			len = _IOC_SIZE(cmd);
> +		ret = copy_to_user(user_arg, hid->name, len) ?  -EFAULT : len;
> +		break;
> +	case HIDIOCGRAWPHYS(0):
> +		len = strlen(hid->phys) + 1;
> +		if (len > _IOC_SIZE(cmd))
> +			len = _IOC_SIZE(cmd);
> +		ret = copy_to_user(user_arg, hid->phys, len) ?  -EFAULT : len;
> +		break;
> +	case HIDIOCGRAWUNIQ(0):
> +		len = strlen(hid->uniq) + 1;
> +		if (len > _IOC_SIZE(cmd))
> +			len = _IOC_SIZE(cmd);
> +		ret = copy_to_user(user_arg, hid->uniq, len) ?  -EFAULT : len;
> +		break;
> +	default:
>  		ret = -ENOTTY;
> +		break;
>  	}
>  out:
>  	up_read(&minors_rwsem);
> -- 
> 2.39.5
> 

^ permalink raw reply

* Re: [PATCH v1 10/14] regulator: dt-bindings: Convert Dialog Semiconductor DA9211 Regulators to YAML
From: Krzysztof Kozlowski @ 2025-08-21  6:53 UTC (permalink / raw)
  To: Ariel D'Alessandro
  Cc: airlied, amergnat, andrew+netdev, andrew-ct.chen,
	angelogioacchino.delregno, broonie, chunkuang.hu, ck.hu, conor+dt,
	davem, dmitry.torokhov, edumazet, flora.fu, houlong.wei, jeesw,
	jmassot, kernel, krzk+dt, kuba, kyrie.wu, lgirdwood,
	linus.walleij, louisalexis.eyraud, maarten.lankhorst,
	matthias.bgg, mchehab, minghsiu.tsai, mripard, p.zabel, pabeni,
	robh, sean.wang, simona, support.opensource, tiffany.lin,
	tzimmermann, yunfei.dong, devicetree, dri-devel, linux-arm-kernel,
	linux-clk, linux-gpio, linux-input, linux-kernel, linux-media,
	linux-mediatek, linux-sound, netdev
In-Reply-To: <20250820171302.324142-11-ariel.dalessandro@collabora.com>

On Wed, Aug 20, 2025 at 02:12:58PM -0300, Ariel D'Alessandro wrote:
> Convert the existing text-based DT bindings for Dialog Semiconductor DA9211
> Voltage Regulators family to a YAML schema. Examples are simplified, as
> these are all equal.

Also not wrapped... fix your editor to recognize how commits are
written.

> 
> Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>
> ---

...

> +---
> +$id: http://devicetree.org/schemas/regulator/dlg,da9211.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: |

Drop |

> +  Dialog Semiconductor DA9211/DA9212/DA9213/DA9223/DA9214/DA9224/DA9215/DA9225
> +  Voltage Regulator
> +
> +maintainers:
> +  - Ariel D'Alessandro <ariel.dalessandro@collabora.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - "dlg,da9211"
> +      - "dlg,da9212"
> +      - "dlg,da9213"
> +      - "dlg,da9223"
> +      - "dlg,da9214"
> +      - "dlg,da9224"
> +      - "dlg,da9215"
> +      - "dlg,da9225"

No quotes. I don't think this was ever tested.

Also, keep it properly ordered


> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  regulators:
> +    type: object
> +    additionalProperties: false
> +    description: |

Drop |

> +      List of regulators provided by the device
> +
> +    patternProperties:
> +      "^BUCK([A-B])$":

[AB]

> +        type: object
> +        $ref: regulator.yaml#
> +        description: |
> +          Properties for a single BUCK regulator
> +
> +        properties:
> +          regulator-initial-mode:
> +            items:
> +              enum: [ 1, 2, 3 ]
> +            description: Defined in include/dt-bindings/regulator/dlg,da9211-regulator.h
> +
> +          regulator-allowed-modes:
> +            items:
> +              enum: [ 1, 2, 3 ]
> +            description: Defined in include/dt-bindings/regulator/dlg,da9211-regulator.h
> +
> +          enable-gpios:
> +            maxItems: 1
> +            description: Specify a valid GPIO for platform control of the regulator

Drop description, obvious.

> +
> +        unevaluatedProperties: false

For nested blocks this goes after $ref: regulator.

> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - regulators
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/regulator/dlg,da9211-regulator.h>
> +
> +    i2c1 {

i2c

> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        da9212: da9212@68 {

Node names should be generic. See also an explanation and list of
examples (not exhaustive) in DT specification:
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation


Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v1 07/14] dt-bindings: display: mediatek,ufoe: Add mediatek,gce-client-reg property
From: Krzysztof Kozlowski @ 2025-08-21  6:50 UTC (permalink / raw)
  To: Ariel D'Alessandro
  Cc: airlied, amergnat, andrew+netdev, andrew-ct.chen,
	angelogioacchino.delregno, broonie, chunkuang.hu, ck.hu, conor+dt,
	davem, dmitry.torokhov, edumazet, flora.fu, houlong.wei, jeesw,
	jmassot, kernel, krzk+dt, kuba, kyrie.wu, lgirdwood,
	linus.walleij, louisalexis.eyraud, maarten.lankhorst,
	matthias.bgg, mchehab, minghsiu.tsai, mripard, p.zabel, pabeni,
	robh, sean.wang, simona, support.opensource, tiffany.lin,
	tzimmermann, yunfei.dong, devicetree, dri-devel, linux-arm-kernel,
	linux-clk, linux-gpio, linux-input, linux-kernel, linux-media,
	linux-mediatek, linux-sound, netdev
In-Reply-To: <20250820171302.324142-8-ariel.dalessandro@collabora.com>

On Wed, Aug 20, 2025 at 02:12:55PM -0300, Ariel D'Alessandro wrote:
> Current, the DT bindings for Mediatek UFOe (Unified Frame Optimization
> engine) is missing the mediatek,gce-client-reg property. Add it and

Why is it missing? If the binding is complete, it cannot be missing...

> update the example as well.
> 
> Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>
> ---
>  .../bindings/display/mediatek/mediatek,ufoe.yaml      | 11 +++++++++++
>  1 file changed, 11 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,ufoe.yaml b/Documentation/devicetree/bindings/display/mediatek/mediatek,ufoe.yaml
> index 61a5e22effbf2..ecb4c0359fec3 100644
> --- a/Documentation/devicetree/bindings/display/mediatek/mediatek,ufoe.yaml
> +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,ufoe.yaml
> @@ -64,6 +64,14 @@ properties:
>        - port@0
>        - port@1
>  
> +  mediatek,gce-client-reg:
> +    description: The register of client driver can be configured by gce with
> +      4 arguments defined in this property, such as phandle of gce, subsys id,
> +      register offset and size. Each GCE subsys id is mapping to a client

Don't explain what DT syntax is. We all know, so that's completely
redundant description. Explain the purpose. Explain Arguments with sechema - items.

Best regards,
Krzysztof


^ 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