Devicetree
 help / color / mirror / Atom feed
* [PATCH v7 0/4] Input: support for STM FTS5
@ 2026-09-07 10:50 David Heidelberg via B4 Relay
  2026-09-07 10:50 ` [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset David Heidelberg via B4 Relay
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-07 10:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio
  Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
	linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
	phone-devel, David Heidelberg, stable, Konrad Dybcio,
	Dmitry Baryshkov

Used on various phones. Minimal viable driver.

Includes device-tree enabling touchscreen on Pixel 3.
Also, tested on Xiaomi Mi 8 (device-tree is not yet inside the mainline).

What is missing:
 - switching between AP and SLPI mode (to be able to wake up phone by touch)
 - firmware loading
 - anything above basic touch

Signed-off-by: David Heidelberg <david@ixit.cz>
---
Changes in v7:
- Added 'Input: stmfts - wait for controller ready after reset',
  the waiting is done for both FTS4 and FTS5, similarly to the
  downstream driver. Should improve reliability everywhere.
- Link to v6: https://patch.msgid.link/20260906-stmfts5-v6-0-50fbf6af8718@ixit.cz

Changes in v6:
- Enable only the multi-touch scan on FTS5 sense-on (0xff was wrong,
  only 0x01 is needed. This broke Xiaomi Mi 8 toucschreen).
- Fixed Y-coordinate comments. (Dmitry)
- Corrected event[0] parsing. (Dmitry)
- Dropped checking < 0 on unsigned area. (Dmitry)
- Dropped unnecessary i2c writes after reset. Verified against
  downstream driver. (Dmitry, Sashiko)
- Added pieces for supporting non-OF integration. (Sashiko)
- Shared input open/close into one impl. (Dmitry).
- Link to v5: https://patch.msgid.link/20260810-stmfts5-v5-0-0c737d929976@ixit.cz

Changes in v5:
- 3.3V is analog, 1.8V is digital supply. Fix that.
- Name the error variables "err" (Dmitry)
- Use scoped_guard() for ->running, and only set it once the scan
  mode has actually been enabled. (Dmitry)
- Use i2c_master_send() instead of open-coding a single-message
  i2c_transfer(). (Dmitry)
- Drivers may report coordinates outside the declared min/max, so
  remove the clamping. (Dmitry)
- BTN_TOUCH and INPUT_PROP_DIRECT are already set up by
  input_mt_init_slots(..., INPUT_MT_DIRECT). (Dmitry)
- Let the input core track contacts: drop the touch_id/stylus_id
  bitmaps and the hand-rolled BTN_TOUCH reporting, and sync the frame
  once per interrupt with input_mt_sync_frame() instead of calling
  input_sync() per contact event. The controller pushes contacts one
  by one into the event FIFO and only resends the ones that changed,
  so INPUT_MT_DROP_UNUSED is wrong here and is dropped as well. (Dmitry)
- Restore the FTS4 interrupts accidentally dropped by introduction of
  FTS5.
- mode-switch-gpios isn't required with stmfts5. Some designs, such as
  Xiaomi Mi 8, has no such GPIO connected and is limited to AP mode.
- Nit: updated example to use interrupts-extended.
- Nit: (maximum x/y coordinate reported + 1), corrected example.
- Link to v4: https://lore.kernel.org/r/20260409-stmfts5-v4-0-64fe62027db5@ixit.cz

Changes in v4:
- Wrap everything below enabling the supplies into stmfts_configure()
  to avoid bunch of gotos to power off on error (Dmitry T.)
- Finished chip specific ops and removed is_fts5. (Dmitry T.)
- Link to v3: https://lore.kernel.org/r/20260403-stmfts5-v3-0-5da768cfd201@ixit.cz

Changes in v3:
- s/touchscreen_pins/touchscreen_irq_n. (Konrad)
- Use interrupts-extended. (Konrad)
- Fixed rebase conflict against 8665ceb926ec ("Input: stmfts - use guard notation when acquiring mutex")
- Rename switch-gpios to mode-switch-gpios.
- Do not define properties in if:then: branches. (Krzysztof)
- Link to v2: https://lore.kernel.org/r/20260315-stmfts5-v2-0-70bc83ee9591@ixit.cz

Changes in v2:
- Fix typo in the binding s/switch-gpio/switch-gpios/.
- Deduplacate allOf. (Rob yamllint)
- Add missing S-off-by. (Dmitry B.)
- Dropped irq-gpios as it's not needed. (Konrad)
- Correct x and y touchscreen area size. (Konrad)
- Correct reset introduction commit description. (Krzysztof)
- Partially implemented chip specific ops. (Dmitry T.)
- Separeted license naming cleanup into separate commit (Dmitry T.)
- Link to v1: https://lore.kernel.org/r/20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz

To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
To: Maxime Coquelin <mcoquelin.stm32@gmail.com>
To: Alexandre Torgue <alexandre.torgue@foss.st.com>
To: Petr Hodina <petr.hodina@protonmail.com>
To: David Heidelberg <david@ixit.cz>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Henrik Rydberg <rydberg@bitmath.org>
To: Bjorn Andersson <andersson@kernel.org>
To: Konrad Dybcio <konradybcio@kernel.org>
Cc: linux-input@vger.kernel.org
Cc: linux-stm32@st-md-mailman.stormreply.com
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Krzysztof Kozlowski <krzk@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-arm-msm@vger.kernel.org

---
David Heidelberg (3):
      Input: stmfts - wait for controller ready after reset
      dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
      Input: stmfts - support FTS5

Petr Hodina (1):
      arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support

 .../bindings/input/touchscreen/st,stmfts.yaml      |  29 +-
 .../arm64/boot/dts/qcom/sdm845-google-blueline.dts |  19 +-
 arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi |   2 +-
 drivers/input/touchscreen/stmfts.c                 | 438 ++++++++++++++++++---
 4 files changed, 429 insertions(+), 59 deletions(-)
---
base-commit: 7079a12d7506b07fb53b54a664bfad5fa9b16d70
change-id: 20260214-stmfts5-b47311fbd732

Best regards,
--  
David Heidelberg <david@ixit.cz>



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

* [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset
  2026-09-07 10:50 [PATCH v7 0/4] Input: support for STM FTS5 David Heidelberg via B4 Relay
@ 2026-09-07 10:50 ` David Heidelberg via B4 Relay
  2026-09-07 11:06   ` sashiko-bot
  2026-09-07 10:50 ` [PATCH v7 2/4] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 David Heidelberg via B4 Relay
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-07 10:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio
  Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
	linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
	phone-devel, David Heidelberg, stable

From: David Heidelberg <david@ixit.cz>

After releasing the reset line stmfts_reset() sleeps a fixed 50 ms and
stmfts_power_on() another 50 ms before the first I2C access.  That is
enough for a warm reset, but when both supplies were really cut during
system suspend the controller boots from cold and can need longer.  If
it does, the first read fails, stmfts_resume() returns an error and the
touchscreen is left powered off with its interrupt disabled.

The controller posts a controller ready event once it has booted, and
the event parser already completes cmd_done on it.  Enable the interrupt
right after releasing reset and wait for that event instead of sleeping,
giving the controller 300 ms, the total budget the vendor driver allows.
Boards without a reset line keep the old timing.

Usually phones such as Pixel 4a and Xiaomi Mi 8 needs longer delay, so
without this change touchscreen stops working.

