Re: [PATCH v2 2/3] Input: add driver for Himax hx83112b touchscreen devices

From: Jeff LaBundy
Date: Wed Oct 12 2022 - 22:35:02 EST


Hi Job,

Nicely done, just a few comments throughout.

On Wed, Oct 12, 2022 at 08:24:17PM +0000, Job Noorman wrote:
> This patch adds support for Himax hx83112b touchscreen devices. As there
> are no publicly available data sheets for these devices, the
> implementation is based on the driver of the downstream Android kernel
> used in the Fairphone 3. This patch is a complete rewrite, though, and
> the code bears no resemblence to the original implementation.
>
> The driver has been tested on the aforementioned phone.
>
> Signed-off-by: Job Noorman <job@xxxxxxxxxxxx>
> ---
> MAINTAINERS | 1 +
> drivers/input/touchscreen/Kconfig | 11 +
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/himax_hx83112b.c | 377 +++++++++++++++++++++
> 4 files changed, 390 insertions(+)
> create mode 100644 drivers/input/touchscreen/himax_hx83112b.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2418bffe9187..51a03f9586f1 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9099,6 +9099,7 @@ M: Job Noorman <job@xxxxxxxxxxxx>
> L: linux-input@xxxxxxxxxxxxxxx
> S: Maintained
> F: Documentation/devicetree/bindings/input/touchscreen/himax,hx83112b.yaml
> +F: drivers/input/touchscreen/himax_hx83112b.c
>
> HIPPI
> M: Jes Sorensen <jes@xxxxxxxxxxxxxxxxxx>
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index dc90a3ea51ee..fd710fd8cd53 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -1379,4 +1379,15 @@ config TOUCHSCREEN_ZINITIX
> To compile this driver as a module, choose M here: the
> module will be called zinitix.
>
> +config TOUCHSCREEN_HIMAX_HX83112B
> + tristate "Himax hx83112b touchscreen driver"
> + depends on I2C

Because regmap is used here, we must select REGMAP_I2C.

> + help
> + Say Y here to enable support for Himax hx83112b touchscreens.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called himax_hx83112b.
> +
> endif
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 557f84fd2075..0f8bf79e01fe 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -116,3 +116,4 @@ obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o
> obj-$(CONFIG_TOUCHSCREEN_RASPBERRYPI_FW) += raspberrypi-ts.o
> obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o
> obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o
> +obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX83112B) += himax_hx83112b.o
> diff --git a/drivers/input/touchscreen/himax_hx83112b.c b/drivers/input/touchscreen/himax_hx83112b.c
> new file mode 100644
> index 000000000000..a7afea795dd5
> --- /dev/null
> +++ b/drivers/input/touchscreen/himax_hx83112b.c
> @@ -0,0 +1,377 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Driver for Himax hx83112b touchscreens
> + *
> + * Copyright (C) 2022 Job Noorman <job@xxxxxxxxxxxx>
> + *
> + * This code is based on "Himax Android Driver Sample Code for QCT platform":
> + *
> + * Copyright (C) 2017 Himax Corporation.
> + */
> +
> +#include <asm/byteorder.h>

This is already included by linux/kernel.h; no need to explicitly include
it here.

> +#include <linux/delay.h>
> +#include <linux/err.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/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/regmap.h>
> +
> +#define HIMAX_ID_83112B 0x83112b
> +
> +#define HIMAX_MAX_POINTS 10
> +
> +#define HIMAX_REG_CFG_SET_ADDR 0x00
> +#define HIMAX_REG_CFG_INIT_READ 0x0c
> +#define HIMAX_REG_CFG_READ_VALUE 0x08
> +#define HIMAX_REG_READ_EVENT 0x30
> +
> +#define HIMAX_CFG_PRODUCT_ID 0x900000d0

Please align these values for better readability.

