linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 0/2] Input: add fts2ba61y touchscreen driver
@ 2025-09-20  1:44 Eric Gonçalves
  2025-09-20  1:44 ` [PATCH v1 1/2] dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen Eric Gonçalves
  2025-09-20  1:44 ` [PATCH v1 2/2] Input: add support for the STM " Eric Gonçalves
  0 siblings, 2 replies; 7+ messages in thread
From: Eric Gonçalves @ 2025-09-20  1:44 UTC (permalink / raw)
  To: Henrik Rydberg, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: Ivaylo Ivanov, devicetree, linux-input, linux-kernel

This patchset adds support for the ST-Microelectronics FTS2BA61Y,
a capacitive multi-touch touchscreen controller. this touchscreen
is used in many mobile devices, like ones from the Galaxy S22 series
and the Z Fold 5. Ivaylo Ivanov wrote the driver originally,
and I'm upstreaming it on his behalf.

Shortly after this patchset, I'll be sending another one that
enables touchscreen on the Galaxy S22 (r0q) board.

Thanks!

Changes from RFC->v1:
- move unevaluatedProperties to after the required: field
- set Ivaylo as the author of driver commit

Eric Gonçalves (1):
  dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen

Ivaylo Ivanov (1):
  Input: add support for the STM FTS2BA61Y touchscreen

 .../input/touchscreen/st,fts2ba61y.yaml       |  52 ++
 drivers/input/touchscreen/Kconfig             |  11 +
 drivers/input/touchscreen/Makefile            |   1 +
 drivers/input/touchscreen/fts2ba61y.c         | 588 ++++++++++++++++++
 4 files changed, 652 insertions(+)
 create mode 100755 Documentation/devicetree/bindings/input/touchscreen/st,fts2ba61y.yaml
 create mode 100644 drivers/input/touchscreen/fts2ba61y.c

-- 
2.51.0


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

* [PATCH v1 1/2] dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen
  2025-09-20  1:44 [PATCH v1 0/2] Input: add fts2ba61y touchscreen driver Eric Gonçalves
@ 2025-09-20  1:44 ` Eric Gonçalves
  2025-09-22 20:08   ` Rob Herring (Arm)
  2025-09-20  1:44 ` [PATCH v1 2/2] Input: add support for the STM " Eric Gonçalves
  1 sibling, 1 reply; 7+ messages in thread
From: Eric Gonçalves @ 2025-09-20  1:44 UTC (permalink / raw)
  To: Henrik Rydberg, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: Ivaylo Ivanov, devicetree, linux-input, linux-kernel

Add the bindings for ST-Microelectronics FTS2BA61Y capacitive touchscreen.

Signed-off-by: Eric Gonçalves <ghatto404@gmail.com>
---
 .../input/touchscreen/st,fts2ba61y.yaml       | 52 +++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100755 Documentation/devicetree/bindings/input/touchscreen/st,fts2ba61y.yaml

diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,fts2ba61y.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,fts2ba61y.yaml
new file mode 100644
index 000000000000..d5565b5360fc
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/st,fts2ba61y.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/st,fts2ba61y.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ST-Microelectronics FTS2BA61Y touchscreen controller
+
+maintainers:
+  - Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>
+
+allOf:
+  - $ref: touchscreen.yaml#
+
+properties:
+  compatible:
+    const: st,fts2ba61y
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  avdd-supply: true
+  vdd-supply: true
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - avdd-supply
+  - vdd-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        touchscreen@0 {
+            compatible = "st,fts2ba61y";
+            reg = <0x0>;
+            interrupt-parent = <&gpa2>;
+            interrupts = <2 IRQ_TYPE_LEVEL_HIGH>;
+            avdd-supply = <&ldo17_reg>;
+            vdd-supply = <&ldo28_reg>;
+        };
+    };
-- 
2.51.0


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

* [PATCH v1 2/2] Input: add support for the STM FTS2BA61Y touchscreen
  2025-09-20  1:44 [PATCH v1 0/2] Input: add fts2ba61y touchscreen driver Eric Gonçalves
  2025-09-20  1:44 ` [PATCH v1 1/2] dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen Eric Gonçalves
