From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
To: Aelin Reidel <aelin@mainlining.org>
Cc: Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Hans de Goede <hansg@kernel.org>,
Neil Armstrong <neil.armstrong@linaro.org>,
Henrik Rydberg <rydberg@bitmath.org>,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux@mainlining.org,
phone-devel@vger.kernel.org,
~postmarketos/upstreaming@lists.sr.ht,
Piyush Raj Chouhan <pc1598@mainlining.org>,
Alexander Koskovich <AKoskovich@pm.me>
Subject: Re: [PATCH v2 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
Date: Thu, 12 Nov 2026 18:35:04 -0800 [thread overview]
Message-ID: <ba73c504ce3b17e8b2197de7393484d9@gmail.com> (raw)
In-Reply-To: <20260228-gtx8-v2-2-3a408c365f6c@mainlining.org>
Hi Aelin,
On Sat, Feb 28, 2026 at 02:56:11AM +0100, Aelin Reidel wrote:
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 7d5b72ee07fa1313da39a625b5129a0459720865..099ccd3679383dcf037bc7c6e6a3dbf0741722b4 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -429,6 +429,21 @@ config TOUCHSCREEN_GOODIX_BERLIN_SPI
> To compile this driver as a module, choose M here: the
> module will be called goodix_berlin_spi.
>
> +config TOUCHSCREEN_GOODIX_GTX8
> + tristate "Goodix GTX8 touchscreen"
> + depends on I2C
> + select REGMAP_I2C
> + help
> + Say Y here if you have a Goodix GTX8 IC connected to
> + your system via I2C. This driver supports Normandy and
> + Yellowstone ICs like the GT9886 and GT9896.
> + They are commonly found in mobile phones.
> +
> + if unsure, say N.
s/if/If/
> +
> + To compile this driver as a module, choose M here: the
> + module will be called goodix_gtx8.
> +
...
> diff --git a/drivers/input/touchscreen/goodix_gtx8.c b/drivers/input/touchscreen/goodix_gtx8.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..3fbebfa8b75672866788adf89d83be16e32abf81
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix_gtx8.c
> @@ -0,0 +1,563 @@
...
> +#include <linux/bitfield.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/unaligned.h>
> +
> +#include "goodix_gtx8.h"
Please include headers directly for all primitives and types used by the
driver (linux/bits.h, linux/delay.h, linux/device.h, linux/err.h,
linux/interrupt.h, linux/pm.h, linux/regulator/consumer.h,
linux/sizes.h, linux/types.h).
Also, since this is a single-module driver and SPI support does not
exist yet, please fold goodix_gtx8.h into goodix_gtx8.c. We can split it
out if and when a separate SPI transport module is added.
> +
> +static const struct regmap_config goodix_gtx8_regmap_conf = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .max_raw_read = I2C_MAX_TRANSFER_SIZE,
> + .max_raw_write = I2C_MAX_TRANSFER_SIZE,
> +};
> +
> +/* vendor & product left unassigned here, should probably be updated from fw info */
> +static const struct input_id goodix_gtx8_input_id = {
> + .bustype = BUS_I2C,
> +};
No need for a separate static struct for this, just assign
input_dev->id.bustype = BUS_I2C in goodix_gtx8_input_dev_config().
> +static int goodix_gtx8_get_remaining_contacts(struct goodix_gtx8_core *cd,
> + int n)
> +{
> + size_t offset = cd->ic_data->header_size + GOODIX_GTX8_TOUCH_SIZE +
> + GOODIX_GTX8_CHECKSUM_SIZE;
> + u32 addr = cd->ic_data->touch_data_addr + offset;
> + int error;
> +
> + error = regmap_raw_read(cd->regmap, addr, &cd->event_buffer[offset],
> + (n - 1) * GOODIX_GTX8_TOUCH_SIZE);
Please add a brief comment here (similar to goodix_berlin_core.c)
explaining why offset includes GOODIX_GTX8_CHECKSUM_SIZE while reading
(n - 1) * GOODIX_GTX8_TOUCH_SIZE bytes (i.e. that the initial read for
the first contact plus checksum already fetched the first 2 bytes of the
second contact).
> + if (error) {
> + dev_err_ratelimited(cd->dev, "failed to get touch data, %d\n",
> + error);
> + return error;
> + }
> +
> + return 0;
> +}
> +
> +static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
> + union goodix_gtx8_touch *touch_data)
> +{
> + union goodix_gtx8_touch *t;
> + int i;
> + u8 finger_id;
> +
> + for (i = 0; i < touch_num; i++) {
> + t = &touch_data[i];
> +
> + if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
> + input_mt_slot(cd->input_dev, t->normandy.finger_id);
> + input_mt_report_slot_state(cd->input_dev,
> + MT_TOOL_FINGER, true);
> +
> + touchscreen_report_pos(cd->input_dev, &cd->props,
> + __le16_to_cpu(t->normandy.x),
> + __le16_to_cpu(t->normandy.y),
> + true);
> + input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
> + t->normandy.w);
> + } else {
> + finger_id = FIELD_GET(
> + GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
> + t->yellowstone.finger_id);
> + input_mt_slot(cd->input_dev, finger_id);
> + input_mt_report_slot_state(cd->input_dev,
> + MT_TOOL_FINGER, true);
> +
> + touchscreen_report_pos(cd->input_dev, &cd->props,
> + __be16_to_cpu(t->yellowstone.x),
> + __be16_to_cpu(t->yellowstone.y),
> + true);
> + input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
> + t->yellowstone.w);
> + }
> + }
Several issues here:
1. finger_id is not checked against GOODIX_GTX8_MAX_TOUCH (10). On
Yellowstone it is a 4-bit field (0..15), and on Normandy
t->normandy.finger_id is an unmasked u8 (where only the lower 4 bits
are the slot ID and upper bits carry event/tool flags such as BIT(7)
for stylus). If finger_id >= GOODIX_GTX8_MAX_TOUCH, input_mt_slot()
ignores the slot change and leaves the previously selected slot
active, so the subsequent calls overwrite another contact's slot.
Also, on Yellowstone the lower 4 bits encode the contact/tool type
(like in goodix_berlin), so non-finger contacts should be skipped.
2. Please use le16_to_cpu() and be16_to_cpu() instead of the underscored
__le16_to_cpu() and __be16_to_cpu() variants.
3. Please extract finger_id, x, y, and w in the if/else block and call
input_mt_slot(), input_mt_report_slot_state(),
touchscreen_report_pos(), and input_report_abs() once below it.
> +
> + input_mt_sync_frame(cd->input_dev);
> + input_sync(cd->input_dev);
> +}
> +
> +static void goodix_gtx8_touch_handler(struct goodix_gtx8_core *cd, u8 touch_num,
> + union goodix_gtx8_touch *touch_data)
> +{
> + int error;
> +
> + touch_num = FIELD_GET(GOODIX_GTX8_TOUCH_COUNT_MASK, touch_num);
> +
> + if (touch_num > GOODIX_GTX8_MAX_TOUCH) {
> + dev_warn(cd->dev, "invalid touch num %d\n", touch_num);
> + return;
> + }
> +
> + if (touch_num > 1) {
> + /* read additional contact data if more than 1 touch event */
> + error = goodix_gtx8_get_remaining_contacts(cd, touch_num);
> + if (error)
> + return;
> + }
> +
> + if (touch_num) {
> + /*
> + * Normandy checksum is for the entire read buffer,
> + * Yellowstone is only for the touch data (since header
> + * has a separate checksum)
> + */
> + if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
> + int len = GOODIX_GTX8_HEADER_SIZE_NORMANDY +
> + touch_num * GOODIX_GTX8_TOUCH_SIZE +
> + GOODIX_GTX8_CHECKSUM_SIZE;
> + if (!goodix_gtx8_checksum_valid_normandy(
> + cd->event_buffer, len)) {
> + dev_err(cd->dev,
> + "touch data checksum error: %*ph\n",
> + len, cd->event_buffer);
> + return;
> + }
> + } else {
> + int len = touch_num * GOODIX_GTX8_TOUCH_SIZE +
> + GOODIX_GTX8_CHECKSUM_SIZE;
> + if (!goodix_gtx8_checksum_valid_yellowstone(
> + (u8 *)touch_data, len)) {
> + dev_err(cd->dev,
> + "touch data checksum error: %*ph\n",
> + len, (u8 *)touch_data);
> + return;
> + }
> + }
> + }
Please use dev_warn_ratelimited() / dev_err_ratelimited() in the IRQ
path so a noisy bus or misbehaving firmware does not flood the logs.
> +static irqreturn_t goodix_gtx8_irq(int irq, void *data)
> +{
...
> + error = regmap_raw_read(
> + cd->regmap, cd->ic_data->touch_data_addr, cd->event_buffer,
> + cd->ic_data->header_size + GOODIX_GTX8_TOUCH_SIZE +
> + GOODIX_GTX8_CHECKSUM_SIZE);
> + if (error) {
> + dev_warn_ratelimited(
> + cd->dev, "failed to get event head data: %d\n", error);
> + goto out;
> + }
...
> + if (status == 0)
> + goto out;
...
> +out_clear:
> + /* Clear up status field */
> + regmap_write(cd->regmap, cd->ic_data->touch_data_addr, 0);
> +
> +out:
> + return IRQ_HANDLED;
> +}
If the initial read fails or status == 0, returning IRQ_HANDLED on a
level-low interrupt prevents the kernel from detecting a stuck interrupt
line. Those paths should return IRQ_NONE.
> +static int goodix_gtx8_dev_confirm(struct goodix_gtx8_core *cd)
> +{
> + u8 rx_buf[1];
> + int retry = 3;
> + int error;
> +
> + while (retry--) {
> + /*
> + * test_addr appears to always be the touch_data_addr for
> + * Normandy, but it doesn't really matter since all we
> + * need is a valid address
> + */
> + error = regmap_raw_read(cd->regmap,
> + cd->ic_data->touch_data_addr, rx_buf,
> + sizeof(rx_buf));
> +
> + if (!error)
> + return 0;
> +
> + usleep_range(5000, 5100);
> + }
> +
> + dev_err(cd->dev, "device confirm failed\n");
> +
> + return -EINVAL;
> +}
Please return 'error' (and log it) instead of replacing it with -EINVAL.
> +static int goodix_gtx8_suspend(struct device *dev)
> +{
> + struct goodix_gtx8_core *cd = dev_get_drvdata(dev);
> +
> + disable_irq(cd->irq);
> + goodix_gtx8_power_off(cd);
> +
> + return 0;
> +}
> +
> +static int goodix_gtx8_resume(struct device *dev)
> +{
> + struct goodix_gtx8_core *cd = dev_get_drvdata(dev);
> + int error;
> +
> + error = goodix_gtx8_power_on(cd);
> + if (error)
> + return error;
> +
> + enable_irq(cd->irq);
> +
> + return 0;
> +}
> +
> +EXPORT_GPL_SIMPLE_DEV_PM_OPS(goodix_gtx8_pm_ops, goodix_gtx8_suspend,
> + goodix_gtx8_resume);
1. Use static DEFINE_SIMPLE_DEV_PM_OPS() instead of exporting
goodix_gtx8_pm_ops when there are no external callers.
2. If goodix_gtx8_power_on() fails in goodix_gtx8_resume(), it leaves
both regulators disabled and cd->irq disabled. A subsequent driver
unbind (via goodix_gtx8_power_off_act) or the next suspend cycle will
call goodix_gtx8_power_off() again on already-disabled regulators
(triggering a regulator underflow WARN) and disable the IRQ a second
time.
Better yet, please wire up input_dev->open and input_dev->close:
power the chip on in probe() only long enough to read the version,
then power it off, request the IRQ with IRQF_ONESHOT | IRQF_NO_AUTOEN,
power on + enable_irq() in open(), and disable_irq() + power off in
close(). Then suspend() and resume() can take &cd->input_dev->mutex
and only power down/up when input_device_enabled(cd->input_dev) is
true, which also avoids keeping the controller powered when nothing
uses the input device.
> +static int goodix_gtx8_probe(struct i2c_client *client)
> +{
...
> + cd->dev = &client->dev;
> + cd->irq = client->irq;
> + cd->regmap = regmap;
> + cd->ic_data = i2c_get_match_data(client);
> +
> + cd->event_buffer =
> + devm_kzalloc(cd->dev, cd->ic_data->event_size, GFP_KERNEL);
> + if (!cd->event_buffer)
> + return -ENOMEM;
Please check if (!cd->ic_data) before dereferencing it.
Also, instead of a separate devm_kzalloc() for cd->event_buffer and
casting it across the driver, you can embed a union of
goodix_gtx8_event_normandy and goodix_gtx8_event_yellowstone directly in
struct goodix_gtx8_core (like goodix_berlin_core.c does).
> + error = goodix_gtx8_power_on(cd);
> + if (error) {
> + dev_err(cd->dev, "failed power on");
> + return error;
> + }
> +
> + error = devm_add_action_or_reset(cd->dev, goodix_gtx8_power_off_act,
> + cd);
> + if (error)
> + return error;
> +
> + error = goodix_gtx8_read_version(cd);
> + if (error) {
> + dev_err(cd->dev, "failed to get version info");
> + return error;
> + }
> +
> + error = goodix_gtx8_input_dev_config(cd);
> + if (error) {
> + dev_err(cd->dev, "failed to set input device");
> + return error;
> + }
> +
> + error = devm_request_threaded_irq(cd->dev, cd->irq, NULL,
> + goodix_gtx8_irq, IRQF_ONESHOT,
> + "goodix-gtx8", cd);
> + if (error) {
> + dev_err(cd->dev, "request threaded IRQ failed: %d\n", error);
> + return error;
> + }
> +
> + dev_set_drvdata(cd->dev, cd);
The three dev_err() messages above are missing trailing '\n' (and
goodix_gtx8_power_on() / goodix_gtx8_read_version() already log their
own errors).
Also, please set drvdata and request the IRQ before registering the
input device in goodix_gtx8_input_dev_config().
> +static const struct i2c_device_id goodix_gtx8_i2c_id[] = {
> + { .name = "gt9886", .driver_data = (long)>9886_data },
> + { .name = "gt9896", .driver_data = (long)>9896_data },
> + {},
> +};
> +MODULE_DEVICE_TABLE(i2c, goodix_gtx8_i2c_id);
> +
> +static const struct of_device_id goodix_gtx8_of_match[] = {
> + { .compatible = "goodix,gt9886", .data = >9886_data },
> + { .compatible = "goodix,gt9896", .data = >9896_data },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, goodix_gtx8_of_match);
> +
> +static struct i2c_driver goodix_gtx8_driver = {
> + .probe = goodix_gtx8_probe,
> + .id_table = goodix_gtx8_i2c_id,
> + .driver = {
> + .name = "goodix-gtx8",
> + .of_match_table = of_match_ptr(goodix_gtx8_of_match),
> + .pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
> + },
> +};
Use (kernel_ulong_t) for .driver_data in goodix_gtx8_i2c_id, and drop
of_match_ptr() so goodix_gtx8_of_match does not trigger an unused
variable warning when CONFIG_OF is disabled.
> diff --git a/drivers/input/touchscreen/goodix_gtx8.h b/drivers/input/touchscreen/goodix_gtx8.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..9b13cfce38720b35bb11f0d7f56d671b31664ade
> --- /dev/null
> +++ b/drivers/input/touchscreen/goodix_gtx8.h
...
> +#define I2C_MAX_TRANSFER_SIZE 256
Please prefix this with GOODIX_GTX8_ so it does not collide with the I2C
subsystem namespace.
> +struct goodix_gtx8_header_yellowstone {
> + u8 status;
> + /* Most likely unused */
> + u8 __unknown1;
> + /* Only the lower 4 bits are actually used */
> + u8 touch_num;
> + /* Most likely unused */
> + u8 __unknown2[3];
> + __le16 checksum;
> +} __packed __aligned(1);
Yellowstone checksum is big-endian (read via get_unaligned_be16() in
goodix_gtx8_checksum_valid_yellowstone()), so this field should be
__be16. Also, __aligned(1) is redundant alongside __packed (here and on
the touch structs below), and the "The data below is u16 aligned"
comments on the event structs are inaccurate for Normandy where x and y
sit at odd byte offsets.
Thanks.
--
Dmitry
next prev parent reply other threads:[~2026-11-13 2:35 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-28 1:56 [PATCH v2 0/3] Input: add initial support for Goodix GTX8 touchscreen ICs Aelin Reidel
2026-02-28 1:56 ` [PATCH v2 1/3] dt-bindings: input: document Goodix GTX8 Touchscreen ICs Aelin Reidel
2026-11-13 2:40 ` Dmitry Torokhov
2026-02-28 1:56 ` [PATCH v2 2/3] Input: add support for " Aelin Reidel
2026-11-13 2:35 ` Dmitry Torokhov [this message]
2026-02-28 1:56 ` [PATCH v2 3/3] MAINTAINERS: add an entry for Goodix GTX8 Touchscreen driver Aelin Reidel
2026-02-28 11:23 ` [PATCH v2 0/3] Input: add initial support for Goodix GTX8 touchscreen ICs Hans de Goede
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=ba73c504ce3b17e8b2197de7393484d9@gmail.com \
--to=dmitry.torokhov@gmail.com \
--cc=AKoskovich@pm.me \
--cc=aelin@mainlining.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=hansg@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@mainlining.org \
--cc=neil.armstrong@linaro.org \
--cc=pc1598@mainlining.org \
--cc=phone-devel@vger.kernel.org \
--cc=robh@kernel.org \
--cc=rydberg@bitmath.org \
--cc=~postmarketos/upstreaming@lists.sr.ht \
/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