Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH v7 0/4] Input: Add support for TouchNetix aXiom touchscreen
@ 2026-07-02 22:32 Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error Marco Felsch
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Marco Felsch @ 2026-07-02 22:32 UTC (permalink / raw)
  To: andrew.thomas, 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,
	Mamta Shukla, 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) updates
 - Touchcontroller config (TH2CFGBIN) updates
 - Poll or IRQ support

Many thanks for Dmitry's input on my v5. I included all changes
requested from him.

Regards,
  Marco

Changes in v7:
- Link to v6: https://lore.kernel.org/all/20260303-v6-10-topic-touchscreen-axiom-v6-0-8ac755add12b@pengutronix.de/
- Rebased on top of 7.2-rc1
- Fix U33 rev.6 usage (Mamta)
- Fix bootloader mode handling, required to disable the IRQ (Andrew)
- Fix bug in usage table population which led to a nullptr bug (Andrew)

Changes in v6:
- Link to v5: https://lore.kernel.org/r/20260111-v6-10-topic-touchscreen-axiom-v5-0-f94e0ae266cb@pengutronix.de
- Fix update POLL mode
- Fix max. input values
- Resolve some "TODO" and "Downstream" comments (Andrew)
- Add 0d, xl fw_variant (Andrew)
- Use 2sec timeout for u02 swreset (Andrew)
- Fix IRQ-runmode input-device registration after FW update
- Drop legacy ALC firmware support, since no fw sanity check could be performed
  which is bad for user experience if they provided the wrong file accidentally.
- drop BLP_PRE_MODE
- make AXIOM_U31 rev.1 required
- axiom_usage_supported: drop iter and instead use new usage_table_by_baseaddr
- axiom_axfw_fw_write: drop goto error handling
- adapt comments
- rework axiom_i2c_probe (Dmitry)
- rework axiom_power_*_device and axiom_parse_firmware (Dmitry)
- rework axiom_register_input_dev (Dmitry)
- rework axiom_register_fwl (Dmitry)
- rework axiom_cfg_fw_cancel (Dmitry)
- rework axiom_cfg_fw_write (Dmitry)
- rework axiom_verify_volatile_mem (Dmitry)
- rework axiom_write_cfg_chunk (Dmitry)
- rework axiom_cfg_fw_prepare (Dmitry)
- rework axiom_axfw_fw_* functions (Dmitry)
- rework axiom_blp_reset (Dmitry)
- rework axiom_blp_write_chunk (Dmitry)
- rework axiom_enter_bootloader_mode (Dmitry)
- rework axiom_axfw_fw_prepare (Dmitry)
- rework regmap_read/write (Dmitry)
- rework axiom_u34_rev1_process_report (Dmitry)
- rework axiom_cdu_wait_idle (Dmitry)
- rework axiom_u64_cds_enabled (Dmitry)
- rework axiom_u42_get_touchslots (Dmitry)
- rework axiom_u33_read (s/ret/error/) (Dmitry)
- rework axiom_u31_device_discover (Dmitry)
- simple s/ret/error/ (Dmitry)
- rework u04 handling (Dmitry)
- rework u02 handling (Dmitry)
- align function name accordingly (Dmitry)
- fix indentation (Dmitry)

Changes in v5:
- Link to v4: https://lore.kernel.org/r/20260106-v6-10-topic-touchscreen-axiom-v4-0-9e9b69c84926@pengutronix.de
- fix sysfs documentation description indentation and date

Changes in v4:
- Link to v3: https://lore.kernel.org/r/20250821-v6-10-topic-touchscreen-axiom-v3-0-940ccee6dba3@pengutronix.de
- rebased on top of v6.19-rc1
- collect r-b tags

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    |   80 +
 .../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       | 3141 ++++++++++++++++++++
 include/linux/firmware.h                           |    2 +
 lib/test_firmware.c                                |    1 +
 9 files changed, 3307 insertions(+)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20240704-v6-10-topic-touchscreen-axiom-105761e81011

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


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

* [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error
  2026-07-02 22:32 [PATCH v7 0/4] Input: Add support for TouchNetix aXiom touchscreen Marco Felsch
@ 2026-07-02 22:32 ` Marco Felsch
  2026-07-02 22:45   ` sashiko-bot
  2026-07-02 22:32 ` [PATCH v7 2/4] dt-bindings: vendor-prefixes: Add TouchNetix AS Marco Felsch
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Marco Felsch @ 2026-07-02 22:32 UTC (permalink / raw)
  To: andrew.thomas, 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

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.

Reviewed-by: Russ Weight <russ.weight@linux.dev>
Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
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 efc33294212fd82bb1a8c426d7430ef96c6620b2..82c4b1fedf4cdd0b41fd9acb0cfc1688d726a6fd 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 0fa3b027f02f16ffc4a28d4209c3af9319dd8bea..86a2f010e0c1e833d8456cadf21743b9f86dbde5 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 7459bba65444d1d153667ab3fc447c375418e938..a822d9641eb4c4db01d9966f76f080ede31027f3 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -1134,6 +1134,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.47.3


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

* [PATCH v7 2/4] dt-bindings: vendor-prefixes: Add TouchNetix AS
  2026-07-02 22:32 [PATCH v7 0/4] Input: Add support for TouchNetix aXiom touchscreen Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error Marco Felsch
@ 2026-07-02 22:32 ` Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 3/4] dt-bindings: input: Add TouchNetix axiom touchscreen Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support Marco Felsch
  3 siblings, 0 replies; 8+ messages in thread
From: Marco Felsch @ 2026-07-02 22:32 UTC (permalink / raw)
  To: andrew.thomas, 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

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 396044f368e7cf0ca1436713ae44f1950259f006..b8450050d7299cea2b5cf6f57fe71f5ead1140c8 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1715,6 +1715,8 @@ patternProperties:
     description: Toradex AG
   "^toshiba,.*":
     description: Toshiba Corporation
+  "^touchnetix,.*":
+    description: TouchNetix AS
   "^toumaz,.*":
     description: Toumaz
   "^tpk,.*":

-- 
2.47.3


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

* [PATCH v7 3/4] dt-bindings: input: Add TouchNetix axiom touchscreen
  2026-07-02 22:32 [PATCH v7 0/4] Input: Add support for TouchNetix aXiom touchscreen Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 2/4] dt-bindings: vendor-prefixes: Add TouchNetix AS Marco Felsch
