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 <p...@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 = ®, + }, + { + .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 <p...@emlix.com>"); +MODULE_DESCRIPTION("ili251x I2C Touchscreen Driver"); +MODULE_LICENSE("GPL"); -- 2.17.0