@ 2025-09-20  1:44 ` Eric Gonçalves
  2025-09-20 18:36   ` kernel test robot
  2025-09-20 21:13   ` Dmitry Torokhov
  1 sibling, 2 replies; 7+ messages in thread
From: Eric Gonçalves @ 2025-09-20  1:44 UTC (permalink / raw)
  To: Henrik Rydberg, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: Ivaylo Ivanov, devicetree, linux-input, linux-kernel

The ST-Microelectronics FTS2BA61Y touchscreen is a capacitive multi-touch
controller connected through SPI at 0x0, the touchscreen is typically
used in mobile devices (like the Galaxy S22 series)

Signed-off-by: Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>
Signed-off-by: Eric Gonçalves <ghatto404@gmail.com>
---
 drivers/input/touchscreen/Kconfig     |  11 +
 drivers/input/touchscreen/Makefile    |   1 +
 drivers/input/touchscreen/fts2ba61y.c | 588 ++++++++++++++++++++++++++
 3 files changed, 600 insertions(+)
 create mode 100644 drivers/input/touchscreen/fts2ba61y.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 196905162945..1e199191f527 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -370,6 +370,17 @@ config TOUCHSCREEN_EXC3000
 	  To compile this driver as a module, choose M here: the
 	  module will be called exc3000.
 
+config TOUCHSCREEN_FTS2BA61Y
+	tristate "ST-Microelectronics FTS2BA61Y touchscreen"
+	depends on SPI
+	help
+	  Say Y here if you have the ST-Microelectronics FTS2BA61Y touchscreen
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called fts2ba61y.
+
 config TOUCHSCREEN_FUJITSU
 	tristate "Fujitsu serial touchscreen"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 97a025c6a377..408a9fd5bd35 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL)	+= egalax_ts_serial.o
 obj-$(CONFIG_TOUCHSCREEN_EXC3000)	+= exc3000.o
+obj-$(CONFIG_TOUCHSCREEN_FTS2BA61Y)	+= fts2ba61y.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix_ts.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE)	+= goodix_berlin_core.o
diff --git a/drivers/input/touchscreen/fts2ba61y.c b/drivers/input/touchscreen/fts2ba61y.c
new file mode 100644
index 000000000000..b3b3abca5404
--- /dev/null
+++ b/drivers/input/touchscreen/fts2ba61y.c
@@ -0,0 +1,588 @@
+// SPDX-License-Identifier: GPL-2.0
+// Based loosely on s6sy761.c
+
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/input/mt.h>
+#include <linux/spi/spi.h>
+#include <linux/input/touchscreen.h>
+#include <linux/unaligned.h>
+#include <linux/regulator/consumer.h>
+
+/* commands */
+#define FTS2BA61Y_CMD_SENSE_ON			0x10
+#define FTS2BA61Y_CMD_SENSE_OFF			0x11
+#define FTS2BA61Y_CMD_READ_PANEL_INFO		0x23
+#define FTS2BA61Y_CMD_READ_FW_VER		0x24
+#define FTS2BA61Y_CMD_TOUCHTYPE			0x30 /* R/W for get/set */
+#define FTS2BA61Y_CMD_CLEAR_EVENTS		0x62
+#define FTS2BA61Y_CMD_READ_EVENT		0x87
+#define FTS2BA61Y_CMD_CUSTOM_W			0xC0
+#define FTS2BA61Y_CMD_CUSTOM_R			0xD1
+#define FTS2BA61Y_CMD_REG_W			0xFA
+#define FTS2BA61Y_CMD_REG_R			0xFB
+
+/* touch type masks */
+#define FTS2BA61Y_MASK_TOUCH			BIT(0)
+#define FTS2BA61Y_MASK_HOVER			BIT(1)
+#define FTS2BA61Y_MASK_COVER			BIT(2)
+#define FTS2BA61Y_MASK_GLOVE			BIT(3)
+#define FTS2BA61Y_MASK_STYLUS			BIT(4)
+#define FTS2BA61Y_MASK_PALM			BIT(5)
+#define FTS2BA61Y_MASK_WET			BIT(6)
+#define FTS2BA61Y_TOUCHTYPE_DEFAULT		(FTS2BA61Y_MASK_TOUCH | \
+						 FTS2BA61Y_MASK_PALM | \
+						 FTS2BA61Y_MASK_WET)
+
+/* event status masks */
+#define FTS2BA61Y_MASK_STYPE			GENMASK(5, 2)
+#define FTS2BA61Y_MASK_EVENT_ID			GENMASK(1, 0)
+
+/* event coordinate masks */
+#define FTS2BA61Y_MASK_TCHSTA			GENMASK(7, 6)
+#define FTS2BA61Y_MASK_TID			GENMASK(5, 2)
+#define FTS2BA61Y_MASK_X_3_0			GENMASK(7, 4)
+#define FTS2BA61Y_MASK_Y_3_0			GENMASK(3, 0)
+#define FTS2BA61Y_MASK_Z			GENMASK(5, 0)
+#define FTS2BA61Y_MASK_TTYPE_3_2		GENMASK(7, 6)
+#define FTS2BA61Y_MASK_TTYPE_1_0		GENMASK(1, 0)
+#define FTS2BA61Y_MASK_LEFT_EVENTS		GENMASK(4, 0)
+
+/* event error status */
+#define FTS2BA61Y_EVENT_STATUSTYPE_INFO		0x2
+
+/* information report */
+#define FTS2BA61Y_INFO_READY_STATUS		0x0
+
+/* event status */
+#define FTS2BA61Y_COORDINATE_EVENT		0x0
+
+/* touch types */
+#define FTS2BA61Y_TOUCHTYPE_NORMAL		0x0
+#define FTS2BA61Y_TOUCHTYPE_HOVER		0x1
+#define FTS2BA61Y_TOUCHTYPE_FLIPCOVER		0x2
+#define FTS2BA61Y_TOUCHTYPE_GLOVE		0x3
+#define FTS2BA61Y_TOUCHTYPE_STYLUS		0x4
+#define FTS2BA61Y_TOUCHTYPE_PALM		0x5
+#define FTS2BA61Y_TOUCHTYPE_WET			0x6
+#define FTS2BA61Y_TOUCHTYPE_PROXIMITY		0x7
+#define FTS2BA61Y_TOUCHTYPE_JIG			0x8
+
+#define FTS2BA61Y_COORDINATE_ACTION_NONE	0x0
+#define FTS2BA61Y_COORDINATE_ACTION_PRESS	0x1
+#define FTS2BA61Y_COORDINATE_ACTION_MOVE	0x2
+#define FTS2BA61Y_COORDINATE_ACTION_RELEASE	0x3
+
+#define FTS2BA61Y_DEV_NAME			"fts2ba61y"
+#define FTS2BA61Y_EVENT_BUFF_SIZE		16
+#define FTS2BA61Y_PANEL_INFO_SIZE		11
+#define FTS2BA61Y_RESET_CMD_SIZE		5
+#define FTS2BA61Y_EVENT_COUNT			31
+#define MAX_TRANSFER_SIZE			256
+
+enum fts2ba61y_regulators {
+	FTS2BA61Y_REGULATOR_VDD,
+	FTS2BA61Y_REGULATOR_AVDD,
+};
+
+struct fts2ba61y_data {
+	struct spi_device *spi;
+	struct regulator_bulk_data regulators[2];
+	struct input_dev *input_dev;
+	struct mutex mutex;
+	struct touchscreen_properties prop;
+
+	u8 tx_count;
+
+	unsigned int max_x;
+	unsigned int max_y;
+};
+
+static int fts2ba61y_write(struct fts2ba61y_data *ts,
+			   u8 *reg, int cmd_len, u8 *data, int data_len)
+{
+	struct spi_message msg;
+	struct spi_transfer xfers;
+	char *tx_buf;
+	int len;
+	int ret;
+
+	tx_buf = kzalloc(cmd_len + data_len + 1, GFP_KERNEL);
+	if (!tx_buf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memset(&xfers, 0, sizeof(xfers));
+	spi_message_init(&msg);
+
+	memcpy(&tx_buf[0], reg, cmd_len);
+	if (data_len && data)
+		memcpy(&tx_buf[cmd_len], data, data_len);
+
+	len = cmd_len + data_len;
+
+	/* custom write cmd */
+	if (reg[0] != FTS2BA61Y_CMD_REG_W &&
+	    reg[0] != FTS2BA61Y_CMD_REG_R) {
+		memmove(tx_buf + 1, tx_buf, len);
+		tx_buf[0] = FTS2BA61Y_CMD_CUSTOM_W;
+		len++;
+	}
+
+	xfers.len = len;
+	xfers.tx_buf = tx_buf;
+
+	spi_message_add_tail(&xfers, &msg);
+
+	mutex_lock(&ts->mutex);
+	ret = spi_sync(ts->spi, &msg);
+	if (ret)
+		dev_err(&ts->spi->dev, "spi transfer error, %d", ret);
+	mutex_unlock(&ts->mutex);
+
+out:
+	kfree(tx_buf);
+	return ret;
+}
+
+static int fts2ba61y_spi_raw_read(struct fts2ba61y_data *ts,
+				  u8 *tx_buf, u8 *rx_buf, int len)
+{
+	struct spi_message msg;
+	struct spi_transfer xfer;
+	int ret;
+
+	memset(&xfer, 0, sizeof(xfer));
+	spi_message_init(&msg);
+
+	xfer.len = len;
+	xfer.tx_buf = tx_buf;
+	xfer.rx_buf = rx_buf;
+	spi_message_add_tail(&xfer, &msg);
+
+	mutex_lock(&ts->mutex);
+	ret = spi_sync(ts->spi, &msg);
+	if (ret)
+		dev_err(&ts->spi->dev, "spi transfer error, %d", ret);
+	mutex_unlock(&ts->mutex);
+
+	return ret;
+}
+
+/*
+ * higher-level wrapper that prepares the buffers for a read.
+ */
+static int fts2ba61y_read(struct fts2ba61y_data *ts,
+			  u8 reg[], int tx_len, u8 buf[], int rx_len)
+{
+	char *tx_buf, *rx_buf;
+	int ret, mem_len;
+	u16 reg_val;
+
+	if (tx_len > 3)
+		mem_len = rx_len + 1 + tx_len;
+	else
+		mem_len = rx_len + 4;
+
+	tx_buf = kzalloc(mem_len, GFP_KERNEL);
+	rx_buf = kzalloc(mem_len, GFP_KERNEL);
+	if (!tx_buf || !rx_buf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	switch (reg[0]) {
+	case FTS2BA61Y_CMD_READ_EVENT:
+	case FTS2BA61Y_CMD_REG_W:
+	case FTS2BA61Y_CMD_REG_R:
+		memcpy(tx_buf, reg, tx_len);
+		break;
+
+	default:
+		tx_buf[0] = FTS2BA61Y_CMD_CUSTOM_R;
+
+		if (tx_len == 1)
+			reg_val = 0;
+		else if (tx_len == 2)
+			reg_val = reg[0];
+		else if (tx_len == 3)
+			reg_val = reg[0] | (reg[1] << 8);
+		else {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		tx_len = 3;
+		put_unaligned_be16(reg_val, &tx_buf[1]);
+
+		ret = fts2ba61y_write(ts, reg, 1, NULL, 0);
+		if (ret < 0)
+			goto out;
+		break;
+	}
+
+	ret = fts2ba61y_spi_raw_read(ts, tx_buf, rx_buf, rx_len + 1 + tx_len);
+	if (ret < 0)
+		goto out;
+
+	memcpy(buf, &rx_buf[1 + tx_len], rx_len);
+
+out:
+	kfree(tx_buf);
+	kfree(rx_buf);
+	return ret;
+}
+
+static int fts2ba61y_wait_for_ready(struct fts2ba61y_data *ts)
+{
+	u8 buffer[FTS2BA61Y_EVENT_BUFF_SIZE];
+	u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
+	u8 status_id, stype;
+	int ret;
+
+	for (int retries = 5; retries > 0; retries--) {
+		ret = fts2ba61y_read(ts, &cmd, 1, buffer, FTS2BA61Y_EVENT_BUFF_SIZE);
+
+		stype = FIELD_GET(FTS2BA61Y_MASK_STYPE, buffer[0]);
+		status_id = buffer[1];
+
+		if (stype == FTS2BA61Y_EVENT_STATUSTYPE_INFO &&
+		    status_id == FTS2BA61Y_INFO_READY_STATUS) {
+			ret = 0;
+			break;
+		} else
+			ret = -ENODEV;
+
+		msleep(20);
+	}
+
+	return ret;
+}
+
+static int fts2ba61y_reset(struct fts2ba61y_data *ts)
+{
+	u8 cmd = FTS2BA61Y_CMD_REG_W;
+	/* the following sequence is undocumented */
+	u8 reset[FTS2BA61Y_RESET_CMD_SIZE] = { 0x20, 0x00,
+					       0x00, 0x24, 0x81 };
+	int ret;
+
+	disable_irq(ts->spi->irq);
+
+	ret = fts2ba61y_write(ts, &cmd, 1, &reset[0], FTS2BA61Y_RESET_CMD_SIZE);
+	if (ret)
+		return ret;
+	msleep(30);
+
+	ret = fts2ba61y_wait_for_ready(ts);
+	if (ret)
+		return ret;
+
+	enable_irq(ts->spi->irq);
+
+	return 0;
+}
+
+static int fts2ba61y_set_channels(struct fts2ba61y_data *ts)
+{
+	int ret;
+	u8 cmd = FTS2BA61Y_CMD_READ_PANEL_INFO;
+	u8 data[FTS2BA61Y_PANEL_INFO_SIZE];
+
+	ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_PANEL_INFO_SIZE);
+	if (ret)
+		return ret;
+
+	ts->max_x = get_unaligned_be16(data);
+	ts->max_y = get_unaligned_be16(data + 2);
+
+	/* if no tx channels defined, at least keep one */
+	ts->tx_count = max_t(u8, data[8], 1);
+
+	return 0;
+}
+
+static int fts2ba61y_set_touch_func(struct fts2ba61y_data *ts)
+{
+	u8 cmd = FTS2BA61Y_CMD_TOUCHTYPE;
+	u16 touchtype = cpu_to_le16(FTS2BA61Y_TOUCHTYPE_DEFAULT);
+
+	return fts2ba61y_write(ts, &cmd, 1, (u8 *)&touchtype, 2);
+}
+
+static int fts2ba61y_hw_init(struct fts2ba61y_data *ts)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
+								ts->regulators);
+	if (ret)
+		return ret;
+
+	msleep(140);
+
+	ret = fts2ba61y_reset(ts);
+	if (ret)
+		return ret;
+
+	ret = fts2ba61y_set_channels(ts);
+	if (ret)
+		return ret;
+
+	return fts2ba61y_set_touch_func(ts);
+}
+
+static int fts2ba61y_get_event(struct fts2ba61y_data *ts, u8 *data, int *n_events)
+{
+	int ret;
+	u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
+
+	ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_EVENT_BUFF_SIZE);
+	if (ret < 0)
+		return ret;
+
+	if (!data[0]) {
+		*n_events = 0;
+		return 0;
+	}
+
+	*n_events = FIELD_GET(FTS2BA61Y_MASK_LEFT_EVENTS, data[7]);
+	if (unlikely(*n_events >= FTS2BA61Y_EVENT_COUNT)) {
+		cmd = FTS2BA61Y_CMD_CLEAR_EVENTS;
+		fts2ba61y_write(ts, &cmd, 1, NULL, 0);
+		*n_events = 0;
+		return -EINVAL;
+	}
+
+	if (*n_events > 0) {
+		ret = fts2ba61y_read(ts, &cmd, 1,
+				     &data[1 * FTS2BA61Y_EVENT_BUFF_SIZE],
+				     FTS2BA61Y_EVENT_BUFF_SIZE * (*n_events));
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void fts2ba61y_report_coordinates(struct fts2ba61y_data *ts,
+					 u8 *event, u8 tid)
+{
+	u8 major = event[4];
+	u8 minor = event[5];
+	u8 z = FIELD_GET(FTS2BA61Y_MASK_Z, event[6]);
+
+	u16 x = (event[1] << 4) |
+		FIELD_GET(FTS2BA61Y_MASK_X_3_0, event[3]);
+	u16 y = (event[2] << 4) |
+		FIELD_GET(FTS2BA61Y_MASK_Y_3_0, event[3]);
+	u16 ttype = (FIELD_GET(FTS2BA61Y_MASK_TTYPE_3_2, event[6]) << 2) |
+		    (FIELD_GET(FTS2BA61Y_MASK_TTYPE_1_0, event[7]) << 0);
+
+	if (ttype != FTS2BA61Y_TOUCHTYPE_NORMAL &&
+	    ttype != FTS2BA61Y_TOUCHTYPE_PALM &&
+	    ttype != FTS2BA61Y_TOUCHTYPE_WET &&
+	    ttype != FTS2BA61Y_TOUCHTYPE_GLOVE)
+		return;
+
+	input_mt_slot(ts->input_dev, tid);
+	input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
+	input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
+	input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
+	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, major);
+	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR, minor);
+	input_report_abs(ts->input_dev, ABS_MT_PRESSURE, z);
+
+	input_mt_sync_frame(ts->input_dev);
+	input_sync(ts->input_dev);
+}
+
+static void fts2ba61y_report_release(struct fts2ba61y_data *ts, u8 tid)
+{
+	input_mt_slot(ts->input_dev, tid);
+	input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
+
+	input_mt_sync_frame(ts->input_dev);
+	input_sync(ts->input_dev);
+}
+
+static void fts2ba61y_handle_coordinates(struct fts2ba61y_data *ts, u8 *event)
+{
+	u8 t_id = FIELD_GET(FTS2BA61Y_MASK_TID, event[0]);
+	u8 action = FIELD_GET(FTS2BA61Y_MASK_TCHSTA, event[0]);
+
+	if (t_id > ts->tx_count)
+		return;
+
+	switch (action) {
+	case FTS2BA61Y_COORDINATE_ACTION_PRESS:
+	case FTS2BA61Y_COORDINATE_ACTION_MOVE:
+		fts2ba61y_report_coordinates(ts, event, t_id);
+		break;
+
+	case FTS2BA61Y_COORDINATE_ACTION_RELEASE:
+		fts2ba61y_report_release(ts, t_id);
+		break;
+	}
+}
+
+static irqreturn_t fts2ba61y_irq_handler(int irq, void *handle)
+{
+	struct fts2ba61y_data *ts = handle;
+	u8 buffer[FTS2BA61Y_EVENT_COUNT * FTS2BA61Y_EVENT_BUFF_SIZE];
+	u8 *event;
+	u8 event_id;
+	int n_events = 0;
+	int ret;
+
+	usleep(1);
+
+	ret = fts2ba61y_get_event(ts, buffer, &n_events);
+	if (ret < 0) {
+		dev_dbg(&ts->spi->dev, "failed to get event: %d", ret);
+		return IRQ_HANDLED;
+	}
+
+	for (int i = 0; i <= n_events; i++) {
+		event = &buffer[i * FTS2BA61Y_EVENT_BUFF_SIZE];
+		event_id = FIELD_GET(FTS2BA61Y_MASK_EVENT_ID, event[0]);
+
+		if (event_id == FTS2BA61Y_COORDINATE_EVENT)
+			fts2ba61y_handle_coordinates(ts, event);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int fts2ba61y_input_open(struct input_dev *dev)
+{
+	struct fts2ba61y_data *ts = input_get_drvdata(dev);
+	u8 cmd = FTS2BA61Y_CMD_SENSE_ON;
+
+	return fts2ba61y_write(ts, &cmd, 1, NULL, 0);
+}
+
+static void fts2ba61y_input_close(struct input_dev *dev)
+{
+	struct fts2ba61y_data *ts = input_get_drvdata(dev);
+	int ret;
+	u8 cmd = FTS2BA61Y_CMD_SENSE_OFF;
+
+	ret = fts2ba61y_write(ts, &cmd, 1, NULL, 0);
+	if (ret)
+		dev_err(&ts->spi->dev, "failed to turn off sensing\n");
+}
+
+static void fts2ba61y_power_off(void *data)
+{
+	struct fts2ba61y_data *ts = data;
+
+	disable_irq(ts->spi->irq);
+	regulator_bulk_disable(ARRAY_SIZE(ts->regulators),
+						   ts->regulators);
+}
+
+static int fts2ba61y_probe(struct spi_device *spi) {
+	struct fts2ba61y_data *ts;
+	struct input_dev *input_dev;
+	int error;
+
+	ts = devm_kzalloc(&spi->dev, sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	ts->spi = spi;
+	mutex_init(&ts->mutex);
+
+	spi->mode = SPI_MODE_0;
+	spi->bits_per_word = 8;
+
+	error = spi_setup(spi);
+	if (error)
+		return error;
+
+	ts->regulators[FTS2BA61Y_REGULATOR_VDD].supply = "vdd";
+	ts->regulators[FTS2BA61Y_REGULATOR_AVDD].supply = "avdd";
+	error = devm_regulator_bulk_get(&spi->dev,
+									ARRAY_SIZE(ts->regulators),
+									ts->regulators);
+	if (error)
+		return error;
+
+	error = fts2ba61y_hw_init(ts);
+	if (error)
+		return error;
+
+	error = devm_add_action_or_reset(&ts->spi->dev, fts2ba61y_power_off, ts);
+	if (error)
+		return error;
+
+	input_dev = devm_input_allocate_device(&spi->dev);
+	if (!input_dev)
+		return -ENOMEM;
+
+	ts->input_dev = input_dev;
+
+	input_dev->name = FTS2BA61Y_DEV_NAME;
+	input_dev->id.bustype = BUS_SPI;
+	input_dev->open = fts2ba61y_input_open;
+	input_dev->close = fts2ba61y_input_close;
+
+	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+	touchscreen_parse_properties(input_dev, true, &ts->prop);
+
+	spi_set_drvdata(spi, ts);
+	input_set_drvdata(input_dev, ts);
+
+	error = input_mt_init_slots(input_dev, ts->tx_count, INPUT_MT_DIRECT);
+	if (error)
+		return error;
+
+	error = input_register_device(input_dev);
+	if (error)
+		return error;
+
+	error = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
+					  fts2ba61y_irq_handler,
+					  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					  "fts2ba61y_irq", ts);
+	return error;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spi_touchscreen_dt_ids[] = {
+	{ .compatible = "st,fts2ba61y" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, spi_touchscreen_dt_ids);
+#endif
+
+static const struct spi_device_id fts2ba61y_spi_ids[] = {
+	{ "fts2ba61y" },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, fts2ba61y_spi_ids);
+
+static struct spi_driver spi_touchscreen_driver = {
+	.driver = {
+		.name = FTS2BA61Y_DEV_NAME,
+		.of_match_table = of_match_ptr(spi_touchscreen_dt_ids),
+	},
+	.probe = fts2ba61y_probe,
+	.id_table = fts2ba61y_spi_ids,
+};
+
+module_spi_driver(spi_touchscreen_driver);
+
+MODULE_AUTHOR("Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>");
+MODULE_DESCRIPTION("ST-Microelectronics FTS2BA61Y Touch Screen");
+MODULE_LICENSE("GPL");
-- 
2.51.0


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

* Re: [PATCH v1 2/2] Input: add support for the STM FTS2BA61Y touchscreen
  2025-09-20  1:44 ` [PATCH v1 2/2] Input: add support for the STM " Eric Gonçalves