@ 2026-07-02 22:32 ` Marco Felsch
  2026-07-02 22:32 ` [PATCH v7 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support Marco Felsch
  3 siblings, 0 replies; 8+ messages in thread
From: Marco Felsch @ 2026-07-02 22:32 UTC (permalink / raw)
  To: andrew.thomas, 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

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>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
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.47.3


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

* [PATCH v7 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support
  2026-07-02 22:32 [PATCH v7 0/4] Input: Add support for TouchNetix aXiom touchscreen Marco Felsch
                   ` (2 preceding siblings ...)
  2026-07-02 22:32 ` [PATCH v7 3/4] dt-bindings: input: Add TouchNetix axiom touchscreen Marco Felsch
@ 2026-07-02 22:32 ` Marco Felsch
  2026-07-02 22:48   ` sashiko-bot
  3 siblings, 1 reply; 8+ messages in thread
From: Marco Felsch @ 2026-07-02 22:32 UTC (permalink / raw)
  To: andrew.thomas, 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, Mamta Shukla,
	Marco Felsch

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)
update the other handles the touchcontroller configuration (TH2CFGBIN)
update.

Co-developed-by: Mamta Shukla <mamta.shukla@leica-geosystems.com>
Signed-off-by: Mamta Shukla <mamta.shukla@leica-geosystems.com>
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 .../testing/sysfs-driver-input-touchnetix-axiom    |   80 +
 drivers/input/touchscreen/Kconfig                  |   17 +
 drivers/input/touchscreen/Makefile                 |    1 +
 drivers/input/touchscreen/touchnetix_axiom.c       | 3141 ++++++++++++++++++++
 4 files changed, 3239 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..8262673630557bf1e595a97ec23e66c1c5370f71
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
@@ -0,0 +1,80 @@
+What:		/sys/bus/i2c/devices/xxx/fw_major
+Date:		Jan 2026
+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:		Jan 2026
+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:		Jan 2026
+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:		Jan 2026
+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:		Jan 2026
+Contact:	linux-input@vger.kernel.org
+Description:
+		Reports the firmware variant provided by the touchscreen. It may
+		be either: "0d", "2d", "3d", "force", "xl" or "unknown".
+
+		Access: Read
+
+		Valid values: Represented as string
+
+What:		/sys/bus/i2c/devices/xxx/device_id
+Date:		Jan 2026
+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:		Jan 2026
+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: 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 9b9ae8ac3f7fd3b9e948083fcd05afabdcd69bca..399db93c337de61a0d759fd667de1fffa10aacb4 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -828,6 +828,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 bfd9de83389d8833dbe7cc2641cb24493cdf3807..0d139fff701aa6466d2ecd6f6b27fcc8006a0575 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..b945ccda326bdd726420e2a26021ba8529d6e8b8
--- /dev/null
+++ b/drivers/input/touchscreen/touchnetix_axiom.c
@@ -0,0 +1,3141 @@
+// 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_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;
+};
+
+struct axiom_u33_rev6 {
+	__le32 runtime_crc;
+	__le32 runtime_nvm_crc;
+	__le32 bootloader_crc;
+	__le32 nvltlusageconfig_crc;
+	__le32 vltusageconfig_crc;
+	__le32 reserved[3];
+	__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 */
+#define AXIOM_MAX_BASEADDR			256
+/*
+ * 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_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;
+	/* Used for access by usage number */
+	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];
+	/* Used for access by usage base address */
+	struct axiom_usage_table_entry *usage_table_by_baseaddr[AXIOM_MAX_BASEADDR];
+};
+
+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_U31, 1),
+	AXIOM_RO_USAGE(AXIOM_U33, 2),
+	AXIOM_RO_USAGE(AXIOM_U33, 3),
+	AXIOM_RO_USAGE(AXIOM_U33, 6),
+	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_REPORT_USAGE(AXIOM_U41, 5, axiom_u41_rev2_process_report),
+	AXIOM_USAGE(AXIOM_U42, 1),
+	AXIOM_USAGE(AXIOM_U42, 4),
+	AXIOM_USAGE(AXIOM_U42, 7),
+	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_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_enable_irq(struct axiom_data *ts)
+{
+	struct i2c_client *client = to_i2c_client(ts->dev);
+
+	if (client->irq && ts->irq_setup_done)
+		enable_irq(client->irq);
+}
+
+static void axiom_disable_irq(struct axiom_data *ts)
+{
+	struct i2c_client *client = to_i2c_client(ts->dev);
+
+	if (client->irq && ts->irq_setup_done)
+		disable_irq(client->irq);
+}
+
+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_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_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 regaddr)
+{
+	struct axiom_usage_table_entry *entry;
+	struct device *dev = ts->dev;
+	unsigned char baseaddr;
+
+	if (axiom_skip_usage_check(ts))
+		return true;
+
+	baseaddr = FIELD_GET(AXIOM_USAGE_BASEADDR_MASK, regaddr);
+	entry = ts->usage_table_by_baseaddr[baseaddr];
+
+	dev_dbg(dev, "Checking support for i2c-reg:%#04x (reg-baseaddr:%#02x)\n",
+		regaddr, baseaddr);
+
+	/*
+	 * Ensure that no one messed with the driver e.g.:
+	 * - by not retrieving the baseaddr properly, or
+	 * - by making use of an usage without adding it to
+	 *   driver_required_usages[].
+	 */
+	if (!entry) {
+		dev_warn(dev, "Device doesn't support this baseaddr, driver bug!\n");
+		return false;
+	}
+
+	if (!entry->info) {
+		dev_warn(dev, "Driver doesn't support this usage, driver bug!\n");
+		return false;
+	}
+
+	return true;
+}
+
+static int axiom_process_report(struct axiom_data *ts, unsigned char usage_num,
+				const u8 *buf, size_t buflen);
+
+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. Poll via axiom_process_report()
+	 * and without the help of axiom_poll() since the input device may not
+	 * be available yet.
+	 */
+	do {
+		axiom_process_report(ts, AXIOM_U34, NULL, 0);
+		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 error, 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.
+	 */
+	error = 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 (error) {
+		dev_err(ts->dev, "Poll u02 timedout with: %#x\n", cmd);
+		return error;
+	}
+
+	return 0;
+}
+
+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 error;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U02);
+	reg += AXIOM_U02_REV1_COMMAND_REG;
+
+	error = regmap_raw_write(ts->regmap, reg, msg, sizeof(*msg));
+	if (error)
+		return error;
+
+	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 error;
+
+	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);
+
+	error = axiom_u02_send_msg(ts, &msg, false);
+	if (error)
+		return error;
+
+	/* Downstream axcfg.py waits for 2sec without checking U01 response */
+	if (!axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
+					       msecs_to_jiffies(2 * MSEC_PER_SEC))) {
+		dev_err(ts->dev, "Error save volatile config timedout\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int axiom_u02_swreset(struct axiom_data *ts)
+{
+	struct axiom_u02_rev1_system_manager_msg msg = {
+		.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SOFTRESET),
+	};
+	int error;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	error = axiom_u02_send_msg(ts, &msg, false);
+	if (error)
+		return error;
+
+	/*
+	 * Downstream axcfg.py waits for 1sec without checking U01 HELLO. Tests
+	 * showed that waiting for the HELLO message isn't enough therefore we
+	 * need to add the additional fsleep(1sec).
+	 * Touchnetix said that the boot can take up to 2sec if all self tests
+	 * are enabled, so wait 2sec for the HELLO message.
+	 */
+	if (!axiom_wait_for_completion_timeout(ts, &ts->boot_complete,
+					       msecs_to_jiffies(2 * MSEC_PER_SEC))) {
+		dev_err(ts->dev, "Error swreset timedout\n");
+		error = -ETIMEDOUT;
+	}
+
+	fsleep(USEC_PER_SEC);
+
+	return error;
+}
+
+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 error;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+		return -EINVAL;
+
+	msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_ENTERBOOTLOADER);
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1);
+	error = axiom_u02_send_msg(ts, &msg, true);
+	if (error) {
+		dev_err(dev, "Failed to send bootloader-key1: %d\n", error);
+		return error;
+	}
+
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2);
+	error = axiom_u02_send_msg(ts, &msg, true);
+	if (error) {
+		dev_err(dev, "Failed to send bootloader-key2: %d\n", error);
+		return error;
+	}
+
+	/* No need to wait since we wait afterwards anyway */
+	msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3);
+	error = axiom_u02_send_msg(ts, &msg, false);
+	if (error) {
+		dev_err(dev, "Failed to send bootloader-key3: %d\n", error);
+		return error;
+	}
+
+	/* 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 */
+	error = 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);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static u8 *axiom_u04_get(struct axiom_data *ts)
+{
+	unsigned int bufsize;
+	unsigned int reg;
+	int error;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U04))
+		return ERR_PTR(-EINVAL);
+
+	bufsize = axiom_usage_size(ts, AXIOM_U04);
+	u8 *buf __free(kfree) = kzalloc(bufsize, GFP_KERNEL);
+	if (!buf)
+		return ERR_PTR(-ENOMEM);
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U04);
+	error = regmap_raw_read(ts->regmap, reg, buf, bufsize);
+	if (error)
+		return ERR_PTR(error);
+
+	return_ptr(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;
+
+	bufsize = axiom_usage_size(ts, AXIOM_U04);
+	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 error;
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &id_high);
+	if (error)
+		return error;
+	id_high = FIELD_GET(AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK, id_high);
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_LOW_REG, &id_low);
+	if (error)
+		return error;
+	ts->device_id = id_high << 8 | id_low;
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG, &val);
+	if (error)
+		return error;
+	ts->fw_major = val;
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MIN_REG, &val);
+	if (error)
+		return error;
+	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;
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_RC_REG, &val);
+	if (error)
+		return error;
+	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);
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG, &val);
+	if (error)
+		return error;
+	ts->fw_status = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_STATUS, val);
+	ts->fw_variant = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_VARIANT, val);
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_HIGH_REG, &val);
+	if (error)
+		return error;
+	ts->jedec_id = val << 8;
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_LOW_REG, &val);
+	if (error)
+		return error;
+	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 *entry;
+	struct regmap *regmap = ts->regmap;
+	unsigned int mode, num_usages;
+	struct device *dev = ts->dev;
+	unsigned int i;
+	int error;
+
+	axiom_set_runmode(ts, AXIOM_DISCOVERY_MODE);
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &mode);
+	if (error) {
+		dev_err(dev, "Failed to read MODE\n");
+		return error;
+	}
+
+	mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
+	if (mode == AXIOM_U31_REV1_MODE_BLP)
+		axiom_set_runmode(ts, AXIOM_BLP_MODE);
+
+	error = axiom_u31_parse_device_info(ts);
+	if (error) {
+		dev_err(dev, "Failed to parse device info\n");
+		return error;
+	}
+
+	/* 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;
+	}
+
+	error = regmap_read(regmap, AXIOM_U31_REV1_NUM_USAGES_REG, &num_usages);
+	if (error) {
+		dev_err(dev, "Failed to read NUM_USAGES\n");
+		return error;
+	}
+
+	struct axiom_u31_usage_table_entry *u31_usage_table __free(kfree) =
+		kcalloc(num_usages, sizeof(*u31_usage_table), GFP_KERNEL);
+	if (!u31_usage_table)
+		return -ENOMEM;
+
+	error = regmap_raw_read(regmap, AXIOM_U31_REV1_PAGE1, u31_usage_table,
+				array_size(num_usages, sizeof(*u31_usage_table)));
+	if (error) {
+		dev_err(dev, "Failed to read NUM_USAGES\n");
+		return error;
+	}
+
+	/*
+	 * 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));
+	memset(ts->usage_table_by_baseaddr, 0, sizeof(ts->usage_table_by_baseaddr));
+
+	for (i = 0, entry = u31_usage_table; i < num_usages; i++, entry++) {
+		unsigned char idx = entry->usage_num;
+		const struct axiom_usage_info *info;
+		bool skip_usage_add = false;
+		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 but skip adding it to the 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);
+			error = -EACCES;
+			skip_usage_add = true;
+		}
+
+		if (skip_usage_add)
+			continue;
+
+		size_bytes = axiom_get_usage_size_bytes(entry);
+
+		ts->usage_table[idx].baseaddr =
+			FIELD_PREP(AXIOM_USAGE_BASEADDR_MASK, entry->start_page);
+		ts->usage_table[idx].size_bytes = size_bytes;
+		ts->usage_table[idx].populated = true;
+		ts->usage_table[idx].info = info;
+		/* Reports are indirectly accessed, no baseaddr */
+		if (!axiom_usage_entry_is_report(entry))
+			ts->usage_table_by_baseaddr[entry->start_page] =
+							  &ts->usage_table[idx];
+
+		if (axiom_usage_entry_is_report(entry) &&
+		    ts->max_report_byte_len < size_bytes)
+			ts->max_report_byte_len = size_bytes;
+	}
+
+	/*
+	 * Each usage support up to 256-byte. The 257-byte would point to the
+	 * next usage.
+	 */
+	if (ts->max_report_byte_len > AXIOM_PAGE_BYTE_LEN) {
+		dev_err(dev, "Invalid report length (%u-byte) detected\n",
+			ts->max_report_byte_len);
+		error = -EACCES;
+	}
+
+	if (error)
+		return error;
+
+	/* From now on we are in TCP mode to include usage revision checks */
+	axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+	error = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc)
+{
+	struct device *dev = ts->dev;
+	unsigned int reg;
+	int error;
+
+	if (!axiom_driver_supports_usage(ts, AXIOM_U33))
+		return -EINVAL;
+
+	/* Reset previous stored values */
+	memset(crc, 0, sizeof(*crc));
+
+	if (axiom_usage_rev(ts, AXIOM_U33) == 2) {
+		struct axiom_u33_rev2 val;
+
+		reg = axiom_usage_baseaddr(ts, AXIOM_U33);
+		error = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+		if (error) {
+			dev_err(dev, "Failed to read u33\n");
+			return error;
+		}
+
+		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);
+		error = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+		if (error) {
+			dev_err(dev, "Failed to read u33\n");
+			return error;
+		}
+
+		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);
+	} else if (axiom_usage_rev(ts, AXIOM_U33) == 6) {
+		struct axiom_u33_rev6 val;
+
+		reg = axiom_usage_baseaddr(ts, AXIOM_U33);
+		error = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+		if (error) {
+			dev_err(dev, "Failed to read u33\n");
+			return error;
+		}
+
+		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->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;
+	case 7:
+		return buf[AXIOM_U42_REV4_REPORT_ID_CONTAINS(touch_num)] ==
+		       AXIOM_U42_REV4_REPORT_ID_TOUCH;
+	default:
+		/* Should never happen */
+		return false;
+	}
+}
+
+static bool axiom_u42_get_touchslots(struct axiom_data *ts)
+{
+	unsigned int bufsize;
+	unsigned int reg;
+	int error, i;
+
+	bufsize = axiom_usage_size(ts, AXIOM_U42);
+	u8 *buf __free(kfree) = kzalloc(bufsize, GFP_KERNEL);
+	if (!buf)
+		return false;
+
+	reg = axiom_usage_baseaddr(ts, AXIOM_U42);
+	error = regmap_raw_read(ts->regmap, reg, buf, bufsize);
+	if (error) {
+		dev_warn(ts->dev, "Failed to read u42\n");
+		return false;
+	}
+
+	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 true;
+}
+
+static void axiom_get_touchslots(struct axiom_data *ts)
+{
+	if (!axiom_driver_supports_usage(ts, AXIOM_U42) ||
+	    !axiom_u42_get_touchslots(ts)) {
+		dev_warn(ts->dev, "Use default touchslots num\n");
+		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 error;
+
+	ts->cds_enabled = false;
+
+	if (axiom_driver_supports_usage(ts, AXIOM_U64)) {
+		reg = axiom_usage_baseaddr(ts, AXIOM_U64);
+		reg += AXIOM_U64_REV2_ENABLECDSPROCESSING_REG;
+
+		error = regmap_read(ts->regmap, reg, &val);
+		if (!error) {
+			val = FIELD_GET(AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK, val);
+			ts->cds_enabled = val ? true : false;
+		}
+	}
+}
+
+static int axiom_cdu_wait_idle(struct axiom_data *ts, u8 cdu_usage_num)
+{
+	unsigned int reg;
+	int error, 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.
+	 */
+	error = 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 (error) {
+		dev_err(ts->dev, "Poll CDU u%02X timedout with: %#x\n",
+			cdu_usage_num, cmd);
+		return error;
+	}
+
+	return 0;
+}
+
+/*********************** 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 error;
+
+	error = regmap_raw_read(regmap, reg, buf, ts->max_report_byte_len);
+	if (error)
+		return error;
+
+	/* TODO: Add overflow statistics */
+
+	/* REPORTLENGTH is in uint16 */
+	len = FIELD_GET(AXIOM_U34_REV1_REPORTLENGTH_MASK, buf[0]);
+	len *= 2;
+
+	/*
+	 * The device keeps the IRQ asserted till the I2C-STOP signal was
+	 * received and optional longer (up to 40us). This can trigger the ISR
+	 * a 2nd time albeit the device data is not ready yet. In such case the
+	 * device sends 0-length reports. Don't treat this as error.
+	 */
+	if (len == 0) {
+		dev_dbg_ratelimited(dev, "0-length report received\n");
+		return 0;
+	}
+
+	/*
+	 * 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:
+		/*
+		 * axiom_driver_supports_usage() is not required since the
+		 * correct .process_report() hooks are assigned during the
+		 * discovery.
+		 */
+		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;
+	struct i2c_msg xfer[2];
+	u16 xferlen, addr;
+	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);
+
+	if (!axiom_usage_supported(ts, addr))
+		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, ARRAY_SIZE(xfer));
+	if (likely(ret == ARRAY_SIZE(xfer)))
+		return 0;
+
+	return ret < 0 ? ret : -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);
+	struct axiom_cmd_header hdr;
+	size_t val_size, msg_size;
+	u16 xferlen, addr;
+	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);
+
+	if (!axiom_usage_supported(ts, addr))
+		return -EINVAL;
+
+	msg_size = sizeof(hdr) + val_size;
+	u8 *buf __free(kfree) = 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);
+	if (likely(ret == msg_size))
+		return 0;
+
+	return ret < 0 ? ret : -EIO;
+}
+
+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 axiom_data *ts,
+						  struct axiom_firmware *afw,
+						  const u8 *data, u32 size)
+{
+	u8 major_ver, minor_ver, rc_ver, status, variant;
+	u32 fw_file_crc32, crc32_calc;
+	struct device *dev = ts->dev;
+	unsigned int signature_len;
+	u16 fw_file_format_ver;
+	u16 fw_file_device_id;
+
+	if (size < sizeof(struct axiom_fw_axfw_hdr)) {
+		dev_err(dev, "Invalid AXFW file size\n");
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+	}
+
+	signature_len = strlen(AXIOM_FW_AXFW_SIGNATURE);
+	if (strncmp(data, AXIOM_FW_AXFW_SIGNATURE, signature_len)) {
+		dev_err(dev, "No AXFW signature, abort firmware update\n");
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	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);
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	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);
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	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);
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	/*
+	 * The afterward fw duplication check requires that the devices
+	 * discovery ran successfully at least once (-> device is in TCP mode).
+	 * This is not always ensured because:
+	 *  * The usage discovery failed either during the probe or after the
+	 *    firmware update.
+	 *  * The device came up in bootloader mode because no valid firmware
+	 *    was found by the bootloader.
+	 *  * Downloading the firmware failed in between and the device is still
+	 *    in bootloader mode, or
+	 */
+	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) {
+		return FW_UPLOAD_ERR_DUPLICATE;
+	}
+
+	dev_info(dev, "Detected AXFW %02u.%02u.%02u (%s)\n",
+		 major_ver, minor_ver, rc_ver,
+		 status ? "production" : "engineering");
+
+	guard(mutex)(&afw->lock);
+	return afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
+}
+
+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];
+	enum fw_upload_err ret;
+
+	scoped_guard(mutex, &afw->lock) {
+		afw->cancel = false;
+	}
+
+	mutex_lock(&ts->fwupdate_lock);
+
+	ret = __axiom_axfw_fw_prepare(ts, afw, data, size);
+
+	/*
+	 * 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 error;
+
+	error = axiom_u02_wait_idle(ts);
+	if (error)
+		goto err_out;
+
+	error = axiom_u02_enter_bootloader(ts);
+	if (error) {
+		dev_err(dev, "Failed to enter bootloader mode\n");
+		goto err_out;
+	}
+
+	axiom_set_runmode(ts, AXIOM_BLP_MODE);
+	return 0;
+
+err_out:
+	return error;
+}
+
+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 error;
+
+	error = axoim_blp_wait_ready(ts);
+	if (error)
+		return error;
+
+	/*
+	 * 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;
+
+		error = regmap_raw_write(ts->regmap, reg, &data[pos], len);
+		if (error) {
+			dev_err(dev, "Bootloader download AXFW chunk failed %d\n", error);
+			return error;
+		}
+
+		pos += len;
+		error = axoim_blp_wait_ready(ts);
+		if (error)
+			return error;
+	}
+
+	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 error;
+
+	error = axoim_blp_wait_ready(ts);
+	if (error)
+		return error;
+
+	/*
+	 * 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 {
+		error = regmap_read(ts->regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG,
+				    &mode);
+		if (!error)
+			break;
+
+		fsleep(250 * USEC_PER_MSEC);
+	} while (attempts--);
+
+	if (error) {
+		dev_err(dev, "Failed to read MODE after BLP reset: %d\n", error);
+		return error;
+	}
+
+	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;
+	}
+
+	axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+	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_unlock_input_return_hw_error(struct axiom_data *ts)
+{
+	axiom_unlock_input_device(ts);
+	return FW_UPLOAD_ERR_HW_ERROR;
+}
+
+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;
+	int error;
+
+	/*
+	 * According Touchnetix the IRQ pin gets reconfigured in bootloader
+	 * mode. Which can cause spurious IRQs. Therefore the IRQ should be
+	 * disabled on the host.
+	 *
+	 * See the cleanup routine for the balanced enable.
+	 */
+	if (axiom_get_runmode(ts) != AXIOM_BLP_MODE)
+		axiom_disable_irq(ts);
+
+	/* Done before cancel check due to cleanup based put */
+	error = pm_runtime_resume_and_get(ts->dev);
+	if (error)
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	scoped_guard(mutex, &afw->lock) {
+		if (afw->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 update\n");
+		return axiom_unlock_input_return_hw_error(ts);
+	}
+
+	/* 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_get_runmode(ts) != AXIOM_BLP_MODE) {
+		error = axiom_enter_bootloader_mode(ts);
+		if (error)
+			return axiom_unlock_input_return_hw_error(ts);
+	}
+
+	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.
+		 */
+		error = axiom_blp_write_chunk(ts, data, len);
+		if (error) {
+			/*
+			 * Tests showed that the bootloader mode must be exited
+			 * if an invalid chunk was received by the bootloader fw
+			 * since all following attempts to download valid chunks
+			 * will fail. Try do so via axiom_blp_reset() but tests
+			 * also showed that this may fail too. So inform the
+			 * user and hope that the full power-cycle helps. To get
+			 * the device back into a working mode where the device
+			 * accepts data again.
+			 */
+			if (axiom_blp_reset(ts))
+				dev_warn(dev, "Couldn't recover device, device requires power-cycle\n");
+			return axiom_unlock_input_return_hw_error(ts);
+		}
+
+		size -= len;
+		*written += len;
+		data += len;
+	}
+
+	error = axiom_blp_reset(ts);
+	if (error) {
+		dev_err(dev, "BLP reset failed\n");
+		return axiom_unlock_input_return_hw_error(ts);
+	}
+
+	error = axiom_u31_device_discover(ts);
+	/* Unlock before the input device gets unregistered/updated */
+	axiom_unlock_input_device(ts);
+	if (error) {
+		/*
+		 * This is critical and we need to avoid that the user-space can
+		 * still use the input-dev.
+		 */
+		axiom_unregister_input_dev(ts);
+		dev_err(dev, "Device discovery failed after AXFW firmware update\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	error = axiom_update_input_dev(ts);
+	if (error) {
+		dev_err(dev, "Input device update failed after AXFW firmware update\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	dev_info(dev, "AXFW update successful\n");
+
+	return FW_UPLOAD_ERR_NONE;
+}
+
+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];
+
+	guard(mutex)(&afw->lock);
+	afw->cancel = true;
+}
+
+static void axiom_axfw_fw_handle_irq(struct axiom_data *ts)
+{
+	struct i2c_client *client = to_i2c_client(ts->dev);
+	struct irq_data *data;
+
+	if (!ts->irq_setup_done)
+		return;
+
+	data = irq_get_irq_data(client->irq);
+	/*
+	 * The cleanup is called in both cases: success and failure. In case
+	 * of failure the device may still be in bootloader mode. Don't enable
+	 * the IRQ in such case to avoid a possible IRQ storm because of the
+	 * unknown IRQ pin behavior in bootloader mode.
+	 *
+	 * Furthermore if the IRQ is enabled at this point this means that the
+	 * firmware update was necessary to get the device in a working state
+	 * because the probe() aborted due to firmware incompatibilities. Skip
+	 * the enable step if the IRQ is not disabled to not mess with the
+	 * refcount.
+	 */
+	if (irqd_irq_disabled(data) &&
+	    axiom_get_runmode(ts) != AXIOM_BLP_MODE)
+		axiom_enable_irq(ts);
+}
+
+static void axiom_axfw_fw_cleanup(struct fw_upload *fw_upload)
+{
+	struct axiom_data *ts = fw_upload->dd_handle;
+
+	mutex_unlock(&ts->fwupdate_lock);
+	axiom_axfw_fw_handle_irq(ts);
+	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;
+	unsigned int i;
+
+	/*
+	 * Provide some debug output of u33, because the th2cfg chunks caused
+	 * a few headaches already.
+	 */
+	for (i = 0; i < cfg->usage_length / sizeof(u32); i++)
+		dev_dbg(ts->dev, "u33 th2cfgbin chunk[%u]:%#x\n", i,
+			get_unaligned_le32(&u33_data[i]));
+
+	switch (cfg->usage_rev) {
+	case 2:
+		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]);
+		crc->u93_profiles = get_unaligned_le32(&u33_data[7]);
+		crc->u94_deltascalemap = get_unaligned_le32(&u33_data[8]);
+		return 0;
+	case 3:
+		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]);
+		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;
+	case 6:
+		crc->runtime = get_unaligned_le32(u33_data);
+		crc->nvltlusageconfig = get_unaligned_le32(&u33_data[3]);
+		crc->vltusageconfig = get_unaligned_le32(&u33_data[4]);
+		crc->u93_profiles = get_unaligned_le32(&u33_data[8]);
+		crc->u94_deltascalemap = get_unaligned_le32(&u33_data[9]);
+		return 0;
+	default:
+		dev_err(ts->dev, "The driver doesn't support u33 revision %u\n",
+			cfg->usage_rev);
+		return -EINVAL;
+	}
+}
+
+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 axiom_data *ts,
+						 struct axiom_firmware *afw,
+						 const u8 *data, u32 size)
+{
+	u32 cur_runtime_crc, fw_runtime_crc;
+	struct axiom_fw_cfg_chunk chunk;
+	struct device *dev = ts->dev;
+	u32 signature;
+	int error;
+
+	if (axiom_get_runmode(ts) != AXIOM_TCP_MODE) {
+		dev_err(dev, "Device not in TCP mode, abort TH2CFG update\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	if (size < sizeof(struct axiom_fw_cfg_hdr)) {
+		dev_err(dev, "Invalid TH2CFG file size\n");
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+	}
+
+	signature = get_unaligned_be32(data);
+	if (signature != AXIOM_FW_CFG_SIGNATURE) {
+		dev_err(dev, "Invalid TH2CFG signature\n");
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	/* 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");
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	error = axiom_set_new_crcs(ts, &chunk);
+	if (error)
+		return FW_UPLOAD_ERR_FW_INVALID;
+
+	/*
+	 * 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))
+		return FW_UPLOAD_ERR_DUPLICATE;
+
+	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);
+		return FW_UPLOAD_ERR_FW_INVALID;
+	}
+
+	guard(mutex)(&afw->lock);
+	return afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
+}
+
+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];
+	enum fw_upload_err ret;
+
+	scoped_guard(mutex, &afw->lock) {
+		afw->cancel = false;
+	}
+
+	mutex_lock(&ts->fwupdate_lock);
+
+	ret = __axiom_cfg_fw_prepare(ts, afw, data, size);
+
+	/*
+	 * 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 error;
+
+	/* Zero out the volatile memory except for the user content in u04 */
+	u8 *buf __free(kfree) = axiom_u04_get(ts);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	error = axiom_u02_fillconfig(ts);
+	if (error)
+		return error;
+
+	error = axiom_u04_set(ts, buf);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+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 error;
+
+	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);
+
+		error = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
+		if (error) {
+			dev_err(dev, "Failed to write CDU u%02X\n",
+				chunk->usage_num);
+			return error;
+		}
+
+		error = axiom_cdu_wait_idle(ts, chunk->usage_num);
+		if (error) {
+			dev_err(dev, "CDU write wait-idle failed\n");
+			return error;
+		}
+
+		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);
+
+	error = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
+	if (error) {
+		dev_err(dev, "Failed to commit CDU u%02X to NVM\n",
+			chunk->usage_num);
+		return error;
+	}
+
+	if (!axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
+					       msecs_to_jiffies(5 * MSEC_PER_SEC))) {
+		dev_err(ts->dev, "Error CDU u%02X commit timedout\n",
+			chunk->usage_num);
+		return -ETIMEDOUT;
+	}
+
+	error = axiom_cdu_wait_idle(ts, chunk->usage_num);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static int axiom_write_cfg_chunk(struct axiom_data *ts,
+				 const struct axiom_fw_cfg_chunk *chunk)
+{
+	unsigned int reg;
+	int error;
+
+	dev_dbg(ts->dev, "Write th2cfg-chunk - u%02X.rev%u length:%u\n",
+		chunk->usage_num, chunk->usage_rev, chunk->usage_length);
+
+	if (ts->usage_table[chunk->usage_num].info &&
+	    ts->usage_table[chunk->usage_num].info->is_cdu) {
+		error = axiom_write_cdu_usage(ts, chunk);
+		if (error)
+			return error;
+	} else {
+		reg = axiom_usage_baseaddr(ts, chunk->usage_num);
+		error = regmap_raw_write(ts->regmap, reg, chunk->usage_content,
+					 chunk->usage_length);
+		if (error)
+			return error;
+	}
+
+	error = axiom_u02_wait_idle(ts);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static int axiom_verify_volatile_mem(struct axiom_data *ts)
+{
+	int error;
+
+	error = axiom_u02_computecrc(ts);
+	if (error)
+		return error;
+
+	/* Query the new CRCs after they are re-computed */
+	error = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+	if (error)
+		return error;
+
+	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;
+	int error;
+
+	/* Done before cancel check due to cleanup based put */
+	error = pm_runtime_resume_and_get(ts->dev);
+	if (error)
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	scoped_guard(mutex, &afw->lock) {
+		if (afw->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;
+	}
+
+	error = axiom_u02_stop(ts);
+	if (error)
+		goto err_swreset;
+
+	error = axiom_zero_volatile_mem(ts);
+	if (error)
+		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);
+		} else {
+			error = axiom_write_cfg_chunk(ts, &chunk);
+			if (error) {
+				axiom_set_runmode(ts, AXIOM_TCP_MODE);
+				goto err_swreset;
+			}
+		}
+
+		data += chunk_len;
+		size -= chunk_len;
+		*written += chunk_len;
+	}
+
+	axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+	/* Ensure that the chunks are written correctly */
+	error = axiom_verify_volatile_mem(ts);
+	if (error) {
+		dev_err(dev, "Failed to verify written config, abort\n");
+		goto err_swreset;
+	}
+
+	error = axiom_u02_save_config(ts);
+	if (error)
+		goto err_swreset;
+
+	/*
+	 * (Re)start the device with the new config. Start the device AE
+	 * either via u02 SW_RESET or u02 CMD_START, the behavior is the same
+	 * according Touchnetix.
+	 */
+	error = axiom_u02_swreset(ts);
+	if (error) {
+		dev_err(dev, "Soft reset failed\n");
+		goto err_unlock;
+	}
+
+	error = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+	if (error)
+		goto err_unlock;
+
+	if (axiom_verify_crcs(ts))
+		goto err_unlock;
+
+	/* Unlock before the input device gets unregistered */
+	axiom_unlock_input_device(ts);
+
+	error = axiom_update_input_dev(ts);
+	if (error) {
+		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 error == -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];
+
+	guard(mutex)(&afw->lock);
+	afw->cancel = true;
+}
+
+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 error;
+
+	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");
+
+	error = devm_add_action_or_reset(dev, axiom_remove_axfw_fwl_action, ts);
+	if (error)
+		return error;
+
+	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");
+
+	error = devm_add_action_or_reset(dev, axiom_remove_cfg_fwl_action, ts);
+	if (error)
+		return error;
+
+	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 2:
+		val = "force";
+		break;
+	case 3:
+		val = "0d";
+		break;
+	case 4:
+		val = "xl";
+		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 error;
+
+	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, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, AXIOM_MAX_XY, 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_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;
+	}
+
+	error = input_mt_init_slots(input, ts->num_slots, INPUT_MT_DIRECT);
+	if (error) {
+		input_free_device(input);
+		dev_err(dev, "Failed to init mt slots\n");
+		return error;
+	}
+
+	/*
+	 * 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 (client->irq) {
+		if (!ts->irq_setup_done) {
+			error = devm_request_threaded_irq(dev, client->irq,
+							  NULL, axiom_irq,
+							  IRQF_ONESHOT,
+							  dev_name(dev), ts);
+			if (error) {
+				dev_err(dev, "Failed to request IRQ\n");
+				return error;
+			}
+			ts->irq_setup_done = true;
+		}
+	} else {
+		error = input_setup_polling(input, axiom_poll);
+		if (error) {
+			input_free_device(input);
+			dev_err(dev, "Setup polling mode failed\n");
+			return error;
+		}
+
+		input_set_poll_interval(input, ts->poll_interval);
+	}
+
+	input_set_drvdata(input, ts);
+	ts->input = input;
+
+	error = input_register_device(input);
+	if (error) {
+		input_free_device(input);
+		ts->input = NULL;
+		dev_err(dev, "Failed to register input device\n");
+	};
+
+	return error;
+}
+
+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 error;
+
+	ts->supplies[0].supply = "vddi";
+	ts->supplies[1].supply = "vdda";
+	ts->num_supplies = ARRAY_SIZE(ts->supplies);
+
+	error = devm_regulator_bulk_get(dev, ts->num_supplies, ts->supplies);
+	if (error)
+		return dev_err_probe(dev, error,
+				     "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_up_device(struct axiom_data *ts)
+{
+	struct device *dev = ts->dev;
+	int error;
+
+	error = regulator_bulk_enable(ts->num_supplies, ts->supplies);
+	if (error) {
+		dev_err(dev, "Failed to enable power supplies\n");
+		return error;
+	}
+
+	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 void axiom_power_down_device(struct axiom_data *ts)
+{
+	regulator_bulk_disable(ts->num_supplies, ts->supplies);
+}
+
+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 error;
+
+	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);
+
+	error = axiom_register_fwl(ts);
+	if (error)
+		return error;
+
+	error = axiom_parse_firmware(ts);
+	if (error)
+		return error;
+
+	error = axiom_power_up_device(ts);
+	if (error)
+		return dev_err_probe(dev, error, "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);
+	error = devm_pm_runtime_enable(dev);
+	if (error)
+		return dev_err_probe(dev, error, "Failed to enable pm-runtime\n");
+
+	error = axiom_register_panel_follower(ts);
+	if (error)
+		return dev_err_probe(dev, error, "Failed to register panel follower\n");
+
+	error = 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.
+	 *
+	 * The U02 usage has to be supported, since this is the usage which is
+	 * used to put the device into bootloader mode!
+	 *
+	 * TODO: Add support for the emergency bootloader mode enter sequence.
+	 * The emergency enter is done by toggling the nRESET pin 5-times in a
+	 * row.
+	 */
+	if (error) {
+		if (IS_ENABLED(CONFIG_FW_UPLOAD) &&
+		    axiom_driver_supports_usage(ts, AXIOM_U02) &&
+		    (axiom_get_runmode(ts) == AXIOM_DISCOVERY_MODE ||
+		     axiom_get_runmode(ts) == AXIOM_BLP_MODE)) {
+			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;
+		}
+		pm_runtime_put_sync(dev);
+		return dev_err_probe(dev, error, "Device discovery failed\n");
+	}
+
+	error = axiom_register_input_dev(ts, false);
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_sync_autosuspend(dev);
+	if (error) {
+		if (!IS_ENABLED(CONFIG_FW_UPLOAD))
+			return dev_err_probe(dev, error, "Failed to register input device\n");
+
+		dev_warn(dev, "Failed to register the input device, wait for user fw update\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);
+
+	axiom_disable_irq(ts);
+	axiom_power_down_device(ts);
+
+	return 0;
+}
+
+static int axiom_runtime_resume(struct device *dev)
+{
+	struct axiom_data *ts = dev_get_drvdata(dev);
+	int error;
+
+	error = axiom_power_up_device(ts);
+	if (error)
+		return error;
+
+	axiom_enable_irq(ts);
+
+	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.47.3


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

* Re: [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error
  2026-07-02 22:32 ` [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error Marco Felsch
@ 2026-07-02 22:45   ` sashiko-bot
  2026-07-02 23:11     ` Marco Felsch
  0 siblings, 1 reply; 8+ messages in thread
From: sashiko-bot @ 2026-07-02 22:45 UTC (permalink / raw)
  To: Marco Felsch; +Cc: dmitry.torokhov, devicetree, linux-input, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The selftest script for firmware uploads does not inject and test the new 'firmware-duplicate' error.
- [Low] Missing ABI documentation for the newly added sysfs error string 'firmware-duplicate'.
--

commit b451d666b20500e69051bac63ead44572b79d708
Author: Marco Felsch <m.felsch@pengutronix.de>

firmware_loader: expand firmware error codes with up-to-date error

This commit adds FW_UPLOAD_ERR_DUPLICATE to allow drivers to inform the
firmware_loader that an update is not required. It also updates the
test_firmware module to support injecting this new error state.

> diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c
> index efc33294212fd..82c4b1fedf4cd 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",

[Severity: Low]
Since a new error string "firmware-duplicate" is exposed to userspace,
should this also be documented in
Documentation/ABI/testing/sysfs-class-firmware?

It looks like the new string is missing from the allowed error values
documented for the sysfs interface.

>  };
>  
>  static const char *fw_upload_progress(struct device *dev,

[ ... ]

> diff --git a/lib/test_firmware.c b/lib/test_firmware.c
> index 7459bba65444d..a822d9641eb4c 100644
> --- a/lib/test_firmware.c
> +++ b/lib/test_firmware.c
> @@ -1134,6 +1134,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",

[Severity: Medium]
Does the test script also need to be updated to exercise this new code path?

While this properly adds injection support to the test module, the hardcoded
errors list in tools/testing/selftests/firmware/fw_upload.sh does not appear
to include "firmware-duplicate", meaning the kselftest won't actually
inject or test this new error type.

>  };
>  
>  static void upload_err_inject_error(struct test_firmware_upload *tst,

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260703-v6-10-topic-touchscreen-axiom-v7-0-0d2a550a7ee8@pengutronix.de?part=1

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

* Re: [PATCH v7 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support
  2026-07-02 22:32 ` [PATCH v7 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support Marco Felsch
@ 2026-07-02 22:48   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-02 22:48 UTC (permalink / raw)
  To: Marco Felsch; +Cc: robh, linux-input, conor+dt, dmitry.torokhov, devicetree

Thank you for your contribution! Sashiko AI review found 18 potential issue(s) to consider:
- [High] Missing bounds checking during firmware chunk parsing allows malformed firmware to trigger integer underflow and out-of-bounds memory accesses.
- [High] Invalid cast of regmap buffer to u16* causes endian-dependent behavior and corrupts register addresses.
- [High] Unbalanced pm_runtime_put in firmware upload cleanup leads to negative PM usage counts.
- [High] Firmware-controlled allocation size allows out-of-bounds heap read when checking touch slots.
- [High] The driver prevents firmware recovery when the device boots in bootloader mode by improperly failing the probe.
- [High] fwupdate_lock is held across system calls violating kernel mutex semantics, and is double-unlocked on error paths.
- [High] Improper devm teardown order causes Use-After-Free of the input device by background firmware upload threads.
- [High] Use-after-free of ts->input in the threaded IRQ handler during config firmware updates and device removal.
- [High] The driver waits on completion primitives for hardware resets and saves without reinitializing them, causing future waits to falsely succeed immediately.
- [High] The driver passes stack-allocated buffers to I2C transfers, which violates DMA requirements and can cause memory corruption.
- [High] NULL pointer dereference in `axiom_remove_axfw_fwl_action` if `devm_add_action_or_reset` fails.
- [Medium] An operator precedence error in subtracting the preamble and postamble causes the calculated report payload length to be artificially inflated.
- [Medium] Probe error paths leak PM runtime references and fail to disable regulators.
- [Medium] I2C read errors are swallowed and falsely reported as success in polling functions.
- [Medium] Missing NULL check on `kasprintf` results in a crash or broken sysfs state when registering the firmware upload interfaces.
- [Medium] The driver lacks system sleep power management callbacks, causing the device to remain powered on during system suspend if not acting as a panel follower.
- [Medium] Dead code in `axiom_register_input_dev` prevents failing initialization when no touchslots are enabled.
- [Low] Typo in the sysfs `device_state` output violates the documented ABI.
--

commit 7ff2ad22a9b84958a9fb53d181dbd9af7a0e7b00
Author: Marco Felsch <m.felsch@pengutronix.de>

Input: Add TouchNetix aXiom I2C Touchscreen support

This patch adds initial support for the TouchNetix AX54A touchscreen
controller. It handles I2C input events, touch coordinate reporting, and
supports firmware and configuration updates via the fw_upload subsystem.

> diff --git a/drivers/input/touchscreen/touchnetix_axiom.c b/drivers/input/touchscreen/touchnetix_axiom.c
> --- /dev/null
> +++ b/drivers/input/touchscreen/touchnetix_axiom.c

[ ... ]

> +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_MODE:		return "bootlaoder";

[Severity: Low]
Does this output "bootlaoder" instead of the documented "bootloader" state?

This typo might break userspace tools reading the device_state sysfs file.

> +	default:			return "unknown";
> +	}
> +}

[ ... ]

> +static int axiom_u02_wait_idle(struct axiom_data *ts)
> +{
> +	unsigned int reg;
> +	int error, 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.
> +	 */
> +	error = 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);

[Severity: Medium]
Can this swallow I2C read errors?

If regmap_raw_read fails, ret will be non-zero (an error code). The
condition evaluates to true, ending the poll successfully. The function
then incorrectly assumes commands completed successfully and returns 0.

> +	if (error) {
> +		dev_err(ts->dev, "Poll u02 timedout with: %#x\n", cmd);
> +		return error;
> +	}
> +
> +	return 0;
> +}

[ ... ]

> +static int axiom_u02_swreset(struct axiom_data *ts)
> +{
> +	struct axiom_u02_rev1_system_manager_msg msg = {
> +		.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SOFTRESET),
> +	};
> +	int error;
> +
> +	if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> +		return -EINVAL;
> +
> +	error = axiom_u02_send_msg(ts, &msg, false);
> +	if (error)
> +		return error;
> +
> +	/*
> +	 * Downstream axcfg.py waits for 1sec without checking U01 HELLO. Tests
> +	 * showed that waiting for the HELLO message isn't enough therefore we
> +	 * need to add the additional fsleep(1sec).
> +	 * Touchnetix said that the boot can take up to 2sec if all self tests
> +	 * are enabled, so wait 2sec for the HELLO message.
> +	 */
> +	if (!axiom_wait_for_completion_timeout(ts, &ts->boot_complete,
> +					       msecs_to_jiffies(2 * MSEC_PER_SEC))) {

[Severity: High]
Is reinit_completion needed here before waiting?

Since boot_complete and nvm_write are signaled via complete, future calls
to axiom_wait_for_completion_timeout will return immediately because
reinit_completion is never called to reset their internal state. This might
cause premature hardware communication.

> +		dev_err(ts->dev, "Error swreset timedout\n");
> +		error = -ETIMEDOUT;
> +	}
> +
> +	fsleep(USEC_PER_SEC);
> +
> +	return error;
> +}