Fixes: 8a1f9de80e45 ("Input: stmfts - add optional reset GPIO support")
Cc: stable@vger.kernel.org
Signed-off-by: David Heidelberg <david@ixit.cz>
---
 drivers/input/touchscreen/stmfts.c | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 972687797f826..711e3ad8587a0 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -59,16 +59,18 @@
 #define STMFTS_MASK_X_MSB			0x0f
 #define STMFTS_MASK_Y_LSB			0xf0
 
 /* key related event masks */
 #define STMFTS_MASK_KEY_NO_TOUCH		0x00
 #define STMFTS_MASK_KEY_MENU			0x01
 #define STMFTS_MASK_KEY_BACK			0x02
 
+#define STMFTS_RESET_TIMEOUT_MS	300
+
 #define STMFTS_EVENT_SIZE	8
 #define STMFTS_STACK_DEPTH	32
 #define STMFTS_DATA_MAX_SIZE	(STMFTS_EVENT_SIZE * STMFTS_STACK_DEPTH)
 #define STMFTS_MAX_FINGERS	10
 #define STMFTS_DEV_NAME		"stmfts"
 
 static const struct regulator_bulk_data stmfts_supplies[] = {
 	{ .supply = "vdd" },
@@ -538,23 +540,30 @@ static int stmfts_read_system_info(struct stmfts_data *sdata)
 	sdata->chip_ver = reg[0];
 	sdata->fw_ver = be16_to_cpup((__be16 *)&reg[2]);
 	sdata->config_id = reg[4];
 	sdata->config_ver = reg[5];
 
 	return 0;
 }
 
