From: Gianluca Boiano <morf3089@gmail.com>
To: hansg@kernel.org, dmitry.torokhov@gmail.com
Cc: alexeymin@minlexx.ru, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org,
Gianluca Boiano <morf3089@gmail.com>
Subject: [PATCH] Input: novatek-nvt-ts - support being panel follower
Date: Sat, 18 Jul 2026 15:53:16 +0200 [thread overview]
Message-ID: <20260718135316.785467-1-morf3089@gmail.com> (raw)
From: Alexey Minnekhanov <alexeymin@minlexx.ru>
Sometimes Novatek touchscreen is paired together with Novatek panel,
and they both need to be powered together in sync.
Add support for nvt-ts driver to operate in panel follower mode, in
which touchscreen isn't in full control of its lifecycle. This is
managed using callbacks from panel, to get informed when the panel is
powered on and off. From there we can match the nvt-ts device's power
state to that of the panel.
Without this probing touchscreen before panel sometimes randomly fails
with I2C read errors.
Split some code from probe function to a separate
initial_power_on_and_register_inputdev function, which is called once
from panel_prepared callback, or from probe function, depending on
mode.
No Kconfig dependency on DRM is needed: drm_panel.h provides inline
stubs when CONFIG_DRM_PANEL is off, so the driver remains usable as a
standalone I2C touchscreen without DRM.
In panel-follower mode, nvt_ts_stop() can be called twice for a single
nvt_ts_start(): first from on_novatek_panel_unpreparing() when the
panel powers off, and again from input_close_device() when userspace
(e.g. systemd) closes the evdev file descriptor. The symmetric problem
exists for nvt_ts_start() on panel re-prepare. Guard both with a
'running' flag to make them idempotent. The flag is safe without
additional locking as all call paths already hold input->mutex.
Signed-off-by: Alexey Minnekhanov <alexeymin@minlexx.ru>
Co-developed-by: Gianluca Boiano <morf3089@gmail.com>
Signed-off-by: Gianluca Boiano <morf3089@gmail.com>
---
drivers/input/touchscreen/novatek-nvt-ts.c | 122 +++++++++++++++++----
1 file changed, 103 insertions(+), 19 deletions(-)
diff --git a/drivers/input/touchscreen/novatek-nvt-ts.c b/drivers/input/touchscreen/novatek-nvt-ts.c
index 0f771f681952..8b290f75a8e4 100644
--- a/drivers/input/touchscreen/novatek-nvt-ts.c
+++ b/drivers/input/touchscreen/novatek-nvt-ts.c
@@ -6,6 +6,7 @@
* Copyright (c) 2023 Hans de Goede <hdegoede@redhat.com>
*/
+#include <drm/drm_panel.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
@@ -59,6 +60,13 @@ struct nvt_ts_data {
struct touchscreen_properties prop;
int max_touches;
u8 buf[NVT_TS_TOUCH_SIZE * NVT_TS_MAX_TOUCHES];
+ /*
+ * Sometimes Novatek touchscreen is paired together with Novatek panel,
+ * and they need to be powered together in sync.
+ */
+ struct drm_panel_follower panel_follower;
+ bool is_panel_follower;
+ bool running;
};
static int nvt_ts_read_data(struct i2c_client *client, u8 reg, u8 *data, int count)
@@ -95,6 +103,9 @@ static irqreturn_t nvt_ts_irq(int irq, void *dev_id)
bool active;
u8 *touch;
+ if (!data->input)
+ return IRQ_HANDLED;
+
error = nvt_ts_read_data(data->client, NVT_TS_TOUCH_START, data->buf,
data->max_touches * NVT_TS_TOUCH_SIZE);
if (error)
@@ -145,6 +156,9 @@ static int nvt_ts_start(struct input_dev *dev)
struct nvt_ts_data *data = input_get_drvdata(dev);
int error;
+ if (data->running)
+ return 0;
+
error = regulator_bulk_enable(ARRAY_SIZE(data->regulators), data->regulators);
if (error) {
dev_err(&data->client->dev, "failed to enable regulators\n");
@@ -154,6 +168,7 @@ static int nvt_ts_start(struct input_dev *dev)
enable_irq(data->client->irq);
gpiod_set_value_cansleep(data->reset_gpio, 0);
+ data->running = true;
return 0;
}
@@ -161,15 +176,23 @@ static void nvt_ts_stop(struct input_dev *dev)
{
struct nvt_ts_data *data = input_get_drvdata(dev);
+ if (!data->running)
+ return;
+
disable_irq(data->client->irq);
gpiod_set_value_cansleep(data->reset_gpio, 1);
regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators);
+
+ data->running = false;
}
static int nvt_ts_suspend(struct device *dev)
{
struct nvt_ts_data *data = i2c_get_clientdata(to_i2c_client(dev));
+ if (data->is_panel_follower)
+ return 0;
+
guard(mutex)(&data->input->mutex);
if (input_device_enabled(data->input))
@@ -182,6 +205,9 @@ static int nvt_ts_resume(struct device *dev)
{
struct nvt_ts_data *data = i2c_get_clientdata(to_i2c_client(dev));
+ if (data->is_panel_follower)
+ return 0;
+
guard(mutex)(&data->input->mutex);
if (input_device_enabled(data->input))
@@ -192,30 +218,17 @@ static int nvt_ts_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(nvt_ts_pm_ops, nvt_ts_suspend, nvt_ts_resume);
-static int nvt_ts_probe(struct i2c_client *client)
+static int nvt_ts_initial_power_on_and_register_inputdev(struct nvt_ts_data *data)
{
- struct device *dev = &client->dev;
+ struct device *dev = &data->client->dev;
int error, width, height, irq_type;
- struct nvt_ts_data *data;
const struct nvt_ts_i2c_chip_data *chip;
struct input_dev *input;
- if (!client->irq) {
- dev_err(dev, "Error no irq specified\n");
- return -EINVAL;
- }
-
- data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- chip = device_get_match_data(&client->dev);
+ chip = device_get_match_data(dev);
if (!chip)
return -EINVAL;
- data->client = client;
- i2c_set_clientdata(client, data);
-
/*
* VCC is the analog voltage supply
* IOVCC is the digital voltage supply
@@ -275,7 +288,7 @@ static int nvt_ts_probe(struct i2c_client *client)
if (!input)
return -ENOMEM;
- input->name = client->name;
+ input->name = data->client->name;
input->id.bustype = BUS_I2C;
input->open = nvt_ts_start;
input->close = nvt_ts_stop;
@@ -292,10 +305,11 @@ static int nvt_ts_probe(struct i2c_client *client)
data->input = input;
input_set_drvdata(input, data);
- error = devm_request_threaded_irq(dev, client->irq, NULL, nvt_ts_irq,
+ error = devm_request_threaded_irq(dev, data->client->irq, NULL,
+ nvt_ts_irq,
IRQF_ONESHOT | IRQF_NO_AUTOEN |
nvt_ts_irq_type[irq_type],
- client->name, data);
+ data->client->name, data);
if (error) {
dev_err(dev, "failed to request irq: %d\n", error);
return error;
@@ -310,6 +324,76 @@ static int nvt_ts_probe(struct i2c_client *client)
return 0;
}
+static int on_novatek_panel_prepared(struct drm_panel_follower *follower)
+{
+ struct nvt_ts_data *data = container_of(follower, struct nvt_ts_data, panel_follower);
+ int ret;
+
+ /* Is this the first power on? */
+ if (!data->input) {
+ ret = nvt_ts_initial_power_on_and_register_inputdev(data);
+ if (ret)
+ return ret;
+ }
+
+ guard(mutex)(&data->input->mutex);
+
+ if (input_device_enabled(data->input))
+ nvt_ts_start(data->input);
+
+ return 0;
+}
+
+static int on_novatek_panel_unpreparing(struct drm_panel_follower *follower)
+{
+ struct nvt_ts_data *data = container_of(follower, struct nvt_ts_data, panel_follower);
+
+ guard(mutex)(&data->input->mutex);
+
+ if (input_device_enabled(data->input))
+ nvt_ts_stop(data->input);
+
+ return 0;
+}
+
+static const struct drm_panel_follower_funcs nvt_ts_follower_funcs = {
+ .panel_prepared = on_novatek_panel_prepared,
+ .panel_unpreparing = on_novatek_panel_unpreparing,
+};
+
+static int nvt_ts_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct nvt_ts_data *data;
+
+ if (!client->irq) {
+ dev_err(dev, "Error no irq specified\n");
+ return -EINVAL;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ i2c_set_clientdata(client, data);
+
+ /* Check if "panel = <&...>" is set in DT */
+ if (drm_is_panel_follower(dev)) {
+ data->is_panel_follower = true;
+ data->panel_follower.funcs = &nvt_ts_follower_funcs;
+ /*
+ * In this mode, we defer everything to the panel callbacks.
+ * The initial power on and input device registration will
+ * happen in on_novatek_panel_prepared().
+ */
+ return devm_drm_panel_add_follower(dev, &data->panel_follower);
+ }
+
+ return nvt_ts_initial_power_on_and_register_inputdev(data);
+}
+
+
static const struct nvt_ts_i2c_chip_data nvt_nt11205_ts_data = {
.chip_id = 0x05,
};
--
2.55.0
next reply other threads:[~2026-07-18 13:53 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-18 13:53 Gianluca Boiano [this message]
2026-07-18 14:03 ` [PATCH] Input: novatek-nvt-ts - support being panel follower 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=20260718135316.785467-1-morf3089@gmail.com \
--to=morf3089@gmail.com \
--cc=alexeymin@minlexx.ru \
--cc=dmitry.torokhov@gmail.com \
--cc=hansg@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.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