[PATCH 4/7] Input: s6sy761 - add SPI support for S6SY791
Lukas Timmermann
foss at timmermann.space
Sun Oct 11 05:44:59 PDT 2026
The S6SY791 uses the same commands as the S6SY761, wrapped in an SPI
frame: a sync byte, length fields, the opcode, the payload and an 8-bit
checksum, padded to a multiple of 4 bytes. Reads are a request frame
followed by a reply frame after an opcode-specific delay, taken from
Google's downstream driver.
The reply is clocked out full duplex with zeroed tx data. On gs101
(spi-s3c64xx) a receive-only transfer leaves MOSI undefined, and the
controller then ignores the next command.
Tested on a Google Pixel 6 Pro (raven).
Signed-off-by: Lukas Timmermann <foss at timmermann.space>
---
drivers/input/touchscreen/Kconfig | 14 ++-
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/s6sy761_spi.c | 172 ++++++++++++++++++++++++++++++++
3 files changed, 183 insertions(+), 4 deletions(-)
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 76651a61324f..49788ca10d80 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -528,20 +528,26 @@ config TOUCHSCREEN_IPROC
module will be called bcm_iproc_tsc.
config TOUCHSCREEN_S6SY761
- tristate "Samsung S6SY761 Touchscreen driver"
- depends on I2C
+ tristate "Samsung S6SY761/S6SY791 Touchscreen driver"
+ depends on I2C || SPI
help
- Say Y if you have the Samsung S6SY761 driver
+ Say Y here if you have a Samsung S6SY761 or S6SY791
+ touchscreen controller connected to your system via I2C or SPI.
If unsure, say N
To compile this driver as a module, choose M here: the
- modules will be called s6sy761_core and s6sy761_i2c.
+ modules will be called s6sy761_core, s6sy761_i2c and
+ s6sy761_spi.
select TOUCHSCREEN_S6SY761_I2C if I2C
+ select TOUCHSCREEN_S6SY761_SPI if SPI
config TOUCHSCREEN_S6SY761_I2C
tristate
+config TOUCHSCREEN_S6SY761_SPI
+ tristate
+
config TOUCHSCREEN_GUNZE
tristate "Gunze AHL-51S touchscreen"
select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 24b8fb1e38d3..b76009f89bec 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -79,6 +79,7 @@ obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o
obj-$(CONFIG_TOUCHSCREEN_RM_TS) += raydium_i2c_ts.o
obj-$(CONFIG_TOUCHSCREEN_S6SY761) += s6sy761_core.o
obj-$(CONFIG_TOUCHSCREEN_S6SY761_I2C) += s6sy761_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_S6SY761_SPI) += s6sy761_spi.o
obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o
obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o
obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o
diff --git a/drivers/input/touchscreen/s6sy761_spi.c b/drivers/input/touchscreen/s6sy761_spi.c
new file mode 100644
index 000000000000..c6627e61c285
--- /dev/null
+++ b/drivers/input/touchscreen/s6sy761_spi.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+// Samsung S6SY761 SPI Touchscreen device driver
+//
+// Copyright (c) 2026 Lukas Timmermann <foss at timmermann.space>
+
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+
+#include "s6sy761_core.h"
+
+#define S6SY761_SPI_SYNC_CODE 0xaa
+#define S6SY761_SPI_HEADER_SIZE 5 /* sync, 2 len, 2 reserved */
+#define S6SY761_SPI_CHECKSUM_SIZE 1
+#define S6SY761_SPI_READ_HEADER_SIZE 7 /* sync, 4 len, opcode, status */
+
+/* opcodes that need a longer wait before the reply is ready */
+#define S6SY761_SPI_CMD_FW_INTEGRITY 0x21
+#define S6SY761_SPI_CMD_READ_ALL_EVENT 0x61
+
+static u8 s6sy761_spi_checksum(const u8 *buf, size_t len)
+{
+ u8 sum = 0;
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ sum += buf[i];
+
+ return sum;
+}
+
+static int s6sy761_spi_write(struct device *dev, u8 *xfer_buf,
+ u8 cmd, size_t length, const void *values)
+{
+ struct spi_device *spi = to_spi_device(dev);
+
+ /* header + opcode + payload + checksum */
+ size_t frame_len = S6SY761_SPI_HEADER_SIZE + 1 + length +
+ S6SY761_SPI_CHECKSUM_SIZE;
+ size_t xfer_len = ALIGN(frame_len, 4);
+
+ if (xfer_len > S6SY761_XFER_BUFFER_SIZE)
+ return -EINVAL;
+
+ xfer_buf[0] = S6SY761_SPI_SYNC_CODE;
+ put_unaligned_be16(length + 1, xfer_buf + 1);
+ xfer_buf[3] = 0;
+ xfer_buf[4] = 0;
+ xfer_buf[5] = cmd;
+
+ if (length)
+ memcpy(xfer_buf + 6, values, length);
+
+ xfer_buf[frame_len - 1] = s6sy761_spi_checksum(xfer_buf, frame_len - 1);
+
+ /* the chip expects transfers in multiples of 4 bytes */
+ memset(xfer_buf + frame_len, 0, xfer_len - frame_len);
+
+ return spi_write(spi, xfer_buf, xfer_len);
+}
+
+static unsigned int s6sy761_spi_read_delay_us(u8 cmd)
+{
+ switch (cmd) {
+ case S6SY761_SPI_CMD_FW_INTEGRITY:
+ return 20000;
+ case S6SY761_SPI_CMD_READ_ALL_EVENT:
+ return 550;
+ default:
+ return 100;
+ }
+}
+
+static int s6sy761_spi_read(struct device *dev, u8 *xfer_buf,
+ u8 cmd, size_t length, void *values)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_transfer reply = { };
+ /* request: header + opcode + checksum, no payload */
+ size_t req_len = S6SY761_SPI_HEADER_SIZE + 1 +
+ S6SY761_SPI_CHECKSUM_SIZE;
+ /* reply: header + data + checksum */
+ size_t resp_len = S6SY761_SPI_READ_HEADER_SIZE + length +
+ S6SY761_SPI_CHECKSUM_SIZE;
+ int ret;
+
+ if (ALIGN(resp_len, 4) > S6SY761_XFER_BUFFER_SIZE)
+ return -EINVAL;
+
+ /* tell the chip what to read and how much */
+ xfer_buf[0] = S6SY761_SPI_SYNC_CODE;
+ put_unaligned_be16(1, xfer_buf + 1); /* write length: opcode */
+ put_unaligned_be16(length, xfer_buf + 3); /* read length */
+ xfer_buf[5] = cmd;
+ xfer_buf[req_len - 1] = s6sy761_spi_checksum(xfer_buf, req_len - 1);
+ memset(xfer_buf + req_len, 0, ALIGN(req_len, 4) - req_len);
+
+ ret = spi_write(spi, xfer_buf, ALIGN(req_len, 4));
+ if (ret)
+ return ret;
+
+ /* give the chip time to prepare the reply */
+ fsleep(s6sy761_spi_read_delay_us(cmd));
+
+ memset(xfer_buf, 0, ALIGN(resp_len, 4));
+ reply.tx_buf = xfer_buf;
+ reply.rx_buf = xfer_buf;
+ reply.len = ALIGN(resp_len, 4);
+ ret = spi_sync_transfer(spi, &reply, 1);
+ if (ret)
+ return ret;
+
+ if (xfer_buf[0] != S6SY761_SPI_SYNC_CODE ||
+ xfer_buf[5] != cmd ||
+ xfer_buf[resp_len - 1] !=
+ s6sy761_spi_checksum(xfer_buf, resp_len - 1))
+ return -EIO;
+
+ memcpy(values, xfer_buf + S6SY761_SPI_READ_HEADER_SIZE, length);
+
+ return 0;
+}
+
+static const struct s6sy761_bus_ops s6sy761_spi_bus_ops = {
+ .bustype = BUS_SPI,
+ .read = s6sy761_spi_read,
+ .write = s6sy761_spi_write,
+};
+
+static int s6sy761_spi_probe(struct spi_device *spi)
+{
+ return s6sy761_probe(&spi->dev, &s6sy761_spi_bus_ops, spi->irq);
+}
+
+static void s6sy761_spi_remove(struct spi_device *spi)
+{
+ s6sy761_remove(&spi->dev);
+}
+
+static const struct of_device_id s6sy761_of_match[] = {
+ { .compatible = "samsung,s6sy791", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, s6sy761_of_match);
+
+static const struct spi_device_id s6sy761_id[] = {
+ { "s6sy791" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, s6sy761_id);
+
+static struct spi_driver s6sy761_driver = {
+ .driver = {
+ .name = S6SY761_DEV_NAME,
+ .dev_groups = s6sy761_sysfs_groups,
+ .of_match_table = s6sy761_of_match,
+ .pm = pm_ptr(&s6sy761_pm_ops),
+ },
+ .probe = s6sy761_spi_probe,
+ .remove = s6sy761_spi_remove,
+ .id_table = s6sy761_id,
+};
+
+module_spi_driver(s6sy761_driver);
+
+MODULE_IMPORT_NS("S6SY761");
+MODULE_AUTHOR("Lukas Timmermann <foss at timmermann.space>");
+MODULE_DESCRIPTION("Samsung S6SY791 Touch Screen");
+MODULE_LICENSE("GPL");
--
2.56.0
More information about the linux-arm-kernel
mailing list