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>; + }; + 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"; + }; +}; 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 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);