[ ... ]

> +static bool axiom_u42_get_touchslots(struct axiom_data *ts)
> +{
> +	unsigned int bufsize;
> +	unsigned int reg;
> +	int error, i;
> +
> +	bufsize = axiom_usage_size(ts, AXIOM_U42);
> +	u8 *buf __free(kfree) = kzalloc(bufsize, GFP_KERNEL);
> +	if (!buf)
> +		return false;
> +
> +	reg = axiom_usage_baseaddr(ts, AXIOM_U42);
> +	error = regmap_raw_read(ts->regmap, reg, buf, bufsize);
> +	if (error) {
> +		dev_warn(ts->dev, "Failed to read u42\n");
> +		return false;
> +	}
> +
> +	ts->enabled_slots = 0;
> +	ts->num_slots = 0;
> +
> +	for (i = 0; i < AXIOM_MAX_TOUCHSLOTS; i++) {
> +		if (axiom_u42_touch_enabled(ts, buf, i)) {

[Severity: High]
Could this lead to an out-of-bounds heap read?

The bufsize is determined by the device via axiom_usage_size. If a
compromised or malfunctioning device reports a size smaller than 18 bytes,
the loop still iterates up to 10 slots checking fixed offsets up to index
17 in axiom_u42_touch_enabled, reading past the allocated buffer.

> +			ts->enabled_slots |= BIT(i);
> +			ts->num_slots++;
> +		}
> +	}
> +
> +	return true;
> +}

