diff mbox

Input: ili251x - add support for Ilitek ILI251x touchscreens

Message ID 20180507131823.28800-1-pp@emlix.com (mailing list archive)
State New, archived
Headers show

Commit Message

Philipp Puschmann May 7, 2018, 1:18 p.m. UTC
The driver supports at least the ili2511 chipset but may support other
Ilitek chipsets using Ilitek i2c protocol v3.x.

The tested ili2511-based touchscreen delivers garbage for more than 6
fingers while it should support up to 10 fingers. The reason is still
unclear and this remains a FIXME in the driver for now.

The usage of pressure is optional. Touchscreens may deliver constant
and so useless pressure data.

Signed-off-by: Philipp Puschmann <pp@emlix.com>
---
 .../bindings/input/touchscreen/ili251x.txt    |  35 ++
 drivers/input/touchscreen/Kconfig             |  12 +
 drivers/input/touchscreen/Makefile            |   1 +
 drivers/input/touchscreen/ili251x.c           | 350 ++++++++++++++++++
 4 files changed, 398 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
 create mode 100644 drivers/input/touchscreen/ili251x.c

Comments

Rob Herring May 8, 2018, 5:51 p.m. UTC | #1
On Mon, May 07, 2018 at 03:18:23PM +0200, Philipp Puschmann wrote:
> The driver supports at least the ili2511 chipset but may support other
> Ilitek chipsets using Ilitek i2c protocol v3.x.
> 
> The tested ili2511-based touchscreen delivers garbage for more than 6
> fingers while it should support up to 10 fingers. The reason is still
> unclear and this remains a FIXME in the driver for now.
> 
> The usage of pressure is optional. Touchscreens may deliver constant
> and so useless pressure data.
> 
> Signed-off-by: Philipp Puschmann <pp@emlix.com>
> ---
>  .../bindings/input/touchscreen/ili251x.txt    |  35 ++

Please make the binding a separate patch.

>  drivers/input/touchscreen/Kconfig             |  12 +
>  drivers/input/touchscreen/Makefile            |   1 +
>  drivers/input/touchscreen/ili251x.c           | 350 ++++++++++++++++++
>  4 files changed, 398 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
>  create mode 100644 drivers/input/touchscreen/ili251x.c
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt b/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
> new file mode 100644
> index 000000000000..f21ad93d3bdd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
> @@ -0,0 +1,35 @@
> +Ilitek ili251x touchscreen driver

Bindings are not drivers.
> +
> +This driver uses protocol version 3 and should be compatible with other
> +Ilitek touch controllers that use protocol 3.x
> +
> +Required properties:
> + - compatible:  "ili251x"

Needs a vendor prefix.

Don't use wildcards in compatible strings. Maybe you want to incorporate 
the protocol version into the compatible string.

> + - reg:         I2C slave address of the chip (0x41)
> + - interrupt-parent: a phandle pointing to the interrupt controller
> +                     serving the interrupt for this chip
> + - interrupts:       interrupt specification for the touchdetect
> +                     interrupt
> +
> +Optional properties:
> + - reset-gpios: GPIO specification for the RESET input
> +
> + - pinctrl-names: should be "default"
> + - pinctrl-0:   a phandle pointing to the pin settings for the
> +                control gpios
> + - max-fingers: the maximum number of fingers to handle

We already have 'silead,max-fingers', so probably time for a common 
property.

> + - pressure: support pressure data

This should be implied by the compatible string.

> + - generic options	   : See touchscreen.txt
> +
> +Example:
> +
> +	ili251x@41 {

touchscreen@41

> +		compatible = "ili251x";
> +		reg = <0x41>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_touchpanel>;
> +		interrupt-parent = <&gpio5>;
> +		interrupts = <20 IRQ_TYPE_EDGE_FALLING>;
> +		reset-gpios = <&gpio5 18 GPIO_ACTIVE_HIGH>;
> +		max-fingers = <6>;
> +	};
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andi Shyti May 8, 2018, 9:49 p.m. UTC | #2
Hi Philipp,

I had a fast look to your driver and I have few comments.

>  .../bindings/input/touchscreen/ili251x.txt    |  35 ++
>  drivers/input/touchscreen/Kconfig             |  12 +
>  drivers/input/touchscreen/Makefile            |   1 +
>  drivers/input/touchscreen/ili251x.c           | 350 ++++++++++++++++++
>  4 files changed, 398 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
>  create mode 100644 drivers/input/touchscreen/ili251x.c

Please split the patch, the bindig should be on a separate patch
and must come before the driver.