> +
> +struct himax_event_point {
> + __be16 x;
> + __be16 y;
> +} __packed;
> +
> +struct himax_event {
> + struct himax_event_point points[HIMAX_MAX_POINTS];
> + u8 majors[HIMAX_MAX_POINTS];
> + u8 pad0[2];
> + u8 num_points;
> + u8 pad1[2];
> + u8 checksum_fix;
> +} __packed;
> +
> +static_assert(sizeof(struct himax_event) == 56);
> +
> +struct himax_ts_data {
> + struct gpio_desc *gpiod_rst;
> + struct input_dev *input_dev;
> + struct i2c_client *client;
> + struct regmap *regmap;
> + struct touchscreen_properties props;
> +};
> +
> +static const struct regmap_config himax_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 32,
> + .val_format_endian = REGMAP_ENDIAN_LITTLE,
> +};
> +
> +static int himax_read_config(struct himax_ts_data *ts, u32 address, u32 *dst)
> +{
> + int error = 0;
> +
> + error = regmap_write(ts->regmap, HIMAX_REG_CFG_SET_ADDR, address);
> + if (error)
> + return error;
> +
> + error = regmap_write(ts->regmap, HIMAX_REG_CFG_INIT_READ, 0x0);
> + if (error)
> + return error;
> +
> + return regmap_read(ts->regmap, HIMAX_REG_CFG_READ_VALUE, dst);
> +}
> +
> +static int himax_read_input_event(struct himax_ts_data *ts,
> + struct himax_event *event)
> +{
> + return regmap_raw_read(ts->regmap, HIMAX_REG_READ_EVENT, event,
> + sizeof(*event));
> +}
> +
> +static void himax_reset(struct himax_ts_data *ts)
> +{
> + gpiod_set_value(ts->gpiod_rst, 1);
> + msleep(20);
> + gpiod_set_value(ts->gpiod_rst, 0);
> +}
> +
> +static int himax_read_product_id(struct himax_ts_data *ts, u32 *product_id)
> +{
> + int error = himax_read_config(ts, HIMAX_CFG_PRODUCT_ID, product_id);
> +
> + if (error)
> + return error;
> +
> + *product_id >>= 8;
> + return 0;
> +}
> +
> +static int himax_check_product_id(struct himax_ts_data *ts)
> +{
> + int error;
> + u32 product_id;
> +
> + error = himax_read_product_id(ts, &product_id);
> + if (error)
> + return error;
> +
> + dev_dbg(&ts->client->dev, "Product id: %x\n", product_id);
> +
> + switch (product_id) {
> + case HIMAX_ID_83112B:
> + return 0;
> +
> + default:
> + return dev_err_probe(&ts->client->dev, -ENODEV,
> + "Unknown product id: %x\n", product_id);

Aside from the existing arguments against dev_err_probe(), there is
no point in using it here as the error is fixed and never equal to
-EPROBE_DEFER. I know the documentation specifically offers this but
doing so seems unnecessarily convoluted; just my $.02.

Also note that -ENODEV is not really the appropriate error code to
use here. It is not that there was no device at this address, rather
the device did not return a valid product ID. Therefore I think that
-EINVAL is more appropriate; again this is just my $.02.

> + }
> +}
> +
> +static int himax_setup_gpio(struct himax_ts_data *ts)
> +{
> + ts->gpiod_rst =
> + devm_gpiod_get(&ts->client->dev, "reset", GPIOD_OUT_HIGH);

This line break is hard to read. This would be better:

ts->gpiod_rst = devm_gpiod_get(...,
...);

> + if (IS_ERR(ts->gpiod_rst)) {
> + return dev_err_probe(&ts->client->dev, PTR_ERR(ts->gpiod_rst),
> + "Failed to get reset GPIO\n");
> + }
> +
> + return 0;
> +}
> +
> +static int himax_input_register(struct himax_ts_data *ts)
> +{
> + int error;
> +
> + ts->input_dev = devm_input_allocate_device(&ts->client->dev);
> + if (!ts->input_dev) {
> + return dev_err_probe(&ts->client->dev, -ENOMEM,
> + "Failed to allocate input device\n");
> + }

Same here, this call to dev_err_probe() really just becomes dev_err()
followed by a return.

That being said, use of dev_err_probe() is generally frowned upon in
the input subsystem and I tend to agree. The argument against it is
that resources that may not be ready in time should be responsible for
the housekeeping done in dev_err_probe() rather than every possible
consumer doing so through every possible error path.

I only mention this because you will likely be asked to change even
the "valid" calls to dev_err_probe().

> +
> + ts->input_dev->name = "Himax Touchscreen";
> +
> + input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X);
> + input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y);
> + input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);
> + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 200, 0, 0);
> +
> + touchscreen_parse_properties(ts->input_dev, true, &ts->props);

