[PATCH 7/7] Input: edt-ft5x06 - support following a DRM panel
From: Luka Panio
Date: Sat Oct 17 2026 - 10:11:50 EST
On touch and display driver (TDDI) chips such as the FT8722 the touch
controller shares its supply and reset with the display panel, so it
can only be accessed while the panel is powered and loses its state,
and for RAM-only parts its firmware, every time the panel goes off.
When the device tree links the touchscreen to its panel, register as a
DRM panel follower, like i2c-hid does.
Signed-off-by: Luka Panio <lukapanio@xxxxxxxxx>
---
drivers/input/touchscreen/Kconfig | 1 +
drivers/input/touchscreen/edt-ft5x06.c | 156 ++++++++++++++++++++++++-
2 files changed, 151 insertions(+), 6 deletions(-)
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 92840ea9c65f..b4fd55028a04 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -794,6 +794,7 @@ config TOUCHSCREEN_PENMOUNT
config TOUCHSCREEN_EDT_FT5X06
tristate "EDT FocalTech FT5x06 I2C Touchscreen support"
depends on I2C
+ depends on DRM_PANEL || !DRM_PANEL
select REGMAP_I2C
help
Say Y here if you have an EDT "Polytouch" touchscreen based
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index 7bcc48e07d62..b2fca6c37205 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -4,6 +4,7 @@
* Daniel Wagener <daniel.wagener@xxxxxxxxxxxxxxxxx> (M09 firmware support)
* Lothar Waßmann <LW@xxxxxxxxxxxxxxxxxxx> (DT support)
* Dario Binacchi <dario.binacchi@xxxxxxxxxxxxxxxxxxxx> (regmap support)
+ * Luka Panio <lukapanio@xxxxxxxxx> (SPI and DRM panel support)
*/
/*
@@ -33,6 +34,9 @@
#include <linux/uaccess.h>
#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_panel.h>
#include "edt-ft5x06.h"
@@ -150,6 +154,12 @@ struct edt_ft5x06_ts_data {
enum edt_ver version;
int (*boot)(struct device *dev, struct gpio_desc *reset_gpio);
u8 tdata_reg;
+
+ struct drm_panel_follower panel_follower;
+ struct work_struct panel_work;
+ bool is_panel_follower;
+ bool powered_off;
+ bool hw_initialized;
unsigned int crc_errors;
unsigned int header_errors;
};
@@ -383,7 +393,7 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
guard(mutex)(&tsdata->mutex);
- if (tsdata->factory_mode)
+ if (tsdata->factory_mode || tsdata->powered_off)
return -EIO;
switch (tsdata->version) {
@@ -441,7 +451,7 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
guard(mutex)(&tsdata->mutex);
- if (tsdata->factory_mode)
+ if (tsdata->factory_mode || tsdata->powered_off)
return -EIO;
error = kstrtouint(buf, 0, &val);
@@ -1120,7 +1130,8 @@ static void edt_ft5x06_disable_regulators(void *arg)
/*
* Take the controller out of reset, identify it and register the input
- * device.
+ * device. Called from probe, or once the panel is up for controllers
+ * following a panel.
*/
static int edt_ft5x06_ts_init_hw(struct edt_ft5x06_ts_data *tsdata)
{
@@ -1206,9 +1217,117 @@ static int edt_ft5x06_ts_init_hw(struct edt_ft5x06_ts_data *tsdata)
edt_ft5x06_ts_prepare_debugfs(tsdata);
+ tsdata->hw_initialized = true;
+
+ return 0;
+}
+
+/*
+ * TDDI controllers share their power and reset with the display panel. When
+ * following a panel, power the touch controller down before the panel goes
+ * off and bring it back once the panel is up again.
+ */
+static void edt_ft5x06_panel_work(struct work_struct *work)
+{
+ struct edt_ft5x06_ts_data *tsdata =
+ container_of(work, struct edt_ft5x06_ts_data, panel_work);
+ int error;
+
+ guard(mutex)(&tsdata->mutex);
+
+ if (!tsdata->powered_off)
+ return;
+
+ if (!tsdata->hw_initialized) {
+ error = edt_ft5x06_ts_init_hw(tsdata);
+ if (error) {
+ /* try again the next time the panel comes up */
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+ dev_err(tsdata->dev,
+ "Failed to initialize the controller: %d\n",
+ error);
+ return;
+ }
+ goto out;
+ }
+
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+
+ if (tsdata->boot) {
+ error = tsdata->boot(tsdata->dev, tsdata->reset_gpio);
+ if (error) {
+ dev_err(tsdata->dev,
+ "Failed to boot the controller: %d\n", error);
+ return;
+ }
+ } else {
+ msleep(300);
+ }
+
+ edt_ft5x06_restore_reg_parameters(tsdata);
+out:
+ tsdata->powered_off = false;
+ enable_irq(tsdata->irq);
+}
+
+static int edt_ft5x06_panel_prepared(struct drm_panel_follower *follower)
+{
+ struct edt_ft5x06_ts_data *tsdata =
+ container_of(follower, struct edt_ft5x06_ts_data, panel_follower);
+
+ /* Downloading firmware takes a while, don't hold up the panel */
+ schedule_work(&tsdata->panel_work);
+
return 0;
}
+static int edt_ft5x06_panel_unpreparing(struct drm_panel_follower *follower)
+{
+ struct edt_ft5x06_ts_data *tsdata =
+ container_of(follower, struct edt_ft5x06_ts_data, panel_follower);
+
+ cancel_work_sync(&tsdata->panel_work);
+
+ guard(mutex)(&tsdata->mutex);
+
+ if (tsdata->powered_off)
+ return 0;
+
+ disable_irq(tsdata->irq);
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+ tsdata->powered_off = true;
+
+ return 0;
+}
+
+static const struct drm_panel_follower_funcs edt_ft5x06_panel_follower_funcs = {
+ .panel_prepared = edt_ft5x06_panel_prepared,
+ .panel_unpreparing = edt_ft5x06_panel_unpreparing,
+};
+
+static void edt_ft5x06_cancel_panel_work(void *data)
+{
+ struct edt_ft5x06_ts_data *tsdata = data;
+
+ cancel_work_sync(&tsdata->panel_work);
+}
+
+static int edt_ft5x06_register_panel_follower(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct device *dev = tsdata->dev;
+ int error;
+
+ INIT_WORK(&tsdata->panel_work, edt_ft5x06_panel_work);
+ error = devm_add_action_or_reset(dev, edt_ft5x06_cancel_panel_work,
+ tsdata);
+ if (error)
+ return error;
+
+ tsdata->panel_follower.funcs = &edt_ft5x06_panel_follower_funcs;
+
+ return devm_drm_panel_add_follower(dev, &tsdata->panel_follower);
+}
+
int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
const struct edt_ft5x06_chip_data *chip_data, u16 bustype,
struct dentry *debugfs)
@@ -1331,14 +1450,24 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
tsdata->factory_mode = false;
dev_set_drvdata(dev, tsdata);
- error = edt_ft5x06_ts_init_hw(tsdata);
- if (error)
- return error;
+ /*
+ * A controller sharing its power with a panel can only be accessed
+ * once the panel is up, so leave it in reset and do the rest of the
+ * initialization from the panel follower callback.
+ */
+ tsdata->is_panel_follower = drm_is_panel_follower(dev);
+ if (!tsdata->is_panel_follower) {
+ error = edt_ft5x06_ts_init_hw(tsdata);
+ if (error)
+ return error;
+ }
irq_flags = irq_get_trigger_type(irq);
if (irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING;
irq_flags |= IRQF_ONESHOT;
+ if (tsdata->is_panel_follower)
+ irq_flags |= IRQF_NO_AUTOEN;
error = devm_request_threaded_irq(dev, irq, NULL, edt_ft5x06_ts_isr,
irq_flags, dev_name(dev), tsdata);
@@ -1347,6 +1476,14 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
return error;
}
+ if (tsdata->is_panel_follower) {
+ tsdata->powered_off = true;
+ error = edt_ft5x06_register_panel_follower(tsdata);
+ if (error)
+ return dev_err_probe(dev, error,
+ "failed to follow the panel\n");
+ }
+
dev_dbg(dev,
"EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n",
irq,
@@ -1374,6 +1511,10 @@ static int edt_ft5x06_ts_suspend(struct device *dev)
if (device_may_wakeup(dev))
return 0;
+ /* The panel powers us down and up */
+ if (tsdata->is_panel_follower)
+ return 0;
+
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
return 0;
@@ -1415,6 +1556,9 @@ static int edt_ft5x06_ts_resume(struct device *dev)
if (device_may_wakeup(dev))
return 0;
+ if (tsdata->is_panel_follower)
+ return 0;
+
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
return 0;
--
2.55.0