[ ... ]

> +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] = { };

[Severity: High]
Can this cause memory corruption on architectures lacking hardware cache
coherence?

The 256-byte buf array is allocated on the stack and passed to
regmap_raw_read, which eventually reaches the I2C core. Stack memory is
generally not DMA-safe, violating I2C transfer requirements.

> +	struct device *dev = ts->dev;
> +	unsigned char report_usage;
> +	u16 crc_report, crc_calc;
> +	unsigned int len;
> +	u8 *payload;
> +	int error;
> +
> +	error = regmap_raw_read(regmap, reg, buf, ts->max_report_byte_len);
> +	if (error)
> +		return error;

[ ... ]

> +	report_usage = buf[1];
> +	payload = &buf[AXIOM_U34_REV1_PREAMBLE_BYTES];
> +	len -= AXIOM_U34_REV1_PREAMBLE_BYTES - AXIOM_U34_REV1_POSTAMBLE_BYTES;

[Severity: Medium]
Could this calculation artificially inflate the payload length?

Mathematically, subtracting without parentheses evaluates like this:
len -= (2 - 4), which results in len += 2.
Should this have parentheses around the values being subtracted?

> +
> +	switch (report_usage) {
> +	case AXIOM_U01:
> +	case AXIOM_U41:

[ ... ]

> +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;

[Severity: High]
Could this lead to a use-after-free if the input device is unregistered?

During config firmware updates (axiom_cfg_fw_write) and module removal,
the input device is freed. If the threaded IRQ handler runs concurrently
and reads ts->input before it is nulled out, it might access a freed pointer
when calling input_mt_slot and touchscreen_report_pos.

> +
> +	targets = get_unaligned_le16(&buf[AXIOM_U41_REV2_TARGETSTATUS_REG]);

[ ... ]

> +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;
> +	struct i2c_msg xfer[2];
> +	u16 xferlen, addr;
> +	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);

[Severity: High]
Does this cast cause endianness issues on big-endian architectures?

Casting a byte buffer directly to a u16 pointer evaluates differently
depending on the host byte order. When cpu_to_le16 is applied afterwards,
the register address sent to the device will be corrupted on big-endian
systems. The same pattern is present in axiom_regmap_write.

> +	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);
> +
> +	if (!axiom_usage_supported(ts, addr))
> +		return -EINVAL;
> +
> +	xfer[0].addr = i2c->addr;
> +	xfer[0].flags = 0;
> +	xfer[0].len = sizeof(hdr);
> +	xfer[0].buf = (u8 *)&hdr;

[Severity: High]
Could passing the stack-allocated hdr struct to i2c_transfer cause issues
with DMA mapping?

The I2C core expects buffers that are DMA-safe, but stack variables might
violate this on non-coherent architectures. A similar stack-buffer is passed
in axiom_regmap_write.

> +
> +	xfer[1].addr = i2c->addr;
> +	xfer[1].flags = I2C_M_RD;
> +	xfer[1].len = val_size;
> +	xfer[1].buf = val_buf;

[ ... ]

> +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];
> +	enum fw_upload_err ret;
> +
> +	scoped_guard(mutex, &afw->lock) {
> +		afw->cancel = false;
> +	}
> +
> +	mutex_lock(&ts->fwupdate_lock);
> +
> +	ret = __axiom_axfw_fw_prepare(ts, afw, data, size);
> +
> +	/*
> +	 * In FW_UPLOAD_ERR_NONE case the complete handler will release the
> +	 * lock.
> +	 */
> +	if (ret != FW_UPLOAD_ERR_NONE)
> +		mutex_unlock(&ts->fwupdate_lock);

