Devicetree
 help / color / mirror / Atom feed
From: Luka Panio <lukapanio@gmail.com>
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Henrik Rydberg <rydberg@bitmath.org>,
	Douglas Anderson <dianders@chromium.org>,
	linux-input@vger.kernel.org, devicetree@vger.kernel.org,
	dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
	Luka Panio <lukapanio@gmail.com>
Subject: [PATCH 6/7] Input: edt-ft5x06 - add SPI transport and FT8722 support
Date: Sat, 17 Oct 2026 16:07:54 +0200	[thread overview]
Message-ID: <20261017140827.290666-7-lukapanio@gmail.com> (raw)
In-Reply-To: <20261017140827.290666-1-lukapanio@gmail.com>

Add an SPI bus driver and FT8722 support to edt-ft5x06.

The FT8722 TDDI has no flash, so add a boot hook that downloads the
firmware every time the controller comes out of reset: enter the boot
ROM, write the program and data RAM, verify both with the ROM's ECC
and start the application.

Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
 drivers/input/touchscreen/Kconfig          |  14 +
 drivers/input/touchscreen/Makefile         |   1 +
 drivers/input/touchscreen/edt-ft5x06-spi.c | 475 +++++++++++++++++++++
 3 files changed, 490 insertions(+)
 create mode 100644 drivers/input/touchscreen/edt-ft5x06-spi.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 9b9ae8ac3f7f..92840ea9c65f 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -805,6 +805,20 @@ config TOUCHSCREEN_EDT_FT5X06
 	  To compile this driver as a module, choose M here: the
 	  module will be called edt-ft5x06.
 
+config TOUCHSCREEN_EDT_FT5X06_SPI
+	tristate "EDT FocalTech FT5x06 SPI Touchscreen support"
+	depends on TOUCHSCREEN_EDT_FT5X06 && SPI
+	select CRC_CCITT
+	help
+	  Say Y here if you have a FocalTech touchscreen controller based
+	  on the FocalTech FT5x06 family of controllers that is connected over
+	  SPI.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called edt-ft5x06-spi.
+
 config TOUCHSCREEN_RASPBERRYPI_FW
 	tristate "Raspberry Pi's firmware base touch screen support"
 	depends on RASPBERRYPI_FIRMWARE || (RASPBERRYPI_FIRMWARE=n && COMPILE_TEST)
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index bfd9de83389d..263c7e8039af 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_TOUCHSCREEN_DA9034)	+= da9034-ts.o
 obj-$(CONFIG_TOUCHSCREEN_DA9052)	+= da9052_tsi.o
 obj-$(CONFIG_TOUCHSCREEN_DYNAPRO)	+= dynapro.o
 obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
+obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06_SPI)	+= edt-ft5x06-spi.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_HYCON_HY46XX)	+= hycon-hy46xx.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
diff --git a/drivers/input/touchscreen/edt-ft5x06-spi.c b/drivers/input/touchscreen/edt-ft5x06-spi.c
new file mode 100644
index 000000000000..d0ae0d4b621d
--- /dev/null
+++ b/drivers/input/touchscreen/edt-ft5x06-spi.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Luka Panio, <lukapanio@gmail.com>
+ *
+ * SPI transport for FocalTech touchscreen controllers handled by the
+ * edt-ft5x06 driver, with firmware download for the RAM-only FT8722.
+ *
+ * Protocol and boot sequence based on the FocalTech vendor driver:
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd.
+ */
+
+#include <linux/crc-ccitt.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+
+#include "edt-ft5x06.h"
+
+#define FTS_SPI_CTRL_WRITE		0x00
+#define FTS_SPI_CTRL_READ		BIT(7)
+#define FTS_SPI_CTRL_CRC		BIT(5)
+#define FTS_SPI_STATUS_ERR		(BIT(7) | BIT(5))
+#define FTS_SPI_HEADER_LEN		4
+#define FTS_SPI_DUMMY_LEN		3
+#define FTS_SPI_CRC_LEN			2
+#define FTS_SPI_RETRIES			3
+#define FTS_SPI_CS_HIGH_US		150
+
+/* Boot ROM commands */
+#define FTS_BOOT_CMD_START		0x55
+#define FTS_BOOT_CMD_READ_ID		0x90
+#define FTS_BOOT_CMD_START_APP		0x08
+#define FTS_BOOT_CMD_SET_ADDR		0xad
+#define FTS_BOOT_CMD_WRITE		0xae
+#define FTS_BOOT_CMD_ECC		0xcc
+#define FTS_BOOT_CMD_ECC_READ		0xcd
+#define FTS_BOOT_CMD_ECC_FINISH		0xce
+#define FTS_BOOT_ECC_FINISH_OK		0xa5
+#define FTS_BOOT_ECC_POLY		0x8408
+
+#define FTS_REG_CHIP_ID			0xa3
+
+/* Firmware image layout */
+#define FTS_FW_INFO_OFFSET		0x100
+#define FTS_FW_MIN_LEN			0x120
+
+#define FTS_BOOT_RETRIES		3
+#define FTS_ECC_RETRIES			100
+
+struct fts_ram_chip {
+	u8 boot_id[2];
+	u8 chip_id;
+	u8 len_unit;
+	u32 pram_max;
+	u32 dram_base;
+	u32 packet_len;
+	unsigned int boot_delay_ms;
+	const char *default_fw;
+};
+
+static int fts_spi_xfer(struct spi_device *spi, const u8 *tx, u8 *rx,
+			size_t len)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = tx,
+		.rx_buf = rx,
+		.len = len,
+	};
+	int ret;
+
+	ret = spi_sync_transfer(spi, &xfer, 1);
+	fsleep(FTS_SPI_CS_HIGH_US);
+
+	return ret;
+}
+
+static int fts_spi_write(struct spi_device *spi, u8 reg, const void *val,
+			 size_t len)
+{
+	size_t xlen = FTS_SPI_HEADER_LEN + (len ? FTS_SPI_DUMMY_LEN + len : 0);
+	u8 *tx, *rx;
+	int ret, i;
+
+	if (len > U16_MAX)
+		return -EINVAL;
+
+	tx = kzalloc(2 * xlen, GFP_KERNEL);
+	if (!tx)
+		return -ENOMEM;
+	rx = tx + xlen;
+
+	tx[0] = reg;
+	tx[1] = FTS_SPI_CTRL_WRITE;
+	put_unaligned_be16(len, &tx[2]);
+	if (len)
+		memcpy(&tx[FTS_SPI_HEADER_LEN + FTS_SPI_DUMMY_LEN], val, len);
+
+	for (i = 0; i < FTS_SPI_RETRIES; i++) {
+		ret = fts_spi_xfer(spi, tx, rx, xlen);
+		if (!ret && !(rx[3] & FTS_SPI_STATUS_ERR))
+			break;
+		if (!ret)
+			ret = -EIO;
+	}
+
+	kfree(tx);
+	return ret;
+}
+
+static int fts_spi_read(struct spi_device *spi, u8 reg, void *val,
+			size_t len)
+{
+	size_t dp = FTS_SPI_HEADER_LEN + FTS_SPI_DUMMY_LEN;
+	size_t xlen = dp + len + FTS_SPI_CRC_LEN;
+	u8 *tx, *rx;
+	int ret, i;
+
+	if (!len || len > U16_MAX)
+		return -EINVAL;
+
+	tx = kzalloc(2 * xlen, GFP_KERNEL);
+	if (!tx)
+		return -ENOMEM;
+	rx = tx + xlen;
+
+	tx[0] = reg;
+	tx[1] = FTS_SPI_CTRL_READ | FTS_SPI_CTRL_CRC;
+	put_unaligned_be16(len, &tx[2]);
+
+	for (i = 0; i < FTS_SPI_RETRIES; i++) {
+		ret = fts_spi_xfer(spi, tx, rx, xlen);
+		if (ret)
+			continue;
+
+		if (rx[3] & FTS_SPI_STATUS_ERR) {
+			ret = -EIO;
+			continue;
+		}
+
+		if (crc_ccitt(0xffff, &rx[dp], len) !=
+		    get_unaligned_le16(&rx[dp + len])) {
+			ret = -EBADMSG;
+			continue;
+		}
+
+		memcpy(val, &rx[dp], len);
+		break;
+	}
+
+	kfree(tx);
+	return ret;
+}
+
+static int fts_spi_regmap_write(void *context, const void *data,
+				size_t count)
+{
+	const u8 *buf = data;
+
+	return fts_spi_write(context, buf[0], buf + 1, count - 1);
+}
+
+static int fts_spi_regmap_read(void *context, const void *reg,
+			       size_t reg_size, void *val, size_t val_size)
+{
+	return fts_spi_read(context, *(const u8 *)reg, val, val_size);
+}
+
+static const struct regmap_bus fts_spi_regmap_bus = {
+	.write = fts_spi_regmap_write,
+	.read = fts_spi_regmap_read,
+};
+
+static const struct regmap_config fts_spi_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static int fts_boot_enter(struct spi_device *spi, struct gpio_desc *reset,
+			  const struct fts_ram_chip *chip)
+{
+	u8 id[2] = { };
+	int ret, i;
+
+	for (i = 0; i < FTS_BOOT_RETRIES; i++) {
+		/* The boot ROM only listens shortly after reset */
+		if (i && reset) {
+			gpiod_set_value_cansleep(reset, 1);
+			usleep_range(1000, 2000);
+			gpiod_set_value_cansleep(reset, 0);
+		}
+		fsleep(chip->boot_delay_ms * USEC_PER_MSEC);
+
+		ret = fts_spi_write(spi, FTS_BOOT_CMD_START, NULL, 0);
+		if (ret)
+			continue;
+
+		fsleep(chip->boot_delay_ms * USEC_PER_MSEC);
+
+		ret = fts_spi_read(spi, FTS_BOOT_CMD_READ_ID, id, sizeof(id));
+		if (ret)
+			continue;
+
+		if (!memcmp(id, chip->boot_id, sizeof(id)))
+			return 0;
+
+		ret = -ENODEV;
+	}
+
+	dev_err(&spi->dev, "boot ROM not responding, last id %02x%02x: %d\n",
+		id[0], id[1], ret);
+	return ret;
+}
+
+static int fts_boot_write_ram(struct spi_device *spi,
+			      const struct fts_ram_chip *chip, u32 base,
+			      const u8 *data, u32 len)
+{
+	u8 addr[3];
+	u32 off, n;
+	int ret;
+
+	for (off = 0; off < len; off += n) {
+		n = min(len - off, chip->packet_len);
+
+		put_unaligned_be24(base + off, addr);
+		ret = fts_spi_write(spi, FTS_BOOT_CMD_SET_ADDR, addr,
+				    sizeof(addr));
+		if (ret)
+			return ret;
+
+		ret = fts_spi_write(spi, FTS_BOOT_CMD_WRITE, data + off, n);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static u16 fts_boot_ecc(const u8 *data, u32 len)
+{
+	u16 ecc = 0;
+	u32 i;
+	int j;
+
+	for (i = 0; i + 1 < len; i += 2) {
+		ecc ^= get_unaligned_be16(data + i);
+		for (j = 0; j < 16; j++)
+			ecc = (ecc & 1) ? (ecc >> 1) ^ FTS_BOOT_ECC_POLY :
+					  ecc >> 1;
+	}
+
+	return ecc;
+}
+
+static int fts_boot_check_ecc(struct spi_device *spi, const u8 *data,
+			      u32 len)
+{
+	u8 cmd[6], val[2];
+	int ret, i;
+
+	put_unaligned_be24(0, &cmd[0]);
+	put_unaligned_be24(len, &cmd[3]);
+	ret = fts_spi_write(spi, FTS_BOOT_CMD_ECC, cmd, sizeof(cmd));
+	if (ret)
+		return ret;
+
+	usleep_range(2000, 3000);
+
+	for (i = 0; i < FTS_ECC_RETRIES; i++) {
+		ret = fts_spi_read(spi, FTS_BOOT_CMD_ECC_FINISH, val, 1);
+		if (ret)
+			return ret;
+		if (val[0] == FTS_BOOT_ECC_FINISH_OK)
+			break;
+		usleep_range(1000, 2000);
+	}
+	if (i == FTS_ECC_RETRIES)
+		return -ETIMEDOUT;
+
+	ret = fts_spi_read(spi, FTS_BOOT_CMD_ECC_READ, val, sizeof(val));
+	if (ret)
+		return ret;
+
+	if (get_unaligned_be16(val) != fts_boot_ecc(data, len)) {
+		dev_err(&spi->dev, "ECC mismatch: %04x != %04x\n",
+			get_unaligned_be16(val), fts_boot_ecc(data, len));
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static u32 fts_fw_len(const struct firmware *fw, unsigned int off,
+		      const struct fts_ram_chip *chip)
+{
+	const u8 *info = fw->data + FTS_FW_INFO_OFFSET + off;
+	u16 len = get_unaligned_be16(info);
+
+	if ((u16)(len + get_unaligned_be16(info + 2)) != 0xffff)
+		return 0;
+
+	return len * chip->len_unit;
+}
+
+static int fts_ram_boot(struct device *dev, struct gpio_desc *reset,
+			const struct fts_ram_chip *chip)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	const struct firmware *fw;
+	const char *fw_name = chip->default_fw;
+	u32 pram_len, dram_len;
+	u8 id;
+	int ret, i;
+
+	device_property_read_string(dev, "firmware-name", &fw_name);
+
+	ret = request_firmware(&fw, fw_name, dev);
+	if (ret)
+		return ret;
+
+	ret = -EINVAL;
+	if (fw->size < FTS_FW_MIN_LEN) {
+		dev_err(dev, "firmware %s too short\n", fw_name);
+		goto out;
+	}
+
+	pram_len = fts_fw_len(fw, 0, chip);
+	dram_len = fts_fw_len(fw, 8, chip);
+	if (!pram_len || pram_len > chip->pram_max ||
+	    pram_len + dram_len > fw->size) {
+		dev_err(dev, "invalid firmware %s\n", fw_name);
+		goto out;
+	}
+
+	ret = fts_boot_enter(spi, reset, chip);
+	if (ret)
+		goto out;
+
+	ret = fts_boot_write_ram(spi, chip, 0, fw->data, pram_len);
+	if (!ret)
+		ret = fts_boot_check_ecc(spi, fw->data, pram_len);
+	if (ret) {
+		dev_err(dev, "failed to load program RAM: %d\n", ret);
+		goto out;
+	}
+
+	if (dram_len) {
+		ret = fts_boot_write_ram(spi, chip, chip->dram_base,
+					 fw->data + pram_len, dram_len);
+		if (!ret)
+			ret = fts_boot_check_ecc(spi, fw->data + pram_len,
+						 dram_len);
+		if (ret) {
+			dev_err(dev, "failed to load data RAM: %d\n", ret);
+			goto out;
+		}
+	}
+
+	ret = fts_spi_write(spi, FTS_BOOT_CMD_START_APP, NULL, 0);
+	if (ret)
+		goto out;
+
+	/* Wait for the application to report the chip ID */
+	for (i = 0; i < 20; i++) {
+		fsleep(10 * USEC_PER_MSEC);
+		if (!fts_spi_read(spi, FTS_REG_CHIP_ID, &id, 1) &&
+		    id == chip->chip_id)
+			break;
+	}
+	if (i == 20) {
+		dev_err(dev, "firmware did not start\n");
+		ret = -EIO;
+		goto out;
+	}
+
+	dev_dbg(dev, "loaded %s (%u + %u bytes)\n", fw_name, pram_len,
+		dram_len);
+out:
+	release_firmware(fw);
+	return ret;
+}
+
+static const struct fts_ram_chip fts_ft8722 = {
+	.boot_id = { 0x87, 0x22 },
+	.chip_id = 0x87,
+	.len_unit = 2,
+	.pram_max = 88 * 1024,
+	.dram_base = 0xd00000,
+	.packet_len = 32 * 1024 - 16,
+	.boot_delay_ms = 8,
+	.default_fw = "focaltech/ft8722.bin",
+};
+
+static int ft8722_boot(struct device *dev, struct gpio_desc *reset)
+{
+	return fts_ram_boot(dev, reset, &fts_ft8722);
+}
+
+static const struct edt_ft5x06_chip_data edt_ft8722_data = {
+	.max_support_points = 10,
+	.tdata_reg = 0x01,
+	.boot = ft8722_boot,
+};
+
+static int edt_ft5x06_spi_probe(struct spi_device *spi)
+{
+	const struct edt_ft5x06_chip_data *chip_data;
+	struct regmap *regmap;
+	int ret;
+
+	chip_data = spi_get_device_match_data(spi);
+	if (!chip_data)
+		return -ENODEV;
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret)
+		return ret;
+
+	regmap = regmap_init(&spi->dev, &fts_spi_regmap_bus, spi,
+			     &fts_spi_regmap_config);
+	if (IS_ERR(regmap))
+		return dev_err_probe(&spi->dev, PTR_ERR(regmap),
+				     "failed to init regmap\n");
+
+	return edt_ft5x06_probe(&spi->dev, regmap, spi->irq, chip_data,
+				BUS_SPI, NULL);
+}
+
+static void edt_ft5x06_spi_remove(struct spi_device *spi)
+{
+	edt_ft5x06_remove(&spi->dev);
+}
+
+static const struct of_device_id edt_ft5x06_spi_of_match[] = {
+	{ .compatible = "focaltech,ft8722-ts", .data = &edt_ft8722_data },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, edt_ft5x06_spi_of_match);
+
+static const struct spi_device_id edt_ft5x06_spi_ids[] = {
+	{ "ft8722-ts", (kernel_ulong_t)&edt_ft8722_data },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, edt_ft5x06_spi_ids);
+
+static struct spi_driver edt_ft5x06_spi_driver = {
+	.driver = {
+		.name = "edt_ft5x06_spi",
+		.dev_groups = edt_ft5x06_groups,
+		.of_match_table = edt_ft5x06_spi_of_match,
+		.pm = pm_sleep_ptr(&edt_ft5x06_pm_ops),
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = edt_ft5x06_spi_ids,
+	.probe = edt_ft5x06_spi_probe,
+	.remove = edt_ft5x06_spi_remove,
+};
+module_spi_driver(edt_ft5x06_spi_driver);
+
+MODULE_IMPORT_NS("EDT_FT5X06");
+MODULE_AUTHOR("Luka Panio <lukapanio@gmail.com>");
+MODULE_DESCRIPTION("EDT FT5x06 SPI Touchscreen Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("focaltech/ft8722.bin");
-- 
2.55.0


  parent reply	other threads:[~2026-10-17 14:08 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
2026-10-17 14:07 ` [PATCH 1/7] Input: edt-ft5x06 - split the bus independent core from the I2C driver Luka Panio
2026-10-17 14:07 ` [PATCH 2/7] Input: edt-ft5x06 - move the controller bring-up into a helper Luka Panio
2026-10-17 14:16   ` sashiko-bot
2026-10-17 14:07 ` [PATCH 3/7] Input: edt-ft5x06 - add an optional controller boot hook Luka Panio
2026-10-17 14:07 ` [PATCH 4/7] Input: edt-ft5x06 - allow a chip specific touch data register Luka Panio
2026-10-17 14:07 ` [PATCH 5/7] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT8722 Luka Panio
2026-10-17 14:07 ` Luka Panio [this message]
2026-10-17 14:29   ` [PATCH 6/7] Input: edt-ft5x06 - add SPI transport and FT8722 support sashiko-bot
2026-10-17 14:07 ` [PATCH 7/7] Input: edt-ft5x06 - support following a DRM panel Luka Panio
2026-10-17 14:30   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261017140827.290666-7-lukapanio@gmail.com \
    --to=lukapanio@gmail.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dianders@chromium.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=rydberg@bitmath.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox