All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.