[Severity: High]
Can this lead to a double-unlock of fwupdate_lock?

If __axiom_axfw_fw_prepare returns an error, this code unlocks the mutex.
However, the firmware upload core will subsequently call the cleanup
callback axiom_axfw_fw_cleanup, which unconditionally unlocks the mutex
again. The same issue exists in axiom_cfg_fw_prepare.

> +
> +	return ret;
> +}

[ ... ]

> +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;
> +	int error;
> +
> +	/*
> +	 * According Touchnetix the IRQ pin gets reconfigured in bootloader
> +	 * mode. Which can cause spurious IRQs. Therefore the IRQ should be
> +	 * disabled on the host.
> +	 *
> +	 * See the cleanup routine for the balanced enable.
> +	 */
> +	if (axiom_get_runmode(ts) != AXIOM_BLP_MODE)
> +		axiom_disable_irq(ts);
> +
> +	/* Done before cancel check due to cleanup based put */
> +	error = pm_runtime_resume_and_get(ts->dev);
> +	if (error)
> +		return FW_UPLOAD_ERR_HW_ERROR;

[Severity: High]
Does this error path lead to an unbalanced PM runtime usage count?

If pm_runtime_resume_and_get fails, it returns an error and no reference
is acquired. The firmware upload core will then call axiom_axfw_fw_cleanup,
which unconditionally calls pm_runtime_put_sync_autosuspend, potentially
causing a refcount underflow.