Since the driver is not reading any default resolution values from the
device, it seems you are expecting touchscreen-size-x/y to be provided
as in your example.

Unless I have misunderstood, please consider promoting these properties
to 'required' in the binding.

> +
> + error = input_mt_init_slots(ts->input_dev, HIMAX_MAX_POINTS,
> + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
> + if (error) {
> + return dev_err_probe(&ts->client->dev, error,
> + "Failed to initialize MT slots");
> + }
> +
> + error = input_register_device(ts->input_dev);
> + if (error) {
> + return dev_err_probe(&ts->client->dev, error,
> + "Failed to register input device");
> + }
> +
> + return 0;
> +}
> +
> +static u8 himax_event_get_num_points(const struct himax_event *event)
> +{
> + if (event->num_points == 0xff)
> + return 0;
> + else
> + return event->num_points & 0x0f;
> +}
> +
> +static u16 himax_event_point_get_x(const struct himax_event_point *point)
> +{
> + return be16_to_cpu(point->x);
> +}
> +
> +static u16 himax_event_point_get_y(const struct himax_event_point *point)
> +{
> + return be16_to_cpu(point->y);
> +}

These two functions seem to add an unnecessary layer and probably get
inlined anyway; I think it is cleaner to simply call be16_to_cpu(...)
directly.

> +
> +static bool himax_event_point_is_valid(const struct himax_event_point *point)
> +{
> + return himax_event_point_get_x(point) != 0xffff &&
> + himax_event_point_get_y(point) != 0xffff;
> +}

How about U16_MAX?