-static void stmfts_reset(struct stmfts_data *sdata)
+static int stmfts_reset(struct stmfts_data *sdata)
 {
 	gpiod_set_value_cansleep(sdata->reset_gpio, 1);
 	msleep(20);
 
+	reinit_completion(&sdata->cmd_done);
 	gpiod_set_value_cansleep(sdata->reset_gpio, 0);
-	msleep(50);
+	enable_irq(sdata->client->irq);
+
+	if (!wait_for_completion_timeout(&sdata->cmd_done,
+					 msecs_to_jiffies(STMFTS_RESET_TIMEOUT_MS)))
+		return -ETIMEDOUT;
+
+	return 0;
 }
 
 static int stmfts_configure(struct stmfts_data *sdata)
 {
 	int err;
 
 	err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
 	if (err)
@@ -593,42 +602,47 @@ static int stmfts_power_on(struct stmfts_data *sdata)
 		return err;
 
 	/*
 	 * The datasheet does not specify the power on time, but considering
 	 * that the reset time is < 10ms, I sleep 20ms to be sure
 	 */
 	msleep(20);
 
-	if (sdata->reset_gpio)
-		stmfts_reset(sdata);
+	if (sdata->reset_gpio) {
+		err = stmfts_reset(sdata);
+		if (err) {
+			dev_err(&sdata->client->dev,
+				"controller not ready after reset: %d\n", err);
+			goto err_disable_irq;
+		}
+	} else {
+		enable_irq(sdata->client->irq);
+		msleep(50);
+	}
 
 	err = stmfts_read_system_info(sdata);
 	if (err)
-		goto err_disable_regulators;
-
-	enable_irq(sdata->client->irq);
-
-	msleep(50);
+		goto err_disable_irq;
 
 	err = stmfts_configure(sdata);
 	if (err)
 		goto err_disable_irq;
 
 	/*
 	 * At this point no one is using the touchscreen
 	 * and I don't really care about the return value
 	 */
 	(void)i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
 
 	return 0;
 
 err_disable_irq:
 	disable_irq(sdata->client->irq);
-err_disable_regulators:
+
 	regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
 	return err;
 }
 
 static void stmfts_power_off(void *data)
 {
 	struct stmfts_data *sdata = data;
 

-- 
2.55.0



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

* [PATCH v7 2/4] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
  2026-09-07 10:50 [PATCH v7 0/4] Input: support for STM FTS5 David Heidelberg via B4 Relay
  2026-09-07 10:50 ` [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset David Heidelberg via B4 Relay
@ 2026-09-07 10:50 ` David Heidelberg via B4 Relay
  2026-09-07 11:00   ` sashiko-bot
  2026-09-07 10:50 ` [PATCH v7 3/4] Input: stmfts - support FTS5 David Heidelberg via B4 Relay
  2026-09-07 10:50 ` [PATCH v7 4/4] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support David Heidelberg via B4 Relay
  3 siblings, 1 reply; 9+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-07 10:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio
  Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
	linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
	phone-devel, David Heidelberg

From: David Heidelberg <david@ixit.cz>

Introduce more recent STM FTS5 touchscreen support.

Signed-off-by: David Heidelberg <david@ixit.cz>
---
 .../bindings/input/touchscreen/st,stmfts.yaml      | 29 ++++++++++++++++------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
index 64c4f24ea3dd0..3de4bc5cd3f78 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
@@ -9,22 +9,21 @@ title: ST-Microelectronics FingerTip touchscreen controller
 maintainers:
   - Krzysztof Kozlowski <krzk@kernel.org>
 
 description:
   The ST-Microelectronics FingerTip device provides a basic touchscreen
   functionality. Along with it the user can enable the touchkey which can work
   as a basic HOME and BACK key for phones.
 
-allOf:
-  - $ref: touchscreen.yaml#
-
 properties:
   compatible:
-    const: st,stmfts
+    enum:
+      - st,stmfts
+      - st,stmfts5
 
   reg:
     maxItems: 1
 
   avdd-supply:
     description: Analogic power supply
 
   interrupts:
@@ -35,42 +34,56 @@ properties:
 
   touch-key-connected:
     type: boolean
     description: The touchkey feature is connected
 
   vdd-supply:
     description: Power supply
 
+  mode-switch-gpios:
+    description: Optional switch between touchscreen SLPI and AP mode.
+    maxItems: 1
+
   reset-gpios:
     description: Reset GPIO (active-low)
     maxItems: 1
 
 required:
   - compatible
   - reg
   - avdd-supply
   - interrupts
   - vdd-supply
 
+allOf:
+  - $ref: touchscreen.yaml#
+  - if:
+      not:
+        properties:
+          compatible:
+            const: st,stmfts5
+    then:
+      properties:
+        mode-switch-gpios: false
+
 unevaluatedProperties: false
 
 examples:
   - |
     #include <dt-bindings/interrupt-controller/irq.h>
 
     i2c {
         #address-cells = <1>;
         #size-cells = <0>;
 
         touchscreen@49 {
             compatible = "st,stmfts";
             reg = <0x49>;
-            interrupt-parent = <&gpa1>;
-            interrupts = <1 IRQ_TYPE_LEVEL_LOW>;
-            touchscreen-size-x = <1599>;
-            touchscreen-size-y = <2559>;
+            interrupts-extended = <&gpa1 1 IRQ_TYPE_LEVEL_LOW>;
+            touchscreen-size-x = <1600>;
+            touchscreen-size-y = <2560>;
             touch-key-connected;
             avdd-supply = <&ldo30_reg>;
             vdd-supply = <&ldo31_reg>;
             ledvdd-supply = <&ldo33_reg>;
         };
     };

-- 
2.55.0



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

* [PATCH v7 3/4] Input: stmfts - support FTS5
  2026-09-07 10:50 [PATCH v7 0/4] Input: support for STM FTS5 David Heidelberg via B4 Relay
  2026-09-07 10:50 ` [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset David Heidelberg via B4 Relay
  2026-09-07 10:50 ` [PATCH v7 2/4] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 David Heidelberg via B4 Relay
@ 2026-09-07 10:50 ` David Heidelberg via B4 Relay
  2026-09-07 11:04   ` sashiko-bot
  2026-09-07 10:50 ` [PATCH v7 4/4] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support David Heidelberg via B4 Relay
  3 siblings, 1 reply; 9+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-07 10:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio
  Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
	linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
	phone-devel, David Heidelberg

From: David Heidelberg <david@ixit.cz>

FTS support SLPI and AP mode, introduce mode-switch GPIO to switch between
those two. Currently we can handle only full power AP mode, so we just
keep the AP on.

Useful for devices like Pixel 3 (blueline), Pixel 4a (sunfish),
Xiaomi Mi 8 (dipper), and many others.

Based on work of Petr Hodina <petr.hodina@protonmail.com>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
 drivers/input/touchscreen/stmfts.c | 406 +++++++++++++++++++++++++++++++++----
 1 file changed, 366 insertions(+), 40 deletions(-)

diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 711e3ad8587a0..99fc9840d3685 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -1,14 +1,18 @@
 // SPDX-License-Identifier: GPL-2.0
-// STMicroelectronics FTS Touchscreen device driver
-//
-// Copyright (c) 2017 Samsung Electronics Co., Ltd.
-// Copyright (c) 2017 Andi Shyti <andi@etezian.org>
+/* STMicroelectronics FTS Touchscreen device driver
+ *
+ * Copyright 2017 Samsung Electronics Co., Ltd.
+ * Copyright 2017 Andi Shyti <andi@etezian.org>
+ * Copyright David Heidelberg <david@ixit.cz>
+ * Copyright Petr Hodina <petr.hodina@protonmail.com>
+ */
 
+#include <linux/bits.h>
 #include <linux/delay.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/input/mt.h>
 #include <linux/input/touchscreen.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/leds.h>
@@ -30,16 +34,21 @@
 #define STMFTS_SS_HOVER_SENSE_ON		0x95
 #define STMFTS_MS_KEY_SENSE_OFF			0x9a
 #define STMFTS_MS_KEY_SENSE_ON			0x9b
 #define STMFTS_SYSTEM_RESET			0xa0
 #define STMFTS_CLEAR_EVENT_STACK		0xa1
 #define STMFTS_FULL_FORCE_CALIBRATION		0xa2
 #define STMFTS_MS_CX_TUNING			0xa3
 #define STMFTS_SS_CX_TUNING			0xa4
+#define STMFTS5_SET_SCAN_MODE			0xa0
+
+/* FTS5 scan mode commands */
+#define STMFTS5_SCAN_MODE_ACTIVE		0x00
+#define STMFTS5_SCAN_ACTIVE_MULTI_TOUCH		BIT(0)
 
 /* events */
 #define STMFTS_EV_NO_EVENT			0x00
 #define STMFTS_EV_MULTI_TOUCH_DETECTED		0x02
 #define STMFTS_EV_MULTI_TOUCH_ENTER		0x03
 #define STMFTS_EV_MULTI_TOUCH_LEAVE		0x04
 #define STMFTS_EV_MULTI_TOUCH_MOTION		0x05
 #define STMFTS_EV_HOVER_ENTER			0x07
@@ -47,22 +56,48 @@
 #define STMFTS_EV_HOVER_MOTION			0x09
 #define STMFTS_EV_KEY_STATUS			0x0e
 #define STMFTS_EV_ERROR				0x0f
 #define STMFTS_EV_CONTROLLER_READY		0x10
 #define STMFTS_EV_SLEEP_OUT_CONTROLLER_READY	0x11
 #define STMFTS_EV_STATUS			0x16
 #define STMFTS_EV_DEBUG				0xdb
 
+/*
+ * FTS5 events: the event ID is in the high nibble of event[0], the low
+ * nibble carries additional payload (bits 5:4 of the touch major axis for
+ * touch events), so it must be masked before comparing.
+ */
+#define STMFTS5_EV_CONTROLLER_READY		0x03
+#define STMFTS5_EV_MULTI_TOUCH_ENTER		0x10
+#define STMFTS5_EV_MULTI_TOUCH_MOTION		0x20
+#define STMFTS5_EV_MULTI_TOUCH_LEAVE		0x30
+#define STMFTS5_EV_STATUS_UPDATE		0x40
+#define STMFTS5_EV_USER_REPORT			0x50
+#define STMFTS5_EV_DEBUG			0xe0
+#define STMFTS5_EV_ERROR			0xf0
+
 /* multi touch related event masks */
 #define STMFTS_MASK_EVENT_ID			0x0f
 #define STMFTS_MASK_TOUCH_ID			0xf0
 #define STMFTS_MASK_LEFT_EVENT			0x0f
 #define STMFTS_MASK_X_MSB			0x0f
 #define STMFTS_MASK_Y_LSB			0xf0
+#define STMFTS5_MASK_EVENT_ID			0xf0
+#define STMFTS5_MASK_TOUCH_TYPE			0x0f
+#define STMFTS5_MASK_MAJOR_MSB			0x0c
+#define STMFTS5_MASK_MINOR_MSB			0xc0
+
+/* touch type classifications */
+#define STMFTS_TOUCH_TYPE_INVALID		0x00
+#define STMFTS_TOUCH_TYPE_FINGER		0x01
+#define STMFTS_TOUCH_TYPE_GLOVE			0x02
+#define STMFTS_TOUCH_TYPE_STYLUS		0x03
+#define STMFTS_TOUCH_TYPE_PALM			0x04
+#define STMFTS_TOUCH_TYPE_HOVER			0x05
 
 /* key related event masks */
 #define STMFTS_MASK_KEY_NO_TOUCH		0x00
 #define STMFTS_MASK_KEY_MENU			0x01
 #define STMFTS_MASK_KEY_BACK			0x02
 
 #define STMFTS_RESET_TIMEOUT_MS	300
 
@@ -73,19 +108,22 @@
 #define STMFTS_DEV_NAME		"stmfts"
 
 static const struct regulator_bulk_data stmfts_supplies[] = {
 	{ .supply = "vdd" },
 	{ .supply = "avdd" },
 };
 
 struct stmfts_data {
+	const struct stmfts_chip_ops *ops;
+
 	struct i2c_client *client;
 	struct input_dev *input;
 	struct gpio_desc *reset_gpio;
+	struct gpio_desc *mode_switch_gpio;
 	struct led_classdev led_cdev;
 	struct mutex mutex;
 
 	struct touchscreen_properties prop;
 
 	struct regulator_bulk_data *supplies;
 
 	/*
@@ -102,19 +140,29 @@ struct stmfts_data {
 
 	u8 data[STMFTS_DATA_MAX_SIZE];
 
 	struct completion cmd_done;
 
 	bool use_key;
 	bool led_status;
 	bool hover_enabled;
+	bool stylus_enabled;
 	bool running;
 };
 
+struct stmfts_chip_ops {
+	int  (*configure)(struct stmfts_data *sdata);
+	int  (*setup_input)(struct stmfts_data *sdata);
+	void (*parse_events)(struct stmfts_data *sdata);
+	int  (*set_scan)(struct stmfts_data *sdata, bool enable);
+	int  (*set_hover)(struct stmfts_data *sdata, bool enable);
+	int  (*runtime_resume)(struct stmfts_data *sdata);
+};
+
 static int stmfts_brightness_set(struct led_classdev *led_cdev,
 				 enum led_brightness value)
 {
 	struct stmfts_data *sdata = container_of(led_cdev,
 					struct stmfts_data, led_cdev);
 	int err;
 
 	if (value != sdata->led_status && sdata->ledvdd) {
@@ -167,16 +215,17 @@ static int stmfts_read_events(struct stmfts_data *sdata)
 
 	ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs));
 	if (ret < 0)
 		return ret;
 
 	return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
 }
 
+/* FTS4 event handling functions */
 static void stmfts_report_contact_event(struct stmfts_data *sdata,
 					const u8 event[])
 {
 	u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4;
 	u16 x = event[1] | ((event[2] & STMFTS_MASK_X_MSB) << 8);
 	u16 y = (event[2] >> 4) | (event[3] << 4);
 	u8 maj = event[4];
 	u8 min = event[5];
@@ -202,16 +251,107 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata,
 	u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4;
 
 	input_mt_slot(sdata->input, slot_id);
 	input_mt_report_slot_inactive(sdata->input);
 
 	input_sync(sdata->input);
 }
 
+/* FTS5 event handling functions */
+static void stmfts5_report_contact_event(struct stmfts_data *sdata,
+					 const u8 event[])
+{
+	/*
+	 * FTS5 touch event format:
+	 * event[0] = event ID (high nibble) | major bits 5:4 (bits 3:2)
+	 * event[1] = touch ID (high nibble) | touch type (low nibble)
+	 * event[2] = X bits 7:0
+	 * event[3] = Y bits 3:0 (high nibble) | X bits 11:8 (low nibble)
+	 * event[4] = Y bits 11:4
+	 * event[5] = pressure
+	 * event[6] = major bits 3:0 (high nibble) | minor bits 3:0 (low nibble)
+	 * event[7] = minor bits 5:4 (bits 7:6)
+	 */
+	u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
+	u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE;
+	u16 x = ((event[3] & STMFTS_MASK_X_MSB) << 8) | event[2];
+	u16 y = (event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4);
+	u8 area = event[5];
+	u8 maj = ((event[0] & STMFTS5_MASK_MAJOR_MSB) << 2) | (event[6] >> 4);
+	u8 min = ((event[7] & STMFTS5_MASK_MINOR_MSB) >> 2) | (event[6] & 0x0f);
+	/* Distance is 0 for touching, max for hovering */
+	int distance = 0;
+	unsigned int tool;
+
+	switch (touch_type) {
+	case STMFTS_TOUCH_TYPE_STYLUS:
+		if (sdata->stylus_enabled) {
+			tool = MT_TOOL_PEN;
+			break;
+		}
+		fallthrough; /* Report as finger if stylus not enabled */
+
+	case STMFTS_TOUCH_TYPE_FINGER:
+	case STMFTS_TOUCH_TYPE_GLOVE:
+		tool = MT_TOOL_FINGER;
+		break;
+
+	case STMFTS_TOUCH_TYPE_PALM:
+		/* Palm touch - report but can be filtered by userspace */
+		tool = MT_TOOL_PALM;
+		break;
+
+	case STMFTS_TOUCH_TYPE_HOVER:
+		tool = MT_TOOL_FINGER;
+		distance = 255;
+		break;
+
+	case STMFTS_TOUCH_TYPE_INVALID:
+	default:
+		dev_warn(&sdata->client->dev,
+			 "invalid touch type %d for slot %d\n",
+			 touch_type, touch_id);
+		return;
+	}
+
+	if (touch_type == STMFTS_TOUCH_TYPE_HOVER) {
+		area = 0;
+	} else if (!area) {
+		/*
+		 * Should not happen for contact events. Set minimum pressure
+		 * to prevent touch from being dropped.
+		 */
+		dev_warn_once(&sdata->client->dev,
+			      "zero pressure on contact event, slot %d\n",
+			      touch_id);
+		area = 1;
+	}
+
+	input_mt_slot(sdata->input, touch_id);
+	input_mt_report_slot_state(sdata->input, tool, true);
+
+	input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
+	input_report_abs(sdata->input, ABS_MT_POSITION_Y, y);
+	input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, maj);
+	input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, min);
+	input_report_abs(sdata->input, ABS_MT_PRESSURE, area);
+	input_report_abs(sdata->input, ABS_MT_DISTANCE, distance);
+}
+
+static void stmfts5_report_contact_release(struct stmfts_data *sdata,
+					   const u8 event[])
+{
+	/* FTM5 format: touch ID is in high 4 bits of event[1] */
+	u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
+
+	input_mt_slot(sdata->input, touch_id);
+	input_mt_report_slot_inactive(sdata->input);
+}
+
 static void stmfts_report_hover_event(struct stmfts_data *sdata,
 				      const u8 event[])
 {
 	u16 x = (event[2] << 4) | (event[4] >> 4);
 	u16 y = (event[3] << 4) | (event[4] & STMFTS_MASK_Y_LSB);
 	u8 z = event[5];
 
 	input_report_abs(sdata->input, ABS_X, x);
@@ -294,29 +434,76 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
 
 		default:
 			dev_err(&sdata->client->dev,
 				"unknown event %#02x\n", event[0]);
 		}
 	}
 }
 