@ 2025-09-20 18:36   ` kernel test robot
  2025-09-20 21:13   ` Dmitry Torokhov
  1 sibling, 0 replies; 7+ messages in thread
From: kernel test robot @ 2025-09-20 18:36 UTC (permalink / raw)
  To: Eric Gonçalves, Henrik Rydberg, Dmitry Torokhov, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: oe-kbuild-all, Ivaylo Ivanov, devicetree, linux-input,
	linux-kernel

Hi Eric,

kernel test robot noticed the following build errors:

[auto build test ERROR on dtor-input/next]
[also build test ERROR on dtor-input/for-linus robh/for-next linus/master v6.17-rc6 next-20250919]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Eric-Gon-alves/dt-bindings-input-Add-ST-Microelectronics-FTS2BA61Y-touchscreen/20250920-094849
base:   https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
patch link:    https://lore.kernel.org/r/20250920014450.37787-3-ghatto404%40gmail.com
patch subject: [PATCH v1 2/2] Input: add support for the STM FTS2BA61Y touchscreen
config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20250921/202509210247.oVPW8pop-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250921/202509210247.oVPW8pop-lkp@intel.com/reproduce)

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

All errors (new ones prefixed by >>):

   drivers/input/touchscreen/fts2ba61y.c: In function 'fts2ba61y_wait_for_ready':
>> drivers/input/touchscreen/fts2ba61y.c:250:25: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration]
     250 |                 stype = FIELD_GET(FTS2BA61Y_MASK_STYPE, buffer[0]);
         |                         ^~~~~~~~~
   drivers/input/touchscreen/fts2ba61y.c: In function 'fts2ba61y_irq_handler':
>> drivers/input/touchscreen/fts2ba61y.c:442:9: error: implicit declaration of function 'usleep'; did you mean 'fsleep'? [-Wimplicit-function-declaration]
     442 |         usleep(1);
         |         ^~~~~~
         |         fsleep


vim +/FIELD_GET +250 drivers/input/touchscreen/fts2ba61y.c

   239	
   240	static int fts2ba61y_wait_for_ready(struct fts2ba61y_data *ts)
   241	{
   242		u8 buffer[FTS2BA61Y_EVENT_BUFF_SIZE];
   243		u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
   244		u8 status_id, stype;
   245		int ret;
   246	
   247		for (int retries = 5; retries > 0; retries--) {
   248			ret = fts2ba61y_read(ts, &cmd, 1, buffer, FTS2BA61Y_EVENT_BUFF_SIZE);
   249	
 > 250			stype = FIELD_GET(FTS2BA61Y_MASK_STYPE, buffer[0]);
   251			status_id = buffer[1];
   252	
   253			if (stype == FTS2BA61Y_EVENT_STATUSTYPE_INFO &&
   254			    status_id == FTS2BA61Y_INFO_READY_STATUS) {
   255				ret = 0;
   256				break;
   257			} else
   258				ret = -ENODEV;
   259	
   260			msleep(20);
   261		}
   262	
   263		return ret;
   264	}
   265	
   266	static int fts2ba61y_reset(struct fts2ba61y_data *ts)
   267	{
   268		u8 cmd = FTS2BA61Y_CMD_REG_W;
   269		/* the following sequence is undocumented */
   270		u8 reset[FTS2BA61Y_RESET_CMD_SIZE] = { 0x20, 0x00,
   271						       0x00, 0x24, 0x81 };
   272		int ret;
   273	
   274		disable_irq(ts->spi->irq);
   275	
   276		ret = fts2ba61y_write(ts, &cmd, 1, &reset[0], FTS2BA61Y_RESET_CMD_SIZE);
   277		if (ret)
   278			return ret;
   279		msleep(30);
   280	
   281		ret = fts2ba61y_wait_for_ready(ts);
   282		if (ret)
   283			return ret;
   284	
   285		enable_irq(ts->spi->irq);
   286	
   287		return 0;
   288	}
   289	
   290	static int fts2ba61y_set_channels(struct fts2ba61y_data *ts)
   291	{
   292		int ret;
   293		u8 cmd = FTS2BA61Y_CMD_READ_PANEL_INFO;
   294		u8 data[FTS2BA61Y_PANEL_INFO_SIZE];
   295	
   296		ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_PANEL_INFO_SIZE);
   297		if (ret)
   298			return ret;
   299	
   300		ts->max_x = get_unaligned_be16(data);
   301		ts->max_y = get_unaligned_be16(data + 2);
   302	
   303		/* if no tx channels defined, at least keep one */
   304		ts->tx_count = max_t(u8, data[8], 1);
   305	
   306		return 0;
   307	}
   308	
   309	static int fts2ba61y_set_touch_func(struct fts2ba61y_data *ts)
   310	{
   311		u8 cmd = FTS2BA61Y_CMD_TOUCHTYPE;
   312		u16 touchtype = cpu_to_le16(FTS2BA61Y_TOUCHTYPE_DEFAULT);
   313	
   314		return fts2ba61y_write(ts, &cmd, 1, (u8 *)&touchtype, 2);
   315	}
   316	
   317	static int fts2ba61y_hw_init(struct fts2ba61y_data *ts)
   318	{
   319		int ret;
   320	
   321		ret = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
   322									ts->regulators);
   323		if (ret)
   324			return ret;
   325	
   326		msleep(140);
   327	
   328		ret = fts2ba61y_reset(ts);
   329		if (ret)
   330			return ret;
   331	
   332		ret = fts2ba61y_set_channels(ts);
   333		if (ret)
   334			return ret;
   335	
   336		return fts2ba61y_set_touch_func(ts);
   337	}
   338	
   339	static int fts2ba61y_get_event(struct fts2ba61y_data *ts, u8 *data, int *n_events)
   340	{
   341		int ret;
   342		u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
   343	
   344		ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_EVENT_BUFF_SIZE);
   345		if (ret < 0)
   346			return ret;
   347	
   348		if (!data[0]) {
   349			*n_events = 0;
   350			return 0;
   351		}
   352	
   353		*n_events = FIELD_GET(FTS2BA61Y_MASK_LEFT_EVENTS, data[7]);
   354		if (unlikely(*n_events >= FTS2BA61Y_EVENT_COUNT)) {
   355			cmd = FTS2BA61Y_CMD_CLEAR_EVENTS;
   356			fts2ba61y_write(ts, &cmd, 1, NULL, 0);
   357			*n_events = 0;
   358			return -EINVAL;
   359		}
   360	
   361		if (*n_events > 0) {
   362			ret = fts2ba61y_read(ts, &cmd, 1,
   363					     &data[1 * FTS2BA61Y_EVENT_BUFF_SIZE],
   364					     FTS2BA61Y_EVENT_BUFF_SIZE * (*n_events));
   365			if (ret)
   366				return ret;
   367		}
   368	
   369		return 0;
   370	}
   371	
   372	static void fts2ba61y_report_coordinates(struct fts2ba61y_data *ts,
   373						 u8 *event, u8 tid)
   374	{
   375		u8 major = event[4];
   376		u8 minor = event[5];
   377		u8 z = FIELD_GET(FTS2BA61Y_MASK_Z, event[6]);
   378	
   379		u16 x = (event[1] << 4) |
   380			FIELD_GET(FTS2BA61Y_MASK_X_3_0, event[3]);
   381		u16 y = (event[2] << 4) |
   382			FIELD_GET(FTS2BA61Y_MASK_Y_3_0, event[3]);
   383		u16 ttype = (FIELD_GET(FTS2BA61Y_MASK_TTYPE_3_2, event[6]) << 2) |
   384			    (FIELD_GET(FTS2BA61Y_MASK_TTYPE_1_0, event[7]) << 0);
   385	
   386		if (ttype != FTS2BA61Y_TOUCHTYPE_NORMAL &&
   387		    ttype != FTS2BA61Y_TOUCHTYPE_PALM &&
   388		    ttype != FTS2BA61Y_TOUCHTYPE_WET &&
   389		    ttype != FTS2BA61Y_TOUCHTYPE_GLOVE)
   390			return;
   391	
   392		input_mt_slot(ts->input_dev, tid);
   393		input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
   394		input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
   395		input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
   396		input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, major);
   397		input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR, minor);
   398		input_report_abs(ts->input_dev, ABS_MT_PRESSURE, z);
   399	
   400		input_mt_sync_frame(ts->input_dev);
   401		input_sync(ts->input_dev);
   402	}
   403	
   404	static void fts2ba61y_report_release(struct fts2ba61y_data *ts, u8 tid)
   405	{
   406		input_mt_slot(ts->input_dev, tid);
   407		input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
   408	
   409		input_mt_sync_frame(ts->input_dev);
   410		input_sync(ts->input_dev);
   411	}
   412	
   413	static void fts2ba61y_handle_coordinates(struct fts2ba61y_data *ts, u8 *event)
   414	{
   415		u8 t_id = FIELD_GET(FTS2BA61Y_MASK_TID, event[0]);
   416		u8 action = FIELD_GET(FTS2BA61Y_MASK_TCHSTA, event[0]);
   417	
   418		if (t_id > ts->tx_count)
   419			return;
   420	
   421		switch (action) {
   422		case FTS2BA61Y_COORDINATE_ACTION_PRESS:
   423		case FTS2BA61Y_COORDINATE_ACTION_MOVE:
   424			fts2ba61y_report_coordinates(ts, event, t_id);
   425			break;
   426	
   427		case FTS2BA61Y_COORDINATE_ACTION_RELEASE:
   428			fts2ba61y_report_release(ts, t_id);
   429			break;
   430		}
   431	}
   432	
   433	static irqreturn_t fts2ba61y_irq_handler(int irq, void *handle)
   434	{
   435		struct fts2ba61y_data *ts = handle;
   436		u8 buffer[FTS2BA61Y_EVENT_COUNT * FTS2BA61Y_EVENT_BUFF_SIZE];
   437		u8 *event;
   438		u8 event_id;
   439		int n_events = 0;
   440		int ret;
   441	
 > 442		usleep(1);
   443	
   444		ret = fts2ba61y_get_event(ts, buffer, &n_events);
   445		if (ret < 0) {
   446			dev_dbg(&ts->spi->dev, "failed to get event: %d", ret);
   447			return IRQ_HANDLED;
   448		}
   449	
   450		for (int i = 0; i <= n_events; i++) {
   451			event = &buffer[i * FTS2BA61Y_EVENT_BUFF_SIZE];
   452			event_id = FIELD_GET(FTS2BA61Y_MASK_EVENT_ID, event[0]);
   453	
   454			if (event_id == FTS2BA61Y_COORDINATE_EVENT)
   455				fts2ba61y_handle_coordinates(ts, event);
   456		}
   457	
   458		return IRQ_HANDLED;
   459	}
   460	

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

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