> +
> +	scoped_guard(mutex, &afw->lock) {
> +		if (afw->cancel)
> +			return FW_UPLOAD_ERR_CANCELED;
> +	}

[ ... ]

> +	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.
> +		 */
> +		error = axiom_blp_write_chunk(ts, data, len);
> +		if (error) {
> +			/*
> +			 * Tests showed that the bootloader mode must be exited
> +			 * if an invalid chunk was received by the bootloader fw
> +			 * since all following attempts to download valid chunks
> +			 * will fail. Try do so via axiom_blp_reset() but tests
> +			 * also showed that this may fail too. So inform the
> +			 * user and hope that the full power-cycle helps. To get
> +			 * the device back into a working mode where the device
> +			 * accepts data again.
> +			 */
> +			if (axiom_blp_reset(ts))
> +				dev_warn(dev, "Couldn't recover device, device requires power-cycle\n");
> +			return axiom_unlock_input_return_hw_error(ts);
> +		}
> +
> +		size -= len;
> +		*written += len;
> +		data += len;

[Severity: High]
Is there a bounds checking issue here that could lead to integer underflow?

The length is calculated and subtracted from size without checking if len
is actually less than or equal to size. If a malformed firmware chunk
reports a large chunk_len, size will wrap around, causing out-of-bounds
accesses on the next iteration. This also occurs in axiom_cfg_fw_write
and __axiom_cfg_fw_prepare.

> +	}

[ ... ]

> +static int axiom_register_fwl(struct axiom_data *ts)
> +{
> +	struct device *dev = ts->dev;
> +	struct fw_upload *fwl;
> +	char *fw_name;
> +	int error;
> +
> +	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);

[Severity: Medium]
Should there be a NULL pointer check for fw_name?