+static void stmfts5_parse_events(struct stmfts_data *sdata)
+{
+	for (int i = 0; i < STMFTS_STACK_DEPTH; i++) {
+		u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
+
+		switch (event[0]) {
+		case STMFTS5_EV_CONTROLLER_READY:
+			complete(&sdata->cmd_done);
+			fallthrough;
+
+		case STMFTS_EV_NO_EVENT:
+			goto sync;
+		}
+
+		switch (event[0] & STMFTS5_MASK_EVENT_ID) {
+		case STMFTS5_EV_STATUS_UPDATE:
+		case STMFTS5_EV_USER_REPORT:
+		case STMFTS5_EV_DEBUG:
+			goto sync;
+
+		case STMFTS5_EV_MULTI_TOUCH_ENTER:
+		case STMFTS5_EV_MULTI_TOUCH_MOTION:
+			stmfts5_report_contact_event(sdata, event);
+			break;
+
+		case STMFTS5_EV_MULTI_TOUCH_LEAVE:
+			stmfts5_report_contact_release(sdata, event);
+			break;
+
+		case STMFTS5_EV_ERROR:
+			dev_warn(&sdata->client->dev,
+				 "error code: 0x%x%x%x%x%x%x",
+				 event[6], event[5], event[4],
+				 event[3], event[2], event[1]);
+			break;
+
+		default:
+			dev_err(&sdata->client->dev,
+				"unknown FTS5 event %#02x\n", event[0]);
+		}
+	}
+
+sync:
+	input_mt_sync_frame(sdata->input);
+	input_sync(sdata->input);
+}
+
 static irqreturn_t stmfts_irq_handler(int irq, void *dev)
 {
 	struct stmfts_data *sdata = dev;
 	int err;
 
 	guard(mutex)(&sdata->mutex);
 
 	err = stmfts_read_events(sdata);
 	if (unlikely(err))
 		dev_err(&sdata->client->dev,
 			"failed to read events: %d\n", err);
 	else
-		stmfts_parse_events(sdata);
+		sdata->ops->parse_events(sdata);
 
 	return IRQ_HANDLED;
 }
 
 static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
 {
 	int err;
 
@@ -328,37 +515,64 @@ static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
 
 	if (!wait_for_completion_timeout(&sdata->cmd_done,
 					 msecs_to_jiffies(1000)))
 		return -ETIMEDOUT;
 
 	return 0;
 }
 