* Re: [PATCH v1 2/2] Input: add support for the STM FTS2BA61Y touchscreen
  2025-09-20  1:44 ` [PATCH v1 2/2] Input: add support for the STM " Eric Gonçalves
  2025-09-20 18:36   ` kernel test robot
@ 2025-09-20 21:13   ` Dmitry Torokhov
  2025-09-27  2:42     ` Eric Gonçalves
  1 sibling, 1 reply; 7+ messages in thread
From: Dmitry Torokhov @ 2025-09-20 21:13 UTC (permalink / raw)
  To: Eric Gonçalves
  Cc: Henrik Rydberg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Ivaylo Ivanov, devicetree, linux-input, linux-kernel

Hi Eric,

On Sat, Sep 20, 2025 at 01:44:50AM +0000, Eric Gonçalves wrote:
> The ST-Microelectronics FTS2BA61Y touchscreen is a capacitive multi-touch
> controller connected through SPI at 0x0, the touchscreen is typically
> used in mobile devices (like the Galaxy S22 series)
> 
> Signed-off-by: Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>
> Signed-off-by: Eric Gonçalves <ghatto404@gmail.com>

Thank you for the patch. A few comments below.

> ---
>  drivers/input/touchscreen/Kconfig     |  11 +
>  drivers/input/touchscreen/Makefile    |   1 +
>  drivers/input/touchscreen/fts2ba61y.c | 588 ++++++++++++++++++++++++++
>  3 files changed, 600 insertions(+)
>  create mode 100644 drivers/input/touchscreen/fts2ba61y.c
> 
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 196905162945..1e199191f527 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -370,6 +370,17 @@ config TOUCHSCREEN_EXC3000
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called exc3000.
>  
> +config TOUCHSCREEN_FTS2BA61Y
> +	tristate "ST-Microelectronics FTS2BA61Y touchscreen"
> +	depends on SPI
> +	help
> +	  Say Y here if you have the ST-Microelectronics FTS2BA61Y touchscreen
> +
> +	  If unsure, say N.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called fts2ba61y.
> +
>  config TOUCHSCREEN_FUJITSU
>  	tristate "Fujitsu serial touchscreen"
>  	select SERIO
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 97a025c6a377..408a9fd5bd35 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
>  obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL)	+= egalax_ts_serial.o
>  obj-$(CONFIG_TOUCHSCREEN_EXC3000)	+= exc3000.o
> +obj-$(CONFIG_TOUCHSCREEN_FTS2BA61Y)	+= fts2ba61y.o
>  obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE)	+= goodix_berlin_core.o
> diff --git a/drivers/input/touchscreen/fts2ba61y.c b/drivers/input/touchscreen/fts2ba61y.c
> new file mode 100644
> index 000000000000..b3b3abca5404
> --- /dev/null
> +++ b/drivers/input/touchscreen/fts2ba61y.c
> @@ -0,0 +1,588 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Based loosely on s6sy761.c
> +
> +#include <linux/delay.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/input/mt.h>
> +#include <linux/spi/spi.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/unaligned.h>
> +#include <linux/regulator/consumer.h>
> +
> +/* commands */
> +#define FTS2BA61Y_CMD_SENSE_ON			0x10
> +#define FTS2BA61Y_CMD_SENSE_OFF			0x11
> +#define FTS2BA61Y_CMD_READ_PANEL_INFO		0x23
> +#define FTS2BA61Y_CMD_READ_FW_VER		0x24
> +#define FTS2BA61Y_CMD_TOUCHTYPE			0x30 /* R/W for get/set */
> +#define FTS2BA61Y_CMD_CLEAR_EVENTS		0x62
> +#define FTS2BA61Y_CMD_READ_EVENT		0x87
> +#define FTS2BA61Y_CMD_CUSTOM_W			0xC0
> +#define FTS2BA61Y_CMD_CUSTOM_R			0xD1
> +#define FTS2BA61Y_CMD_REG_W			0xFA
> +#define FTS2BA61Y_CMD_REG_R			0xFB
> +
> +/* touch type masks */
> +#define FTS2BA61Y_MASK_TOUCH			BIT(0)
> +#define FTS2BA61Y_MASK_HOVER			BIT(1)
> +#define FTS2BA61Y_MASK_COVER			BIT(2)
> +#define FTS2BA61Y_MASK_GLOVE			BIT(3)
> +#define FTS2BA61Y_MASK_STYLUS			BIT(4)
> +#define FTS2BA61Y_MASK_PALM			BIT(5)
> +#define FTS2BA61Y_MASK_WET			BIT(6)
> +#define FTS2BA61Y_TOUCHTYPE_DEFAULT		(FTS2BA61Y_MASK_TOUCH | \
> +						 FTS2BA61Y_MASK_PALM | \
> +						 FTS2BA61Y_MASK_WET)
> +
> +/* event status masks */
> +#define FTS2BA61Y_MASK_STYPE			GENMASK(5, 2)
> +#define FTS2BA61Y_MASK_EVENT_ID			GENMASK(1, 0)
> +
> +/* event coordinate masks */
> +#define FTS2BA61Y_MASK_TCHSTA			GENMASK(7, 6)
> +#define FTS2BA61Y_MASK_TID			GENMASK(5, 2)
> +#define FTS2BA61Y_MASK_X_3_0			GENMASK(7, 4)
> +#define FTS2BA61Y_MASK_Y_3_0			GENMASK(3, 0)
> +#define FTS2BA61Y_MASK_Z			GENMASK(5, 0)
> +#define FTS2BA61Y_MASK_TTYPE_3_2		GENMASK(7, 6)
> +#define FTS2BA61Y_MASK_TTYPE_1_0		GENMASK(1, 0)
> +#define FTS2BA61Y_MASK_LEFT_EVENTS		GENMASK(4, 0)
> +
> +/* event error status */
> +#define FTS2BA61Y_EVENT_STATUSTYPE_INFO		0x2
> +
> +/* information report */
> +#define FTS2BA61Y_INFO_READY_STATUS		0x0
> +
> +/* event status */
> +#define FTS2BA61Y_COORDINATE_EVENT		0x0
> +
> +/* touch types */
> +#define FTS2BA61Y_TOUCHTYPE_NORMAL		0x0
> +#define FTS2BA61Y_TOUCHTYPE_HOVER		0x1
> +#define FTS2BA61Y_TOUCHTYPE_FLIPCOVER		0x2
> +#define FTS2BA61Y_TOUCHTYPE_GLOVE		0x3
> +#define FTS2BA61Y_TOUCHTYPE_STYLUS		0x4
> +#define FTS2BA61Y_TOUCHTYPE_PALM		0x5
> +#define FTS2BA61Y_TOUCHTYPE_WET			0x6
> +#define FTS2BA61Y_TOUCHTYPE_PROXIMITY		0x7
> +#define FTS2BA61Y_TOUCHTYPE_JIG			0x8
> +
> +#define FTS2BA61Y_COORDINATE_ACTION_NONE	0x0
> +#define FTS2BA61Y_COORDINATE_ACTION_PRESS	0x1
> +#define FTS2BA61Y_COORDINATE_ACTION_MOVE	0x2
> +#define FTS2BA61Y_COORDINATE_ACTION_RELEASE	0x3
> +
> +#define FTS2BA61Y_DEV_NAME			"fts2ba61y"
> +#define FTS2BA61Y_EVENT_BUFF_SIZE		16
> +#define FTS2BA61Y_PANEL_INFO_SIZE		11
> +#define FTS2BA61Y_RESET_CMD_SIZE		5
> +#define FTS2BA61Y_EVENT_COUNT			31
> +#define MAX_TRANSFER_SIZE			256
> +
> +enum fts2ba61y_regulators {
> +	FTS2BA61Y_REGULATOR_VDD,
> +	FTS2BA61Y_REGULATOR_AVDD,
> +};
> +
> +struct fts2ba61y_data {
> +	struct spi_device *spi;
> +	struct regulator_bulk_data regulators[2];
> +	struct input_dev *input_dev;
> +	struct mutex mutex;
> +	struct touchscreen_properties prop;
> +
> +	u8 tx_count;
> +
> +	unsigned int max_x;
> +	unsigned int max_y;
> +};
> +
> +static int fts2ba61y_write(struct fts2ba61y_data *ts,
> +			   u8 *reg, int cmd_len, u8 *data, int data_len)
> +{
> +	struct spi_message msg;
> +	struct spi_transfer xfers;
> +	char *tx_buf;
> +	int len;
> +	int ret;

Please use "error" for variables that only contain error codes or 0.

> +
> +	tx_buf = kzalloc(cmd_len + data_len + 1, GFP_KERNEL);
> +	if (!tx_buf) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}

Instead of allocating and freeing memory on each transfer consider
allocating tx and rx scratch buffers in fts2ba61y_data structure (either
as ____cacheline_aligned or as separate allocations).

If you absolutely need a per transfer allocations then use

	u8 *tx_buf __free(kfree) = kzalloc(...);

> +
> +	memset(&xfers, 0, sizeof(xfers));
> +	spi_message_init(&msg);
> +
> +	memcpy(&tx_buf[0], reg, cmd_len);
> +	if (data_len && data)
> +		memcpy(&tx_buf[cmd_len], data, data_len);
> +
> +	len = cmd_len + data_len;
> +
> +	/* custom write cmd */
> +	if (reg[0] != FTS2BA61Y_CMD_REG_W &&
> +	    reg[0] != FTS2BA61Y_CMD_REG_R) {
> +		memmove(tx_buf + 1, tx_buf, len);
> +		tx_buf[0] = FTS2BA61Y_CMD_CUSTOM_W;
> +		len++;
> +	}
> +
> +	xfers.len = len;
> +	xfers.tx_buf = tx_buf;
> +
> +	spi_message_add_tail(&xfers, &msg);
> +
> +	mutex_lock(&ts->mutex);