> +#define MAX_FINGERS		10
> +#define REG_TOUCHDATA		0x10
> +#define TOUCHDATA_FINGERS	6
> +#define REG_TOUCHDATA2		0x14
> +#define TOUCHDATA2_FINGERS	4
> +#define REG_PANEL_INFO		0x20
> +#define REG_FIRMWARE_VERSION	0x40
> +#define REG_PROTO_VERSION	0x42
> +#define REG_CALIBRATE		0xcc

Can you please group and sort these definitions by type? REGs
with REGs and others together?

Please start the defines names with a unique identifier,
ILI251X_REG_* instead of just REG_*

> +struct finger {
> +	u8 x_high:6;
> +	u8 dummy:1;
> +	u8 status:1;
> +	u8 x_low;
> +	u8 y_high;
> +	u8 y_low;
> +	u8 pressure;
> +} __packed;
> +
> +struct touchdata {
> +	u8 status;
> +	struct finger fingers[MAX_FINGERS];
> +} __packed;
> +
> +struct panel_info {
> +	u8 x_low;
> +	u8 x_high;
> +	u8 y_low;
> +	u8 y_high;
> +	u8 xchannel_num;
> +	u8 ychannel_num;
> +	u8 max_fingers;
> +} __packed;
> +
> +struct firmware_version {
> +	u8 id;
> +	u8 major;
> +	u8 minor;
> +} __packed;
> +
> +struct protocol_version {
> +	u8 major;
> +	u8 minor;
> +} __packed;

panel_info, firmware_version and protocol_version are used very
little in the driver (just in probe). Is it really necessary to
keep a definition?

Is there a way to get rid of them?

> +struct ili251x_data {
> +	struct i2c_client *client;
> +	struct input_dev *input;
> +	unsigned int max_fingers;
> +	bool use_pressure;
> +	struct gpio_desc *reset_gpio;
> +};

Please start also the strct definitions with the unique
identifier ili251x_* like you did with ili251x_data.