+static int stmfts_set_scan(struct stmfts_data *sdata, bool enable)
+{
+	return i2c_smbus_write_byte(sdata->client,
+				    enable ? STMFTS_MS_MT_SENSE_ON :
+					     STMFTS_MS_MT_SENSE_OFF);
+}
+
+static int stmfts_set_hover(struct stmfts_data *sdata, bool enable)
+{
+	return i2c_smbus_write_byte(sdata->client,
+				    enable ? STMFTS_SS_HOVER_SENSE_ON :
+					     STMFTS_SS_HOVER_SENSE_OFF);
+}
+
+static int stmfts5_set_scan(struct stmfts_data *sdata, bool enable)
+{
+	u8 scan_mode_cmd[3] = { STMFTS5_SET_SCAN_MODE, STMFTS5_SCAN_MODE_ACTIVE,
+				enable ? STMFTS5_SCAN_ACTIVE_MULTI_TOUCH : 0 };
+	int err;
+
+	err = i2c_master_send(sdata->client, scan_mode_cmd,
+			      sizeof(scan_mode_cmd));
+	if (err != sizeof(scan_mode_cmd))
+		return err < 0 ? err : -EIO;
+
+	return 0;
+}
+
 static int stmfts_input_open(struct input_dev *dev)
 {
 	struct stmfts_data *sdata = input_get_drvdata(dev);
 	int err;
 
 	err = pm_runtime_resume_and_get(&sdata->client->dev);
 	if (err)
 		return err;
 
-	err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_ON);
+	err = sdata->ops->set_scan(sdata, true);
 	if (err) {
 		pm_runtime_put_sync(&sdata->client->dev);
 		return err;
 	}
 
 	scoped_guard(mutex, &sdata->mutex) {
 		sdata->running = true;
 
-		if (sdata->hover_enabled) {
-			err = i2c_smbus_write_byte(sdata->client,
-						   STMFTS_SS_HOVER_SENSE_ON);
+		if (sdata->hover_enabled && sdata->ops->set_hover) {
+			err = sdata->ops->set_hover(sdata, true);
 			if (err)
 				dev_warn(&sdata->client->dev,
 					 "failed to enable hover\n");
 		}
 	}
 
 	if (sdata->use_key) {
 		err = i2c_smbus_write_byte(sdata->client,
@@ -372,27 +586,26 @@ static int stmfts_input_open(struct input_dev *dev)
 	return 0;
 }
 
 static void stmfts_input_close(struct input_dev *dev)
 {
 	struct stmfts_data *sdata = input_get_drvdata(dev);
 	int err;
 
-	err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_OFF);
+	err = sdata->ops->set_scan(sdata, false);
 	if (err)
 		dev_warn(&sdata->client->dev,
 			 "failed to disable touchscreen: %d\n", err);
 
 	scoped_guard(mutex, &sdata->mutex) {
 		sdata->running = false;
 
-		if (sdata->hover_enabled) {
-			err = i2c_smbus_write_byte(sdata->client,
-						   STMFTS_SS_HOVER_SENSE_OFF);
+		if (sdata->hover_enabled && sdata->ops->set_hover) {
+			err = sdata->ops->set_hover(sdata, false);
 			if (err)
 				dev_warn(&sdata->client->dev,
 					 "failed to disable hover: %d\n", err);
 		}
 	}
 
 	if (sdata->use_key) {
 		err = i2c_smbus_write_byte(sdata->client,
@@ -484,20 +697,18 @@ static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev,
 	if (kstrtoul(buf, 0, &value))
 		return -EINVAL;
 
 	hover = !!value;
 
 	guard(mutex)(&sdata->mutex);
 
 	if (hover != sdata->hover_enabled) {
-		if (sdata->running) {
-			err = i2c_smbus_write_byte(sdata->client,
-						   value ? STMFTS_SS_HOVER_SENSE_ON :
-							   STMFTS_SS_HOVER_SENSE_OFF);
+		if (sdata->running && sdata->ops->set_hover) {
+			err = sdata->ops->set_hover(sdata, hover);
 			if (err)
 				return err;
 		}
 
 		sdata->hover_enabled = hover;
 	}
 
 	return len;
@@ -560,16 +771,20 @@ static int stmfts_reset(struct stmfts_data *sdata)
 
 	return 0;
 }
 
 static int stmfts_configure(struct stmfts_data *sdata)
 {
 	int err;
 
+	err = stmfts_read_system_info(sdata);
+	if (err)
+		return err;
+
 	err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
 	if (err)
 		return err;
 
 	err = stmfts_command(sdata, STMFTS_SLEEP_OUT);
 	if (err)
 		return err;
 
@@ -614,21 +829,17 @@ static int stmfts_power_on(struct stmfts_data *sdata)
 				"controller not ready after reset: %d\n", err);
 			goto err_disable_irq;
 		}
 	} else {
 		enable_irq(sdata->client->irq);
 		msleep(50);
 	}
 
-	err = stmfts_read_system_info(sdata);
-	if (err)
-		goto err_disable_irq;
-
-	err = stmfts_configure(sdata);
+	err = sdata->ops->configure(sdata);
 	if (err)
 		goto err_disable_irq;
 
 	/*
 	 * At this point no one is using the touchscreen
 	 * and I don't really care about the return value
 	 */
 	(void)i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
@@ -637,28 +848,85 @@ static int stmfts_power_on(struct stmfts_data *sdata)
 
 err_disable_irq:
 	disable_irq(sdata->client->irq);
 
 	regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
 	return err;
 }
 
+static int stmfts5_configure(struct stmfts_data *sdata)
+{
+	u8 event[STMFTS_EVENT_SIZE];
+	int err;
+
+	/* Verify I2C communication */
+	err = i2c_smbus_read_i2c_block_data(sdata->client,
+					    STMFTS_READ_ALL_EVENT,
+					    sizeof(event), event);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
 static void stmfts_power_off(void *data)
 {
 	struct stmfts_data *sdata = data;
 
 	disable_irq(sdata->client->irq);
 
 	if (sdata->reset_gpio)
 		gpiod_set_value_cansleep(sdata->reset_gpio, 1);
 
 	regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
 }
 
+static int stmfts_setup_input(struct stmfts_data *sdata)
+{
+	struct device *dev = &sdata->client->dev;
+
+	input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
+	input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
+
+	sdata->use_key = device_property_read_bool(dev, "touch-key-connected");
+	if (sdata->use_key) {
+		input_set_capability(sdata->input, EV_KEY, KEY_MENU);
+		input_set_capability(sdata->input, EV_KEY, KEY_BACK);
+	}
+
+	return input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
+				   INPUT_MT_DIRECT);
+}
+
+static int stmfts5_setup_input(struct stmfts_data *sdata)
+{
+	struct device *dev = &sdata->client->dev;
+
+	sdata->mode_switch_gpio = devm_gpiod_get_optional(dev, "mode-switch",
+							  GPIOD_OUT_HIGH);
+	if (IS_ERR(sdata->mode_switch_gpio))
+		return dev_err_probe(dev, PTR_ERR(sdata->mode_switch_gpio),
+				     "Failed to get GPIO 'switch'\n");
+
+	/* Set resolution for accurate calibration */
+	if (!input_abs_get_res(sdata->input, ABS_MT_POSITION_X)) {
+		input_abs_set_res(sdata->input, ABS_MT_POSITION_X, 10);
+		input_abs_set_res(sdata->input, ABS_MT_POSITION_Y, 10);
+	}
+
+	input_set_abs_params(sdata->input, ABS_MT_DISTANCE, 0, 255, 0, 0);
+
+	/* Enable stylus support if requested */
+	sdata->stylus_enabled = device_property_read_bool(dev, "stylus-enabled");
+
+	return input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
+				   INPUT_MT_DIRECT);
+}
+
 static int stmfts_enable_led(struct stmfts_data *sdata)
 {
 	int err;
 
 	/* get the regulator for powering the leds on */
 	sdata->ledvdd = devm_regulator_get(&sdata->client->dev, "ledvdd");
 	if (IS_ERR(sdata->ledvdd))
 		return PTR_ERR(sdata->ledvdd);
@@ -694,16 +962,18 @@ static int stmfts_probe(struct i2c_client *client)
 		return -ENOMEM;
 
 	i2c_set_clientdata(client, sdata);
 
 	sdata->client = client;
 	mutex_init(&sdata->mutex);
 	init_completion(&sdata->cmd_done);
 
+	sdata->ops = i2c_get_match_data(client);
+
 	err = devm_regulator_bulk_get_const(dev,
 					    ARRAY_SIZE(stmfts_supplies),
 					    stmfts_supplies,
 					    &sdata->supplies);
 	if (err)
 		return err;
 
 	sdata->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
@@ -721,28 +991,19 @@ static int stmfts_probe(struct i2c_client *client)
 	sdata->input->close = stmfts_input_close;
 
 	input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_X);
 	input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_Y);
 	touchscreen_parse_properties(sdata->input, true, &sdata->prop);
 
 	input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
 	input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