> +
> +static bool himax_process_event_point(struct himax_ts_data *ts,
> + const struct himax_event *event,
> + int point_index)
> +{
> + const struct himax_event_point *point = &event->points[point_index];
> + u16 x = himax_event_point_get_x(point);
> + u16 y = himax_event_point_get_y(point);
> + u8 w = event->majors[point_index];
> +
> + if (!himax_event_point_is_valid(point))
> + return false;
> +
> + input_mt_slot(ts->input_dev, point_index);
> + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
> + touchscreen_report_pos(ts->input_dev, &ts->props, x, y, true);
> + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w);
> + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w);
> + return true;
> +}
> +
> +static void himax_process_event(struct himax_ts_data *ts,
> + const struct himax_event *event)
> +{
> + int i;
> + int num_points_left = himax_event_get_num_points(event);
> +
> + for (i = 0; i < HIMAX_MAX_POINTS && num_points_left > 0; i++) {
> + if (himax_process_event_point(ts, event, i))
> + num_points_left--;
> + }
> +
> + input_mt_sync_frame(ts->input_dev);
> + input_sync(ts->input_dev);
> +}
> +
> +static bool himax_verify_checksum(struct himax_ts_data *ts,
> + const struct himax_event *event)
> +{
> + u8 *data = (u8 *)event;
> + int i;
> + u16 checksum = 0;
> +
> + for (i = 0; i < sizeof(*event); i++)
> + checksum += data[i];
> +
> + if ((checksum & 0x00ff) != 0) {
> + dev_err(&ts->client->dev, "Wrong event checksum: %04x\n",
> + checksum);
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static void himax_handle_input(struct himax_ts_data *ts)
> +{
> + int error;
> + struct himax_event event;
> +
> + error = himax_read_input_event(ts, &event);
> + if (error) {
> + dev_err(&ts->client->dev, "Failed to read input event: %d\n",
> + error);
> + return;
> + }
> +
> + if (!himax_verify_checksum(ts, &event))
> + return;
> +
> + himax_process_event(ts, &event);
> +}
> +
> +static irqreturn_t himax_irq_handler(int irq, void *dev_id)
> +{
> + struct himax_ts_data *ts = dev_id;
> +
> + himax_handle_input(ts);

Is it accurate to assume that the act of reading the event status
register(s) is what acknowledges the interrupt and de-asserts the
interrupt pin?

If so, I think it is safer to define himax_handle_input() with an
integer return type, then return IRQ_NONE upon failure. If the I2C
adapter goes south such that reads are never initiated and the pin
is stuck low causing an interrupt storm, the handler would get cut
off quickly.

Just for my own understanding, _when_ does the pin get de-asserted?
Is it early in the I2C read, or after the stop condition? In case
of the latter, consider a delay to prevent the interrupt from being
immediately triggered once more after the handler has returned, but
the pin hasn't quite returned to a logic-high level.

> + return IRQ_HANDLED;
> +}
> +
> +static int himax_request_irq(struct himax_ts_data *ts)
> +{
> + struct i2c_client *client = ts->client;
> +
> + return devm_request_threaded_irq(&client->dev, client->irq, NULL,
> + himax_irq_handler, IRQF_ONESHOT,
> + client->name, ts);
> +}

This wrapper seems unnecessary.

> +
> +static int himax_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + int error;
> + struct device *dev = &client->dev;
> + struct himax_ts_data *ts;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> + return dev_err_probe(dev, -ENODEV,
> + "I2C check functionality failed\n");
> + }

No need for curly braces around if () blocks that only have one expression.

> +
> + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
> + if (!ts)
> + return -ENOMEM;
> +
> + i2c_set_clientdata(client, ts);
> + ts->client = client;
> +
> + ts->regmap = devm_regmap_init_i2c(client, &himax_regmap_config);
> + if (IS_ERR(ts->regmap)) {
> + return dev_err_probe(&client->dev, PTR_ERR(ts->regmap),
> + "Failed to initialize regmap");
> + }

And here.

> +
> + error = himax_setup_gpio(ts);
> + if (error)
> + return error;
> +
> + himax_reset(ts);

It looks like we're expecting an I2C read to occur directly after reset
is de-asserted. Understanding that no datasheet is available to specify
how much time the device takes to exit reset, does the downstream driver
at least include some delay?

> +
> + error = himax_check_product_id(ts);
> + if (error)
> + return error;
> +
> + error = himax_input_register(ts);
> + if (error)
> + return error;
> +
> + error = himax_request_irq(ts);
> + if (error)
> + return error;
> +
> + return 0;

Nit: I think it is cleaner to simply return himax_request_irq(ts) here,
or actually just devm_request_threaded_irq().

> +}
> +
> +static int himax_suspend(struct device *dev)
> +{
> + struct himax_ts_data *ts = dev_get_drvdata(dev);
> +
> + disable_irq(ts->client->irq);
> + return 0;
> +}
> +
> +static int himax_resume(struct device *dev)
> +{
> + struct himax_ts_data *ts = dev_get_drvdata(dev);
> +
> + enable_irq(ts->client->irq);
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(himax_pm_ops, himax_suspend, himax_resume);
> +
> +static const struct i2c_device_id himax_ts_id[] = {
> + { "hx83112b", 0 },
> + { /* sentinel */ },

Nit: no need for a trailing comma after the sentinel because no patch
would ever add a line below it.

> +};
> +MODULE_DEVICE_TABLE(i2c, himax_ts_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id himax_of_match[] = {
> + { .compatible = "himax,hx83112b" },
> + { /* sentinel */ },

And here.

> +};
> +MODULE_DEVICE_TABLE(of, himax_of_match);
> +#endif
> +
> +static struct i2c_driver himax_ts_driver = {
> + .probe = himax_probe,
> + .id_table = himax_ts_id,
> + .driver = {
> + .name = "Himax-hx83112b-TS",
> + .of_match_table = of_match_ptr(himax_of_match),
> + .pm = &himax_pm_ops,
> + },
> +};
> +module_i2c_driver(himax_ts_driver);
> +
> +MODULE_AUTHOR("Job Noorman <job@xxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Himax hx83112b touchscreen driver");
> +MODULE_LICENSE("GPL");
> --
> 2.38.0
>
>