> +
> +static int ili251x_read_reg(struct i2c_client *client, u8 reg, void *buf,
> +			    size_t len)
> +{
> +	struct i2c_msg msg[2] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &reg,
> +		},
> +		{
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= len,
> +			.buf	= buf,
> +		}
> +	};
> +
> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
> +		dev_err(&client->dev, "i2c transfer failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}

I do not see the need for a ili251x_read_reg function. You are
not reading more than 240 bytes per time, am I right?

In this case I would use the smbus functions (at least whenever
possible in case I miscalculated the 240b), this is ju a
duplicated code.

> +static void ili251x_report_events(struct ili251x_data *data,
> +				  const struct touchdata *touchdata)
> +{
> +	struct input_dev *input = data->input;
> +	unsigned int i;
> +	bool touch;
> +	unsigned int x, y;
> +	const struct finger *finger;
> +	unsigned int reported_fingers = 0;
> +
> +	/* the touch chip does not count the real fingers but switches between
> +	 * 0, 6 and 10 reported fingers *
> +	 *
> +	 * FIXME: With a tested ili2511 we received only garbage for fingers
> +	 *        6-9. As workaround we add a device tree option to limit the
> +	 *        handled number of fingers
> +	 */
> +	if (touchdata->status == 1)
> +		reported_fingers = 6;
> +	else if (touchdata->status == 2)
> +		reported_fingers = 10;
> +
> +	for (i = 0; i < reported_fingers && i < data->max_fingers; i++) {
> +		input_mt_slot(input, i);
> +
> +		finger = &touchdata->fingers[i];
> +
> +		touch = finger->status;
> +		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
> +		x = finger->x_low | (finger->x_high << 8);
> +		y = finger->y_low | (finger->y_high << 8);

the x and y calculation can go uinside the if() below, right?

> +		if (touch) {
> +			input_report_abs(input, ABS_MT_POSITION_X, x);
> +			input_report_abs(input, ABS_MT_POSITION_Y, y);
> +			if (data->use_pressure)
> +				input_report_abs(input, ABS_MT_PRESSURE,
> +						 finger->pressure);
> +
> +		}

just a small nitpick, that is more a matter of preference, with

  if(!touch)
    continue;

we save a level of indentation.

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Dmitry Torokhov May 9, 2018, 10:41 p.m. UTC | #3
Hi Philipp,

On Mon, May 07, 2018 at 03:18:23PM +0200, Philipp Puschmann wrote:
> The driver supports at least the ili2511 chipset but may support other
> Ilitek chipsets using Ilitek i2c protocol v3.x.
> 
> The tested ili2511-based touchscreen delivers garbage for more than 6
> fingers while it should support up to 10 fingers. The reason is still
> unclear and this remains a FIXME in the driver for now.
> 
> The usage of pressure is optional. Touchscreens may deliver constant
> and so useless pressure data.

Is it dependent on model or what? I would much rather we did not have DT
property for this.

> 
> Signed-off-by: Philipp Puschmann <pp@emlix.com>
> ---
>  .../bindings/input/touchscreen/ili251x.txt    |  35 ++
>  drivers/input/touchscreen/Kconfig             |  12 +
>  drivers/input/touchscreen/Makefile            |   1 +
>  drivers/input/touchscreen/ili251x.c           | 350 ++++++++++++++++++
>  4 files changed, 398 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
>  create mode 100644 drivers/input/touchscreen/ili251x.c
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt b/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
> new file mode 100644
> index 000000000000..f21ad93d3bdd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
> @@ -0,0 +1,35 @@
> +Ilitek ili251x touchscreen driver
> +
> +This driver uses protocol version 3 and should be compatible with other
> +Ilitek touch controllers that use protocol 3.x
> +
> +Required properties:
> + - compatible:  "ili251x"
> + - reg:         I2C slave address of the chip (0x41)
> + - interrupt-parent: a phandle pointing to the interrupt controller
> +                     serving the interrupt for this chip
> + - interrupts:       interrupt specification for the touchdetect
> +                     interrupt
> +
> +Optional properties:
> + - reset-gpios: GPIO specification for the RESET input
> +
> + - pinctrl-names: should be "default"
> + - pinctrl-0:   a phandle pointing to the pin settings for the
> +                control gpios
> + - max-fingers: the maximum number of fingers to handle
> + - pressure: support pressure data
> + - generic options	   : See touchscreen.txt
> +
> +Example:
> +
> +	ili251x@41 {
> +		compatible = "ili251x";
> +		reg = <0x41>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_touchpanel>;
> +		interrupt-parent = <&gpio5>;
> +		interrupts = <20 IRQ_TYPE_EDGE_FALLING>;
> +		reset-gpios = <&gpio5 18 GPIO_ACTIVE_HIGH>;
> +		max-fingers = <6>;
> +	};
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 4f15496fec8b..569528834d48 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -380,6 +380,18 @@ config TOUCHSCREEN_ILI210X
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called ili210x.
>  
> +config TOUCHSCREEN_ILI251X
> +	tristate "Ilitek ILI251X based touchscreen"
> +	depends on I2C
> +	help
> +	  Say Y here if you have a ILI251X based touchscreen
> +	  controller. This driver supports ILI2511.
> +
> +	  If unsure, say N.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called ili251x.
> +
>  config TOUCHSCREEN_IPROC
>  	tristate "IPROC touch panel driver support"
>  	depends on ARCH_BCM_IPROC || COMPILE_TEST
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index dddae7973436..e795b62e5f64 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix.o
>  obj-$(CONFIG_TOUCHSCREEN_HIDEEP)	+= hideep.o
>  obj-$(CONFIG_TOUCHSCREEN_ILI210X)	+= ili210x.o
> +obj-$(CONFIG_TOUCHSCREEN_ILI251X)	+= ili251x.o
>  obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC)	+= imx6ul_tsc.o
>  obj-$(CONFIG_TOUCHSCREEN_INEXIO)	+= inexio.o
>  obj-$(CONFIG_TOUCHSCREEN_IPROC)		+= bcm_iproc_tsc.o
> diff --git a/drivers/input/touchscreen/ili251x.c b/drivers/input/touchscreen/ili251x.c
> new file mode 100644
> index 000000000000..203367b59902
> --- /dev/null
> +++ b/drivers/input/touchscreen/ili251x.c
> @@ -0,0 +1,350 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2018, emlix GmbH. All rights reserved. */
> +
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/irq.h>
> +
> +#define MAX_FINGERS		10
> +#define REG_TOUCHDATA		0x10
> +#define TOUCHDATA_FINGERS	6
> +#define REG_TOUCHDATA2		0x14
> +#define TOUCHDATA2_FINGERS	4
> +#define REG_PANEL_INFO		0x20
> +#define REG_FIRMWARE_VERSION	0x40
> +#define REG_PROTO_VERSION	0x42
> +#define REG_CALIBRATE		0xcc
> +
> +struct finger {
> +	u8 x_high:6;
> +	u8 dummy:1;
> +	u8 status:1;

This does not work on BE as the order of bits will be reversed. Please
use masks, shifts, etc instead.

> +	u8 x_low;
> +	u8 y_high;
> +	u8 y_low;
> +	u8 pressure;
> +} __packed;
> +
> +struct touchdata {
> +	u8 status;
> +	struct finger fingers[MAX_FINGERS];
> +} __packed;
> +
> +struct panel_info {
> +	u8 x_low;
> +	u8 x_high;

__le16?

> +	u8 y_low;
> +	u8 y_high;

__le16

> +	u8 xchannel_num;
> +	u8 ychannel_num;
> +	u8 max_fingers;
> +} __packed;

This does not need to be packed.

> +
> +struct firmware_version {
> +	u8 id;
> +	u8 major;
> +	u8 minor;
> +} __packed;
> +
> +struct protocol_version {
> +	u8 major;
> +	u8 minor;
> +} __packed;
> +
> +struct ili251x_data {
> +	struct i2c_client *client;
> +	struct input_dev *input;
> +	unsigned int max_fingers;
> +	bool use_pressure;
> +	struct gpio_desc *reset_gpio;
> +};
> +
> +static int ili251x_read_reg(struct i2c_client *client, u8 reg, void *buf,
> +			    size_t len)
> +{
> +	struct i2c_msg msg[2] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &reg,
> +		},
> +		{
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= len,
> +			.buf	= buf,
> +		}
> +	};
> +
> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
> +		dev_err(&client->dev, "i2c transfer failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ili251x_report_events(struct ili251x_data *data,
> +				  const struct touchdata *touchdata)
> +{
> +	struct input_dev *input = data->input;
> +	unsigned int i;
> +	bool touch;
> +	unsigned int x, y;
> +	const struct finger *finger;
> +	unsigned int reported_fingers = 0;
> +
> +	/* the touch chip does not count the real fingers but switches between
> +	 * 0, 6 and 10 reported fingers *
> +	 *
> +	 * FIXME: With a tested ili2511 we received only garbage for fingers
> +	 *        6-9. As workaround we add a device tree option to limit the
> +	 *        handled number of fingers
> +	 */
> +	if (touchdata->status == 1)
> +		reported_fingers = 6;
> +	else if (touchdata->status == 2)
> +		reported_fingers = 10;
> +
> +	for (i = 0; i < reported_fingers && i < data->max_fingers; i++) {
> +		input_mt_slot(input, i);
> +
> +		finger = &touchdata->fingers[i];
> +
> +		touch = finger->status;
> +		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
> +		x = finger->x_low | (finger->x_high << 8);
> +		y = finger->y_low | (finger->y_high << 8);
> +
> +		if (touch) {
> +			input_report_abs(input, ABS_MT_POSITION_X, x);
> +			input_report_abs(input, ABS_MT_POSITION_Y, y);
> +			if (data->use_pressure)
> +				input_report_abs(input, ABS_MT_PRESSURE,
> +						 finger->pressure);
> +
> +		}
> +	}
> +
> +	input_mt_report_pointer_emulation(input, false);
> +	input_sync(input);
> +}
> +
> +static irqreturn_t ili251x_irq(int irq, void *irq_data)
> +{
> +	struct ili251x_data *data = irq_data;
> +	struct i2c_client *client = data->client;
> +	struct touchdata touchdata;
> +	int error;
> +
> +	error = ili251x_read_reg(client, REG_TOUCHDATA,
> +				 &touchdata,
> +				 sizeof(touchdata) -
> +					sizeof(struct finger)*TOUCHDATA2_FINGERS);
> +
> +	if (!error && touchdata.status == 2 && data->max_fingers > 6)
> +		error = ili251x_read_reg(client, REG_TOUCHDATA2,
> +					 &touchdata.fingers[TOUCHDATA_FINGERS],
> +					 sizeof(struct finger)*TOUCHDATA2_FINGERS);
> +
> +	if (!error)
> +		ili251x_report_events(data, &touchdata);
> +	else
> +		dev_err(&client->dev,
> +			"Unable to get touchdata, err = %d\n", error);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void ili251x_reset(struct ili251x_data *data)
> +{
> +	if (data->reset_gpio) {
> +		gpiod_set_value(data->reset_gpio, 1);
> +		usleep_range(50, 100);
> +		gpiod_set_value(data->reset_gpio, 0);
> +		msleep(100);
> +	}
> +}
> +
> +static int ili251x_i2c_probe(struct i2c_client *client,
> +				       const struct i2c_device_id *id)
> +{
> +	struct device *dev = &client->dev;
> +	struct ili251x_data *data;
> +	struct input_dev *input;
> +	struct panel_info panel;
> +	struct device_node *np = dev->of_node;
> +	struct firmware_version firmware;
> +	struct protocol_version protocol;
> +	int xmax, ymax;
> +	int error;
> +
> +	dev_dbg(dev, "Probing for ili251x I2C Touschreen driver");
> +
> +	if (client->irq <= 0) {
> +		dev_err(dev, "No IRQ!\n");
> +		return -EINVAL;
> +	}
> +
> +	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> +	input = devm_input_allocate_device(dev);
> +	if (!data || !input)

Please use separate tests here.

> +		return -ENOMEM;
> +
> +	data->client = client;
> +	data->input = input;
> +	data->use_pressure = false;
> +
> +	data->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> +						   GPIOD_OUT_HIGH);
> +	if (IS_ERR(data->reset_gpio)) {
> +		error = PTR_ERR(data->reset_gpio);
> +		if (error != -EPROBE_DEFER)
> +			dev_err(dev,
> +				"Failed to get reset GPIO: %d\n", error);
> +		return error;
> +	}
> +
> +	ili251x_reset(data);
> +
> +	error = ili251x_read_reg(client, REG_FIRMWARE_VERSION,
> +				 &firmware, sizeof(firmware));
> +	if (error) {
> +		dev_err(dev, "Failed to get firmware version, err: %d\n",
> +			error);
> +		return error;
> +	}
> +
> +	error = ili251x_read_reg(client, REG_PROTO_VERSION,
> +				 &protocol, sizeof(protocol));
> +	if (error) {
> +		dev_err(dev, "Failed to get protocol version, err: %d\n",
> +			error);
> +		return error;
> +	}
> +	if (protocol.major != 3) {
> +		dev_err(dev, "This driver expects protocol version 3.x, Chip uses: %d\n",
> +				protocol.major);
> +		return -EINVAL;
> +	}
> +
> +	error = ili251x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel));
> +	if (error) {
> +		dev_err(dev, "Failed to get panel information, err: %d\n",
> +			error);
> +		return error;
> +	}
> +
> +	data->max_fingers = panel.max_fingers;
> +	if (np) {
> +		int max_fingers;
> +
> +		error = of_property_read_u32(np, "max-fingers", &max_fingers);
> +		if (!error && max_fingers < data->max_fingers)
> +			data->max_fingers = max_fingers;
> +
> +		if (of_property_read_bool(np, "pressure"))
> +			data->use_pressure = true;
> +	}
> +
> +	xmax = panel.x_low | (panel.x_high << 8);
> +	ymax = panel.y_low | (panel.y_high << 8);
> +
> +	/* Setup input device */
> +	input->name = "ili251x Touchscreen";
> +	input->id.bustype = BUS_I2C;
> +	input->dev.parent = dev;

