All of lore.kernel.org
 help / color / mirror / Atom feed
From: Philipp Puschmann <pp@emlix.com>
To: dmitry.torokhov@gmail.com
Cc: robh+dt@kernel.org, mark.rutland@arm.com, rydberg@bitmath.org,
	pp@emlix.com, andi@etezian.org, linux-input@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] Input: ili251x - add support for Ilitek ILI251x touchscreens
Date: Mon,  7 May 2018 15:18:23 +0200	[thread overview]
Message-ID: <20180507131823.28800-1-pp@emlix.com> (raw)

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

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");
-- 
2.17.0

             reply	other threads:[~2018-05-07 13:27 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-07 13:18 Philipp Puschmann [this message]
2018-05-08 17:51 ` [PATCH] Input: ili251x - add support for Ilitek ILI251x touchscreens Rob Herring
2018-05-08 21:49 ` Andi Shyti
2018-05-09 22:41 ` Dmitry Torokhov
2018-05-15 14:31   ` Philipp Puschmann

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=20180507131823.28800-1-pp@emlix.com \
    --to=pp@emlix.com \
    --cc=andi@etezian.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=robh+dt@kernel.org \
    --cc=rydberg@bitmath.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 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.