-	input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
 	input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
-	input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
-
-	sdata->use_key = device_property_read_bool(dev, "touch-key-connected");
-	if (sdata->use_key) {
-		input_set_capability(sdata->input, EV_KEY, KEY_MENU);
-		input_set_capability(sdata->input, EV_KEY, KEY_BACK);
-	}
 
-	err = input_mt_init_slots(sdata->input,
-				  STMFTS_MAX_FINGERS, INPUT_MT_DIRECT);
+	err = sdata->ops->setup_input(sdata);
 	if (err)
 		return err;
 
 	input_set_drvdata(sdata->input, sdata);
 
 	/*
 	 * stmfts_power_on expects interrupt to be disabled, but
 	 * at this point the device is still off and I do not trust
@@ -803,27 +1064,71 @@ static int stmfts_runtime_suspend(struct device *dev)
 
 	ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
 	if (ret)
 		dev_warn(dev, "failed to suspend device: %d\n", ret);
 
 	return ret;
 }
 
+static int stmfts_chip_runtime_resume(struct stmfts_data *sdata)
+{
+	return i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_OUT);
+}
+
+static int stmfts5_chip_runtime_resume(struct stmfts_data *sdata)
+{
+	struct i2c_client *client = sdata->client;
+	struct device *dev = &client->dev;
+	u8 int_enable_cmd[4] = { 0xB6, 0x00, 0x2C, 0x01 };
+	int err;
+
+	err = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
+	if (err)
+		return err;
+
+	msleep(20);
+
+	/* Perform capacitance tuning after wakeup */
+	err = i2c_smbus_write_byte(client, STMFTS_MS_CX_TUNING);
+	if (err)
+		dev_warn(dev, "MS_CX_TUNING failed: %d\n", err);
+	msleep(20);
+
+	err = i2c_smbus_write_byte(client, STMFTS_SS_CX_TUNING);
+	if (err)
+		dev_warn(dev, "SS_CX_TUNING failed: %d\n", err);
+	msleep(20);
+
+	/* Force calibration */
+	err = i2c_smbus_write_byte(client, STMFTS_FULL_FORCE_CALIBRATION);
+	if (err)
+		dev_warn(dev, "FORCE_CALIBRATION failed: %d\n", err);
+	msleep(50);
+
+	/* Enable controller interrupts */
+	err = i2c_master_send(client, int_enable_cmd, sizeof(int_enable_cmd));
+	if (err != sizeof(int_enable_cmd))
+		return err < 0 ? err : -EIO;
+
+	msleep(20);
+
+	return 0;
+}
+
 static int stmfts_runtime_resume(struct device *dev)
 {
 	struct stmfts_data *sdata = dev_get_drvdata(dev);
-	struct i2c_client *client = sdata->client;
-	int ret;
+	int err;
 
-	ret = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
-	if (ret)
-		dev_err(dev, "failed to resume device: %d\n", ret);
+	err = sdata->ops->runtime_resume(sdata);
+	if (err)
+		dev_err(dev, "failed to resume device: %d\n", err);
 
-	return ret;
+	return err;
 }
 
 static int stmfts_suspend(struct device *dev)
 {
 	struct stmfts_data *sdata = dev_get_drvdata(dev);
 
 	stmfts_power_off(sdata);
 
@@ -837,26 +1142,45 @@ static int stmfts_resume(struct device *dev)
 	return stmfts_power_on(sdata);
 }
 
 static const struct dev_pm_ops stmfts_pm_ops = {
 	SYSTEM_SLEEP_PM_OPS(stmfts_suspend, stmfts_resume)
 	RUNTIME_PM_OPS(stmfts_runtime_suspend, stmfts_runtime_resume, NULL)
 };
 