This is done by devm_input_allocate_device(), please drop.

> +
> +	__set_bit(EV_SYN, input->evbit);
> +	__set_bit(EV_KEY, input->evbit);
> +	__set_bit(EV_ABS, input->evbit);
> +	__set_bit(BTN_TOUCH, input->keybit);
> +
> +	/* Single touch */
> +	input_set_abs_params(input, ABS_X, 0, xmax, 0, 0);
> +	input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0);
> +
> +	/* Multi touch */
> +	input_mt_init_slots(input, data->max_fingers, 0);

Error handling please.

The touchscreens should be marked as INPUT_MT_DIRECT.

> +	input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0);
> +	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0);
> +	if (data->use_pressure)
> +		input_set_abs_params(input, ABS_MT_PRESSURE, 0, U8_MAX, 0, 0);

You want to set up absolute axes before you call input_mt_init_slots()
so that single-touch emulation is set up properly.

> +
> +	i2c_set_clientdata(client, data);
> +
> +	error = devm_request_threaded_irq(dev, client->irq, NULL, ili251x_irq,
> +				 IRQF_ONESHOT, client->name, data);
> +
> +	if (error) {
> +		dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",
> +			error);
> +		return error;
> +	}
> +
> +	error = input_register_device(data->input);
> +	if (error) {
> +		dev_err(dev, "Cannot register input device, err: %d\n", error);
> +		return error;
> +	}
> +
> +	device_init_wakeup(dev, 1);
> +
> +	dev_info(dev,
> +		"ili251x initialized (IRQ: %d), firmware version %d.%d.%d fingers %d",
> +		client->irq, firmware.id, firmware.major, firmware.minor,
> +		data->max_fingers);

