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 <[email protected]>
---
 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, <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("EDT FT5x06 SPI Touchscreen Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("focaltech/ft8722.bin");
-- 
2.55.0

Reply via email to