From 1339ab53e98e434da0971ac894d95385c7c3108c Mon Sep 17 00:00:00 2001 From: Samuel Hsu Date: Mon, 8 Apr 2019 16:42:17 +0200 Subject: [PATCH 1/4] Input: ili210x - fetch touchscreen geometry from DT commit f67cc3e927d8414ad3872e046764534ea1f5db0d upstream Fetching the geometry from the ILI251x registers seems unreliable and sometimes returns all zeroes. Add support for fetching the geometry and axis inversion from DT instead. Signed-off-by: Marek Vasut Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/ili210x.c | 321 +++++++++++++++++----------- 1 file changed, 194 insertions(+), 127 deletions(-) diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c index 6f76eeedf4652e..9169aa03958ad0 100644 --- a/drivers/input/touchscreen/ili210x.c +++ b/drivers/input/touchscreen/ili210x.c @@ -4,11 +4,15 @@ #include #include #include +#include #include #include -#include +#include +#include +#include -#define MAX_TOUCHES 2 +#define ILI210X_TOUCHES 2 +#define ILI251X_TOUCHES 10 #define DEFAULT_POLL_PERIOD 20 /* Touchscreen commands */ @@ -17,41 +21,32 @@ #define REG_FIRMWARE_VERSION 0x40 #define REG_CALIBRATE 0xcc -struct finger { - u8 x_low; - u8 x_high; - u8 y_low; - u8 y_high; -} __packed; - -struct touchdata { - u8 status; - struct finger finger[MAX_TOUCHES]; -} __packed; - -struct panel_info { - struct finger finger_max; - u8 xchannel_num; - u8 ychannel_num; -} __packed; - struct firmware_version { u8 id; u8 major; u8 minor; } __packed; +enum ili2xxx_model { + MODEL_ILI210X, + MODEL_ILI251X, +}; + struct ili210x { struct i2c_client *client; struct input_dev *input; - bool (*get_pendown_state)(void); unsigned int poll_period; struct delayed_work dwork; + struct gpio_desc *reset_gpio; + struct touchscreen_properties prop; + enum ili2xxx_model model; + unsigned int max_touches; }; static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf, size_t len) { + struct ili210x *priv = i2c_get_clientdata(client); struct i2c_msg msg[2] = { { .addr = client->addr, @@ -67,7 +62,38 @@ static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf, } }; - if (i2c_transfer(client->adapter, msg, 2) != 2) { + if (priv->model == MODEL_ILI251X) { + if (i2c_transfer(client->adapter, msg, 1) != 1) { + dev_err(&client->dev, "i2c transfer failed\n"); + return -EIO; + } + + usleep_range(5000, 5500); + + if (i2c_transfer(client->adapter, msg + 1, 1) != 1) { + dev_err(&client->dev, "i2c transfer failed\n"); + return -EIO; + } + } else { + if (i2c_transfer(client->adapter, msg, 2) != 2) { + dev_err(&client->dev, "i2c transfer failed\n"); + return -EIO; + } + } + + return 0; +} + +static int ili210x_read(struct i2c_client *client, void *buf, size_t len) +{ + struct i2c_msg msg = { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + }; + + if (i2c_transfer(client->adapter, &msg, 1) != 1) { dev_err(&client->dev, "i2c transfer failed\n"); return -EIO; } @@ -75,42 +101,72 @@ static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf, return 0; } -static void ili210x_report_events(struct input_dev *input, - const struct touchdata *touchdata) +static bool ili210x_touchdata_to_coords(struct ili210x *priv, u8 *touchdata, + unsigned int finger, + unsigned int *x, unsigned int *y) { - int i; - bool touch; - unsigned int x, y; - const struct finger *finger; + if (finger >= ILI210X_TOUCHES) + return false; - for (i = 0; i < MAX_TOUCHES; i++) { - input_mt_slot(input, i); + if (touchdata[0] & BIT(finger)) + return false; - finger = &touchdata->finger[i]; + *x = get_unaligned_be16(touchdata + 1 + (finger * 4) + 0); + *y = get_unaligned_be16(touchdata + 1 + (finger * 4) + 2); - touch = touchdata->status & (1 << i); - input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); - if (touch) { - x = finger->x_low | (finger->x_high << 8); - y = finger->y_low | (finger->y_high << 8); + return true; +} + +static bool ili251x_touchdata_to_coords(struct ili210x *priv, u8 *touchdata, + unsigned int finger, + unsigned int *x, unsigned int *y) +{ + if (finger >= ILI251X_TOUCHES) + return false; + + *x = get_unaligned_be16(touchdata + 1 + (finger * 5) + 0); + if (!(*x & BIT(15))) /* Touch indication */ + return false; - input_report_abs(input, ABS_MT_POSITION_X, x); - input_report_abs(input, ABS_MT_POSITION_Y, y); + *x &= 0x3fff; + *y = get_unaligned_be16(touchdata + 1 + (finger * 5) + 2); + + return true; +} + +static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata) +{ + struct input_dev *input = priv->input; + int i; + bool contact = false, touch = false; + unsigned int x = 0, y = 0; + + for (i = 0; i < priv->max_touches; i++) { + if (priv->model == MODEL_ILI210X) { + touch = ili210x_touchdata_to_coords(priv, touchdata, + i, &x, &y); + } else if (priv->model == MODEL_ILI251X) { + touch = ili251x_touchdata_to_coords(priv, touchdata, + i, &x, &y); + if (touch) + contact = true; } + + input_mt_slot(input, i); + input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); + if (!touch) + continue; + touchscreen_report_pos(input, &priv->prop, x, y, + true); } input_mt_report_pointer_emulation(input, false); input_sync(input); -} -static bool get_pendown_state(const struct ili210x *priv) -{ - bool state = false; - - if (priv->get_pendown_state) - state = priv->get_pendown_state(); + if (priv->model == MODEL_ILI210X) + contact = touchdata[0] & 0xf3; - return state; + return contact; } static void ili210x_work(struct work_struct *work) @@ -118,20 +174,29 @@ static void ili210x_work(struct work_struct *work) struct ili210x *priv = container_of(work, struct ili210x, dwork.work); struct i2c_client *client = priv->client; - struct touchdata touchdata; - int error; + u8 touchdata[64] = { 0 }; + bool touch; + int error = -EINVAL; + + if (priv->model == MODEL_ILI210X) { + error = ili210x_read_reg(client, REG_TOUCHDATA, + touchdata, sizeof(touchdata)); + } else if (priv->model == MODEL_ILI251X) { + error = ili210x_read_reg(client, REG_TOUCHDATA, + touchdata, 31); + if (!error && touchdata[0] == 2) + error = ili210x_read(client, &touchdata[31], 20); + } - error = ili210x_read_reg(client, REG_TOUCHDATA, - &touchdata, sizeof(touchdata)); if (error) { dev_err(&client->dev, "Unable to get touchdata, err = %d\n", error); return; } - ili210x_report_events(priv->input, &touchdata); + touch = ili210x_report_events(priv, touchdata); - if ((touchdata.status & 0xf3) || get_pendown_state(priv)) + if (touch) schedule_delayed_work(&priv->dwork, msecs_to_jiffies(priv->poll_period)); } @@ -180,103 +245,119 @@ static const struct attribute_group ili210x_attr_group = { .attrs = ili210x_attributes, }; +static void ili210x_power_down(void *data) +{ + struct gpio_desc *reset_gpio = data; + + gpiod_set_value_cansleep(reset_gpio, 1); +} + +static void ili210x_cancel_work(void *data) +{ + struct ili210x *priv = data; + + cancel_delayed_work_sync(&priv->dwork); +} + static int ili210x_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; - const struct ili210x_platform_data *pdata = dev_get_platdata(dev); struct ili210x *priv; + struct gpio_desc *reset_gpio; struct input_dev *input; - struct panel_info panel; struct firmware_version firmware; - int xmax, ymax; + enum ili2xxx_model model; int error; - dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver"); + model = (enum ili2xxx_model)id->driver_data; - if (!pdata) { - dev_err(dev, "No platform data!\n"); - return -EINVAL; - } + dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver"); if (client->irq <= 0) { dev_err(dev, "No IRQ!\n"); return -EINVAL; } - /* Get firmware version */ - error = ili210x_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; - } + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); - /* get panel info */ - error = ili210x_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; + if (reset_gpio) { + error = devm_add_action_or_reset(dev, ili210x_power_down, + reset_gpio); + if (error) + return error; + + usleep_range(50, 100); + gpiod_set_value_cansleep(reset_gpio, 0); + msleep(100); } - xmax = panel.finger_max.x_low | (panel.finger_max.x_high << 8); - ymax = panel.finger_max.y_low | (panel.finger_max.y_high << 8); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; - priv = kzalloc(sizeof(*priv), GFP_KERNEL); - input = input_allocate_device(); - if (!priv || !input) { - error = -ENOMEM; - goto err_free_mem; - } + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; priv->client = client; priv->input = input; - priv->get_pendown_state = pdata->get_pendown_state; - priv->poll_period = pdata->poll_period ? : DEFAULT_POLL_PERIOD; + priv->poll_period = DEFAULT_POLL_PERIOD; INIT_DELAYED_WORK(&priv->dwork, ili210x_work); + priv->reset_gpio = reset_gpio; + priv->model = model; + if (model == MODEL_ILI210X) + priv->max_touches = ILI210X_TOUCHES; + if (model == MODEL_ILI251X) + priv->max_touches = ILI251X_TOUCHES; + + i2c_set_clientdata(client, priv); + + /* Get firmware version */ + error = ili210x_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; + } /* Setup input device */ input->name = "ILI210x 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, MAX_TOUCHES, 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); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, 0xffff, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0); + touchscreen_parse_properties(input, true, &priv->prop); + input_mt_init_slots(input, priv->max_touches, INPUT_MT_DIRECT); - i2c_set_clientdata(client, priv); + error = devm_add_action(dev, ili210x_cancel_work, priv); + if (error) + return error; - error = request_irq(client->irq, ili210x_irq, pdata->irq_flags, - client->name, priv); + error = devm_request_irq(dev, client->irq, ili210x_irq, 0, + client->name, priv); if (error) { dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", error); - goto err_free_mem; + return error; } - error = sysfs_create_group(&dev->kobj, &ili210x_attr_group); + error = devm_device_add_group(dev, &ili210x_attr_group); if (error) { dev_err(dev, "Unable to create sysfs attributes, err: %d\n", error); - goto err_free_irq; + return error; } error = input_register_device(priv->input); if (error) { dev_err(dev, "Cannot register input device, err: %d\n", error); - goto err_remove_sysfs; + return error; } device_init_wakeup(dev, 1); @@ -286,28 +367,6 @@ static int ili210x_i2c_probe(struct i2c_client *client, client->irq, firmware.id, firmware.major, firmware.minor); return 0; - -err_remove_sysfs: - sysfs_remove_group(&dev->kobj, &ili210x_attr_group); -err_free_irq: - free_irq(client->irq, priv); -err_free_mem: - input_free_device(input); - kfree(priv); - return error; -} - -static int ili210x_i2c_remove(struct i2c_client *client) -{ - struct ili210x *priv = i2c_get_clientdata(client); - - sysfs_remove_group(&client->dev.kobj, &ili210x_attr_group); - free_irq(priv->client->irq, priv); - cancel_delayed_work_sync(&priv->dwork); - input_unregister_device(priv->input); - kfree(priv); - - return 0; } static int __maybe_unused ili210x_i2c_suspend(struct device *dev) @@ -334,19 +393,27 @@ static SIMPLE_DEV_PM_OPS(ili210x_i2c_pm, ili210x_i2c_suspend, ili210x_i2c_resume); static const struct i2c_device_id ili210x_i2c_id[] = { - { "ili210x", 0 }, + { "ili210x", MODEL_ILI210X }, + { "ili251x", MODEL_ILI251X }, { } }; MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id); +static const struct of_device_id ili210x_dt_ids[] = { + { .compatible = "ilitek,ili210x", .data = (void *)MODEL_ILI210X }, + { .compatible = "ilitek,ili251x", .data = (void *)MODEL_ILI251X }, + { }, +}; +MODULE_DEVICE_TABLE(of, ili210x_dt_ids); + static struct i2c_driver ili210x_ts_driver = { .driver = { .name = "ili210x_i2c", .pm = &ili210x_i2c_pm, + .of_match_table = ili210x_dt_ids, }, .id_table = ili210x_i2c_id, .probe = ili210x_i2c_probe, - .remove = ili210x_i2c_remove, }; module_i2c_driver(ili210x_ts_driver); From 51bbd9fd788d745bceca6f3b7231a9ec3bfc1214 Mon Sep 17 00:00:00 2001 From: Samuel Hsu Date: Mon, 8 Apr 2019 16:49:51 +0200 Subject: [PATCH 2/4] Input: ili210x - add DT binding document commit 41a852e002e65ab7a1e6841b485d72d022e95df2 upstream Add DT binding document for the Ilitek ILI210x and ILI251x touchscreen controllers. Signed-off-by: Marek Vasut Reviewed-by: Rob Herring Signed-off-by: Dmitry Torokhov --- .../bindings/input/ilitek,ili2xxx.txt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/ilitek,ili2xxx.txt diff --git a/Documentation/devicetree/bindings/input/ilitek,ili2xxx.txt b/Documentation/devicetree/bindings/input/ilitek,ili2xxx.txt new file mode 100644 index 00000000000000..c9b1eacd34d55e --- /dev/null +++ b/Documentation/devicetree/bindings/input/ilitek,ili2xxx.txt @@ -0,0 +1,26 @@ +Ilitek ILI210x/ILI251x touchscreen controller + +Required properties: +- compatible: + ilitek,ili210x for ILI210x + ilitek,ili251x for ILI251x + +- reg: The I2C address of the device + +- interrupts: The sink for the touchscreen's IRQ output + See ../interrupt-controller/interrupts.txt + +Optional properties for main touchpad device: + +- reset-gpios: GPIO specifier for the touchscreen's reset pin (active low) + +Example: + + touchscreen@41 { + compatible = "ilitek,ili251x"; + reg = <0x41>; + interrupt-parent = <&gpio4>; + interrupts = <7 IRQ_TYPE_EDGE_FALLING>; + reset-gpios = <&gpio5 21 GPIO_ACTIVE_LOW>; + }; + From a8efecbc7909d087797f56e325c995340d14a81b Mon Sep 17 00:00:00 2001 From: Samuel Hsu Date: Mon, 8 Apr 2019 16:54:34 +0200 Subject: [PATCH 3/4] configs: Add TOUCHSCREEN_ILI210X=m Signed-off-by: Samuel Hsu --- arch/arm/configs/bcm2709_defconfig | 1 + arch/arm/configs/bcmrpi_defconfig | 1 + arch/arm64/configs/bcmrpi3_defconfig | 1 + 3 files changed, 3 insertions(+) diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig index a3ac6106858c53..a35e772c588ba7 100644 --- a/arch/arm/configs/bcm2709_defconfig +++ b/arch/arm/configs/bcm2709_defconfig @@ -565,6 +565,7 @@ CONFIG_TOUCHSCREEN_ADS7846=m CONFIG_TOUCHSCREEN_EGALAX=m CONFIG_TOUCHSCREEN_EXC3000=m CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m CONFIG_TOUCHSCREEN_EDT_FT5X06=m CONFIG_TOUCHSCREEN_RPI_FT5406=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig index e30ea316c52a09..6327e983fb5ceb 100644 --- a/arch/arm/configs/bcmrpi_defconfig +++ b/arch/arm/configs/bcmrpi_defconfig @@ -559,6 +559,7 @@ CONFIG_TOUCHSCREEN_ADS7846=m CONFIG_TOUCHSCREEN_EGALAX=m CONFIG_TOUCHSCREEN_EXC3000=m CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m CONFIG_TOUCHSCREEN_EDT_FT5X06=m CONFIG_TOUCHSCREEN_RPI_FT5406=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m diff --git a/arch/arm64/configs/bcmrpi3_defconfig b/arch/arm64/configs/bcmrpi3_defconfig index c1ca03862d224f..3f9e05c01a6b88 100644 --- a/arch/arm64/configs/bcmrpi3_defconfig +++ b/arch/arm64/configs/bcmrpi3_defconfig @@ -547,6 +547,7 @@ CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_ADS7846=m CONFIG_TOUCHSCREEN_EGALAX=m CONFIG_TOUCHSCREEN_EKTF2127=m +CONFIG_TOUCHSCREEN_ILI210X=m CONFIG_TOUCHSCREEN_RPI_FT5406=m CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_TOUCHSCREEN_STMPE=m From 3a6a5b048a31c70837709b5b5cebbc901fad7e3a Mon Sep 17 00:00:00 2001 From: Samuel Hsu Date: Mon, 8 Apr 2019 17:06:44 +0200 Subject: [PATCH 4/4] BCM2708: Add core Device Tree support, ilitek251x Signed-off-by: Samuel Hsu --- arch/arm/boot/dts/overlays/Makefile | 1 + arch/arm/boot/dts/overlays/README | 11 +++++ .../boot/dts/overlays/ilitek251x-overlay.dts | 45 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 arch/arm/boot/dts/overlays/ilitek251x-overlay.dts diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile index 036d49b7a52b38..c5b615401ddf30 100644 --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -67,6 +67,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ i2c0-bcm2708.dtbo \ i2c1-bcm2708.dtbo \ i2s-gpio28-31.dtbo \ + ilitek251x.dtbo \ iqaudio-dac.dtbo \ iqaudio-dacplus.dtbo \ iqaudio-digi-wm8804-audio.dtbo \ diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README index 089e12255eb7e0..1f1fb9a3645e0f 100644 --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -1146,6 +1146,17 @@ Load: dtoverlay=i2s-gpio28-31 Params: +Name: ilitek251x +Info: Enables I2C connected Ilitek 251x multiple touch controller using + GPIO 4 (pin 7 on GPIO header) for interrupt. +Load: dtoverlay=ilitek251x,= +Params: interrupt GPIO used for interrupt (default 4) + sizex Touchscreen size x, horizontal resolution of + touchscreen (in pixels) + sizey Touchscreen size y, vertical resolution of + touchscreen (in pixels) + + Name: iqaudio-dac Info: Configures the IQaudio DAC audio card Load: dtoverlay=iqaudio-dac, diff --git a/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts new file mode 100644 index 00000000000000..fb1ccc314a290d --- /dev/null +++ b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts @@ -0,0 +1,45 @@ +// Device tree overlay for I2C connected Ilitek multiple touch controller +/dts-v1/; +/plugin/; + + / { + compatible = "brcm,bcm2708"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + ili251x_pins: ili251x_pins { + brcm,pins = <4>; // interrupt + brcm,function = <0>; // in + brcm,pull = <2>; // pull-up // + }; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ili251x: ili251x@41 { + compatible = "ilitek,ili251x"; + reg = <0x41>; + pinctrl-names = "default"; + pinctrl-0 = <&ili251x_pins>; + interrupt-parent = <&gpio>; + interrupts = <4 8>; // high-to-low edge triggered + touchscreen-size-x = <16384>; + touchscreen-size-y = <9600>; + }; + }; + }; + + __overrides__ { + interrupt = <&ili251x_pins>,"brcm,pins:0", + <&ili251x>,"interrupts:0"; + sizex = <&ili251x>,"touchscreen-size-x:0"; + sizey = <&ili251x>,"touchscreen-size-y:0"; + }; +};