If kasprintf fails under memory pressure, it returns NULL, which is then
passed directly to firmware_upload_register.

> +	kfree(fw_name);
> +	if (IS_ERR(fwl))
> +		return dev_err_probe(dev, PTR_ERR(fwl),
> +				     "Failed to register firmware upload\n");
> +
> +	error = devm_add_action_or_reset(dev, axiom_remove_axfw_fwl_action, ts);
> +	if (error)
> +		return error;
> +
> +	ts->fw[AXIOM_FW_AXFW].fwl = fwl;

[Severity: High]
Could this cause a NULL pointer dereference if devm_add_action_or_reset fails?

If it fails, devm_add_action_or_reset executes axiom_remove_axfw_fwl_action
immediately. However, ts->fw[AXIOM_FW_AXFW].fwl is currently NULL because
it hasn't been assigned yet, causing firmware_upload_unregister to crash.

> +
> +	mutex_init(&ts->fw[AXIOM_FW_CFG].lock);

[ ... ]

> +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 error;

[ ... ]

> +	axiom_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;
> +	}

[Severity: Medium]
Is the -EINVAL error path dead code?

The outer if-statement requires update_in_process to be true. The inner
if-statement also checks if update_in_process is true and returns 0.
The code will never reach the dev_err and -EINVAL return.

> +
> +	error = input_mt_init_slots(input, ts->num_slots, INPUT_MT_DIRECT);

[ ... ]

> +static int axiom_i2c_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct axiom_data *ts;
> +	int error;

[ ... ]

> +	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);
> +	error = devm_pm_runtime_enable(dev);
> +	if (error)
> +		return dev_err_probe(dev, error, "Failed to enable pm-runtime\n");
> +
> +	error = axiom_register_panel_follower(ts);
> +	if (error)
> +		return dev_err_probe(dev, error, "Failed to register panel follower\n");

[Severity: Medium]
Do these error returns leak the PM runtime reference and leave the power
supplies enabled?

If devm_pm_runtime_enable or axiom_register_panel_follower fail, the
function returns directly without dropping the reference acquired by
pm_runtime_get_noresume or disabling the regulators.

> +
> +	error = 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.
> +	 *
> +	 * The U02 usage has to be supported, since this is the usage which is
> +	 * used to put the device into bootloader mode!
> +	 *
> +	 * TODO: Add support for the emergency bootloader mode enter sequence.
> +	 * The emergency enter is done by toggling the nRESET pin 5-times in a
> +	 * row.
> +	 */
> +	if (error) {
> +		if (IS_ENABLED(CONFIG_FW_UPLOAD) &&
> +		    axiom_driver_supports_usage(ts, AXIOM_U02) &&
> +		    (axiom_get_runmode(ts) == AXIOM_DISCOVERY_MODE ||
> +		     axiom_get_runmode(ts) == AXIOM_BLP_MODE)) {
> +			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;
> +		}
> +		pm_runtime_put_sync(dev);
> +		return dev_err_probe(dev, error, "Device discovery failed\n");
> +	}

[Severity: High]
Does this logic prevent firmware recovery when the device boots in bootloader
mode?

When starting in BLP mode, axiom_u31_device_discover returns -EACCES
without populating the usage table. The recovery check here evaluates
axiom_driver_supports_usage(ts, AXIOM_U02), which returns false because the
usage table is empty. This aborts the probe instead of enabling the upload
interface.

> +
> +	error = axiom_register_input_dev(ts, false);

[ ... ]

> +static void axiom_i2c_remove(struct i2c_client *client)
> +{
> +	struct axiom_data *ts = i2c_get_clientdata(client);
> +
> +	axiom_unregister_input_dev(ts);
> +}

[Severity: High]
Could this lead to a use-after-free during unbind?

The firmware upload cleanup actions are registered via devres, meaning they
execute after axiom_i2c_remove. However, axiom_i2c_remove explicitly frees
the input device. If a background firmware upload thread is active, it will
access the freed input device.

> +
> +static int axiom_runtime_suspend(struct device *dev)
> +{
> +	struct axiom_data *ts = dev_get_drvdata(dev);
> +
> +	axiom_disable_irq(ts);
> +	axiom_power_down_device(ts);
> +
> +	return 0;
> +}
> +
> +static int axiom_runtime_resume(struct device *dev)
> +{
> +	struct axiom_data *ts = dev_get_drvdata(dev);
> +	int error;
> +
> +	error = axiom_power_up_device(ts);
> +	if (error)
> +		return error;
> +
> +	axiom_enable_irq(ts);
> +
> +	return 0;
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(axiom_pm_ops, axiom_runtime_suspend,
> +				 axiom_runtime_resume, NULL);

[Severity: Medium]
Should this include system sleep power management callbacks?

DEFINE_RUNTIME_DEV_PM_OPS is used, but without SYSTEM_SLEEP_PM_OPS. If the
touchscreen is held open by userspace during a system suspend (S3), the
device will remain in RPM_ACTIVE and stay powered on.

> +
> +static const struct i2c_device_id axiom_i2c_id_table[] = {
> +	{ "ax54a" },
> +	{ },
> +};

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260703-v6-10-topic-touchscreen-axiom-v7-0-0d2a550a7ee8@pengutronix.de?part=4

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

* Re: [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error
  2026-07-02 22:45   ` sashiko-bot
@ 2026-07-02 23:11     ` Marco Felsch
  0 siblings, 0 replies; 8+ messages in thread
From: Marco Felsch @ 2026-07-02 23:11 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: dmitry.torokhov, devicetree, linux-input, conor+dt, robh

On 26-07-02, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] The selftest script for firmware uploads does not inject and test the new 'firmware-duplicate' error.
> - [Low] Missing ABI documentation for the newly added sysfs error string 'firmware-duplicate'.
> --
> 
> commit b451d666b20500e69051bac63ead44572b79d708
> Author: Marco Felsch <m.felsch@pengutronix.de>
> 
> firmware_loader: expand firmware error codes with up-to-date error
> 
> This commit adds FW_UPLOAD_ERR_DUPLICATE to allow drivers to inform the
> firmware_loader that an update is not required. It also updates the
> test_firmware module to support injecting this new error state.
> 
> > diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c
> > index efc33294212fd..82c4b1fedf4cd 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",
> 
> [Severity: Low]
> Since a new error string "firmware-duplicate" is exposed to userspace,
> should this also be documented in
> Documentation/ABI/testing/sysfs-class-firmware?

I checked the firmware-invalid which is also not documented. I could add
2nd patch to fix this if this is urgent.

> 
> It looks like the new string is missing from the allowed error values
> documented for the sysfs interface.
> 
> >  };
> >  
> >  static const char *fw_upload_progress(struct device *dev,
> 
> [ ... ]
> 
> > diff --git a/lib/test_firmware.c b/lib/test_firmware.c
> > index 7459bba65444d..a822d9641eb4c 100644
> > --- a/lib/test_firmware.c
> > +++ b/lib/test_firmware.c
> > @@ -1134,6 +1134,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",
> 
> [Severity: Medium]
> Does the test script also need to be updated to exercise this new code path?
> 
> While this properly adds injection support to the test module, the hardcoded
> errors list in tools/testing/selftests/firmware/fw_upload.sh does not appear
> to include "firmware-duplicate", meaning the kselftest won't actually
> inject or test this new error type.

I'm not sure how much sense it is to have a test reporting this error.

Regards,
  Marco



> 
> >  };
> >  
> >  static void upload_err_inject_error(struct test_firmware_upload *tst,
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260703-v6-10-topic-touchscreen-axiom-v7-0-0d2a550a7ee8@pengutronix.de?part=1
> 

-- 
#gernperDu 
#CallMeByMyFirstName

Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | https://www.pengutronix.de/ |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-9    |

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

end of thread, other threads:[~2026-07-02 23:11 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-02 22:32 [PATCH v7 0/4] Input: Add support for TouchNetix aXiom touchscreen Marco Felsch
2026-07-02 22:32 ` [PATCH v7 1/4] firmware_loader: expand firmware error codes with up-to-date error Marco Felsch
2026-07-02 22:45   ` sashiko-bot
2026-07-02 23:11     ` Marco Felsch
2026-07-02 22:32 ` [PATCH v7 2/4] dt-bindings: vendor-prefixes: Add TouchNetix AS Marco Felsch
2026-07-02 22:32 ` [PATCH v7 3/4] dt-bindings: input: Add TouchNetix axiom touchscreen Marco Felsch
2026-07-02 22:32 ` [PATCH v7 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support Marco Felsch
2026-07-02 22:48   ` sashiko-bot

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