dve_dbg() if you really need this, but I would rather drop.

> +
> +	return 0;
> +}
> +
> +static int __maybe_unused ili251x_i2c_suspend(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +
> +	if (device_may_wakeup(&client->dev))
> +		enable_irq_wake(client->irq);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused ili251x_i2c_resume(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +
> +	if (device_may_wakeup(&client->dev))
> +		disable_irq_wake(client->irq);
> +
> +	return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(ili251x_i2c_pm,
> +			 ili251x_i2c_suspend, ili251x_i2c_resume);

All this boilerplate can be removed if you use dev_pm_set_wake_irq() in
probe().

> +
> +static const struct i2c_device_id ili251x_i2c_id[] = {
> +	{ "ili251x", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ili251x_i2c_id);
> +
> +static struct i2c_driver ili251x_ts_driver = {
> +	.driver = {
> +		.name = "ili251x_i2c",
> +		.pm = &ili251x_i2c_pm,
> +	},
> +	.id_table = ili251x_i2c_id,
> +	.probe = ili251x_i2c_probe,
> +};
> +
> +module_i2c_driver(ili251x_ts_driver);
> +
> +MODULE_AUTHOR("Philipp Puschmann <pp@emlix.com>");
> +MODULE_DESCRIPTION("ili251x I2C Touchscreen Driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.17.0
> 

Thanks.
Philipp Puschmann May 15, 2018, 2:31 p.m. UTC | #4
Hi Dmitry,
hi Rob,
hi Andi,

thank you for your replies and your helpful hints. I will take care of them and
send a V2 of this driver + dt-bindings soon. As i have a working version of this
driver working on it has not a very priority so it may need some time.

Most of your points are correct and don't need further discussion. For the pressure
data i'm unsure, see below.

Am 10.05.2018 um 00:41 schrieb Dmitry Torokhov:
>> The usage of pressure is optional. Touchscreens may deliver constant
>> and so useless pressure data.
> 
> Is it dependent on model or what? I would much rather we did not have DT
> property for this.
I don't know. My touchscreen here delivers a constant (useless) pressure of
10. So my chip delivers "something". I'm not sure if the chip always delivers
no real pressure data or if this is dependent from the wiring.
This was the reason i put it in the dt.


Thanks
Philipp
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt b/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
new file mode 100644
index 000000000000..f21ad93d3bdd
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/ili251x.txt
@@ -0,0 +1,35 @@ 
+Ilitek ili251x touchscreen driver
+
+This driver uses protocol version 3 and should be compatible with other
+Ilitek touch controllers that use protocol 3.x
+
+Required properties:
+ - compatible:  "ili251x"
+ - reg:         I2C slave address of the chip (0x41)
+ - interrupt-parent: a phandle pointing to the interrupt controller
+                     serving the interrupt for this chip
+ - interrupts:       interrupt specification for the touchdetect
+                     interrupt
+
+Optional properties:
+ - reset-gpios: GPIO specification for the RESET input
+
+ - pinctrl-names: should be "default"
+ - pinctrl-0:   a phandle pointing to the pin settings for the
+                control gpios
+ - max-fingers: the maximum number of fingers to handle
+ - pressure: support pressure data
+ - generic options	   : See touchscreen.txt
+
+Example:
+
+	ili251x@41 {
+		compatible = "ili251x";
+		reg = <0x41>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_touchpanel>;
+		interrupt-parent = <&gpio5>;
+		interrupts = <20 IRQ_TYPE_EDGE_FALLING>;
+		reset-gpios = <&gpio5 18 GPIO_ACTIVE_HIGH>;
+		max-fingers = <6>;
+	};
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 4f15496fec8b..569528834d48 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -380,6 +380,18 @@  config TOUCHSCREEN_ILI210X
 	  To compile this driver as a module, choose M here: the
 	  module will be called ili210x.
 
+config TOUCHSCREEN_ILI251X
+	tristate "Ilitek ILI251X based touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have a ILI251X based touchscreen
+	  controller. This driver supports ILI2511.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ili251x.
+
 config TOUCHSCREEN_IPROC
 	tristate "IPROC touch panel driver support"
 	depends on ARCH_BCM_IPROC || COMPILE_TEST
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index dddae7973436..e795b62e5f64 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -43,6 +43,7 @@  obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix.o
 obj-$(CONFIG_TOUCHSCREEN_HIDEEP)	+= hideep.o
 obj-$(CONFIG_TOUCHSCREEN_ILI210X)	+= ili210x.o
+obj-$(CONFIG_TOUCHSCREEN_ILI251X)	+= ili251x.o
 obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC)	+= imx6ul_tsc.o
 obj-$(CONFIG_TOUCHSCREEN_INEXIO)	+= inexio.o
 obj-$(CONFIG_TOUCHSCREEN_IPROC)		+= bcm_iproc_tsc.o
diff --git a/drivers/input/touchscreen/ili251x.c b/drivers/input/touchscreen/ili251x.c
new file mode 100644
index 000000000000..203367b59902
--- /dev/null
+++ b/drivers/input/touchscreen/ili251x.c
@@ -0,0 +1,350 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018, emlix GmbH. All rights reserved. */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/irq.h>
+
+#define MAX_FINGERS		10
+#define REG_TOUCHDATA		0x10
+#define TOUCHDATA_FINGERS	6
+#define REG_TOUCHDATA2		0x14
+#define TOUCHDATA2_FINGERS	4
+#define REG_PANEL_INFO		0x20
+#define REG_FIRMWARE_VERSION	0x40
+#define REG_PROTO_VERSION	0x42
+#define REG_CALIBRATE		0xcc
+
+struct finger {
+	u8 x_high:6;
+	u8 dummy:1;
+	u8 status:1;
+	u8 x_low;
+	u8 y_high;
+	u8 y_low;
+	u8 pressure;
+} __packed;
+
+struct touchdata {
+	u8 status;
+	struct finger fingers[MAX_FINGERS];
+} __packed;
+
+struct panel_info {
+	u8 x_low;
+	u8 x_high;
+	u8 y_low;
+	u8 y_high;
+	u8 xchannel_num;
+	u8 ychannel_num;
+	u8 max_fingers;
+} __packed;
+
+struct firmware_version {
+	u8 id;
+	u8 major;
+	u8 minor;
+} __packed;
+
+struct protocol_version {
+	u8 major;
+	u8 minor;
+} __packed;
+
+struct ili251x_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+	unsigned int max_fingers;
+	bool use_pressure;
+	struct gpio_desc *reset_gpio;
+};
+
+static int ili251x_read_reg(struct i2c_client *client, u8 reg, void *buf,
+			    size_t len)
+{
+	struct i2c_msg msg[2] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &reg,
+		},
+		{
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= len,
+			.buf	= buf,
+		}
+	};
+
+	if (i2c_transfer(client->adapter, msg, 2) != 2) {
+		dev_err(&client->dev, "i2c transfer failed\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void ili251x_report_events(struct ili251x_data *data,
+				  const struct touchdata *touchdata)
+{
+	struct input_dev *input = data->input;
+	unsigned int i;
+	bool touch;
+	unsigned int x, y;
+	const struct finger *finger;
+	unsigned int reported_fingers = 0;
+
+	/* the touch chip does not count the real fingers but switches between
+	 * 0, 6 and 10 reported fingers *
+	 *
+	 * FIXME: With a tested ili2511 we received only garbage for fingers
+	 *        6-9. As workaround we add a device tree option to limit the
+	 *        handled number of fingers
+	 */
+	if (touchdata->status == 1)
+		reported_fingers = 6;
+	else if (touchdata->status == 2)
+		reported_fingers = 10;
+
+	for (i = 0; i < reported_fingers && i < data->max_fingers; i++) {
+		input_mt_slot(input, i);
+
+		finger = &touchdata->fingers[i];
+
+		touch = finger->status;
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
+		x = finger->x_low | (finger->x_high << 8);
+		y = finger->y_low | (finger->y_high << 8);
+
+		if (touch) {
+			input_report_abs(input, ABS_MT_POSITION_X, x);
+			input_report_abs(input, ABS_MT_POSITION_Y, y);
+			if (data->use_pressure)
+				input_report_abs(input, ABS_MT_PRESSURE,
+						 finger->pressure);
+
+		}
+	}
+
+	input_mt_report_pointer_emulation(input, false);
+	input_sync(input);
+}
+
+static irqreturn_t ili251x_irq(int irq, void *irq_data)
+{
+	struct ili251x_data *data = irq_data;
+	struct i2c_client *client = data->client;
+	struct touchdata touchdata;
+	int error;
+
+	error = ili251x_read_reg(client, REG_TOUCHDATA,
+				 &touchdata,
+				 sizeof(touchdata) -
+					sizeof(struct finger)*TOUCHDATA2_FINGERS);
+
+	if (!error && touchdata.status == 2 && data->max_fingers > 6)
+		error = ili251x_read_reg(client, REG_TOUCHDATA2,
+					 &touchdata.fingers[TOUCHDATA_FINGERS],
+					 sizeof(struct finger)*TOUCHDATA2_FINGERS);
+
+	if (!error)
+		ili251x_report_events(data, &touchdata);
+	else
+		dev_err(&client->dev,
+			"Unable to get touchdata, err = %d\n", error);
+
+	return IRQ_HANDLED;
+}
+
+static void ili251x_reset(struct ili251x_data *data)
+{
+	if (data->reset_gpio) {
+		gpiod_set_value(data->reset_gpio, 1);
+		usleep_range(50, 100);
+		gpiod_set_value(data->reset_gpio, 0);
+		msleep(100);
+	}
+}
+
+static int ili251x_i2c_probe(struct i2c_client *client,
+				       const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct ili251x_data *data;
+	struct input_dev *input;
+	struct panel_info panel;
+	struct device_node *np = dev->of_node;
+	struct firmware_version firmware;
+	struct protocol_version protocol;
+	int xmax, ymax;
+	int error;
+
+	dev_dbg(dev, "Probing for ili251x I2C Touschreen driver");
+
+	if (client->irq <= 0) {
+		dev_err(dev, "No IRQ!\n");
+		return -EINVAL;
+	}
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	input = devm_input_allocate_device(dev);
+	if (!data || !input)
+		return -ENOMEM;
+
+	data->client = client;
+	data->input = input;
+	data->use_pressure = false;
+
+	data->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						   GPIOD_OUT_HIGH);
+	if (IS_ERR(data->reset_gpio)) {
+		error = PTR_ERR(data->reset_gpio);
+		if (error != -EPROBE_DEFER)
+			dev_err(dev,
+				"Failed to get reset GPIO: %d\n", error);
+		return error;
+	}
+
+	ili251x_reset(data);
+
+	error = ili251x_read_reg(client, REG_FIRMWARE_VERSION,
+				 &firmware, sizeof(firmware));
+	if (error) {
+		dev_err(dev, "Failed to get firmware version, err: %d\n",
+			error);
+		return error;
+	}
+
+	error = ili251x_read_reg(client, REG_PROTO_VERSION,
+				 &protocol, sizeof(protocol));
+	if (error) {
+		dev_err(dev, "Failed to get protocol version, err: %d\n",
+			error);
+		return error;
+	}
+	if (protocol.major != 3) {
+		dev_err(dev, "This driver expects protocol version 3.x, Chip uses: %d\n",
+				protocol.major);
+		return -EINVAL;
+	}
+
+	error = ili251x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel));
+	if (error) {
+		dev_err(dev, "Failed to get panel information, err: %d\n",
+			error);
+		return error;
+	}
+
+	data->max_fingers = panel.max_fingers;
+	if (np) {
+		int max_fingers;
+
+		error = of_property_read_u32(np, "max-fingers", &max_fingers);
+		if (!error && max_fingers < data->max_fingers)
+			data->max_fingers = max_fingers;
+
+		if (of_property_read_bool(np, "pressure"))
+			data->use_pressure = true;
+	}
+
+	xmax = panel.x_low | (panel.x_high << 8);
+	ymax = panel.y_low | (panel.y_high << 8);
+
+	/* Setup input device */
+	input->name = "ili251x Touchscreen";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = dev;
+
+	__set_bit(EV_SYN, input->evbit);
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(EV_ABS, input->evbit);
+	__set_bit(BTN_TOUCH, input->keybit);
+
+	/* Single touch */
+	input_set_abs_params(input, ABS_X, 0, xmax, 0, 0);
+	input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0);
+
+	/* Multi touch */
+	input_mt_init_slots(input, data->max_fingers, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0);
+	if (data->use_pressure)
+		input_set_abs_params(input, ABS_MT_PRESSURE, 0, U8_MAX, 0, 0);
+
+	i2c_set_clientdata(client, data);
+
+	error = devm_request_threaded_irq(dev, client->irq, NULL, ili251x_irq,
+				 IRQF_ONESHOT, client->name, data);
+
+	if (error) {
+		dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",
+			error);
+		return error;
+	}
+
+	error = input_register_device(data->input);
+	if (error) {
+		dev_err(dev, "Cannot register input device, err: %d\n", error);
+		return error;
+	}
+
+	device_init_wakeup(dev, 1);
+
+	dev_info(dev,
+		"ili251x initialized (IRQ: %d), firmware version %d.%d.%d fingers %d",
+		client->irq, firmware.id, firmware.major, firmware.minor,
+		data->max_fingers);
+
+	return 0;
+}
+
+static int __maybe_unused ili251x_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	if (device_may_wakeup(&client->dev))
+		enable_irq_wake(client->irq);
+
+	return 0;
+}
+
+static int __maybe_unused ili251x_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	if (device_may_wakeup(&client->dev))
+		disable_irq_wake(client->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ili251x_i2c_pm,
+			 ili251x_i2c_suspend, ili251x_i2c_resume);
+
+static const struct i2c_device_id ili251x_i2c_id[] = {
+	{ "ili251x", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ili251x_i2c_id);
+
+static struct i2c_driver ili251x_ts_driver = {
+	.driver = {
+		.name = "ili251x_i2c",
+		.pm = &ili251x_i2c_pm,
+	},
+	.id_table = ili251x_i2c_id,
+	.probe = ili251x_i2c_probe,
+};
+
+module_i2c_driver(ili251x_ts_driver);
+
+MODULE_AUTHOR("Philipp Puschmann <pp@emlix.com>");
+MODULE_DESCRIPTION("ili251x I2C Touchscreen Driver");
+MODULE_LICENSE("GPL");