All of lore.kernel.org
 help / color / mirror / Atom feed
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)&gt9886_data },
> +	{ .name = "gt9896", .driver_data = (long)&gt9896_data },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, goodix_gtx8_i2c_id);
> +
> +static const struct of_device_id goodix_gtx8_of_match[] = {
> +	{ .compatible = "goodix,gt9886", .data = &gt9886_data },
> +	{ .compatible = "goodix,gt9896", .data = &gt9896_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

  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.