* [PATCH v5 0/3] Input: support for STM FTS5
@ 2026-08-10 15:21 ` David Heidelberg
0 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-08-10 15:21 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
Used on various phones. Minimal viable driver.
Includes device-tree enabling touchscreen on Pixel 3.
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 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
---
David Heidelberg (2):
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 | 430 +++++++++++++++++++--
4 files changed, 436 insertions(+), 44 deletions(-)
---
base-commit: 95d6a9ccef99117115e41e9adb271243bd5e985b
change-id: 20260214-stmfts5-b47311fbd732
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v5 0/3] Input: support for STM FTS5 @ 2026-08-10 15:21 ` David Heidelberg 0 siblings, 0 replies; 11+ messages in thread From: David Heidelberg @ 2026-08-10 15:21 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 Used on various phones. Minimal viable driver. Includes device-tree enabling touchscreen on Pixel 3. 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 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 --- David Heidelberg (2): 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 | 430 +++++++++++++++++++-- 4 files changed, 436 insertions(+), 44 deletions(-) --- base-commit: 95d6a9ccef99117115e41e9adb271243bd5e985b change-id: 20260214-stmfts5-b47311fbd732 Best regards, -- David Heidelberg <david@ixit.cz> ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 1/3] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 2026-08-10 15:21 ` David Heidelberg @ 2026-08-10 15:21 ` David Heidelberg -1 siblings, 0 replies; 11+ messages in thread From: David Heidelberg via B4 Relay @ 2026-08-10 15:21 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] 11+ messages in thread
* [PATCH v5 1/3] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 @ 2026-08-10 15:21 ` David Heidelberg 0 siblings, 0 replies; 11+ messages in thread From: David Heidelberg @ 2026-08-10 15:21 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 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] 11+ messages in thread
* Re: [PATCH v5 1/3] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 2026-08-10 15:21 ` David Heidelberg (?) @ 2026-08-10 15:30 ` sashiko-bot -1 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-10 15:30 UTC (permalink / raw) To: David Heidelberg; +Cc: conor+dt, linux-input, devicetree, dmitry.torokhov, robh Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Schema validation logic incorrectly disables the `mode-switch-gpios` property for all compatible strings due to a missing `contains:` keyword. -- --- Patch [1]: [PATCH v5 1/3] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 --- commit 38a7826302d5c029e7cdf3f5bbdf93b5ab26d21e Author: David Heidelberg <david@ixit.cz> dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 This commit introduces support for the more recent STM FTS5 touchscreen in the device tree bindings. It adds the new compatible string and an optional mode-switch-gpios property for 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: Low] Does this conditional logic work as intended for a string array? Since the compatible property evaluates as a JSON array of strings in dt-schema, comparing it directly using const: st,stmfts5 will always evaluate to false. Because this is wrapped in a not: block, the overall condition becomes true for all nodes, meaning mode-switch-gpios: false is applied unconditionally even for the st,stmfts5 compatible. Should this use contains: const: st,stmfts5 instead so that valid device trees using this property are not erroneously rejected during schema checks? > + then: > + properties: > + mode-switch-gpios: false > + > unevaluatedProperties: false -- Sashiko AI review · https://sashiko.dev/#/patchset/20260810-stmfts5-v5-0-0c737d929976@ixit.cz?part=1 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 1/3] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5 2026-08-10 15:21 ` David Heidelberg (?) (?) @ 2026-08-11 7:03 ` Krzysztof Kozlowski -1 siblings, 0 replies; 11+ messages in thread From: Krzysztof Kozlowski @ 2026-08-11 7:03 UTC (permalink / raw) To: David Heidelberg 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, devicetree, linux-arm-msm, phone-devel On Mon, Aug 10, 2026 at 05:21:40PM +0200, David Heidelberg wrote: > 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>; This is not explained in the commit msg. > touch-key-connected; > avdd-supply = <&ldo30_reg>; > vdd-supply = <&ldo31_reg>; > ledvdd-supply = <&ldo33_reg>; > }; > }; > > -- > 2.55.0 > ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 2/3] Input: stmfts - support FTS5 2026-08-10 15:21 ` David Heidelberg @ 2026-08-10 15:21 ` David Heidelberg -1 siblings, 0 replies; 11+ messages in thread From: David Heidelberg via B4 Relay @ 2026-08-10 15:21 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 | 430 ++++++++++++++++++++++++++++++++++--- 1 file changed, 396 insertions(+), 34 deletions(-) diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c index 972687797f826..1da113d7197e6 100644 --- a/drivers/input/touchscreen/stmfts.c +++ b/drivers/input/touchscreen/stmfts.c @@ -1,23 +1,27 @@ // 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/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> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> /* I2C commands */ #define STMFTS_READ_INFO 0x80 #define STMFTS_READ_STATUS 0x84 #define STMFTS_READ_ONE_EVENT 0x85 #define STMFTS_READ_ALL_EVENT 0x86 @@ -30,16 +34,17 @@ #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 /* 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 +52,42 @@ #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 +/* events FTS5 */ +#define STMFTS5_EV_CONTROLLER_READY 0x03 +/* FTM5 event IDs (full byte, not masked) */ +#define STMFTS5_EV_MULTI_TOUCH_ENTER 0x13 +#define STMFTS5_EV_MULTI_TOUCH_MOTION 0x23 +#define STMFTS5_EV_MULTI_TOUCH_LEAVE 0x33 +#define STMFTS5_EV_STATUS_UPDATE 0x43 +#define STMFTS5_EV_USER_REPORT 0x53 +#define STMFTS5_EV_DEBUG 0xe3 +#define STMFTS5_EV_ERROR 0xf3 + /* 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_TOUCH_TYPE 0x0f + +/* 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_EVENT_SIZE 8 #define STMFTS_STACK_DEPTH 32 @@ -71,19 +96,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; /* @@ -100,19 +128,31 @@ 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); + void (*power_off)(struct stmfts_data *sdata); + int (*setup_input)(struct stmfts_data *sdata); + int (*input_open)(struct input_dev *dev); + void (*input_close)(struct input_dev *dev); + void (*parse_events)(struct stmfts_data *sdata); + 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) { @@ -165,16 +205,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]; @@ -200,16 +241,114 @@ 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[]) +{ + u8 area; + u8 maj; + u8 min; + /* FTM5 event format: + * event[0] = event ID (0x13/0x23) + * event[1] = touch type (low 4 bits) | touch ID (high 4 bits) + * event[2] = X LSB + * event[3] = X MSB (low 4 bits) | Y MSB (high 4 bits) + * event[4] = Y LSB + * event[5] = pressure + * event[6] = major (low 4 bits) | minor (high 4 bits) + * event[7] = minor (high 2 bits) + */ + u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4; + u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE; + int x, y, distance; + unsigned int tool = MT_TOOL_FINGER; + + /* Parse coordinates with better precision */ + x = (((int)event[3] & STMFTS_MASK_X_MSB) << 8) | event[2]; + y = ((int)event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4); + + /* Parse pressure - ensure non-zero for active touch */ + area = event[5]; + if (area <= 0 && touch_type != STMFTS_TOUCH_TYPE_HOVER) { + /* 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; + } + + /* Parse touch area with improved bit extraction */ + maj = (((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4)); + min = (((event[7] & 0xC0) >> 2) | (event[6] & 0x0F)); + + /* Distance is 0 for touching, max for hovering */ + distance = 0; + + /* Classify touch type and set appropriate tool and parameters */ + 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; + area = 0; + 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; + } + + 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); @@ -292,29 +431,72 @@ 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: + 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; @@ -326,16 +508,29 @@ 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 stmfts5_set_scan_mode(struct stmfts_data *sdata, const u8 val) +{ + u8 scan_mode_cmd[3] = { STMFTS5_SET_SCAN_MODE, 0x00, val }; + 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; @@ -365,16 +560,37 @@ static int stmfts_input_open(struct input_dev *dev) /* I can still use only the touch screen */ dev_warn(&sdata->client->dev, "failed to enable touchkey\n"); } return 0; } +static int stmfts5_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 = stmfts5_set_scan_mode(sdata, 0xff); + if (err) { + pm_runtime_put_sync(&sdata->client->dev); + return err; + } + + scoped_guard(mutex, &sdata->mutex) + sdata->running = true; + + 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); if (err) dev_warn(&sdata->client->dev, @@ -398,16 +614,32 @@ static void stmfts_input_close(struct input_dev *dev) if (err) dev_warn(&sdata->client->dev, "failed to disable touchkey: %d\n", err); } pm_runtime_put_sync(&sdata->client->dev); } +static void stmfts5_input_close(struct input_dev *dev) +{ + struct stmfts_data *sdata = input_get_drvdata(dev); + int err; + + err = stmfts5_set_scan_mode(sdata, 0x00); + if (err) + dev_warn(&sdata->client->dev, + "failed to disable touchscreen: %d\n", err); + + scoped_guard(mutex, &sdata->mutex) + sdata->running = false; + + pm_runtime_put_sync(&sdata->client->dev); +} + static ssize_t stmfts_sysfs_chip_id(struct device *dev, struct device_attribute *attr, char *buf) { struct stmfts_data *sdata = dev_get_drvdata(dev); return sysfs_emit(buf, "%#x\n", sdata->chip_id); } @@ -482,20 +714,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; @@ -551,16 +781,20 @@ static void stmfts_reset(struct stmfts_data *sdata) gpiod_set_value_cansleep(sdata->reset_gpio, 0); msleep(50); } 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; @@ -596,55 +830,123 @@ static int stmfts_power_on(struct stmfts_data *sdata) * 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); - err = stmfts_read_system_info(sdata); - if (err) - goto err_disable_regulators; - enable_irq(sdata->client->irq); msleep(50); - 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); 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 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 stmfts5_chip_power_off(struct stmfts_data *sdata) +{ + i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN); + msleep(20); +} + 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); + if (sdata->ops->power_off) + sdata->ops->power_off(sdata); + 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_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 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); @@ -680,16 +982,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 = of_device_get_match_data(dev); + 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); @@ -698,37 +1002,28 @@ static int stmfts_probe(struct i2c_client *client) "Failed to get GPIO 'reset'\n"); sdata->input = devm_input_allocate_device(dev); if (!sdata->input) return -ENOMEM; sdata->input->name = STMFTS_DEV_NAME; sdata->input->id.bustype = BUS_I2C; - sdata->input->open = stmfts_input_open; - sdata->input->close = stmfts_input_close; + sdata->input->open = sdata->ops->input_open; + sdata->input->close = sdata->ops->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 @@ -789,27 +1084,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); @@ -824,18 +1163,39 @@ static int stmfts_resume(struct device *dev) } 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) }; #ifdef CONFIG_OF +static const struct stmfts_chip_ops stmfts4_ops = { + .configure = stmfts_configure, + .setup_input = stmfts_setup_input, + .input_open = stmfts_input_open, + .input_close = stmfts_input_close, + .parse_events = stmfts_parse_events, + .set_hover = stmfts_set_hover, + .runtime_resume = stmfts_chip_runtime_resume, +}; + +static const struct stmfts_chip_ops stmfts5_ops = { + .configure = stmfts5_configure, + .power_off = stmfts5_chip_power_off, + .setup_input = stmfts5_setup_input, + .input_open = stmfts5_input_open, + .input_close = stmfts5_input_close, + .parse_events = stmfts5_parse_events, + .runtime_resume = stmfts5_chip_runtime_resume, +}; + 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" }, { } @@ -853,10 +1213,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] 11+ messages in thread
* [PATCH v5 2/3] Input: stmfts - support FTS5 @ 2026-08-10 15:21 ` David Heidelberg 0 siblings, 0 replies; 11+ messages in thread From: David Heidelberg @ 2026-08-10 15:21 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 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 | 430 ++++++++++++++++++++++++++++++++++--- 1 file changed, 396 insertions(+), 34 deletions(-) diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c index 972687797f826..1da113d7197e6 100644 --- a/drivers/input/touchscreen/stmfts.c +++ b/drivers/input/touchscreen/stmfts.c @@ -1,23 +1,27 @@ // 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/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> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> /* I2C commands */ #define STMFTS_READ_INFO 0x80 #define STMFTS_READ_STATUS 0x84 #define STMFTS_READ_ONE_EVENT 0x85 #define STMFTS_READ_ALL_EVENT 0x86 @@ -30,16 +34,17 @@ #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 /* 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 +52,42 @@ #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 +/* events FTS5 */ +#define STMFTS5_EV_CONTROLLER_READY 0x03 +/* FTM5 event IDs (full byte, not masked) */ +#define STMFTS5_EV_MULTI_TOUCH_ENTER 0x13 +#define STMFTS5_EV_MULTI_TOUCH_MOTION 0x23 +#define STMFTS5_EV_MULTI_TOUCH_LEAVE 0x33 +#define STMFTS5_EV_STATUS_UPDATE 0x43 +#define STMFTS5_EV_USER_REPORT 0x53 +#define STMFTS5_EV_DEBUG 0xe3 +#define STMFTS5_EV_ERROR 0xf3 + /* 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_TOUCH_TYPE 0x0f + +/* 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_EVENT_SIZE 8 #define STMFTS_STACK_DEPTH 32 @@ -71,19 +96,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; /* @@ -100,19 +128,31 @@ 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); + void (*power_off)(struct stmfts_data *sdata); + int (*setup_input)(struct stmfts_data *sdata); + int (*input_open)(struct input_dev *dev); + void (*input_close)(struct input_dev *dev); + void (*parse_events)(struct stmfts_data *sdata); + 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) { @@ -165,16 +205,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]; @@ -200,16 +241,114 @@ 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[]) +{ + u8 area; + u8 maj; + u8 min; + /* FTM5 event format: + * event[0] = event ID (0x13/0x23) + * event[1] = touch type (low 4 bits) | touch ID (high 4 bits) + * event[2] = X LSB + * event[3] = X MSB (low 4 bits) | Y MSB (high 4 bits) + * event[4] = Y LSB + * event[5] = pressure + * event[6] = major (low 4 bits) | minor (high 4 bits) + * event[7] = minor (high 2 bits) + */ + u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4; + u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE; + int x, y, distance; + unsigned int tool = MT_TOOL_FINGER; + + /* Parse coordinates with better precision */ + x = (((int)event[3] & STMFTS_MASK_X_MSB) << 8) | event[2]; + y = ((int)event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4); + + /* Parse pressure - ensure non-zero for active touch */ + area = event[5]; + if (area <= 0 && touch_type != STMFTS_TOUCH_TYPE_HOVER) { + /* 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; + } + + /* Parse touch area with improved bit extraction */ + maj = (((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4)); + min = (((event[7] & 0xC0) >> 2) | (event[6] & 0x0F)); + + /* Distance is 0 for touching, max for hovering */ + distance = 0; + + /* Classify touch type and set appropriate tool and parameters */ + 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; + area = 0; + 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; + } + + 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); @@ -292,29 +431,72 @@ 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: + 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; @@ -326,16 +508,29 @@ 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 stmfts5_set_scan_mode(struct stmfts_data *sdata, const u8 val) +{ + u8 scan_mode_cmd[3] = { STMFTS5_SET_SCAN_MODE, 0x00, val }; + 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; @@ -365,16 +560,37 @@ static int stmfts_input_open(struct input_dev *dev) /* I can still use only the touch screen */ dev_warn(&sdata->client->dev, "failed to enable touchkey\n"); } return 0; } +static int stmfts5_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 = stmfts5_set_scan_mode(sdata, 0xff); + if (err) { + pm_runtime_put_sync(&sdata->client->dev); + return err; + } + + scoped_guard(mutex, &sdata->mutex) + sdata->running = true; + + 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); if (err) dev_warn(&sdata->client->dev, @@ -398,16 +614,32 @@ static void stmfts_input_close(struct input_dev *dev) if (err) dev_warn(&sdata->client->dev, "failed to disable touchkey: %d\n", err); } pm_runtime_put_sync(&sdata->client->dev); } +static void stmfts5_input_close(struct input_dev *dev) +{ + struct stmfts_data *sdata = input_get_drvdata(dev); + int err; + + err = stmfts5_set_scan_mode(sdata, 0x00); + if (err) + dev_warn(&sdata->client->dev, + "failed to disable touchscreen: %d\n", err); + + scoped_guard(mutex, &sdata->mutex) + sdata->running = false; + + pm_runtime_put_sync(&sdata->client->dev); +} + static ssize_t stmfts_sysfs_chip_id(struct device *dev, struct device_attribute *attr, char *buf) { struct stmfts_data *sdata = dev_get_drvdata(dev); return sysfs_emit(buf, "%#x\n", sdata->chip_id); } @@ -482,20 +714,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; @@ -551,16 +781,20 @@ static void stmfts_reset(struct stmfts_data *sdata) gpiod_set_value_cansleep(sdata->reset_gpio, 0); msleep(50); } 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; @@ -596,55 +830,123 @@ static int stmfts_power_on(struct stmfts_data *sdata) * 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); - err = stmfts_read_system_info(sdata); - if (err) - goto err_disable_regulators; - enable_irq(sdata->client->irq); msleep(50); - 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); 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 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 stmfts5_chip_power_off(struct stmfts_data *sdata) +{ + i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN); + msleep(20); +} + 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); + if (sdata->ops->power_off) + sdata->ops->power_off(sdata); + 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_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 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); @@ -680,16 +982,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 = of_device_get_match_data(dev); + 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); @@ -698,37 +1002,28 @@ static int stmfts_probe(struct i2c_client *client) "Failed to get GPIO 'reset'\n"); sdata->input = devm_input_allocate_device(dev); if (!sdata->input) return -ENOMEM; sdata->input->name = STMFTS_DEV_NAME; sdata->input->id.bustype = BUS_I2C; - sdata->input->open = stmfts_input_open; - sdata->input->close = stmfts_input_close; + sdata->input->open = sdata->ops->input_open; + sdata->input->close = sdata->ops->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 @@ -789,27 +1084,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); @@ -824,18 +1163,39 @@ static int stmfts_resume(struct device *dev) } 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) }; #ifdef CONFIG_OF +static const struct stmfts_chip_ops stmfts4_ops = { + .configure = stmfts_configure, + .setup_input = stmfts_setup_input, + .input_open = stmfts_input_open, + .input_close = stmfts_input_close, + .parse_events = stmfts_parse_events, + .set_hover = stmfts_set_hover, + .runtime_resume = stmfts_chip_runtime_resume, +}; + +static const struct stmfts_chip_ops stmfts5_ops = { + .configure = stmfts5_configure, + .power_off = stmfts5_chip_power_off, + .setup_input = stmfts5_setup_input, + .input_open = stmfts5_input_open, + .input_close = stmfts5_input_close, + .parse_events = stmfts5_parse_events, + .runtime_resume = stmfts5_chip_runtime_resume, +}; + 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" }, { } @@ -853,10 +1213,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] 11+ messages in thread
* Re: [PATCH v5 2/3] Input: stmfts - support FTS5 2026-08-10 15:21 ` David Heidelberg (?) @ 2026-08-10 15:44 ` sashiko-bot -1 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-10 15:44 UTC (permalink / raw) To: David Heidelberg; +Cc: dmitry.torokhov, devicetree, conor+dt, robh, linux-input Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: New issues: - [High] Unconditional dereference of `sdata->ops` leads to NULL pointer dereference if probed via I2C board info. - [High] The FTS5 Y-coordinate is entirely corrupted due to erroneous MSB and LSB bit shifts. - [Medium] The power-off sequence attempts an I2C write after the chip has already been placed into hardware reset. - [Medium] Touch Major and Minor axes are corrupted due to logic errors combining bits with the constant Event ID. Pre-existing issues: - [High] The touchscreen is left in a sleep state after system resume if it was active prior to suspend. -- --- Patch [2]: [PATCH v5 2/3] Input: stmfts - support FTS5 --- commit 8851d4460d3a4355e8954e1dd8b5a724d25fb3be Author: David Heidelberg <david@ixit.cz> Subject: Input: stmfts - support FTS5 This commit adds support for FTS5 touchscreens to the stmfts driver. It introduces a mode-switch GPIO for SLPI and AP modes, currently only handling full power AP mode. > diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c > index 972687797f826..1da113d7197e6 100644 > --- a/drivers/input/touchscreen/stmfts.c > +++ b/drivers/input/touchscreen/stmfts.c [ ... ] > @@ -205,6 +246,104 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata, > input_sync(sdata->input); > } > > +/* FTS5 event handling functions */ > +static void stmfts5_report_contact_event(struct stmfts_data *sdata, > + const u8 event[]) > +{ [ ... ] > + /* Parse coordinates with better precision */ > + x = (((int)event[3] & STMFTS_MASK_X_MSB) << 8) | event[2]; > + y = ((int)event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4); [Severity: High] Does this bitwise logic for the Y coordinate scramble the value? According to the comment above this code, event[4] is the 8-bit Y LSB, and event[3] contains the 4-bit Y MSB. By shifting event[4] up by 4 and event[3] down by 4, this appears to treat the 8-bit LSB as the most significant bits and the 4-bit MSB as the least significant bits. Should this be (((int)event[3] & STMFTS_MASK_Y_LSB) << 4) | event[4] instead? > + > + /* Parse pressure - ensure non-zero for active touch */ > + area = event[5]; [ ... ] > + > + /* Parse touch area with improved bit extraction */ > + maj = (((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4)); > + min = (((event[7] & 0xC0) >> 2) | (event[6] & 0x0F)); [Severity: Medium] Is it intentional to use event[0] to calculate maj? event[0] holds the event ID (like STMFTS5_EV_MULTI_TOUCH_ENTER which is 0x13). For valid contact events, event[0] & 0x0C will always be zero, meaning maj only captures the upper 4 bits of event[6]. Does this result in malformed touch area statistics being reported? [ ... ] > @@ -601,15 +835,11 @@ static int stmfts_power_on(struct stmfts_data *sdata) > if (sdata->reset_gpio) > stmfts_reset(sdata); > > - err = stmfts_read_system_info(sdata); > - if (err) > - goto err_disable_regulators; > - > enable_irq(sdata->client->irq); > > msleep(50); > > - err = stmfts_configure(sdata); > + err = sdata->ops->configure(sdata); > if (err) > goto err_disable_irq; [Severity: High] This is a pre-existing issue, but does stmfts_power_on() unintentionally leave the touchscreen dead after a system resume if it was active before suspend? When resuming, the device is powered on and unconditionally put to sleep with STMFTS_SLEEP_IN at the end of 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); Because the PM core still considers the device RPM_ACTIVE from before the suspend, it will not automatically invoke stmfts_runtime_resume(). Thus, STMFTS_SLEEP_OUT is never sent and the hardware remains unresponsive until userspace closes and reopens the device. [ ... ] > @@ -637,9 +887,61 @@ static void stmfts_power_off(void *data) > if (sdata->reset_gpio) > gpiod_set_value_cansleep(sdata->reset_gpio, 1); > > + if (sdata->ops->power_off) > + sdata->ops->power_off(sdata); [Severity: Medium] Will this cause an I2C transaction timeout? By asserting the reset GPIO to 1 just before calling sdata->ops->power_off(), the chip is placed into hardware reset. When stmfts5_chip_power_off() immediately tries to send the STMFTS_SLEEP_IN I2C command: static void stmfts5_chip_power_off(struct stmfts_data *sdata) { i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN); msleep(20); } The unresponsive chip would likely NACK the transfer or cause a bus timeout since it is held in reset. [ ... ] > @@ -685,6 +987,8 @@ static int stmfts_probe(struct i2c_client *client) > mutex_init(&sdata->mutex); > init_completion(&sdata->cmd_done); > > + sdata->ops = of_device_get_match_data(dev); > + [ ... ] > @@ -703,8 +1007,8 @@ static int stmfts_probe(struct i2c_client *client) > > sdata->input->name = STMFTS_DEV_NAME; > sdata->input->id.bustype = BUS_I2C; > - sdata->input->open = stmfts_input_open; > - sdata->input->close = stmfts_input_close; > + sdata->input->open = sdata->ops->input_open; > + sdata->input->close = sdata->ops->input_close; [Severity: High] What happens if this driver is instantiated via I2C sysfs board info? Since dev->of_node would be NULL, of_device_get_match_data() would return NULL. Would this cause a NULL pointer dereference when accessing sdata->ops->input_open shortly after? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260810-stmfts5-v5-0-0c737d929976@ixit.cz?part=2 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 3/3] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support 2026-08-10 15:21 ` David Heidelberg @ 2026-08-10 15:21 ` David Heidelberg -1 siblings, 0 replies; 11+ messages in thread From: David Heidelberg via B4 Relay @ 2026-08-10 15:21 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 9368fd6b87515..bd3b5d7b7396e 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] 11+ messages in thread
* [PATCH v5 3/3] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support @ 2026-08-10 15:21 ` David Heidelberg 0 siblings, 0 replies; 11+ messages in thread From: David Heidelberg @ 2026-08-10 15:21 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 9368fd6b87515..bd3b5d7b7396e 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] 11+ messages in thread
end of thread, other threads:[~2026-08-11 7:03 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-10 15:21 [PATCH v5 0/3] Input: support for STM FTS5 David Heidelberg via B4 Relay 2026-08-10 15:21 ` David Heidelberg 2026-08-10 15:21 ` [PATCH v5 1/3] dt-bindings: input: touchscreen: st,stmfts: Introduce " David Heidelberg via B4 Relay 2026-08-10 15:21 ` David Heidelberg 2026-08-10 15:30 ` sashiko-bot 2026-08-11 7:03 ` Krzysztof Kozlowski 2026-08-10 15:21 ` [PATCH v5 2/3] Input: stmfts - support FTS5 David Heidelberg via B4 Relay 2026-08-10 15:21 ` David Heidelberg 2026-08-10 15:44 ` sashiko-bot 2026-08-10 15:21 ` [PATCH v5 3/3] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support David Heidelberg via B4 Relay 2026-08-10 15:21 ` David Heidelberg
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.