+static const struct stmfts_chip_ops stmfts4_ops = {
+	.configure	= stmfts_configure,
+	.setup_input	= stmfts_setup_input,
+	.parse_events	= stmfts_parse_events,
+	.set_scan	= stmfts_set_scan,
+	.set_hover	= stmfts_set_hover,
+	.runtime_resume	= stmfts_chip_runtime_resume,
+};
+
+static const struct stmfts_chip_ops stmfts5_ops = {
+	.configure	= stmfts5_configure,
+	.setup_input	= stmfts5_setup_input,
+	.parse_events	= stmfts5_parse_events,
+	.set_scan	= stmfts5_set_scan,
+	.runtime_resume	= stmfts5_chip_runtime_resume,
+};
+
 #ifdef CONFIG_OF
 static const struct of_device_id stmfts_of_match[] = {
-	{ .compatible = "st,stmfts", },
+	{ .compatible = "st,stmfts",	.data = &stmfts4_ops },
+	{ .compatible = "st,stmfts5",	.data = &stmfts5_ops },
 	{ },
 };
 MODULE_DEVICE_TABLE(of, stmfts_of_match);
 #endif
 
 static const struct i2c_device_id stmfts_id[] = {
-	{ .name = "stmfts" },
+	{ .name = "stmfts",	.driver_data = (kernel_ulong_t)&stmfts4_ops },
+	{ .name = "stmfts5",	.driver_data = (kernel_ulong_t)&stmfts5_ops },
 	{ }
 };
 MODULE_DEVICE_TABLE(i2c, stmfts_id);
 
 static struct i2c_driver stmfts_driver = {
 	.driver = {
 		.name = STMFTS_DEV_NAME,
 		.dev_groups = stmfts_sysfs_groups,
@@ -867,10 +1191,12 @@ static struct i2c_driver stmfts_driver = {
 	.probe = stmfts_probe,
 	.remove = stmfts_remove,
 	.id_table = stmfts_id,
 };
 
 module_i2c_driver(stmfts_driver);
 
 MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>");
+MODULE_AUTHOR("David Heidelberg <david@ixit.cz>");
+MODULE_AUTHOR("Petr Hodina <petr.hodina@protonmail.com>");
 MODULE_DESCRIPTION("STMicroelectronics FTS Touch Screen");
 MODULE_LICENSE("GPL");

-- 
2.55.0



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

* [PATCH v7 4/4] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support
  2026-09-07 10:50 [PATCH v7 0/4] Input: support for STM FTS5 David Heidelberg via B4 Relay
                   ` (2 preceding siblings ...)
  2026-09-07 10:50 ` [PATCH v7 3/4] Input: stmfts - support FTS5 David Heidelberg via B4 Relay
@ 2026-09-07 10:50 ` David Heidelberg via B4 Relay
  2026-09-07 10:58   ` Abel Vesa
  3 siblings, 1 reply; 9+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-07 10:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio
  Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
	linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
	phone-devel, David Heidelberg, Konrad Dybcio, Dmitry Baryshkov

From: Petr Hodina <petr.hodina@protonmail.com>

Basic touchscreen connected to second i2c bus.

Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
 arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts | 19 ++++++++++++++++++-
 arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi  |  2 +-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts b/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
index fa89be500fb85..51c835dca43f1 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
+++ b/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
@@ -21,17 +21,34 @@ &framebuffer0 {
 	stride = <(1080 * 4)>;
 };
 
 &i2c2 {
 	clock-frequency = <1000000>;
 
 	status = "okay";
 
-	/* ST,FTS @ 49 */
+	touchscreen@49 {
+		compatible = "st,stmfts5";
+		reg = <0x49>;
+
+		pinctrl-0 = <&touchscreen_irq_n>, <&touchscreen_reset>;
+		pinctrl-names = "default";
+
+		interrupts-extended = <&tlmm 125 IRQ_TYPE_LEVEL_LOW>;
+
+		mode-switch-gpios = <&tlmm 136 GPIO_ACTIVE_HIGH>;
+		reset-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>;
+
+		avdd-supply = <&vreg_l19a_3p3>;
+		vdd-supply = <&vreg_l14a_1p8>;
+
+		touchscreen-size-x = <1080>;
+		touchscreen-size-y = <2160>;
+	};
 };
 
 &mdss_dsi0 {
 	vdda-supply = <&vdda_mipi_dsi0_1p2>;
 
 	status = "okay";
 
 	panel@0 {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi
index 9e952f9862f24..c9db3ea50f5ed 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi
@@ -492,17 +492,17 @@ nfc_enable_default: nfc-enable-default-state {
 
 	touchscreen_reset: ts-reset-state {
 		pins = "gpio99";
 		function = "gpio";
 		drive-strength = <8>;
 		bias-pull-up;
 	};
 
-	touchscreen_pins: ts-pins-gpio-state {
+	touchscreen_irq_n: ts-irq-n-gpio-state {
 		pins = "gpio125";
 		function = "gpio";
 		drive-strength = <2>;
 		bias-disable;
 	};
 
 	touchscreen_i2c_pins: qup-i2c2-gpio-state {
 		pins = "gpio27", "gpio28";

-- 
2.55.0



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

* Re: [PATCH v7 4/4] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support
  2026-09-07 10:50 ` [PATCH v7 4/4] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support David Heidelberg via B4 Relay
@ 2026-09-07 10:58   ` Abel Vesa
  0 siblings, 0 replies; 9+ messages in thread
From: Abel Vesa @ 2026-09-07 10:58 UTC (permalink / raw)
  To: david
  Cc: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio, Petr Hodina, linux-input,
	linux-stm32, linux-arm-kernel, linux-kernel, Krzysztof Kozlowski,
	devicetree, linux-arm-msm, phone-devel, Konrad Dybcio,
	Dmitry Baryshkov

On 26-09-07 12:50:22, David Heidelberg via B4 Relay wrote:
> From: Petr Hodina <petr.hodina@protonmail.com>
> 
> Basic touchscreen connected to second i2c bus.
> 
> Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> Co-developed-by: David Heidelberg <david@ixit.cz>
> Signed-off-by: David Heidelberg <david@ixit.cz>

Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>

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

* Re: [PATCH v7 2/4] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
  2026-09-07 10:50 ` [PATCH v7 2/4] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 David Heidelberg via B4 Relay
@ 2026-09-07 11:00   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-07 11:00 UTC (permalink / raw)
  To: David Heidelberg; +Cc: robh, linux-input, conor+dt, devicetree, dmitry.torokhov

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The conditional block restricting 'mode-switch-gpios' is missing the 'contains' keyword under 'compatible', which will cause it to erroneously evaluate to false for all devices and block the use of 'mode-switch-gpios' entirely.
--

commit 30e556decedf2eedf468c2bb05ec4af06f53437e
Author: David Heidelberg <david@ixit.cz>

dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5

This commit introduces support for the STM FTS5 touchscreen variant to the
existing st,stmfts device tree binding. It adds the new compatible string
and introduces an optional mode-switch-gpios property specific to the FTS5
variant.

> diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> index 64c4f24ea3dd0..3de4bc5cd3f78 100644
> --- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> +++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
[ ... ]
> @@ -51,6 +54,17 @@ required:
>    - interrupts
>    - vdd-supply
>  
> +allOf:
> +  - $ref: touchscreen.yaml#
> +  - if:
> +      not:
> +        properties:
> +          compatible:
> +            const: st,stmfts5

[Severity: Medium]
Does this compatible string check need a contains directive? Because the
compatible property is globally evaluated as an array of strings in DT
schemas, using a strict const match here will always fail.

As a result, the not wrapper will always evaluate to true, unconditionally
applying the mode-switch-gpios: false restriction to all nodes, including
st,stmfts5. Valid device trees using st,stmfts5 with mode-switch-gpios
will likely fail dt_binding_check.

Should this be written as contains: const: st,stmfts5 instead?

> +    then:
> +      properties:
> +        mode-switch-gpios: false
> +
>  unevaluatedProperties: false

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260907-stmfts5-v7-0-4a53381cd038@ixit.cz?part=2

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

* Re: [PATCH v7 3/4] Input: stmfts - support FTS5
  2026-09-07 10:50 ` [PATCH v7 3/4] Input: stmfts - support FTS5 David Heidelberg via B4 Relay
@ 2026-09-07 11:04   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-07 11:04 UTC (permalink / raw)
  To: David Heidelberg; +Cc: conor+dt, robh, devicetree, dmitry.torokhov, linux-input

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [Medium] The FTS5 driver silently drops all stylus and palm tool-type events because the ABS_MT_TOOL_TYPE capability is never configured on the input device.
- [Medium] Prematurely exiting the event parsing loop on non-terminal events can cause dropped touch events and partial frames.

Pre-existing issues:
- [High] sysfs attribute returns uninitialized kernel memory due to incorrect error check.
--

commit aa502b398bf6eeb31cabf6ce200968e3d20a68a7
Author: David Heidelberg <david@ixit.cz>
Subject: Input: stmfts - support FTS5

This commit introduces support for the FTS5 protocol in the stmfts
touchscreen driver. It adds FTS5-specific functions for device configuration,
input setup (including initial stylus handling), and event parsing.

This is a pre-existing issue, but while reviewing I noticed a potential
memory leak in the sysfs read handler stmfts_sysfs_read_status() that
was not introduced by this patch:

drivers/input/touchscreen/stmfts.c:stmfts_sysfs_read_status() {
    ...
    err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_STATUS,
                                        sizeof(status), status);
    if (err)
        return err;

    return sysfs_emit(buf, "%#02x\n", status[0]);
}

[Severity: High]
Does this error check leak uninitialized memory? The function
i2c_smbus_read_i2c_block_data() returns the positive number of bytes read
on success. By treating any non-zero return value as an error, a successful
read will immediately return this positive byte count to the sysfs core,
bypassing sysfs_emit() and potentially leaking up to 4 bytes of uninitialized
kernel memory to userspace.

> diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
> --- a/drivers/input/touchscreen/stmfts.c
> +++ b/drivers/input/touchscreen/stmfts.c
[ ... ]
> +static void stmfts5_report_contact_event(struct stmfts_data *sdata,
> +					 const u8 event[])
> +{
[ ... ]
> +	switch (touch_type) {
> +	case STMFTS_TOUCH_TYPE_STYLUS:
> +		if (sdata->stylus_enabled) {
> +			tool = MT_TOOL_PEN;
> +			break;
> +		}
> +		fallthrough; /* Report as finger if stylus not enabled */
[ ... ]
> +	input_mt_slot(sdata->input, touch_id);
> +	input_mt_report_slot_state(sdata->input, tool, true);
[ ... ]
> +static void stmfts5_parse_events(struct stmfts_data *sdata)
> +{
> +	for (int i = 0; i < STMFTS_STACK_DEPTH; i++) {
> +		u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
[ ... ]
> +		switch (event[0] & STMFTS5_MASK_EVENT_ID) {
> +		case STMFTS5_EV_STATUS_UPDATE:
> +		case STMFTS5_EV_USER_REPORT:
> +		case STMFTS5_EV_DEBUG:
> +			goto sync;

[Severity: Medium]
Could this premature loop exit cause subsequent touch events in the same
buffer to be dropped?

If STMFTS5_EV_STATUS_UPDATE, STMFTS5_EV_USER_REPORT, or STMFTS5_EV_DEBUG 
occurs alongside standard touch events in a single read buffer, the 
goto sync; statement will break out of the event parsing loop entirely. 
Would using continue be more appropriate here to skip the non-touch event 
and continue processing the rest of the buffer?

> +
> +		case STMFTS5_EV_MULTI_TOUCH_ENTER:
> +		case STMFTS5_EV_MULTI_TOUCH_MOTION:
> +			stmfts5_report_contact_event(sdata, event);
> +			break;
[ ... ]
> +static int stmfts5_setup_input(struct stmfts_data *sdata)
> +{
[ ... ]
> +	input_set_abs_params(sdata->input, ABS_MT_DISTANCE, 0, 255, 0, 0);
> +
> +	/* Enable stylus support if requested */
> +	sdata->stylus_enabled = device_property_read_bool(dev, "stylus-enabled");
> +
> +	return input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
> +				   INPUT_MT_DIRECT);
> +}

[Severity: Medium]
Does this driver need to configure the ABS_MT_TOOL_TYPE capability for
stylus and palm events to be processed?

In stmfts5_report_contact_event() above, the tool type is reported to the
input core. However, because input_set_abs_params() or input_set_capability()
is not called for ABS_MT_TOOL_TYPE here in stmfts5_setup_input(), the input 
subsystem will silently drop these tool type events, degrading the hardware 
functionality to basic finger touches.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260907-stmfts5-v7-0-4a53381cd038@ixit.cz?part=3

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

* Re: [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset
  2026-09-07 10:50 ` [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset David Heidelberg via B4 Relay
@ 2026-09-07 11:06   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-07 11:06 UTC (permalink / raw)
  To: David Heidelberg; +Cc: devicetree, robh, linux-input, conor+dt, dmitry.torokhov

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

Pre-existing issues:
- [High] Unbalanced disable_irq() and regulator_bulk_disable() on consecutive suspend/resume failures.
- [High] System resume fails to restore the touchscreen's active state, leaving the hardware locked in SLEEP_IN mode if it was in use prior to suspend.
- [High] The driver fragments multi-touch frames by calling `input_sync()` after parsing every individual contact event.
- [High] Information leak of uninitialized kernel stack memory via the 'status' sysfs attribute, and functional bug where valid status reads return uninitialized buffer data.
--

commit 1a68dcc09b6690980cb8297285fc328a7de4c568
Author: David Heidelberg <david@ixit.cz>

Input: stmfts - wait for controller ready after reset

After releasing the reset line stmfts_reset() sleeps a fixed 50 ms and
stmfts_power_on() another 50 ms before the first I2C access.  That is
enough for a warm reset, but when both supplies were really cut during
system suspend the controller boots from cold and can need longer.  If
it does, the first read fails, stmfts_resume() returns an error and the
touchscreen is left powered off with its interrupt disabled.

> diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
> index 972687797f826..711e3ad8587a0 100644
> --- a/drivers/input/touchscreen/stmfts.c
> +++ b/drivers/input/touchscreen/stmfts.c

[ ... ]

> @@ -598,16 +607,21 @@ static int stmfts_power_on(struct stmfts_data *sdata)
>  	 */
>  	msleep(20);
>  
> -	if (sdata->reset_gpio)
> -		stmfts_reset(sdata);
> +	if (sdata->reset_gpio) {
> +		err = stmfts_reset(sdata);
> +		if (err) {
> +			dev_err(&sdata->client->dev,
> +				"controller not ready after reset: %d\n", err);
> +			goto err_disable_irq;
> +		}
> +	} else {
> +		enable_irq(sdata->client->irq);
> +		msleep(50);
> +	}

[Severity: High]
This is a pre-existing issue, but if system resume fails here in
stmfts_reset(), stmfts_power_on() cleans up and returns an error. On the next
suspend cycle, won't stmfts_suspend() unconditionally call stmfts_power_off()
again?

drivers/input/touchscreen/stmfts.c:stmfts_suspend() {
    ...
	stmfts_power_off(sdata);
    ...
}

And won't this increment the IRQ disable depth to 2 and unbalanced regulator
usage counts, eventually causing the touchscreen to become permanently
unresponsive?


[Severity: High]
This is a pre-existing issue, but stmfts_power_on() ends with a command to put
the controller to sleep:

drivers/input/touchscreen/stmfts.c:stmfts_power_on() {
    ...
	/*
	 * At this point no one is using the touchscreen
	 * and I don't really care about the return value
	 */
	(void)i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);

	return 0;
    ...
}

If the touchscreen was held open by userspace (sdata->running) prior to
suspend, won't this leave the hardware asleep upon system resume? Since the PM
core does not automatically invoke runtime_resume, does the driver need to
check sdata->running and manually wake the device here?


[Severity: High]
This is a pre-existing issue, but is it intentional to call input_sync() for
every individual contact event here?

drivers/input/touchscreen/stmfts.c:stmfts_report_contact_event() {
    ...
	input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true);
	input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
    ...
	input_sync(sdata->input);
}

Since this is called for each finger during multi-finger touch frames, doesn't
this fragment multi-touch frames by emitting a sync after each finger rather
than grouping them at the end of the event parsing loop?


[Severity: High]
This is a pre-existing issue, but is the return value of
i2c_smbus_read_i2c_block_data() handled correctly here?

drivers/input/touchscreen/stmfts.c:stmfts_sysfs_read_status() {
    ...
	err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_STATUS,
					    sizeof(status), status);
	if (err)
		return err;

	return sysfs_emit(buf, "%#02x\n", status[0]);
}

Since i2c_smbus_read_i2c_block_data() returns the number of bytes read on
success (e.g. 4), won't "if (err)" evaluate to true and return the byte count
directly, bypassing sysfs_emit()? 

Additionally, if it were to return 0, wouldn't sysfs_emit() leak uninitialized
kernel stack memory (status[0]) to userspace?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260907-stmfts5-v7-0-4a53381cd038@ixit.cz?part=1

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

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

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-07 10:50 [PATCH v7 0/4] Input: support for STM FTS5 David Heidelberg via B4 Relay
2026-09-07 10:50 ` [PATCH v7 1/4] Input: stmfts - wait for controller ready after reset David Heidelberg via B4 Relay
2026-09-07 11:06   ` sashiko-bot
2026-09-07 10:50 ` [PATCH v7 2/4] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 David Heidelberg via B4 Relay
2026-09-07 11:00   ` sashiko-bot
2026-09-07 10:50 ` [PATCH v7 3/4] Input: stmfts - support FTS5 David Heidelberg via B4 Relay
2026-09-07 11:04   ` sashiko-bot
2026-09-07 10:50 ` [PATCH v7 4/4] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support David Heidelberg via B4 Relay
2026-09-07 10:58   ` Abel Vesa

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