Why is this mutex needed? spi_sync() does the bus lock already, what
else needs protection. Even with shared scratch buffers I believe the
driver at any one point would only have one read or write operation in
progress...

> +	ret = spi_sync(ts->spi, &msg);
> +	if (ret)
> +		dev_err(&ts->spi->dev, "spi transfer error, %d", ret);
> +	mutex_unlock(&ts->mutex);
> +
> +out:
> +	kfree(tx_buf);
> +	return ret;
> +}
> +
> +static int fts2ba61y_spi_raw_read(struct fts2ba61y_data *ts,
> +				  u8 *tx_buf, u8 *rx_buf, int len)
> +{
> +	struct spi_message msg;
> +	struct spi_transfer xfer;
> +	int ret;
> +
> +	memset(&xfer, 0, sizeof(xfer));
> +	spi_message_init(&msg);
> +
> +	xfer.len = len;
> +	xfer.tx_buf = tx_buf;
> +	xfer.rx_buf = rx_buf;
> +	spi_message_add_tail(&xfer, &msg);
> +
> +	mutex_lock(&ts->mutex);
> +	ret = spi_sync(ts->spi, &msg);
> +	if (ret)
> +		dev_err(&ts->spi->dev, "spi transfer error, %d", ret);
> +	mutex_unlock(&ts->mutex);
> +
> +	return ret;
> +}
> +
> +/*
> + * higher-level wrapper that prepares the buffers for a read.
> + */
> +static int fts2ba61y_read(struct fts2ba61y_data *ts,
> +			  u8 reg[], int tx_len, u8 buf[], int rx_len)

As far as I can see fts2ba61y_read() is always used with a single byte
command. Why not make it "u8 cmd" or "u8 reg" and drop tx_len.

Same goes for fts2ba61y_write(). Also the read buffer might make sense
as void * instead of u8 *, so that you do not have to cast.

> +{
> +	char *tx_buf, *rx_buf;
> +	int ret, mem_len;
> +	u16 reg_val;
> +
> +	if (tx_len > 3)
> +		mem_len = rx_len + 1 + tx_len;
> +	else
> +		mem_len = rx_len + 4;

A commend why we need this "+ 4" would be useful. 

> +
> +	tx_buf = kzalloc(mem_len, GFP_KERNEL);
> +	rx_buf = kzalloc(mem_len, GFP_KERNEL);
> +	if (!tx_buf || !rx_buf) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	switch (reg[0]) {
> +	case FTS2BA61Y_CMD_READ_EVENT:
> +	case FTS2BA61Y_CMD_REG_W:
> +	case FTS2BA61Y_CMD_REG_R:
> +		memcpy(tx_buf, reg, tx_len);
> +		break;
> +
> +	default:
> +		tx_buf[0] = FTS2BA61Y_CMD_CUSTOM_R;
> +
> +		if (tx_len == 1)
> +			reg_val = 0;
> +		else if (tx_len == 2)
> +			reg_val = reg[0];
> +		else if (tx_len == 3)
> +			reg_val = reg[0] | (reg[1] << 8);
> +		else {

If one branch has braces all of them have to have braces.

> +			ret = -EINVAL;
> +			goto out;
> +		}
> +
> +		tx_len = 3;
> +		put_unaligned_be16(reg_val, &tx_buf[1]);
> +
> +		ret = fts2ba61y_write(ts, reg, 1, NULL, 0);
> +		if (ret < 0)
> +			goto out;
> +		break;
> +	}
> +
> +	ret = fts2ba61y_spi_raw_read(ts, tx_buf, rx_buf, rx_len + 1 + tx_len);
> +	if (ret < 0)
> +		goto out;
> +
> +	memcpy(buf, &rx_buf[1 + tx_len], rx_len);
> +
> +out:
> +	kfree(tx_buf);
> +	kfree(rx_buf);
> +	return ret;
> +}
> +
> +static int fts2ba61y_wait_for_ready(struct fts2ba61y_data *ts)
> +{
> +	u8 buffer[FTS2BA61Y_EVENT_BUFF_SIZE];
> +	u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
> +	u8 status_id, stype;
> +	int ret;
> +
> +	for (int retries = 5; retries > 0; retries--) {
> +		ret = fts2ba61y_read(ts, &cmd, 1, buffer, FTS2BA61Y_EVENT_BUFF_SIZE);
> +
> +		stype = FIELD_GET(FTS2BA61Y_MASK_STYPE, buffer[0]);
> +		status_id = buffer[1];
> +
> +		if (stype == FTS2BA61Y_EVENT_STATUSTYPE_INFO &&
> +		    status_id == FTS2BA61Y_INFO_READY_STATUS) {
> +			ret = 0;
> +			break;
> +		} else
> +			ret = -ENODEV;

"else" needs braces as well.

> +
> +		msleep(20);
> +	}
> +
> +	return ret;
> +}
> +
> +static int fts2ba61y_reset(struct fts2ba61y_data *ts)
> +{
> +	u8 cmd = FTS2BA61Y_CMD_REG_W;
> +	/* the following sequence is undocumented */
> +	u8 reset[FTS2BA61Y_RESET_CMD_SIZE] = { 0x20, 0x00,
> +					       0x00, 0x24, 0x81 };
> +	int ret;
> +
> +	disable_irq(ts->spi->irq);
> +
> +	ret = fts2ba61y_write(ts, &cmd, 1, &reset[0], FTS2BA61Y_RESET_CMD_SIZE);
> +	if (ret)
> +		return ret;

You end up with interrupts disabled on error which may be unexpected.
Better use

	guard(disable_irq)(&ts->spi->irq);

> +	msleep(30);
> +
> +	ret = fts2ba61y_wait_for_ready(ts);
> +	if (ret)
> +		return ret;
> +
> +	enable_irq(ts->spi->irq);
> +
> +	return 0;
> +}
> +
> +static int fts2ba61y_set_channels(struct fts2ba61y_data *ts)
> +{
> +	int ret;
> +	u8 cmd = FTS2BA61Y_CMD_READ_PANEL_INFO;
> +	u8 data[FTS2BA61Y_PANEL_INFO_SIZE];
> +
> +	ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_PANEL_INFO_SIZE);
> +	if (ret)
> +		return ret;
> +
> +	ts->max_x = get_unaligned_be16(data);
> +	ts->max_y = get_unaligned_be16(data + 2);
> +
> +	/* if no tx channels defined, at least keep one */
> +	ts->tx_count = max_t(u8, data[8], 1);
> +
> +	return 0;
> +}
> +
> +static int fts2ba61y_set_touch_func(struct fts2ba61y_data *ts)
> +{
> +	u8 cmd = FTS2BA61Y_CMD_TOUCHTYPE;
> +	u16 touchtype = cpu_to_le16(FTS2BA61Y_TOUCHTYPE_DEFAULT);
> +
> +	return fts2ba61y_write(ts, &cmd, 1, (u8 *)&touchtype, 2);
> +}
> +
> +static int fts2ba61y_hw_init(struct fts2ba61y_data *ts)
> +{
> +	int ret;
> +
> +	ret = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
> +								ts->regulators);
> +	if (ret)
> +		return ret;
> +
> +	msleep(140);
> +
> +	ret = fts2ba61y_reset(ts);
> +	if (ret)
> +		return ret;

You need to disable regulators on error.

> +
> +	ret = fts2ba61y_set_channels(ts);
> +	if (ret)
> +		return ret;
> +
> +	return fts2ba61y_set_touch_func(ts);

In functions with multiple points of failure do not end with "return
function_call();". Use standard

	error = operation(...);
	if (error)
		return error;

	return 0;

> +}
> +
> +static int fts2ba61y_get_event(struct fts2ba61y_data *ts, u8 *data, int *n_events)
> +{
> +	int ret;
> +	u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
> +
> +	ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_EVENT_BUFF_SIZE);
> +	if (ret < 0)

fts2ba61y_read() does not return positive success values, so:

	if (error)
		return error;

