From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
To: Alexander Koskovich <akoskovich@pm.me>
Cc: Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Henrik Rydberg <rydberg@bitmath.org>,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH 2/2] Input: add FocalTech FT3683G touchscreen driver
Date: Tue, 3 Nov 2026 12:02:21 -0800 [thread overview]
Message-ID: <auo7GvY0Ufq_7DTn@google.com> (raw)
In-Reply-To: <20261030-focaltech-ft3683g-v1-2-38b2f941b039@pm.me>
Hi Alexander,
On Fri, Oct 30, 2026 at 05:27:15AM +0000, Alexander Koskovich wrote:
> Add a driver for the FocalTech FT3683G, a flexible OLED capacitive
> touch controller supporting 10-point multitouch over SPI. This touch
> controller is found in the Nothing Phone (3a).
>
> The driver implements the SPI transport layer, touch event parsing for
> the FocalTech protocol v2 format, and basic power management.
>
> The existing edt-ft5x06 driver is not reused, as the FT3683G uses
> FocalTech's newer v2 reporting protocol (protocol version header) over
> SPI with a custom framing (length header, dummy bytes, status byte and
> a CRC-16/MCRF4XX trailer) instead of plain register access.
>
> Signed-off-by: Alexander Koskovich <akoskovich@pm.me>
> ---
> MAINTAINERS | 1 +
> drivers/input/touchscreen/Kconfig | 12 +
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/focaltech_ft3683g.c | 365 ++++++++++++++++++++++++++
> 4 files changed, 379 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 68efbf580964..79f3d6211cb5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10503,6 +10503,7 @@ M: Alexander Koskovich <akoskovich@pm.me>
> L: linux-input@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/input/touchscreen/focaltech,ft3683g.yaml
> +F: drivers/input/touchscreen/focaltech_ft3683g.c
>
> FOCUSRITE CONTROL PROTOCOL/SCARLETT2 MIXER DRIVERS (Scarlett Gen 2+, Clarett, and Vocaster)
> M: Geoffrey D. Bennett <g@b4.vu>
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 9b9ae8ac3f7f..f7ade03f4a0e 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -1414,4 +1414,16 @@ config TOUCHSCREEN_HIMAX_HX83112B
> To compile this driver as a module, choose M here: the
> module will be called himax_hx83112b.
>
> +config TOUCHSCREEN_FOCALTECH_FT3683G
> + tristate "Focaltech FT3683G touchscreen driver"
> + depends on SPI
> + select CRC_CCITT
> + help
> + Say Y here to enable support for FocalTech FT3683G touchscreens.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called focaltech_ft3683g.
> +
> endif
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index bfd9de83389d..45cdd7bb7d6f 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -119,3 +119,4 @@ obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o
> obj-$(CONFIG_TOUCHSCREEN_IQS7211) += iqs7211.o
> obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o
> obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX83112B) += himax_hx83112b.o
> +obj-$(CONFIG_TOUCHSCREEN_FOCALTECH_FT3683G) += focaltech_ft3683g.o
> diff --git a/drivers/input/touchscreen/focaltech_ft3683g.c b/drivers/input/touchscreen/focaltech_ft3683g.c
> new file mode 100644
> index 000000000000..5c8133ffb265
> --- /dev/null
> +++ b/drivers/input/touchscreen/focaltech_ft3683g.c
> @@ -0,0 +1,365 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * FocalTech FT3683G Touchscreen Driver
> + *
> + * Copyright (c) 2012-2020 FocalTech Systems, Ltd.
> + * Copyright (c) Alexander Koskovich <akoskovich@pm.me>
> + */
> +
> +#include <linux/cache.h>
> +#include <linux/crc-ccitt.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/string.h>
> +
> +#define FT3683G_MAX_POINTS 10
> +
> +#define FT3683G_SPI_CMD_READ 0xa0
> +#define FT3683G_SPI_DUMMY 3
> +#define FT3683G_SPI_RETRIES 3
> +#define FT3683G_SPI_CS_DELAY_US 150
> +
> +#define FT3683G_REG_CHIP_ID 0xa3
> +#define FT3683G_REG_CHIP_ID2 0x9f
> +
> +#define FT3683G_CHIP_IDH 0x56
> +#define FT3683G_CHIP_IDL 0x72
> +
> +#define FT3683G_REG_TOUCH_DATA 0x01
> +
> +#define FT3683G_TOUCH_LEN 8
> +#define FT3683G_TOUCH_HEADER 4
> +#define FT3683G_TOUCH_BUF_SIZE (FT3683G_MAX_POINTS * FT3683G_TOUCH_LEN + \
> + FT3683G_TOUCH_HEADER)
> +
> +#define FT3683G_TOUCH_OFF_XH 0
> +#define FT3683G_TOUCH_OFF_XL 1
> +#define FT3683G_TOUCH_OFF_YH 2
> +#define FT3683G_TOUCH_OFF_YL 3
> +
> +#define FT3683G_EVENT_DOWN 0
> +#define FT3683G_EVENT_UP 1
> +#define FT3683G_EVENT_CONTACT 2
> +
> +#define FT3683G_PROTOCOL_V2 0x02
> +
> +#define FT3683G_FW_INIT 0xff
> +
> +struct ft3683g_data {
> + struct spi_device *spi;
> + struct input_dev *input;
> + struct touchscreen_properties props;
> + struct regulator_bulk_data supplies[2];
> + struct gpio_desc *reset_gpio;
> + struct device *dev;
> + struct mutex spi_lock; /* protects tx_buf/rx_buf */
In what cases will there be concurrent accss?
> + u8 tx_buf[256] ____cacheline_aligned;
> + u8 rx_buf[256] ____cacheline_aligned;
> +};
> +
> +static int ft3683g_spi_read(struct ft3683g_data *ts, u8 addr,
> + u8 *data, u32 len)
> +{
> + struct spi_transfer xfer = { };
> + u32 dp = 4 + FT3683G_SPI_DUMMY;
> + u32 txlen = dp + len + 2;
> + u16 crc_calc, crc_read;
> + int error, i;
> +
> + guard(mutex)(&ts->spi_lock);
> +
> + memset(ts->tx_buf, 0, txlen);
> + ts->tx_buf[0] = addr;
> + ts->tx_buf[1] = FT3683G_SPI_CMD_READ;
> + ts->tx_buf[2] = (len >> 8) & 0xff;
> + ts->tx_buf[3] = len & 0xff;
cpu_to_be16p()? or put_unaligned_be16()?
> +
> + xfer.tx_buf = ts->tx_buf;
> + xfer.rx_buf = ts->rx_buf;
> + xfer.len = txlen;
> +
> + for (i = 0; i < FT3683G_SPI_RETRIES; i++) {
> + error = spi_sync_transfer(ts->spi, &xfer, 1);
> + if (error)
> + continue;
> +
> + if (ts->rx_buf[3] & 0xa0) {
What is 0xa0? GENMASK()? Something els?
> + fsleep(FT3683G_SPI_CS_DELAY_US);
> + continue;
> + }
> +
> + crc_calc = crc_ccitt(0xffff, &ts->rx_buf[dp], len);
> + crc_read = ts->rx_buf[dp + len] |
> + (ts->rx_buf[dp + len + 1] << 8);
get_unaligned_le16()?
> + if (crc_calc != crc_read) {
> + fsleep(FT3683G_SPI_CS_DELAY_US);
> + continue;
> + }
> +
> + memcpy(data, &ts->rx_buf[dp], len);
> + return 0;
> + }
> +
> + return error ? error : -EIO;
> +}
> +
> +static int ft3683g_read_reg(struct ft3683g_data *ts, u8 addr, u8 *val)
> +{
> + return ft3683g_spi_read(ts, addr, val, 1);
> +}
> +
> +static int ft3683g_check_chip_id(struct ft3683g_data *ts)
> +{
> + u8 idh, idl;
> + int error;
> +
> + error = ft3683g_read_reg(ts, FT3683G_REG_CHIP_ID, &idh);
> + if (error)
> + return error;
> +
> + error = ft3683g_read_reg(ts, FT3683G_REG_CHIP_ID2, &idl);
> + if (error)
> + return error;
> +
> + if (idh != FT3683G_CHIP_IDH || idl != FT3683G_CHIP_IDL) {
> + dev_err(ts->dev,
> + "chip ID mismatch: expected %02x%02x, got %02x%02x\n",
> + FT3683G_CHIP_IDH, FT3683G_CHIP_IDL, idh, idl);
> + return -ENODEV;
> + }
> +
> + dev_dbg(ts->dev, "chip ID: %02x%02x\n", idh, idl);
> + return 0;
> +}
> +
> +static irqreturn_t ft3683g_irq(int irq, void *dev_id)
> +{
> + struct ft3683g_data *ts = dev_id;
> + u8 buf[FT3683G_TOUCH_BUF_SIZE];
> + unsigned int x, y, id, event;
> + int event_num, i, base;
> + u8 protocol;
> + int error;
> +
> + error = ft3683g_spi_read(ts, FT3683G_REG_TOUCH_DATA,
> + buf, sizeof(buf));
> + if (error) {
> + dev_err_ratelimited(ts->dev, "failed to read touch data: %d\n",
> + error);
> + return IRQ_HANDLED;
Why not IRQ_NONE?
> + }
> +
> + if (!memchr_inv(&buf[1], FT3683G_FW_INIT, 4)) {
> + input_mt_sync_frame(ts->input);
> + input_sync(ts->input);
> + return IRQ_HANDLED;
I'd do "goto out".
> + }
> +
> + protocol = (buf[1] >> 4) & 0x0f;
> + if (protocol != FT3683G_PROTOCOL_V2)
> + return IRQ_HANDLED;
> +
> + event_num = buf[1] & 0x0f;
> + if (!event_num || event_num > FT3683G_MAX_POINTS)
> + return IRQ_HANDLED;
> +
> + for (i = 0; i < event_num; i++) {
> + base = FT3683G_TOUCH_LEN * i + FT3683G_TOUCH_HEADER;
> +
> + event = buf[base + FT3683G_TOUCH_OFF_XH] >> 6;
> + id = buf[base + FT3683G_TOUCH_OFF_YH] >> 4;
> + if (id >= FT3683G_MAX_POINTS)
> + continue;
> +
> + input_mt_slot(ts->input, id);
> +
> + if (event == FT3683G_EVENT_DOWN ||
> + event == FT3683G_EVENT_CONTACT) {
Could be written as:
if (input_mt_report_slot_state(ts->input, MT_TOOL_FINGER,
event == FT3683G_EVENT_DOWN ||
event == FT3683G_EVENT_CONTACT) {
...
>
> + x = ((buf[base + FT3683G_TOUCH_OFF_XH] & 0x0f) << 8) |
> + buf[base + FT3683G_TOUCH_OFF_XL];
get_unaligned_xx16()?
> + y = ((buf[base + FT3683G_TOUCH_OFF_YH] & 0x0f) << 8) |
> + buf[base + FT3683G_TOUCH_OFF_YL];
> +
> + input_mt_report_slot_state(ts->input,
> + MT_TOOL_FINGER, true);
> + touchscreen_report_pos(ts->input, &ts->props,
> + x, y, true);
> + } else {
> + input_mt_report_slot_state(ts->input,
> + MT_TOOL_FINGER, false);
> + }
> + }
> +
> + input_mt_sync_frame(ts->input);
> + input_sync(ts->input);
> + return IRQ_HANDLED;
> +}
> +
> +static int ft3683g_power_on(struct ft3683g_data *ts)
> +{
> + int error;
> +
> + gpiod_set_value_cansleep(ts->reset_gpio, 1);
> + usleep_range(2000, 3000);
> +
> + error = regulator_bulk_enable(ARRAY_SIZE(ts->supplies), ts->supplies);
> + if (error) {
> + dev_err(ts->dev, "failed to enable regulators: %d\n", error);
> + return error;
> + }
> +
> + usleep_range(2000, 3000);
> + gpiod_set_value_cansleep(ts->reset_gpio, 0);
> + msleep(50);
> +
> + return 0;
> +}
> +
> +static void ft3683g_power_off(struct ft3683g_data *ts)
> +{
> + gpiod_set_value_cansleep(ts->reset_gpio, 1);
> + regulator_bulk_disable(ARRAY_SIZE(ts->supplies), ts->supplies);
> +}
> +
> +static void ft3683g_power_off_act(void *data)
> +{
> + struct ft3683g_data *ts = data;
> +
> + ft3683g_power_off(ts);
> +}
> +
> +static int ft3683g_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct ft3683g_data *ts;
> + int error;
> +
> + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
> + if (!ts)
> + return -ENOMEM;
> +
> + ts->dev = dev;
> + ts->spi = spi;
> + spi_set_drvdata(spi, ts);
> + mutex_init(&ts->spi_lock);
> +
> + ts->supplies[0].supply = "vddio";
> + ts->supplies[1].supply = "vci";
> + error = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->supplies),
> + ts->supplies);
> + if (error)
> + return dev_err_probe(dev, error, "failed to get regulators\n");
> +
> + ts->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(ts->reset_gpio))
> + return dev_err_probe(dev, PTR_ERR(ts->reset_gpio),
> + "failed to request reset gpio\n");
> +
> + error = ft3683g_power_on(ts);
> + if (error)
> + return error;
> +
> + error = devm_add_action_or_reset(dev, ft3683g_power_off_act, ts);
> + if (error)
> + return error;
> +
> + error = ft3683g_check_chip_id(ts);
> + if (error)
> + return error;
> +
> + ts->input = devm_input_allocate_device(dev);
> + if (!ts->input)
> + return -ENOMEM;
> +
> + ts->input->name = "FocalTech FT3683G Touchscreen";
> + ts->input->id.bustype = BUS_SPI;
> + input_set_drvdata(ts->input, ts);
> +
> + input_set_capability(ts->input, EV_ABS, ABS_MT_POSITION_X);
> + input_set_capability(ts->input, EV_ABS, ABS_MT_POSITION_Y);
> + touchscreen_parse_properties(ts->input, true, &ts->props);
> +
> + error = input_mt_init_slots(ts->input, FT3683G_MAX_POINTS,
> + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
> + if (error)
> + return dev_err_probe(dev, error, "failed to init MT slots\n");
> +
> + error = devm_request_threaded_irq(dev, spi->irq, NULL, ft3683g_irq,
> + IRQF_ONESHOT, "ft3683g", ts);
> + if (error)
> + return dev_err_probe(dev, error, "failed to request IRQ\n");
> +
> + error = input_register_device(ts->input);
> + if (error)
> + return dev_err_probe(dev, error,
> + "failed to register input device\n");
> +
> + return 0;
> +}
> +
> +static int ft3683g_suspend(struct device *dev)
> +{
> + struct ft3683g_data *ts = dev_get_drvdata(dev);
> +
> + disable_irq(ts->spi->irq);
> +
> + input_mt_sync_frame(ts->input);
> + input_sync(ts->input);
Why is this needed? We do not do that in any other drivers.
Thanks.
--
Dmitry
prev parent reply other threads:[~2026-11-03 20:02 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 5:27 [PATCH 0/2] Input: add support for the FocalTech FT3683G touchscreen Alexander Koskovich
2026-10-30 5:27 ` [PATCH 1/2] dt-bindings: input: touchscreen: Document FocalTech FT3683G Alexander Koskovich
2026-11-02 11:19 ` Krzysztof Kozlowski
2026-10-30 5:27 ` [PATCH 2/2] Input: add FocalTech FT3683G touchscreen driver Alexander Koskovich
2026-11-03 20:02 ` Dmitry Torokhov [this message]
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=auo7GvY0Ufq_7DTn@google.com \
--to=dmitry.torokhov@gmail.com \
--cc=akoskovich@pm.me \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.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