> +		return ret;
> +
> +	if (!data[0]) {
> +		*n_events = 0;
> +		return 0;
> +	}
> +
> +	*n_events = FIELD_GET(FTS2BA61Y_MASK_LEFT_EVENTS, data[7]);
> +	if (unlikely(*n_events >= FTS2BA61Y_EVENT_COUNT)) {
> +		cmd = FTS2BA61Y_CMD_CLEAR_EVENTS;
> +		fts2ba61y_write(ts, &cmd, 1, NULL, 0);
> +		*n_events = 0;
> +		return -EINVAL;
> +	}
> +
> +	if (*n_events > 0) {
> +		ret = fts2ba61y_read(ts, &cmd, 1,
> +				     &data[1 * FTS2BA61Y_EVENT_BUFF_SIZE],
> +				     FTS2BA61Y_EVENT_BUFF_SIZE * (*n_events));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void fts2ba61y_report_coordinates(struct fts2ba61y_data *ts,
> +					 u8 *event, u8 tid)
> +{
> +	u8 major = event[4];
> +	u8 minor = event[5];
> +	u8 z = FIELD_GET(FTS2BA61Y_MASK_Z, event[6]);
> +
> +	u16 x = (event[1] << 4) |
> +		FIELD_GET(FTS2BA61Y_MASK_X_3_0, event[3]);
> +	u16 y = (event[2] << 4) |
> +		FIELD_GET(FTS2BA61Y_MASK_Y_3_0, event[3]);
> +	u16 ttype = (FIELD_GET(FTS2BA61Y_MASK_TTYPE_3_2, event[6]) << 2) |
> +		    (FIELD_GET(FTS2BA61Y_MASK_TTYPE_1_0, event[7]) << 0);
> +
> +	if (ttype != FTS2BA61Y_TOUCHTYPE_NORMAL &&
> +	    ttype != FTS2BA61Y_TOUCHTYPE_PALM &&
> +	    ttype != FTS2BA61Y_TOUCHTYPE_WET &&
> +	    ttype != FTS2BA61Y_TOUCHTYPE_GLOVE)
> +		return;
> +
> +	input_mt_slot(ts->input_dev, tid);
> +	input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
> +	input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
> +	input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
> +	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, major);
> +	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR, minor);
> +	input_report_abs(ts->input_dev, ABS_MT_PRESSURE, z);
> +
> +	input_mt_sync_frame(ts->input_dev);
> +	input_sync(ts->input_dev);
> +}
> +
> +static void fts2ba61y_report_release(struct fts2ba61y_data *ts, u8 tid)
> +{
> +	input_mt_slot(ts->input_dev, tid);
> +	input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
> +
> +	input_mt_sync_frame(ts->input_dev);
> +	input_sync(ts->input_dev);
> +}
> +
> +static void fts2ba61y_handle_coordinates(struct fts2ba61y_data *ts, u8 *event)
> +{
> +	u8 t_id = FIELD_GET(FTS2BA61Y_MASK_TID, event[0]);
> +	u8 action = FIELD_GET(FTS2BA61Y_MASK_TCHSTA, event[0]);
> +
> +	if (t_id > ts->tx_count)
> +		return;
> +
> +	switch (action) {
> +	case FTS2BA61Y_COORDINATE_ACTION_PRESS:
> +	case FTS2BA61Y_COORDINATE_ACTION_MOVE:
> +		fts2ba61y_report_coordinates(ts, event, t_id);
> +		break;
> +
> +	case FTS2BA61Y_COORDINATE_ACTION_RELEASE:
> +		fts2ba61y_report_release(ts, t_id);
> +		break;
> +	}
> +}
> +
> +static irqreturn_t fts2ba61y_irq_handler(int irq, void *handle)
> +{
> +	struct fts2ba61y_data *ts = handle;
> +	u8 buffer[FTS2BA61Y_EVENT_COUNT * FTS2BA61Y_EVENT_BUFF_SIZE];
> +	u8 *event;
> +	u8 event_id;
> +	int n_events = 0;
> +	int ret;
> +
> +	usleep(1);

Why?

> +
> +	ret = fts2ba61y_get_event(ts, buffer, &n_events);
> +	if (ret < 0) {
> +		dev_dbg(&ts->spi->dev, "failed to get event: %d", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	for (int i = 0; i <= n_events; i++) {
> +		event = &buffer[i * FTS2BA61Y_EVENT_BUFF_SIZE];
> +		event_id = FIELD_GET(FTS2BA61Y_MASK_EVENT_ID, event[0]);
> +
> +		if (event_id == FTS2BA61Y_COORDINATE_EVENT)
> +			fts2ba61y_handle_coordinates(ts, event);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int fts2ba61y_input_open(struct input_dev *dev)
> +{
> +	struct fts2ba61y_data *ts = input_get_drvdata(dev);
> +	u8 cmd = FTS2BA61Y_CMD_SENSE_ON;
> +
> +	return fts2ba61y_write(ts, &cmd, 1, NULL, 0);
> +}
> +
> +static void fts2ba61y_input_close(struct input_dev *dev)
> +{
> +	struct fts2ba61y_data *ts = input_get_drvdata(dev);
> +	int ret;
> +	u8 cmd = FTS2BA61Y_CMD_SENSE_OFF;
> +
> +	ret = fts2ba61y_write(ts, &cmd, 1, NULL, 0);
> +	if (ret)
> +		dev_err(&ts->spi->dev, "failed to turn off sensing\n");
> +}
> +
> +static void fts2ba61y_power_off(void *data)
> +{
> +	struct fts2ba61y_data *ts = data;
> +
> +	disable_irq(ts->spi->irq);

This may get called before interrupt is requested. Why does it need to
be here?

> +	regulator_bulk_disable(ARRAY_SIZE(ts->regulators),
> +						   ts->regulators);
> +}
> +
> +static int fts2ba61y_probe(struct spi_device *spi) {
> +	struct fts2ba61y_data *ts;
> +	struct input_dev *input_dev;
> +	int error;
> +
> +	ts = devm_kzalloc(&spi->dev, sizeof(*ts), GFP_KERNEL);
> +	if (!ts)
> +		return -ENOMEM;
> +
> +	ts->spi = spi;
> +	mutex_init(&ts->mutex);
> +
> +	spi->mode = SPI_MODE_0;
> +	spi->bits_per_word = 8;
> +
> +	error = spi_setup(spi);
> +	if (error)
> +		return error;
> +
> +	ts->regulators[FTS2BA61Y_REGULATOR_VDD].supply = "vdd";
> +	ts->regulators[FTS2BA61Y_REGULATOR_AVDD].supply = "avdd";
> +	error = devm_regulator_bulk_get(&spi->dev,
> +									ARRAY_SIZE(ts->regulators),
> +									ts->regulators);
> +	if (error)
> +		return error;
> +
> +	error = fts2ba61y_hw_init(ts);
> +	if (error)
> +		return error;
> +
> +	error = devm_add_action_or_reset(&ts->spi->dev, fts2ba61y_power_off, ts);
> +	if (error)
> +		return error;
> +
> +	input_dev = devm_input_allocate_device(&spi->dev);
> +	if (!input_dev)
> +		return -ENOMEM;
> +
> +	ts->input_dev = input_dev;
> +
> +	input_dev->name = FTS2BA61Y_DEV_NAME;
> +	input_dev->id.bustype = BUS_SPI;
> +	input_dev->open = fts2ba61y_input_open;
> +	input_dev->close = fts2ba61y_input_close;
> +
> +	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
> +	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
> +	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
> +	input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
> +	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
> +
> +	touchscreen_parse_properties(input_dev, true, &ts->prop);
> +
> +	spi_set_drvdata(spi, ts);
> +	input_set_drvdata(input_dev, ts);
> +
> +	error = input_mt_init_slots(input_dev, ts->tx_count, INPUT_MT_DIRECT);
> +	if (error)
> +		return error;
> +
> +	error = input_register_device(input_dev);
> +	if (error)
> +		return error;
> +
> +	error = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
> +					  fts2ba61y_irq_handler,
> +					  IRQF_TRIGGER_LOW | IRQF_ONESHOT,

Do not encode interrupt polarity, let device tree specify it according
to system design. So just IRQF_ONESHOT.

> +					  "fts2ba61y_irq", ts);
> +	return error;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id spi_touchscreen_dt_ids[] = {
> +	{ .compatible = "st,fts2ba61y" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, spi_touchscreen_dt_ids);
> +#endif
> +
> +static const struct spi_device_id fts2ba61y_spi_ids[] = {
> +	{ "fts2ba61y" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(spi, fts2ba61y_spi_ids);
> +
> +static struct spi_driver spi_touchscreen_driver = {
> +	.driver = {
> +		.name = FTS2BA61Y_DEV_NAME,
> +		.of_match_table = of_match_ptr(spi_touchscreen_dt_ids),
> +	},
> +	.probe = fts2ba61y_probe,
> +	.id_table = fts2ba61y_spi_ids,
> +};
> +
> +module_spi_driver(spi_touchscreen_driver);
> +
> +MODULE_AUTHOR("Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>");
> +MODULE_DESCRIPTION("ST-Microelectronics FTS2BA61Y Touch Screen");
> +MODULE_LICENSE("GPL");

Thanks.

-- 
Dmitry

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

* Re: [PATCH v1 1/2] dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen
  2025-09-20  1:44 ` [PATCH v1 1/2] dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen Eric Gonçalves
@ 2025-09-22 20:08   ` Rob Herring (Arm)
  0 siblings, 0 replies; 7+ messages in thread
From: Rob Herring (Arm) @ 2025-09-22 20:08 UTC (permalink / raw)
  To: Eric Gonçalves
  Cc: Dmitry Torokhov, Ivaylo Ivanov, Krzysztof Kozlowski, devicetree,
	linux-kernel, linux-input, Conor Dooley, Henrik Rydberg


On Sat, 20 Sep 2025 01:44:49 +0000, Eric Gonçalves wrote:
> Add the bindings for ST-Microelectronics FTS2BA61Y capacitive touchscreen.
> 
> Signed-off-by: Eric Gonçalves <ghatto404@gmail.com>
> ---
>  .../input/touchscreen/st,fts2ba61y.yaml       | 52 +++++++++++++++++++
>  1 file changed, 52 insertions(+)
>  create mode 100755 Documentation/devicetree/bindings/input/touchscreen/st,fts2ba61y.yaml
> 

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>


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

* Re: [PATCH v1 2/2] Input: add support for the STM FTS2BA61Y touchscreen
  2025-09-20 21:13   ` Dmitry Torokhov
@ 2025-09-27  2:42     ` Eric Gonçalves
  0 siblings, 0 replies; 7+ messages in thread
From: Eric Gonçalves @ 2025-09-27  2:42 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Henrik Rydberg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Ivaylo Ivanov, devicetree, linux-input, linux-kernel


On 9/20/25 21:13, Dmitry Torokhov wrote:
> Hi Eric,
>
> On Sat, Sep 20, 2025 at 01:44:50AM +0000, Eric Gonçalves wrote:
>> The ST-Microelectronics FTS2BA61Y touchscreen is a capacitive multi-touch
>> controller connected through SPI at 0x0, the touchscreen is typically
>> used in mobile devices (like the Galaxy S22 series)
>>
>> Signed-off-by: Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>
>> Signed-off-by: Eric Gonçalves <ghatto404@gmail.com>
> Thank you for the patch. A few comments below.
>
>> ---
>>   drivers/input/touchscreen/Kconfig     |  11 +
>>   drivers/input/touchscreen/Makefile    |   1 +
>>   drivers/input/touchscreen/fts2ba61y.c | 588 ++++++++++++++++++++++++++
>>   3 files changed, 600 insertions(+)
>>   create mode 100644 drivers/input/touchscreen/fts2ba61y.c
>>
>> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
>> index 196905162945..1e199191f527 100644
>> --- a/drivers/input/touchscreen/Kconfig
>> +++ b/drivers/input/touchscreen/Kconfig
>> @@ -370,6 +370,17 @@ config TOUCHSCREEN_EXC3000
>>   	  To compile this driver as a module, choose M here: the
>>   	  module will be called exc3000.
>>   
>> +config TOUCHSCREEN_FTS2BA61Y
>> +	tristate "ST-Microelectronics FTS2BA61Y touchscreen"
>> +	depends on SPI
>> +	help
>> +	  Say Y here if you have the ST-Microelectronics FTS2BA61Y touchscreen
>> +
>> +	  If unsure, say N.
>> +
>> +	  To compile this driver as a module, choose M here: the
>> +	  module will be called fts2ba61y.
>> +
>>   config TOUCHSCREEN_FUJITSU
>>   	tristate "Fujitsu serial touchscreen"
>>   	select SERIO
>> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
>> index 97a025c6a377..408a9fd5bd35 100644
>> --- a/drivers/input/touchscreen/Makefile
>> +++ b/drivers/input/touchscreen/Makefile
>> @@ -43,6 +43,7 @@ obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
>>   obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
>>   obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL)	+= egalax_ts_serial.o
>>   obj-$(CONFIG_TOUCHSCREEN_EXC3000)	+= exc3000.o
>> +obj-$(CONFIG_TOUCHSCREEN_FTS2BA61Y)	+= fts2ba61y.o
>>   obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
>>   obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix_ts.o
>>   obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE)	+= goodix_berlin_core.o
>> diff --git a/drivers/input/touchscreen/fts2ba61y.c b/drivers/input/touchscreen/fts2ba61y.c
>> new file mode 100644
>> index 000000000000..b3b3abca5404
>> --- /dev/null
>> +++ b/drivers/input/touchscreen/fts2ba61y.c
>> @@ -0,0 +1,588 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +// Based loosely on s6sy761.c
>> +
>> +#include <linux/delay.h>
>> +#include <linux/input.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/input/mt.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/input/touchscreen.h>
>> +#include <linux/unaligned.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +/* commands */
>> +#define FTS2BA61Y_CMD_SENSE_ON			0x10
>> +#define FTS2BA61Y_CMD_SENSE_OFF			0x11
>> +#define FTS2BA61Y_CMD_READ_PANEL_INFO		0x23
>> +#define FTS2BA61Y_CMD_READ_FW_VER		0x24
>> +#define FTS2BA61Y_CMD_TOUCHTYPE			0x30 /* R/W for get/set */
>> +#define FTS2BA61Y_CMD_CLEAR_EVENTS		0x62
>> +#define FTS2BA61Y_CMD_READ_EVENT		0x87
>> +#define FTS2BA61Y_CMD_CUSTOM_W			0xC0
>> +#define FTS2BA61Y_CMD_CUSTOM_R			0xD1
>> +#define FTS2BA61Y_CMD_REG_W			0xFA
>> +#define FTS2BA61Y_CMD_REG_R			0xFB
>> +
>> +/* touch type masks */
>> +#define FTS2BA61Y_MASK_TOUCH			BIT(0)
>> +#define FTS2BA61Y_MASK_HOVER			BIT(1)
>> +#define FTS2BA61Y_MASK_COVER			BIT(2)
>> +#define FTS2BA61Y_MASK_GLOVE			BIT(3)
>> +#define FTS2BA61Y_MASK_STYLUS			BIT(4)
>> +#define FTS2BA61Y_MASK_PALM			BIT(5)
>> +#define FTS2BA61Y_MASK_WET			BIT(6)
>> +#define FTS2BA61Y_TOUCHTYPE_DEFAULT		(FTS2BA61Y_MASK_TOUCH | \
>> +						 FTS2BA61Y_MASK_PALM | \
>> +						 FTS2BA61Y_MASK_WET)
>> +
>> +/* event status masks */
>> +#define FTS2BA61Y_MASK_STYPE			GENMASK(5, 2)
>> +#define FTS2BA61Y_MASK_EVENT_ID			GENMASK(1, 0)
>> +
>> +/* event coordinate masks */
>> +#define FTS2BA61Y_MASK_TCHSTA			GENMASK(7, 6)
>> +#define FTS2BA61Y_MASK_TID			GENMASK(5, 2)
>> +#define FTS2BA61Y_MASK_X_3_0			GENMASK(7, 4)
>> +#define FTS2BA61Y_MASK_Y_3_0			GENMASK(3, 0)
>> +#define FTS2BA61Y_MASK_Z			GENMASK(5, 0)
>> +#define FTS2BA61Y_MASK_TTYPE_3_2		GENMASK(7, 6)
>> +#define FTS2BA61Y_MASK_TTYPE_1_0		GENMASK(1, 0)
>> +#define FTS2BA61Y_MASK_LEFT_EVENTS		GENMASK(4, 0)
>> +
>> +/* event error status */
>> +#define FTS2BA61Y_EVENT_STATUSTYPE_INFO		0x2
>> +
>> +/* information report */
>> +#define FTS2BA61Y_INFO_READY_STATUS		0x0
>> +
>> +/* event status */
>> +#define FTS2BA61Y_COORDINATE_EVENT		0x0
>> +
>> +/* touch types */
>> +#define FTS2BA61Y_TOUCHTYPE_NORMAL		0x0
>> +#define FTS2BA61Y_TOUCHTYPE_HOVER		0x1
>> +#define FTS2BA61Y_TOUCHTYPE_FLIPCOVER		0x2
>> +#define FTS2BA61Y_TOUCHTYPE_GLOVE		0x3
>> +#define FTS2BA61Y_TOUCHTYPE_STYLUS		0x4
>> +#define FTS2BA61Y_TOUCHTYPE_PALM		0x5
>> +#define FTS2BA61Y_TOUCHTYPE_WET			0x6
>> +#define FTS2BA61Y_TOUCHTYPE_PROXIMITY		0x7
>> +#define FTS2BA61Y_TOUCHTYPE_JIG			0x8
>> +
>> +#define FTS2BA61Y_COORDINATE_ACTION_NONE	0x0
>> +#define FTS2BA61Y_COORDINATE_ACTION_PRESS	0x1
>> +#define FTS2BA61Y_COORDINATE_ACTION_MOVE	0x2
>> +#define FTS2BA61Y_COORDINATE_ACTION_RELEASE	0x3
>> +
>> +#define FTS2BA61Y_DEV_NAME			"fts2ba61y"
>> +#define FTS2BA61Y_EVENT_BUFF_SIZE		16
>> +#define FTS2BA61Y_PANEL_INFO_SIZE		11
>> +#define FTS2BA61Y_RESET_CMD_SIZE		5
>> +#define FTS2BA61Y_EVENT_COUNT			31
>> +#define MAX_TRANSFER_SIZE			256
>> +
>> +enum fts2ba61y_regulators {
>> +	FTS2BA61Y_REGULATOR_VDD,
>> +	FTS2BA61Y_REGULATOR_AVDD,
>> +};
>> +
>> +struct fts2ba61y_data {
>> +	struct spi_device *spi;
>> +	struct regulator_bulk_data regulators[2];
>> +	struct input_dev *input_dev;
>> +	struct mutex mutex;
>> +	struct touchscreen_properties prop;
>> +
>> +	u8 tx_count;
>> +
>> +	unsigned int max_x;
>> +	unsigned int max_y;
>> +};
>> +
>> +static int fts2ba61y_write(struct fts2ba61y_data *ts,
>> +			   u8 *reg, int cmd_len, u8 *data, int data_len)
>> +{
>> +	struct spi_message msg;
>> +	struct spi_transfer xfers;
>> +	char *tx_buf;
>> +	int len;
>> +	int ret;
> Please use "error" for variables that only contain error codes or 0.
Okay
>
>> +
>> +	tx_buf = kzalloc(cmd_len + data_len + 1, GFP_KERNEL);
>> +	if (!tx_buf) {
>> +		ret = -ENOMEM;
>> +		goto out;
>> +	}
> Instead of allocating and freeing memory on each transfer consider
> allocating tx and rx scratch buffers in fts2ba61y_data structure (either
> as ____cacheline_aligned or as separate allocations).
>
> If you absolutely need a per transfer allocations then use
>
> 	u8 *tx_buf __free(kfree) = kzalloc(...);
Will do
>
>> +
>> +	memset(&xfers, 0, sizeof(xfers));
>> +	spi_message_init(&msg);
>> +
>> +	memcpy(&tx_buf[0], reg, cmd_len);
>> +	if (data_len && data)
>> +		memcpy(&tx_buf[cmd_len], data, data_len);
>> +
>> +	len = cmd_len + data_len;
>> +
>> +	/* custom write cmd */
>> +	if (reg[0] != FTS2BA61Y_CMD_REG_W &&
>> +	    reg[0] != FTS2BA61Y_CMD_REG_R) {
>> +		memmove(tx_buf + 1, tx_buf, len);
>> +		tx_buf[0] = FTS2BA61Y_CMD_CUSTOM_W;
>> +		len++;
>> +	}
>> +
>> +	xfers.len = len;
>> +	xfers.tx_buf = tx_buf;
>> +
>> +	spi_message_add_tail(&xfers, &msg);
>> +
>> +	mutex_lock(&ts->mutex);
> Why is this mutex needed? spi_sync() does the bus lock already, what
> else needs protection. Even with shared scratch buffers I believe the
> driver at any one point would only have one read or write operation in
> progress...
Yeah you're right, the mutex was kept because it was in downstream code. 
Will drop
>> +	ret = spi_sync(ts->spi, &msg);
>> +	if (ret)
>> +		dev_err(&ts->spi->dev, "spi transfer error, %d", ret);
>> +	mutex_unlock(&ts->mutex);
>> +
>> +out:
>> +	kfree(tx_buf);
>> +	return ret;
>> +}
>> +
>> +static int fts2ba61y_spi_raw_read(struct fts2ba61y_data *ts,
>> +				  u8 *tx_buf, u8 *rx_buf, int len)
>> +{
>> +	struct spi_message msg;
>> +	struct spi_transfer xfer;
>> +	int ret;
>> +
>> +	memset(&xfer, 0, sizeof(xfer));
>> +	spi_message_init(&msg);
>> +
>> +	xfer.len = len;
>> +	xfer.tx_buf = tx_buf;
>> +	xfer.rx_buf = rx_buf;
>> +	spi_message_add_tail(&xfer, &msg);
>> +
>> +	mutex_lock(&ts->mutex);
>> +	ret = spi_sync(ts->spi, &msg);
>> +	if (ret)
>> +		dev_err(&ts->spi->dev, "spi transfer error, %d", ret);
>> +	mutex_unlock(&ts->mutex);
>> +
>> +	return ret;
>> +}
>> +
>> +/*
>> + * higher-level wrapper that prepares the buffers for a read.
>> + */
>> +static int fts2ba61y_read(struct fts2ba61y_data *ts,
>> +			  u8 reg[], int tx_len, u8 buf[], int rx_len)
> As far as I can see fts2ba61y_read() is always used with a single byte
> command. Why not make it "u8 cmd" or "u8 reg" and drop tx_len.
>
> Same goes for fts2ba61y_write(). Also the read buffer might make sense
> as void * instead of u8 *, so that you do not have to cast.
Alright
>
>> +{
>> +	char *tx_buf, *rx_buf;
>> +	int ret, mem_len;
>> +	u16 reg_val;
>> +
>> +	if (tx_len > 3)
>> +		mem_len = rx_len + 1 + tx_len;
>> +	else
>> +		mem_len = rx_len + 4;
> A commend why we need this "+ 4" would be useful.
Will do
>
>> +
>> +	tx_buf = kzalloc(mem_len, GFP_KERNEL);
>> +	rx_buf = kzalloc(mem_len, GFP_KERNEL);
>> +	if (!tx_buf || !rx_buf) {
>> +		ret = -ENOMEM;
>> +		goto out;
>> +	}
>> +
>> +	switch (reg[0]) {
>> +	case FTS2BA61Y_CMD_READ_EVENT:
>> +	case FTS2BA61Y_CMD_REG_W:
>> +	case FTS2BA61Y_CMD_REG_R:
>> +		memcpy(tx_buf, reg, tx_len);
>> +		break;
>> +
>> +	default:
>> +		tx_buf[0] = FTS2BA61Y_CMD_CUSTOM_R;
>> +
>> +		if (tx_len == 1)
>> +			reg_val = 0;
>> +		else if (tx_len == 2)
>> +			reg_val = reg[0];
>> +		else if (tx_len == 3)
>> +			reg_val = reg[0] | (reg[1] << 8);
>> +		else {
> If one branch has braces all of them have to have braces.
>
>> +			ret = -EINVAL;
>> +			goto out;
>> +		}
>> +
>> +		tx_len = 3;
>> +		put_unaligned_be16(reg_val, &tx_buf[1]);
>> +
>> +		ret = fts2ba61y_write(ts, reg, 1, NULL, 0);
>> +		if (ret < 0)
>> +			goto out;
>> +		break;
>> +	}
>> +
>> +	ret = fts2ba61y_spi_raw_read(ts, tx_buf, rx_buf, rx_len + 1 + tx_len);
>> +	if (ret < 0)
>> +		goto out;
>> +
>> +	memcpy(buf, &rx_buf[1 + tx_len], rx_len);
>> +
>> +out:
>> +	kfree(tx_buf);
>> +	kfree(rx_buf);
>> +	return ret;
>> +}
>> +
>> +static int fts2ba61y_wait_for_ready(struct fts2ba61y_data *ts)
>> +{
>> +	u8 buffer[FTS2BA61Y_EVENT_BUFF_SIZE];
>> +	u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
>> +	u8 status_id, stype;
>> +	int ret;
>> +
>> +	for (int retries = 5; retries > 0; retries--) {
>> +		ret = fts2ba61y_read(ts, &cmd, 1, buffer, FTS2BA61Y_EVENT_BUFF_SIZE);
>> +
>> +		stype = FIELD_GET(FTS2BA61Y_MASK_STYPE, buffer[0]);
>> +		status_id = buffer[1];
>> +
>> +		if (stype == FTS2BA61Y_EVENT_STATUSTYPE_INFO &&
>> +		    status_id == FTS2BA61Y_INFO_READY_STATUS) {
>> +			ret = 0;
>> +			break;
>> +		} else
>> +			ret = -ENODEV;
> "else" needs braces as well.
>
>> +
>> +		msleep(20);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int fts2ba61y_reset(struct fts2ba61y_data *ts)
>> +{
>> +	u8 cmd = FTS2BA61Y_CMD_REG_W;
>> +	/* the following sequence is undocumented */
>> +	u8 reset[FTS2BA61Y_RESET_CMD_SIZE] = { 0x20, 0x00,
>> +					       0x00, 0x24, 0x81 };
>> +	int ret;
>> +
>> +	disable_irq(ts->spi->irq);
>> +
>> +	ret = fts2ba61y_write(ts, &cmd, 1, &reset[0], FTS2BA61Y_RESET_CMD_SIZE);
>> +	if (ret)
>> +		return ret;
> You end up with interrupts disabled on error which may be unexpected.
> Better use
>
> 	guard(disable_irq)(&ts->spi->irq);
>
>> +	msleep(30);
>> +
>> +	ret = fts2ba61y_wait_for_ready(ts);
>> +	if (ret)
>> +		return ret;
>> +
>> +	enable_irq(ts->spi->irq);
>> +
>> +	return 0;
>> +}
>> +
>> +static int fts2ba61y_set_channels(struct fts2ba61y_data *ts)
>> +{
>> +	int ret;
>> +	u8 cmd = FTS2BA61Y_CMD_READ_PANEL_INFO;
>> +	u8 data[FTS2BA61Y_PANEL_INFO_SIZE];
>> +
>> +	ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_PANEL_INFO_SIZE);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ts->max_x = get_unaligned_be16(data);
>> +	ts->max_y = get_unaligned_be16(data + 2);
>> +
>> +	/* if no tx channels defined, at least keep one */
>> +	ts->tx_count = max_t(u8, data[8], 1);
>> +
>> +	return 0;
>> +}
>> +
>> +static int fts2ba61y_set_touch_func(struct fts2ba61y_data *ts)
>> +{
>> +	u8 cmd = FTS2BA61Y_CMD_TOUCHTYPE;
>> +	u16 touchtype = cpu_to_le16(FTS2BA61Y_TOUCHTYPE_DEFAULT);
>> +
>> +	return fts2ba61y_write(ts, &cmd, 1, (u8 *)&touchtype, 2);
>> +}
>> +
>> +static int fts2ba61y_hw_init(struct fts2ba61y_data *ts)
>> +{
>> +	int ret;
>> +
>> +	ret = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
>> +								ts->regulators);
>> +	if (ret)
>> +		return ret;
>> +
>> +	msleep(140);
>> +
>> +	ret = fts2ba61y_reset(ts);
>> +	if (ret)
>> +		return ret;
> You need to disable regulators on error.
>
>> +
>> +	ret = fts2ba61y_set_channels(ts);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return fts2ba61y_set_touch_func(ts);
> In functions with multiple points of failure do not end with "return
> function_call();". Use standard
>
> 	error = operation(...);
> 	if (error)
> 		return error;
>
> 	return 0;
>
>> +}
>> +
>> +static int fts2ba61y_get_event(struct fts2ba61y_data *ts, u8 *data, int *n_events)
>> +{
>> +	int ret;
>> +	u8 cmd = FTS2BA61Y_CMD_READ_EVENT;
>> +
>> +	ret = fts2ba61y_read(ts, &cmd, 1, data, FTS2BA61Y_EVENT_BUFF_SIZE);
>> +	if (ret < 0)
> fts2ba61y_read() does not return positive success values, so:
>
> 	if (error)
> 		return error;
>
>> +		return ret;
>> +
>> +	if (!data[0]) {
>> +		*n_events = 0;
>> +		return 0;
>> +	}
>> +
>> +	*n_events = FIELD_GET(FTS2BA61Y_MASK_LEFT_EVENTS, data[7]);
>> +	if (unlikely(*n_events >= FTS2BA61Y_EVENT_COUNT)) {
>> +		cmd = FTS2BA61Y_CMD_CLEAR_EVENTS;
>> +		fts2ba61y_write(ts, &cmd, 1, NULL, 0);
>> +		*n_events = 0;
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (*n_events > 0) {
>> +		ret = fts2ba61y_read(ts, &cmd, 1,
>> +				     &data[1 * FTS2BA61Y_EVENT_BUFF_SIZE],
>> +				     FTS2BA61Y_EVENT_BUFF_SIZE * (*n_events));
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void fts2ba61y_report_coordinates(struct fts2ba61y_data *ts,
>> +					 u8 *event, u8 tid)
>> +{
>> +	u8 major = event[4];
>> +	u8 minor = event[5];
>> +	u8 z = FIELD_GET(FTS2BA61Y_MASK_Z, event[6]);
>> +
>> +	u16 x = (event[1] << 4) |
>> +		FIELD_GET(FTS2BA61Y_MASK_X_3_0, event[3]);
>> +	u16 y = (event[2] << 4) |
>> +		FIELD_GET(FTS2BA61Y_MASK_Y_3_0, event[3]);
>> +	u16 ttype = (FIELD_GET(FTS2BA61Y_MASK_TTYPE_3_2, event[6]) << 2) |
>> +		    (FIELD_GET(FTS2BA61Y_MASK_TTYPE_1_0, event[7]) << 0);
>> +
>> +	if (ttype != FTS2BA61Y_TOUCHTYPE_NORMAL &&
>> +	    ttype != FTS2BA61Y_TOUCHTYPE_PALM &&
>> +	    ttype != FTS2BA61Y_TOUCHTYPE_WET &&
>> +	    ttype != FTS2BA61Y_TOUCHTYPE_GLOVE)
>> +		return;
>> +
>> +	input_mt_slot(ts->input_dev, tid);
>> +	input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
>> +	input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
>> +	input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
>> +	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, major);
>> +	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR, minor);
>> +	input_report_abs(ts->input_dev, ABS_MT_PRESSURE, z);
>> +
>> +	input_mt_sync_frame(ts->input_dev);
>> +	input_sync(ts->input_dev);
>> +}
>> +
>> +static void fts2ba61y_report_release(struct fts2ba61y_data *ts, u8 tid)
>> +{
>> +	input_mt_slot(ts->input_dev, tid);
>> +	input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
>> +
>> +	input_mt_sync_frame(ts->input_dev);
>> +	input_sync(ts->input_dev);
>> +}
>> +
>> +static void fts2ba61y_handle_coordinates(struct fts2ba61y_data *ts, u8 *event)
>> +{
>> +	u8 t_id = FIELD_GET(FTS2BA61Y_MASK_TID, event[0]);
>> +	u8 action = FIELD_GET(FTS2BA61Y_MASK_TCHSTA, event[0]);
>> +
>> +	if (t_id > ts->tx_count)
>> +		return;
>> +
>> +	switch (action) {
>> +	case FTS2BA61Y_COORDINATE_ACTION_PRESS:
>> +	case FTS2BA61Y_COORDINATE_ACTION_MOVE:
>> +		fts2ba61y_report_coordinates(ts, event, t_id);
>> +		break;
>> +
>> +	case FTS2BA61Y_COORDINATE_ACTION_RELEASE:
>> +		fts2ba61y_report_release(ts, t_id);
>> +		break;
>> +	}
>> +}
>> +
>> +static irqreturn_t fts2ba61y_irq_handler(int irq, void *handle)
>> +{
>> +	struct fts2ba61y_data *ts = handle;
>> +	u8 buffer[FTS2BA61Y_EVENT_COUNT * FTS2BA61Y_EVENT_BUFF_SIZE];
>> +	u8 *event;
>> +	u8 event_id;
>> +	int n_events = 0;
>> +	int ret;
>> +
>> +	usleep(1);
> Why?
Must've been added by mistake.
>
>> +
>> +	ret = fts2ba61y_get_event(ts, buffer, &n_events);
>> +	if (ret < 0) {
>> +		dev_dbg(&ts->spi->dev, "failed to get event: %d", ret);
>> +		return IRQ_HANDLED;
>> +	}
>> +
>> +	for (int i = 0; i <= n_events; i++) {
>> +		event = &buffer[i * FTS2BA61Y_EVENT_BUFF_SIZE];
>> +		event_id = FIELD_GET(FTS2BA61Y_MASK_EVENT_ID, event[0]);
>> +
>> +		if (event_id == FTS2BA61Y_COORDINATE_EVENT)
>> +			fts2ba61y_handle_coordinates(ts, event);
>> +	}
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int fts2ba61y_input_open(struct input_dev *dev)
>> +{
>> +	struct fts2ba61y_data *ts = input_get_drvdata(dev);
>> +	u8 cmd = FTS2BA61Y_CMD_SENSE_ON;
>> +
>> +	return fts2ba61y_write(ts, &cmd, 1, NULL, 0);
>> +}
>> +
>> +static void fts2ba61y_input_close(struct input_dev *dev)
>> +{
>> +	struct fts2ba61y_data *ts = input_get_drvdata(dev);
>> +	int ret;
>> +	u8 cmd = FTS2BA61Y_CMD_SENSE_OFF;
>> +
>> +	ret = fts2ba61y_write(ts, &cmd, 1, NULL, 0);
>> +	if (ret)
>> +		dev_err(&ts->spi->dev, "failed to turn off sensing\n");
>> +}
>> +
>> +static void fts2ba61y_power_off(void *data)
>> +{
>> +	struct fts2ba61y_data *ts = data;
>> +
>> +	disable_irq(ts->spi->irq);
> This may get called before interrupt is requested. Why does it need to
> be here?
To ensure the interrupt doesn't fire during shutdown.
>
>> +	regulator_bulk_disable(ARRAY_SIZE(ts->regulators),
>> +						   ts->regulators);
>> +}
>> +
>> +static int fts2ba61y_probe(struct spi_device *spi) {
>> +	struct fts2ba61y_data *ts;
>> +	struct input_dev *input_dev;
>> +	int error;
>> +
>> +	ts = devm_kzalloc(&spi->dev, sizeof(*ts), GFP_KERNEL);
>> +	if (!ts)
>> +		return -ENOMEM;
>> +
>> +	ts->spi = spi;
>> +	mutex_init(&ts->mutex);
>> +
>> +	spi->mode = SPI_MODE_0;
>> +	spi->bits_per_word = 8;
>> +
>> +	error = spi_setup(spi);
>> +	if (error)
>> +		return error;
>> +
>> +	ts->regulators[FTS2BA61Y_REGULATOR_VDD].supply = "vdd";
>> +	ts->regulators[FTS2BA61Y_REGULATOR_AVDD].supply = "avdd";
>> +	error = devm_regulator_bulk_get(&spi->dev,
>> +									ARRAY_SIZE(ts->regulators),
>> +									ts->regulators);
>> +	if (error)
>> +		return error;
>> +
>> +	error = fts2ba61y_hw_init(ts);
>> +	if (error)
>> +		return error;
>> +
>> +	error = devm_add_action_or_reset(&ts->spi->dev, fts2ba61y_power_off, ts);
>> +	if (error)
>> +		return error;
>> +
>> +	input_dev = devm_input_allocate_device(&spi->dev);
>> +	if (!input_dev)
>> +		return -ENOMEM;
>> +
>> +	ts->input_dev = input_dev;
>> +
>> +	input_dev->name = FTS2BA61Y_DEV_NAME;
>> +	input_dev->id.bustype = BUS_SPI;
>> +	input_dev->open = fts2ba61y_input_open;
>> +	input_dev->close = fts2ba61y_input_close;
>> +
>> +	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
>> +	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
>> +	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
>> +	input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
>> +	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
>> +
>> +	touchscreen_parse_properties(input_dev, true, &ts->prop);
>> +
>> +	spi_set_drvdata(spi, ts);
>> +	input_set_drvdata(input_dev, ts);
>> +
>> +	error = input_mt_init_slots(input_dev, ts->tx_count, INPUT_MT_DIRECT);
>> +	if (error)
>> +		return error;
>> +
>> +	error = input_register_device(input_dev);
>> +	if (error)
>> +		return error;
>> +
>> +	error = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
>> +					  fts2ba61y_irq_handler,
>> +					  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> Do not encode interrupt polarity, let device tree specify it according
> to system design. So just IRQF_ONESHOT.
Okay
>
>> +					  "fts2ba61y_irq", ts);
>> +	return error;
>> +}
>> +
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id spi_touchscreen_dt_ids[] = {
>> +	{ .compatible = "st,fts2ba61y" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, spi_touchscreen_dt_ids);
>> +#endif
>> +
>> +static const struct spi_device_id fts2ba61y_spi_ids[] = {
>> +	{ "fts2ba61y" },
>> +	{ },
>> +};
>> +MODULE_DEVICE_TABLE(spi, fts2ba61y_spi_ids);
>> +
>> +static struct spi_driver spi_touchscreen_driver = {
>> +	.driver = {
>> +		.name = FTS2BA61Y_DEV_NAME,
>> +		.of_match_table = of_match_ptr(spi_touchscreen_dt_ids),
>> +	},
>> +	.probe = fts2ba61y_probe,
>> +	.id_table = fts2ba61y_spi_ids,
>> +};
>> +
>> +module_spi_driver(spi_touchscreen_driver);
>> +
>> +MODULE_AUTHOR("Ivaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>");
>> +MODULE_DESCRIPTION("ST-Microelectronics FTS2BA61Y Touch Screen");
>> +MODULE_LICENSE("GPL");
> Thanks.
Thank you for the review! I will send a v2 soon.

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

end of thread, other threads:[~2025-09-27  2:42 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-20  1:44 [PATCH v1 0/2] Input: add fts2ba61y touchscreen driver Eric Gonçalves
2025-09-20  1:44 ` [PATCH v1 1/2] dt-bindings: input: Add ST-Microelectronics FTS2BA61Y touchscreen Eric Gonçalves
2025-09-22 20:08   ` Rob Herring (Arm)
2025-09-20  1:44 ` [PATCH v1 2/2] Input: add support for the STM " Eric Gonçalves
2025-09-20 18:36   ` kernel test robot
2025-09-20 21:13   ` Dmitry Torokhov
2025-09-27  2:42     ` Eric Gonçalves

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).