summaryrefslogtreecommitdiffstats
path: root/drivers/input/touchscreen
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/input/touchscreen
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/input/touchscreen')
-rw-r--r--drivers/input/touchscreen/88pm860x-ts.c304
-rw-r--r--drivers/input/touchscreen/Kconfig1382
-rw-r--r--drivers/input/touchscreen/Makefile118
-rw-r--r--drivers/input/touchscreen/ad7877.c824
-rw-r--r--drivers/input/touchscreen/ad7879-i2c.c74
-rw-r--r--drivers/input/touchscreen/ad7879-spi.c71
-rw-r--r--drivers/input/touchscreen/ad7879.c635
-rw-r--r--drivers/input/touchscreen/ad7879.h21
-rw-r--r--drivers/input/touchscreen/ads7846.c1435
-rw-r--r--drivers/input/touchscreen/ar1021_i2c.c192
-rw-r--r--drivers/input/touchscreen/atmel_mxt_ts.c3390
-rw-r--r--drivers/input/touchscreen/auo-pixcir-ts.c648
-rw-r--r--drivers/input/touchscreen/bcm_iproc_tsc.c522
-rw-r--r--drivers/input/touchscreen/bu21013_ts.c630
-rw-r--r--drivers/input/touchscreen/bu21029_ts.c484
-rw-r--r--drivers/input/touchscreen/chipone_icn8318.c278
-rw-r--r--drivers/input/touchscreen/chipone_icn8505.c508
-rw-r--r--drivers/input/touchscreen/colibri-vf50-ts.c378
-rw-r--r--drivers/input/touchscreen/cy8ctma140.c353
-rw-r--r--drivers/input/touchscreen/cy8ctmg110_ts.c289
-rw-r--r--drivers/input/touchscreen/cyttsp4_core.c2180
-rw-r--r--drivers/input/touchscreen/cyttsp4_core.h448
-rw-r--r--drivers/input/touchscreen/cyttsp4_i2c.c73
-rw-r--r--drivers/input/touchscreen/cyttsp4_spi.c187
-rw-r--r--drivers/input/touchscreen/cyttsp_core.c736
-rw-r--r--drivers/input/touchscreen/cyttsp_core.h146
-rw-r--r--drivers/input/touchscreen/cyttsp_i2c.c78
-rw-r--r--drivers/input/touchscreen/cyttsp_i2c_common.c85
-rw-r--r--drivers/input/touchscreen/cyttsp_spi.c186
-rw-r--r--drivers/input/touchscreen/da9034-ts.c365
-rw-r--r--drivers/input/touchscreen/da9052_tsi.c342
-rw-r--r--drivers/input/touchscreen/dynapro.c185
-rw-r--r--drivers/input/touchscreen/edt-ft5x06.c1515
-rw-r--r--drivers/input/touchscreen/eeti_ts.c303
-rw-r--r--drivers/input/touchscreen/egalax_ts.c283
-rw-r--r--drivers/input/touchscreen/egalax_ts_serial.c190
-rw-r--r--drivers/input/touchscreen/ektf2127.c362
-rw-r--r--drivers/input/touchscreen/elants_i2c.c1701
-rw-r--r--drivers/input/touchscreen/elo.c406
-rw-r--r--drivers/input/touchscreen/exc3000.c470
-rw-r--r--drivers/input/touchscreen/fsl-imx25-tcq.c588
-rw-r--r--drivers/input/touchscreen/fujitsu_ts.c173
-rw-r--r--drivers/input/touchscreen/goodix.c1582
-rw-r--r--drivers/input/touchscreen/goodix.h120
-rw-r--r--drivers/input/touchscreen/goodix_fwupload.c427
-rw-r--r--drivers/input/touchscreen/gunze.c169
-rw-r--r--drivers/input/touchscreen/hampshire.c184
-rw-r--r--drivers/input/touchscreen/hideep.c1122
-rw-r--r--drivers/input/touchscreen/hp680_ts_input.c128
-rw-r--r--drivers/input/touchscreen/htcpen.c243
-rw-r--r--drivers/input/touchscreen/hycon-hy46xx.c591
-rw-r--r--drivers/input/touchscreen/ili210x.c1053
-rw-r--r--drivers/input/touchscreen/ilitek_ts_i2c.c690
-rw-r--r--drivers/input/touchscreen/imagis.c367
-rw-r--r--drivers/input/touchscreen/imx6ul_tsc.c569
-rw-r--r--drivers/input/touchscreen/inexio.c186
-rw-r--r--drivers/input/touchscreen/ipaq-micro-ts.c161
-rw-r--r--drivers/input/touchscreen/iqs5xx.c1103
-rw-r--r--drivers/input/touchscreen/jornada720_ts.c157
-rw-r--r--drivers/input/touchscreen/lpc32xx_ts.c399
-rw-r--r--drivers/input/touchscreen/mainstone-wm97xx.c286
-rw-r--r--drivers/input/touchscreen/max11801_ts.c241
-rw-r--r--drivers/input/touchscreen/mc13783_ts.c242
-rw-r--r--drivers/input/touchscreen/mcs5000_ts.c288
-rw-r--r--drivers/input/touchscreen/melfas_mip4.c1605
-rw-r--r--drivers/input/touchscreen/migor_ts.c234
-rw-r--r--drivers/input/touchscreen/mk712.c215
-rw-r--r--drivers/input/touchscreen/mms114.c651
-rw-r--r--drivers/input/touchscreen/msg2638.c337
-rw-r--r--drivers/input/touchscreen/mtouch.c200
-rw-r--r--drivers/input/touchscreen/mxs-lradc-ts.c703
-rw-r--r--drivers/input/touchscreen/pcap_ts.c254
-rw-r--r--drivers/input/touchscreen/penmount.c315
-rw-r--r--drivers/input/touchscreen/pixcir_i2c_ts.c628
-rw-r--r--drivers/input/touchscreen/raspberrypi-ts.c228
-rw-r--r--drivers/input/touchscreen/raydium_i2c_ts.c1295
-rw-r--r--drivers/input/touchscreen/resistive-adc-touch.c307
-rw-r--r--drivers/input/touchscreen/rohm_bu21023.c1194
-rw-r--r--drivers/input/touchscreen/s3c2410_ts.c464
-rw-r--r--drivers/input/touchscreen/s6sy761.c552
-rw-r--r--drivers/input/touchscreen/silead.c842
-rw-r--r--drivers/input/touchscreen/sis_i2c.c404
-rw-r--r--drivers/input/touchscreen/st1232.c402
-rw-r--r--drivers/input/touchscreen/stmfts.c821
-rw-r--r--drivers/input/touchscreen/stmpe-ts.c379
-rw-r--r--drivers/input/touchscreen/sun4i-ts.c413
-rw-r--r--drivers/input/touchscreen/sur40.c1190
-rw-r--r--drivers/input/touchscreen/surface3_spi.c421
-rw-r--r--drivers/input/touchscreen/sx8654.c479
-rw-r--r--drivers/input/touchscreen/ti_am335x_tsc.c567
-rw-r--r--drivers/input/touchscreen/touchit213.c214
-rw-r--r--drivers/input/touchscreen/touchright.c174
-rw-r--r--drivers/input/touchscreen/touchwin.c181
-rw-r--r--drivers/input/touchscreen/tps6507x-ts.c294
-rw-r--r--drivers/input/touchscreen/ts4800-ts.c223
-rw-r--r--drivers/input/touchscreen/tsc2004.c79
-rw-r--r--drivers/input/touchscreen/tsc2005.c94
-rw-r--r--drivers/input/touchscreen/tsc2007.h100
-rw-r--r--drivers/input/touchscreen/tsc2007_core.c441
-rw-r--r--drivers/input/touchscreen/tsc2007_iio.c135
-rw-r--r--drivers/input/touchscreen/tsc200x-core.c628
-rw-r--r--drivers/input/touchscreen/tsc200x-core.h79
-rw-r--r--drivers/input/touchscreen/tsc40.c172
-rw-r--r--drivers/input/touchscreen/ucb1400_ts.c458
-rw-r--r--drivers/input/touchscreen/usbtouchscreen.c1865
-rw-r--r--drivers/input/touchscreen/wacom_i2c.c275
-rw-r--r--drivers/input/touchscreen/wacom_w8001.c709
-rw-r--r--drivers/input/touchscreen/wdt87xx_i2c.c1185
-rw-r--r--drivers/input/touchscreen/wm831x-ts.c400
-rw-r--r--drivers/input/touchscreen/wm9705.c345
-rw-r--r--drivers/input/touchscreen/wm9712.c466
-rw-r--r--drivers/input/touchscreen/wm9713.c476
-rw-r--r--drivers/input/touchscreen/wm97xx-core.c910
-rw-r--r--drivers/input/touchscreen/zet6223.c259
-rw-r--r--drivers/input/touchscreen/zforce_ts.c956
-rw-r--r--drivers/input/touchscreen/zinitix.c631
-rw-r--r--drivers/input/touchscreen/zylonite-wm97xx.c220
117 files changed, 60915 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/88pm860x-ts.c b/drivers/input/touchscreen/88pm860x-ts.c
new file mode 100644
index 000000000..81a3ea4b9
--- /dev/null
+++ b/drivers/input/touchscreen/88pm860x-ts.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Touchscreen driver for Marvell 88PM860x
+ *
+ * Copyright (C) 2009 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/mfd/88pm860x.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#define MEAS_LEN (8)
+#define ACCURATE_BIT (12)
+
+/* touch register */
+#define MEAS_EN3 (0x52)
+
+#define MEAS_TSIX_1 (0x8D)
+#define MEAS_TSIX_2 (0x8E)
+#define MEAS_TSIY_1 (0x8F)
+#define MEAS_TSIY_2 (0x90)
+#define MEAS_TSIZ1_1 (0x91)
+#define MEAS_TSIZ1_2 (0x92)
+#define MEAS_TSIZ2_1 (0x93)
+#define MEAS_TSIZ2_2 (0x94)
+
+/* bit definitions of touch */
+#define MEAS_PD_EN (1 << 3)
+#define MEAS_TSIX_EN (1 << 4)
+#define MEAS_TSIY_EN (1 << 5)
+#define MEAS_TSIZ1_EN (1 << 6)
+#define MEAS_TSIZ2_EN (1 << 7)
+
+struct pm860x_touch {
+ struct input_dev *idev;
+ struct i2c_client *i2c;
+ struct pm860x_chip *chip;
+ int irq;
+ int res_x; /* resistor of Xplate */
+};
+
+static irqreturn_t pm860x_touch_handler(int irq, void *data)
+{
+ struct pm860x_touch *touch = data;
+ struct pm860x_chip *chip = touch->chip;
+ unsigned char buf[MEAS_LEN];
+ int x, y, pen_down;
+ int z1, z2, rt = 0;
+ int ret;
+
+ ret = pm860x_bulk_read(touch->i2c, MEAS_TSIX_1, MEAS_LEN, buf);
+ if (ret < 0)
+ goto out;
+
+ pen_down = buf[1] & (1 << 6);
+ x = ((buf[0] & 0xFF) << 4) | (buf[1] & 0x0F);
+ y = ((buf[2] & 0xFF) << 4) | (buf[3] & 0x0F);
+ z1 = ((buf[4] & 0xFF) << 4) | (buf[5] & 0x0F);
+ z2 = ((buf[6] & 0xFF) << 4) | (buf[7] & 0x0F);
+
+ if (pen_down) {
+ if ((x != 0) && (z1 != 0) && (touch->res_x != 0)) {
+ rt = z2 / z1 - 1;
+ rt = (rt * touch->res_x * x) >> ACCURATE_BIT;
+ dev_dbg(chip->dev, "z1:%d, z2:%d, rt:%d\n",
+ z1, z2, rt);
+ }
+ input_report_abs(touch->idev, ABS_X, x);
+ input_report_abs(touch->idev, ABS_Y, y);
+ input_report_abs(touch->idev, ABS_PRESSURE, rt);
+ input_report_key(touch->idev, BTN_TOUCH, 1);
+ dev_dbg(chip->dev, "pen down at [%d, %d].\n", x, y);
+ } else {
+ input_report_abs(touch->idev, ABS_PRESSURE, 0);
+ input_report_key(touch->idev, BTN_TOUCH, 0);
+ dev_dbg(chip->dev, "pen release\n");
+ }
+ input_sync(touch->idev);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int pm860x_touch_open(struct input_dev *dev)
+{
+ struct pm860x_touch *touch = input_get_drvdata(dev);
+ int data, ret;
+
+ data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN
+ | MEAS_TSIZ1_EN | MEAS_TSIZ2_EN;
+ ret = pm860x_set_bits(touch->i2c, MEAS_EN3, data, data);
+ if (ret < 0)
+ goto out;
+ return 0;
+out:
+ return ret;
+}
+
+static void pm860x_touch_close(struct input_dev *dev)
+{
+ struct pm860x_touch *touch = input_get_drvdata(dev);
+ int data;
+
+ data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN
+ | MEAS_TSIZ1_EN | MEAS_TSIZ2_EN;
+ pm860x_set_bits(touch->i2c, MEAS_EN3, data, 0);
+}
+
+#ifdef CONFIG_OF
+static int pm860x_touch_dt_init(struct platform_device *pdev,
+ struct pm860x_chip *chip,
+ int *res_x)
+{
+ struct device_node *np = pdev->dev.parent->of_node;
+ struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
+ : chip->companion;
+ int data, n, ret;
+ if (!np)
+ return -ENODEV;
+ np = of_get_child_by_name(np, "touch");
+ if (!np) {
+ dev_err(&pdev->dev, "Can't find touch node\n");
+ return -EINVAL;
+ }
+ /* set GPADC MISC1 register */
+ data = 0;
+ if (!of_property_read_u32(np, "marvell,88pm860x-gpadc-prebias", &n))
+ data |= (n << 1) & PM8607_GPADC_PREBIAS_MASK;
+ if (!of_property_read_u32(np, "marvell,88pm860x-gpadc-slot-cycle", &n))
+ data |= (n << 3) & PM8607_GPADC_SLOT_CYCLE_MASK;
+ if (!of_property_read_u32(np, "marvell,88pm860x-gpadc-off-scale", &n))
+ data |= (n << 5) & PM8607_GPADC_OFF_SCALE_MASK;
+ if (!of_property_read_u32(np, "marvell,88pm860x-gpadc-sw-cal", &n))
+ data |= (n << 7) & PM8607_GPADC_SW_CAL_MASK;
+ if (data) {
+ ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data);
+ if (ret < 0)
+ goto err_put_node;
+ }
+ /* set tsi prebias time */
+ if (!of_property_read_u32(np, "marvell,88pm860x-tsi-prebias", &data)) {
+ ret = pm860x_reg_write(i2c, PM8607_TSI_PREBIAS, data);
+ if (ret < 0)
+ goto err_put_node;
+ }
+ /* set prebias & prechg time of pen detect */
+ data = 0;
+ if (!of_property_read_u32(np, "marvell,88pm860x-pen-prebias", &n))
+ data |= n & PM8607_PD_PREBIAS_MASK;
+ if (!of_property_read_u32(np, "marvell,88pm860x-pen-prechg", &n))
+ data |= n & PM8607_PD_PRECHG_MASK;
+ if (data) {
+ ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data);
+ if (ret < 0)
+ goto err_put_node;
+ }
+ of_property_read_u32(np, "marvell,88pm860x-resistor-X", res_x);
+
+ of_node_put(np);
+
+ return 0;
+
+err_put_node:
+ of_node_put(np);
+
+ return -EINVAL;
+}
+#else
+#define pm860x_touch_dt_init(x, y, z) (-1)
+#endif
+
+static int pm860x_touch_probe(struct platform_device *pdev)
+{
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm860x_touch_pdata *pdata = dev_get_platdata(&pdev->dev);
+ struct pm860x_touch *touch;
+ struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
+ : chip->companion;
+ int irq, ret, res_x = 0, data = 0;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -EINVAL;
+
+ if (pm860x_touch_dt_init(pdev, chip, &res_x)) {
+ if (pdata) {
+ /* set GPADC MISC1 register */
+ data = 0;
+ data |= (pdata->gpadc_prebias << 1)
+ & PM8607_GPADC_PREBIAS_MASK;
+ data |= (pdata->slot_cycle << 3)
+ & PM8607_GPADC_SLOT_CYCLE_MASK;
+ data |= (pdata->off_scale << 5)
+ & PM8607_GPADC_OFF_SCALE_MASK;
+ data |= (pdata->sw_cal << 7)
+ & PM8607_GPADC_SW_CAL_MASK;
+ if (data) {
+ ret = pm860x_reg_write(i2c,
+ PM8607_GPADC_MISC1, data);
+ if (ret < 0)
+ return -EINVAL;
+ }
+ /* set tsi prebias time */
+ if (pdata->tsi_prebias) {
+ data = pdata->tsi_prebias;
+ ret = pm860x_reg_write(i2c,
+ PM8607_TSI_PREBIAS, data);
+ if (ret < 0)
+ return -EINVAL;
+ }
+ /* set prebias & prechg time of pen detect */
+ data = 0;
+ data |= pdata->pen_prebias
+ & PM8607_PD_PREBIAS_MASK;
+ data |= (pdata->pen_prechg << 5)
+ & PM8607_PD_PRECHG_MASK;
+ if (data) {
+ ret = pm860x_reg_write(i2c,
+ PM8607_PD_PREBIAS, data);
+ if (ret < 0)
+ return -EINVAL;
+ }
+ res_x = pdata->res_x;
+ } else {
+ dev_err(&pdev->dev, "failed to get platform data\n");
+ return -EINVAL;
+ }
+ }
+ /* enable GPADC */
+ ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, PM8607_GPADC_EN,
+ PM8607_GPADC_EN);
+ if (ret)
+ return ret;
+
+ touch = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_touch),
+ GFP_KERNEL);
+ if (!touch)
+ return -ENOMEM;
+
+ touch->idev = devm_input_allocate_device(&pdev->dev);
+ if (!touch->idev) {
+ dev_err(&pdev->dev, "Failed to allocate input device!\n");
+ return -ENOMEM;
+ }
+
+ touch->idev->name = "88pm860x-touch";
+ touch->idev->phys = "88pm860x/input0";
+ touch->idev->id.bustype = BUS_I2C;
+ touch->idev->dev.parent = &pdev->dev;
+ touch->idev->open = pm860x_touch_open;
+ touch->idev->close = pm860x_touch_close;
+ touch->chip = chip;
+ touch->i2c = i2c;
+ touch->irq = irq;
+ touch->res_x = res_x;
+ input_set_drvdata(touch->idev, touch);
+
+ ret = devm_request_threaded_irq(&pdev->dev, touch->irq, NULL,
+ pm860x_touch_handler, IRQF_ONESHOT,
+ "touch", touch);
+ if (ret < 0)
+ return ret;
+
+ __set_bit(EV_ABS, touch->idev->evbit);
+ __set_bit(ABS_X, touch->idev->absbit);
+ __set_bit(ABS_Y, touch->idev->absbit);
+ __set_bit(ABS_PRESSURE, touch->idev->absbit);
+ __set_bit(EV_SYN, touch->idev->evbit);
+ __set_bit(EV_KEY, touch->idev->evbit);
+ __set_bit(BTN_TOUCH, touch->idev->keybit);
+
+ input_set_abs_params(touch->idev, ABS_X, 0, 1 << ACCURATE_BIT, 0, 0);
+ input_set_abs_params(touch->idev, ABS_Y, 0, 1 << ACCURATE_BIT, 0, 0);
+ input_set_abs_params(touch->idev, ABS_PRESSURE, 0, 1 << ACCURATE_BIT,
+ 0, 0);
+
+ ret = input_register_device(touch->idev);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to register touch!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver pm860x_touch_driver = {
+ .driver = {
+ .name = "88pm860x-touch",
+ },
+ .probe = pm860x_touch_probe,
+};
+module_platform_driver(pm860x_touch_driver);
+
+MODULE_DESCRIPTION("Touchscreen driver for Marvell Semiconductor 88PM860x");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:88pm860x-touch");
+
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
new file mode 100644
index 000000000..dc90a3ea5
--- /dev/null
+++ b/drivers/input/touchscreen/Kconfig
@@ -0,0 +1,1382 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Touchscreen driver configuration
+#
+menuconfig INPUT_TOUCHSCREEN
+ bool "Touchscreens"
+ help
+ Say Y here, and a list of supported touchscreens will be displayed.
+ This option doesn't affect the kernel.
+
+ If unsure, say Y.
+
+if INPUT_TOUCHSCREEN
+
+config TOUCHSCREEN_88PM860X
+ tristate "Marvell 88PM860x touchscreen"
+ depends on MFD_88PM860X
+ help
+ Say Y here if you have a 88PM860x PMIC and want to enable
+ support for the built-in touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called 88pm860x-ts.
+
+config TOUCHSCREEN_ADS7846
+ tristate "ADS7846/TSC2046/AD7873 and AD(S)7843 based touchscreens"
+ depends on SPI_MASTER
+ depends on HWMON = n || HWMON
+ help
+ Say Y here if you have a touchscreen interface using the
+ ADS7846/TSC2046/AD7873 or ADS7843/AD7843 controller,
+ and your board-specific setup code includes that in its
+ table of SPI devices.
+
+ If HWMON is selected, and the driver is told the reference voltage
+ on your board, you will also get hwmon interfaces for the voltage
+ (and on ads7846/tsc2046/ad7873, temperature) sensors of this chip.
+
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called ads7846.
+
+config TOUCHSCREEN_AD7877
+ tristate "AD7877 based touchscreens"
+ depends on SPI_MASTER
+ help
+ Say Y here if you have a touchscreen interface using the
+ AD7877 controller, and your board-specific initialization
+ code includes that in its table of SPI devices.
+
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7877.
+
+config TOUCHSCREEN_AD7879
+ tristate "Analog Devices AD7879-1/AD7889-1 touchscreen interface"
+ help
+ Say Y here if you want to support a touchscreen interface using
+ the AD7879-1/AD7889-1 controller.
+
+ You should select a bus connection too.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7879.
+
+config TOUCHSCREEN_AD7879_I2C
+ tristate "support I2C bus connection"
+ depends on TOUCHSCREEN_AD7879 && I2C
+ select REGMAP_I2C
+ help
+ Say Y here if you have AD7879-1/AD7889-1 hooked to an I2C bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7879-i2c.
+
+config TOUCHSCREEN_AD7879_SPI
+ tristate "support SPI bus connection"
+ depends on TOUCHSCREEN_AD7879 && SPI_MASTER
+ select REGMAP_SPI
+ help
+ Say Y here if you have AD7879-1/AD7889-1 hooked to a SPI bus.
+
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7879-spi.
+
+config TOUCHSCREEN_ADC
+ tristate "Generic ADC based resistive touchscreen"
+ depends on IIO
+ select IIO_BUFFER
+ select IIO_BUFFER_CB
+ help
+ Say Y here if you want to use the generic ADC
+ resistive touchscreen driver.
+
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called resistive-adc-touch.ko.
+
+config TOUCHSCREEN_AR1021_I2C
+ tristate "Microchip AR1020/1021 i2c touchscreen"
+ depends on I2C && OF
+ help
+ Say Y here if you have the Microchip AR1020 or AR1021 touchscreen
+ controller chip in your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ar1021_i2c.
+
+config TOUCHSCREEN_ATMEL_MXT
+ tristate "Atmel mXT I2C Touchscreen"
+ depends on I2C
+ select FW_LOADER
+ help
+ Say Y here if you have Atmel mXT series I2C touchscreen,
+ such as AT42QT602240/ATMXT224, connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called atmel_mxt_ts.
+
+config TOUCHSCREEN_ATMEL_MXT_T37
+ bool "Support T37 Diagnostic Data"
+ depends on TOUCHSCREEN_ATMEL_MXT
+ depends on VIDEO_DEV=y || (TOUCHSCREEN_ATMEL_MXT=m && VIDEO_DEV=m)
+ select VIDEOBUF2_VMALLOC
+ help
+ Say Y here if you want support to output data from the T37
+ Diagnostic Data object using a V4L device.
+
+config TOUCHSCREEN_AUO_PIXCIR
+ tristate "AUO in-cell touchscreen using Pixcir ICs"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have a AUO display with in-cell touchscreen
+ using Pixcir ICs.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called auo-pixcir-ts.
+
+config TOUCHSCREEN_BU21013
+ tristate "BU21013 based touch panel controllers"
+ depends on I2C
+ help
+ Say Y here if you have a bu21013 touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bu21013_ts.
+
+config TOUCHSCREEN_BU21029
+ tristate "Rohm BU21029 based touch panel controllers"
+ depends on I2C
+ help
+ Say Y here if you have a Rohm BU21029 touchscreen controller
+ connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bu21029_ts.
+
+config TOUCHSCREEN_CHIPONE_ICN8318
+ tristate "chipone icn8318 touchscreen controller"
+ depends on GPIOLIB || COMPILE_TEST
+ depends on I2C
+ depends on OF
+ help
+ Say Y here if you have a ChipOne icn8318 based I2C touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called chipone_icn8318.
+
+config TOUCHSCREEN_CHIPONE_ICN8505
+ tristate "chipone icn8505 touchscreen controller"
+ depends on I2C && ACPI
+ help
+ Say Y here if you have a ChipOne icn8505 based I2C touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called chipone_icn8505.
+
+config TOUCHSCREEN_CY8CTMA140
+ tristate "cy8ctma140 touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have a Cypress CY8CTMA140 capacitive
+ touchscreen also just known as "TMA140"
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cy8ctma140.
+
+config TOUCHSCREEN_CY8CTMG110
+ tristate "cy8ctmg110 touchscreen"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have a cy8ctmg110 capacitive touchscreen on
+ an AAVA device.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cy8ctmg110_ts.
+
+config TOUCHSCREEN_CYTTSP_CORE
+ tristate "Cypress TTSP touchscreen"
+ help
+ Say Y here if you have a touchscreen using controller from
+ the Cypress TrueTouch(tm) Standard Product family connected
+ to your system. You will also need to select appropriate
+ bus connection below.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyttsp_core.
+
+config TOUCHSCREEN_CYTTSP_I2C
+ tristate "support I2C bus connection"
+ depends on TOUCHSCREEN_CYTTSP_CORE && I2C
+ help
+ Say Y here if the touchscreen is connected via I2C bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyttsp_i2c.
+
+config TOUCHSCREEN_CYTTSP_SPI
+ tristate "support SPI bus connection"
+ depends on TOUCHSCREEN_CYTTSP_CORE && SPI_MASTER
+ help
+ Say Y here if the touchscreen is connected via SPI bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyttsp_spi.
+
+config TOUCHSCREEN_CYTTSP4_CORE
+ tristate "Cypress TrueTouch Gen4 Touchscreen Driver"
+ help
+ Core driver for Cypress TrueTouch(tm) Standard Product
+ Generation4 touchscreen controllers.
+
+ Say Y here if you have a Cypress Gen4 touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here.
+
+config TOUCHSCREEN_CYTTSP4_I2C
+ tristate "support I2C bus connection"
+ depends on TOUCHSCREEN_CYTTSP4_CORE && I2C
+ help
+ Say Y here if the touchscreen is connected via I2C bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyttsp4_i2c.
+
+config TOUCHSCREEN_CYTTSP4_SPI
+ tristate "support SPI bus connection"
+ depends on TOUCHSCREEN_CYTTSP4_CORE && SPI_MASTER
+ help
+ Say Y here if the touchscreen is connected via SPI bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyttsp4_spi.
+
+config TOUCHSCREEN_DA9034
+ tristate "Touchscreen support for Dialog Semiconductor DA9034"
+ depends on PMIC_DA903X
+ default y
+ help
+ Say Y here to enable the support for the touchscreen found
+ on Dialog Semiconductor DA9034 PMIC.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da9034-ts.
+
+config TOUCHSCREEN_DA9052
+ tristate "Dialog DA9052/DA9053 TSI"
+ depends on PMIC_DA9052
+ help
+ Say Y here to support the touchscreen found on Dialog Semiconductor
+ DA9052-BC and DA9053-AA/Bx PMICs.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da9052_tsi.
+
+config TOUCHSCREEN_DYNAPRO
+ tristate "Dynapro serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have a Dynapro serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dynapro.
+
+config TOUCHSCREEN_HAMPSHIRE
+ tristate "Hampshire serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have a Hampshire serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hampshire.
+
+config TOUCHSCREEN_EETI
+ tristate "EETI touchscreen panel support"
+ depends on I2C
+ help
+ Say Y here to enable support for I2C connected EETI touch panels.
+
+ To compile this driver as a module, choose M here: the
+ module will be called eeti_ts.
+
+config TOUCHSCREEN_EGALAX
+ tristate "EETI eGalax multi-touch panel support"
+ depends on I2C && OF
+ help
+ Say Y here to enable support for I2C connected EETI
+ eGalax multi-touch panels.
+
+ To compile this driver as a module, choose M here: the
+ module will be called egalax_ts.
+
+config TOUCHSCREEN_EGALAX_SERIAL
+ tristate "EETI eGalax serial touchscreen"
+ select SERIO
+ help
+ Say Y here to enable support for serial connected EETI
+ eGalax touch panels.
+
+ To compile this driver as a module, choose M here: the
+ module will be called egalax_ts_serial.
+
+config TOUCHSCREEN_EXC3000
+ tristate "EETI EXC3000 multi-touch panel support"
+ depends on I2C
+ help
+ Say Y here to enable support for I2C connected EETI
+ EXC3000 multi-touch panels.
+
+ To compile this driver as a module, choose M here: the
+ module will be called exc3000.
+
+config TOUCHSCREEN_FUJITSU
+ tristate "Fujitsu serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have the Fujitsu touchscreen (such as one
+ installed in Lifebook P series laptop) connected to your
+ system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called fujitsu-ts.
+
+config TOUCHSCREEN_GOODIX
+ tristate "Goodix I2C touchscreen"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have the Goodix touchscreen (such as one
+ installed in Onda v975w tablets) connected to your
+ system. It also supports 5-finger chip models, which can be
+ found on ARM tablets, like Wexler TAB7200 and MSI Primo73.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called goodix.
+
+config TOUCHSCREEN_HIDEEP
+ tristate "HiDeep Touch IC"
+ depends on I2C
+ help
+ Say Y here if you have a touchscreen using HiDeep.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here : the
+ module will be called hideep_ts.
+
+config TOUCHSCREEN_HYCON_HY46XX
+ tristate "Hycon hy46xx touchscreen support"
+ depends on I2C
+ help
+ Say Y here if you have a touchscreen using Hycon hy46xx
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hycon-hy46xx.
+
+config TOUCHSCREEN_ILI210X
+ tristate "Ilitek ILI210X based touchscreen"
+ depends on I2C
+ select CRC_CCITT
+ help
+ Say Y here if you have a ILI210X based touchscreen
+ controller. This driver supports models ILI2102,
+ ILI2102s, ILI2103, ILI2103s and ILI2105.
+ Such kind of chipsets can be found in Amazon Kindle Fire
+ touchscreens.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ili210x.
+
+config TOUCHSCREEN_ILITEK
+ tristate "Ilitek I2C 213X/23XX/25XX/Lego Series Touch ICs"
+ depends on I2C
+ help
+ Say Y here if you have touchscreen with ILITEK touch IC,
+ it supports 213X/23XX/25XX and other Lego series.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ilitek_ts_i2c.
+
+config TOUCHSCREEN_IPROC
+ tristate "IPROC touch panel driver support"
+ depends on ARCH_BCM_IPROC || COMPILE_TEST
+ help
+ Say Y here if you want to add support for the IPROC touch
+ controller to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bcm_iproc_tsc.
+
+config TOUCHSCREEN_S3C2410
+ tristate "Samsung S3C2410/generic touchscreen input driver"
+ depends on ARCH_S3C24XX || SAMSUNG_DEV_TS
+ depends on S3C_ADC
+ help
+ Say Y here if you have the s3c2410 touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called s3c2410_ts.
+
+config TOUCHSCREEN_S6SY761
+ tristate "Samsung S6SY761 Touchscreen driver"
+ depends on I2C
+ help
+ Say Y if you have the Samsung S6SY761 driver
+
+ If unsure, say N
+
+ To compile this driver as module, choose M here: the
+ module will be called s6sy761.
+
+config TOUCHSCREEN_GUNZE
+ tristate "Gunze AHL-51S touchscreen"
+ select SERIO
+ help
+ Say Y here if you have the Gunze AHL-51 touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gunze.
+
+config TOUCHSCREEN_EKTF2127
+ tristate "Elan eKTF2127 I2C touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have an Elan eKTF2127 touchscreen
+ connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ektf2127.
+
+config TOUCHSCREEN_ELAN
+ tristate "Elan eKTH I2C touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have an Elan eKTH I2C touchscreen
+ connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called elants_i2c.
+
+config TOUCHSCREEN_ELO
+ tristate "Elo serial touchscreens"
+ select SERIO
+ help
+ Say Y here if you have an Elo serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called elo.
+
+config TOUCHSCREEN_WACOM_W8001
+ tristate "Wacom W8001 penabled serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have an Wacom W8001 penabled serial touchscreen
+ connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wacom_w8001.
+
+config TOUCHSCREEN_WACOM_I2C
+ tristate "Wacom Tablet support (I2C)"
+ depends on I2C
+ help
+ Say Y here if you want to use the I2C version of the Wacom
+ Pen Tablet.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module
+ will be called wacom_i2c.
+
+config TOUCHSCREEN_LPC32XX
+ tristate "LPC32XX touchscreen controller"
+ depends on ARCH_LPC32XX
+ help
+ Say Y here if you have a LPC32XX device and want
+ to support the built-in touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called lpc32xx_ts.
+
+config TOUCHSCREEN_MAX11801
+ tristate "MAX11801 based touchscreens"
+ depends on I2C
+ help
+ Say Y here if you have a MAX11801 based touchscreen
+ controller.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called max11801_ts.
+
+config TOUCHSCREEN_MCS5000
+ tristate "MELFAS MCS-5000 touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have the MELFAS MCS-5000 touchscreen controller
+ chip in your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcs5000_ts.
+
+config TOUCHSCREEN_MMS114
+ tristate "MELFAS MMS114 touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have the MELFAS MMS114 touchscreen controller
+ chip in your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mms114.
+
+config TOUCHSCREEN_MELFAS_MIP4
+ tristate "MELFAS MIP4 Touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have a MELFAS MIP4 Touchscreen device.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here:
+ the module will be called melfas_mip4.
+
+config TOUCHSCREEN_MSG2638
+ tristate "MStar msg2638 touchscreen support"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have an I2C touchscreen using MStar msg2638.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called msg2638.
+
+config TOUCHSCREEN_MTOUCH
+ tristate "MicroTouch serial touchscreens"
+ select SERIO
+ help
+ Say Y here if you have a MicroTouch (3M) serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mtouch.
+
+config TOUCHSCREEN_IMAGIS
+ tristate "Imagis touchscreen support"
+ depends on I2C
+ help
+ Say Y here if you have an Imagis IST30xxC touchscreen.
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imagis.
+
+config TOUCHSCREEN_IMX6UL_TSC
+ tristate "Freescale i.MX6UL touchscreen controller"
+ depends on ((OF && GPIOLIB) || COMPILE_TEST) && HAS_IOMEM
+ help
+ Say Y here if you have a Freescale i.MX6UL, and want to
+ use the internal touchscreen controller.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx6ul_tsc.
+
+config TOUCHSCREEN_INEXIO
+ tristate "iNexio serial touchscreens"
+ select SERIO
+ help
+ Say Y here if you have an iNexio serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called inexio.
+
+config TOUCHSCREEN_MK712
+ tristate "ICS MicroClock MK712 touchscreen"
+ help
+ Say Y here if you have the ICS MicroClock MK712 touchscreen
+ controller chip in your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mk712.
+
+config TOUCHSCREEN_HP600
+ tristate "HP Jornada 6xx touchscreen"
+ depends on SH_HP6XX && SH_ADC
+ help
+ Say Y here if you have a HP Jornada 620/660/680/690 and want to
+ support the built-in touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hp680_ts_input.
+
+config TOUCHSCREEN_HP7XX
+ tristate "HP Jornada 7xx touchscreen"
+ depends on SA1100_JORNADA720_SSP
+ help
+ Say Y here if you have a HP Jornada 710/720/728 and want
+ to support the built-in touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called jornada720_ts.
+
+config TOUCHSCREEN_IPAQ_MICRO
+ tristate "HP iPAQ Atmel Micro ASIC touchscreen"
+ depends on MFD_IPAQ_MICRO
+ help
+ Say Y here to enable support for the touchscreen attached to
+ the Atmel Micro peripheral controller on iPAQ h3100/h3600/h3700
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ipaq-micro-ts.
+
+config TOUCHSCREEN_HTCPEN
+ tristate "HTC Shift X9500 touchscreen"
+ depends on ISA
+ help
+ Say Y here if you have an HTC Shift UMPC also known as HTC X9500
+ Clio / Shangrila and want to support the built-in touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called htcpen.
+
+config TOUCHSCREEN_PENMOUNT
+ tristate "Penmount serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have a Penmount serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called penmount.
+
+config TOUCHSCREEN_EDT_FT5X06
+ tristate "EDT FocalTech FT5x06 I2C Touchscreen support"
+ depends on I2C
+ help
+ Say Y here if you have an EDT "Polytouch" touchscreen based
+ on the FocalTech FT5x06 family of controllers connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called edt-ft5x06.
+
+config TOUCHSCREEN_RASPBERRYPI_FW
+ tristate "Raspberry Pi's firmware base touch screen support"
+ depends on RASPBERRYPI_FIRMWARE || (RASPBERRYPI_FIRMWARE=n && COMPILE_TEST)
+ help
+ Say Y here if you have the official Raspberry Pi 7 inch screen on
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called raspberrypi-ts.
+
+config TOUCHSCREEN_MIGOR
+ tristate "Renesas MIGO-R touchscreen"
+ depends on (SH_MIGOR || COMPILE_TEST) && I2C
+ help
+ Say Y here to enable MIGO-R touchscreen support.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called migor_ts.
+
+config TOUCHSCREEN_TOUCHRIGHT
+ tristate "Touchright serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have a Touchright serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called touchright.
+
+config TOUCHSCREEN_TOUCHWIN
+ tristate "Touchwin serial touchscreen"
+ select SERIO
+ help
+ Say Y here if you have a Touchwin serial touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called touchwin.
+
+config TOUCHSCREEN_TI_AM335X_TSC
+ tristate "TI Touchscreen Interface"
+ depends on MFD_TI_AM335X_TSCADC
+ help
+ Say Y here if you have 4/5/8 wire touchscreen controller
+ to be connected to the ADC controller on your TI AM335x SoC.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ti_am335x_tsc.
+
+config TOUCHSCREEN_UCB1400
+ tristate "Philips UCB1400 touchscreen"
+ depends on AC97_BUS
+ depends on UCB1400_CORE
+ help
+ This enables support for the Philips UCB1400 touchscreen interface.
+ The UCB1400 is an AC97 audio codec. The touchscreen interface
+ will be initialized only after the ALSA subsystem has been
+ brought up and the UCB1400 detected. You therefore have to
+ configure ALSA support as well (either built-in or modular,
+ independently of whether this driver is itself built-in or
+ modular) for this driver to work.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ucb1400_ts.
+
+config TOUCHSCREEN_PIXCIR
+ tristate "PIXCIR I2C touchscreens"
+ depends on I2C
+ help
+ Say Y here if you have a pixcir i2c touchscreen
+ controller.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pixcir_i2c_ts.
+
+config TOUCHSCREEN_WDT87XX_I2C
+ tristate "Weida HiTech I2C touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have a Weida WDT87XX I2C touchscreen
+ connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wdt87xx_i2c.
+
+config TOUCHSCREEN_WM831X
+ tristate "Support for WM831x touchscreen controllers"
+ depends on MFD_WM831X
+ help
+ This enables support for the touchscreen controller on the WM831x
+ series of PMICs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wm831x-ts.
+
+config TOUCHSCREEN_WM97XX
+ tristate "Support for WM97xx AC97 touchscreen controllers"
+ depends on AC97_BUS || AC97_BUS_NEW
+ help
+ Say Y here if you have a Wolfson Microelectronics WM97xx
+ touchscreen connected to your system. Note that this option
+ only enables core driver, you will also need to select
+ support for appropriate chip below.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wm97xx-ts.
+
+config TOUCHSCREEN_WM9705
+ bool "WM9705 Touchscreen interface support"
+ depends on TOUCHSCREEN_WM97XX
+ default y
+ help
+ Say Y here to enable support for the Wolfson Microelectronics
+ WM9705 touchscreen controller.
+
+config TOUCHSCREEN_WM9712
+ bool "WM9712 Touchscreen interface support"
+ depends on TOUCHSCREEN_WM97XX
+ default y
+ help
+ Say Y here to enable support for the Wolfson Microelectronics
+ WM9712 touchscreen controller.
+
+config TOUCHSCREEN_WM9713
+ bool "WM9713 Touchscreen interface support"
+ depends on TOUCHSCREEN_WM97XX
+ default y
+ help
+ Say Y here to enable support for the Wolfson Microelectronics
+ WM9713 touchscreen controller.
+
+config TOUCHSCREEN_WM97XX_MAINSTONE
+ tristate "WM97xx Mainstone/Palm accelerated touch"
+ depends on TOUCHSCREEN_WM97XX && ARCH_PXA
+ depends on SND_PXA2XX_LIB_AC97
+ help
+ Say Y here for support for streaming mode with WM97xx touchscreens
+ on Mainstone, Palm Tungsten T5, TX and LifeDrive systems.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mainstone-wm97xx.
+
+config TOUCHSCREEN_WM97XX_ZYLONITE
+ tristate "Zylonite accelerated touch"
+ depends on TOUCHSCREEN_WM97XX && MACH_ZYLONITE
+ depends on SND_PXA2XX_LIB_AC97
+ select TOUCHSCREEN_WM9713
+ help
+ Say Y here for support for streaming mode with the touchscreen
+ on Zylonite systems.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zylonite-wm97xx.
+
+config TOUCHSCREEN_USB_COMPOSITE
+ tristate "USB Touchscreen Driver"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ USB Touchscreen driver for:
+ - eGalax Touchkit USB (also includes eTurboTouch CT-410/510/700)
+ - PanJit TouchSet USB
+ - 3M MicroTouch USB (EX II series)
+ - ITM
+ - some other eTurboTouch
+ - Gunze AHL61
+ - DMC TSC-10/25
+ - IRTOUCHSYSTEMS/UNITOP
+ - IdealTEK URTC1000
+ - GoTop Super_Q2/GogoPen/PenPower tablets
+ - JASTEC USB Touch Controller/DigiTech DTR-02U
+ - Zytronic controllers
+ - Elo TouchSystems 2700 IntelliTouch
+ - EasyTouch USB Touch Controller from Data Module
+ - e2i (Mimo monitors)
+
+ Have a look at <http://linux.chapter7.ch/touchkit/> for
+ a usage description and the required user-space stuff.
+
+ To compile this driver as a module, choose M here: the
+ module will be called usbtouchscreen.
+
+config TOUCHSCREEN_MXS_LRADC
+ tristate "Freescale i.MX23/i.MX28 LRADC touchscreen"
+ depends on MFD_MXS_LRADC
+ help
+ Say Y here if you have a touchscreen connected to the low-resolution
+ analog-to-digital converter (LRADC) on an i.MX23 or i.MX28 processor.
+
+ To compile this driver as a module, choose M here: the module will be
+ called mxs-lradc-ts.
+
+config TOUCHSCREEN_MX25
+ tristate "Freescale i.MX25 touchscreen input driver"
+ depends on MFD_MX25_TSADC
+ help
+ Enable support for touchscreen connected to your i.MX25.
+
+ To compile this driver as a module, choose M here: the
+ module will be called fsl-imx25-tcq.
+
+config TOUCHSCREEN_MC13783
+ tristate "Freescale MC13783 touchscreen input driver"
+ depends on MFD_MC13XXX
+ help
+ Say Y here if you have an Freescale MC13783 PMIC on your
+ board and want to use its touchscreen
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mc13783_ts.
+
+config TOUCHSCREEN_USB_EGALAX
+ default y
+ bool "eGalax, eTurboTouch CT-410/510/700 device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_PANJIT
+ default y
+ bool "PanJit device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_3M
+ default y
+ bool "3M/Microtouch EX II series device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_ITM
+ default y
+ bool "ITM device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_ETURBO
+ default y
+ bool "eTurboTouch (non-eGalax compatible) device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_GUNZE
+ default y
+ bool "Gunze AHL61 device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_DMC_TSC10
+ default y
+ bool "DMC TSC-10/25 device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_IRTOUCH
+ default y
+ bool "IRTOUCHSYSTEMS/UNITOP device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_IDEALTEK
+ default y
+ bool "IdealTEK URTC1000 device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_GENERAL_TOUCH
+ default y
+ bool "GeneralTouch Touchscreen device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_GOTOP
+ default y
+ bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_JASTEC
+ default y
+ bool "JASTEC/DigiTech DTR-02U USB touch controller device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_ELO
+ default y
+ bool "Elo TouchSystems 2700 IntelliTouch controller device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_E2I
+ default y
+ bool "e2i Touchscreen controller (e.g. from Mimo 740)" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_ZYTRONIC
+ default y
+ bool "Zytronic controller" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_ETT_TC45USB
+ default y
+ bool "ET&T USB series TC4UM/TC5UH touchscreen controller support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_NEXIO
+ default y
+ bool "NEXIO/iNexio device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+
+config TOUCHSCREEN_USB_EASYTOUCH
+ default y
+ bool "EasyTouch USB Touch controller device support" if EXPERT
+ depends on TOUCHSCREEN_USB_COMPOSITE
+ help
+ Say Y here if you have an EasyTouch USB Touch controller.
+ If unsure, say N.
+
+config TOUCHSCREEN_TOUCHIT213
+ tristate "Sahara TouchIT-213 touchscreen"
+ select SERIO
+ help
+ Say Y here if you have a Sahara TouchIT-213 Tablet PC.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called touchit213.
+
+config TOUCHSCREEN_TS4800
+ tristate "TS-4800 touchscreen"
+ depends on HAS_IOMEM && OF
+ depends on SOC_IMX51 || COMPILE_TEST
+ select MFD_SYSCON
+ help
+ Say Y here if you have a touchscreen on a TS-4800 board.
+
+ On TS-4800, the touchscreen is not handled directly by Linux but by
+ a companion FPGA.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ts4800_ts.
+
+config TOUCHSCREEN_TSC_SERIO
+ tristate "TSC-10/25/40 serial touchscreen support"
+ select SERIO
+ help
+ Say Y here if you have a TSC-10, 25 or 40 serial touchscreen connected
+ to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tsc40.
+
+config TOUCHSCREEN_TSC200X_CORE
+ tristate
+
+config TOUCHSCREEN_TSC2004
+ tristate "TSC2004 based touchscreens"
+ depends on I2C
+ select REGMAP_I2C
+ select TOUCHSCREEN_TSC200X_CORE
+ help
+ Say Y here if you have a TSC2004 based touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tsc2004.
+
+config TOUCHSCREEN_TSC2005
+ tristate "TSC2005 based touchscreens"
+ depends on SPI_MASTER
+ select REGMAP_SPI
+ select TOUCHSCREEN_TSC200X_CORE
+ help
+ Say Y here if you have a TSC2005 based touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tsc2005.
+
+config TOUCHSCREEN_TSC2007
+ tristate "TSC2007 based touchscreens"
+ depends on I2C
+ help
+ Say Y here if you have a TSC2007 based touchscreen.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tsc2007.
+
+config TOUCHSCREEN_TSC2007_IIO
+ bool "IIO interface for external ADC input and temperature"
+ depends on TOUCHSCREEN_TSC2007
+ depends on IIO=y || IIO=TOUCHSCREEN_TSC2007
+ help
+ Saying Y here adds an iio interface to the tsc2007 which
+ provides values for the AUX input (used for e.g. battery
+ or ambient light monitoring), temperature and raw input
+ values.
+
+config TOUCHSCREEN_PCAP
+ tristate "Motorola PCAP touchscreen"
+ depends on EZX_PCAP
+ help
+ Say Y here if you have a Motorola EZX telephone and
+ want to enable support for the built-in touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcap_ts.
+
+config TOUCHSCREEN_RM_TS
+ tristate "Raydium I2C Touchscreen"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have Raydium series I2C touchscreen,
+ such as RM32380, connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called raydium_i2c_ts.
+
+config TOUCHSCREEN_SILEAD
+ tristate "Silead I2C touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have the Silead touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called silead.
+
+config TOUCHSCREEN_SIS_I2C
+ tristate "SiS 9200 family I2C touchscreen"
+ depends on I2C
+ select CRC_ITU_T
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ This enables support for SiS 9200 family over I2C based touchscreens.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sis_i2c.
+
+config TOUCHSCREEN_ST1232
+ tristate "Sitronix ST1232 or ST1633 touchscreen controllers"
+ depends on I2C
+ help
+ Say Y here if you want to support the Sitronix ST1232
+ or ST1633 touchscreen controller.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called st1232_ts.
+
+config TOUCHSCREEN_STMFTS
+ tristate "STMicroelectronics STMFTS touchscreen"
+ depends on I2C
+ depends on LEDS_CLASS
+ help
+ Say Y here if you want support for STMicroelectronics
+ STMFTS touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stmfts.
+
+config TOUCHSCREEN_STMPE
+ tristate "STMicroelectronics STMPE touchscreens"
+ depends on MFD_STMPE
+ depends on (OF || COMPILE_TEST)
+ help
+ Say Y here if you want support for STMicroelectronics
+ STMPE touchscreen controllers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stmpe-ts.
+
+config TOUCHSCREEN_SUN4I
+ tristate "Allwinner sun4i resistive touchscreen controller support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ depends on HWMON
+ depends on THERMAL || !THERMAL_OF
+ help
+ This selects support for the resistive touchscreen controller
+ found on Allwinner sunxi SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sun4i-ts.
+
+config TOUCHSCREEN_SUR40
+ tristate "Samsung SUR40 (Surface 2.0/PixelSense) touchscreen"
+ depends on USB && MEDIA_USB_SUPPORT && HAS_DMA
+ depends on VIDEO_DEV
+ select VIDEOBUF2_DMA_SG
+ help
+ Say Y here if you want support for the Samsung SUR40 touchscreen
+ (also known as Microsoft Surface 2.0 or Microsoft PixelSense).
+
+ To compile this driver as a module, choose M here: the
+ module will be called sur40.
+
+config TOUCHSCREEN_SURFACE3_SPI
+ tristate "Ntrig/Microsoft Surface 3 SPI touchscreen"
+ depends on SPI
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have the Ntrig/Microsoft SPI touchscreen
+ controller chip as found on the Surface 3 in your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called surface3_spi.
+
+config TOUCHSCREEN_SX8654
+ tristate "Semtech SX8654 touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have a Semtech SX8654 touchscreen controller.
+
+ If unsure, say N
+
+ To compile this driver as a module, choose M here: the
+ module will be called sx8654.
+
+config TOUCHSCREEN_TPS6507X
+ tristate "TPS6507x based touchscreens"
+ depends on I2C
+ help
+ Say Y here if you have a TPS6507x based touchscreen
+ controller.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tps6507x_ts.
+
+config TOUCHSCREEN_ZET6223
+ tristate "Zeitec ZET6223 touchscreen driver"
+ depends on I2C
+ help
+ Say Y here if you have a touchscreen using Zeitec ZET6223
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zet6223.
+
+config TOUCHSCREEN_ZFORCE
+ tristate "Neonode zForce infrared touchscreens"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have a touchscreen using the zforce
+ infraread technology from Neonode.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zforce_ts.
+
+config TOUCHSCREEN_COLIBRI_VF50
+ tristate "Toradex Colibri on board touchscreen driver"
+ depends on IIO
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have a Colibri VF50 and plan to use
+ the on-board provided 4-wire touchscreen driver.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called colibri_vf50_ts.
+
+config TOUCHSCREEN_ROHM_BU21023
+ tristate "ROHM BU21023/24 Dual touch support resistive touchscreens"
+ depends on I2C
+ help
+ Say Y here if you have a touchscreen using ROHM BU21023/24.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bu21023_ts.
+
+config TOUCHSCREEN_IQS5XX
+ tristate "Azoteq IQS550/572/525 trackpad/touchscreen controller"
+ depends on I2C
+ help
+ Say Y to enable support for the Azoteq IQS550/572/525
+ family of trackpad/touchscreen controllers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iqs5xx.
+
+config TOUCHSCREEN_ZINITIX
+ tristate "Zinitix touchscreen support"
+ depends on I2C
+ help
+ Say Y here if you have a touchscreen using Zinitix bt541,
+ or something similar enough.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zinitix.
+
+endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
new file mode 100644
index 000000000..557f84fd2
--- /dev/null
+++ b/drivers/input/touchscreen/Makefile
@@ -0,0 +1,118 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the touchscreen drivers.
+#
+
+# Each configuration option enables a list of files.
+
+wm97xx-ts-y := wm97xx-core.o
+goodix_ts-y := goodix.o goodix_fwupload.o
+
+obj-$(CONFIG_TOUCHSCREEN_88PM860X) += 88pm860x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o
+obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.o
+obj-$(CONFIG_TOUCHSCREEN_AD7879_I2C) += ad7879-i2c.o
+obj-$(CONFIG_TOUCHSCREEN_AD7879_SPI) += ad7879-spi.o
+obj-$(CONFIG_TOUCHSCREEN_ADC) += resistive-adc-touch.o
+obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o
+obj-$(CONFIG_TOUCHSCREEN_AR1021_I2C) += ar1021_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o
+obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR) += auo-pixcir-ts.o
+obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o
+obj-$(CONFIG_TOUCHSCREEN_BU21029) += bu21029_ts.o
+obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318) += chipone_icn8318.o
+obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8505) += chipone_icn8505.o
+obj-$(CONFIG_TOUCHSCREEN_CY8CTMA140) += cy8ctma140.o
+obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o
+obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o
+obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C) += cyttsp_i2c.o cyttsp_i2c_common.o
+obj-$(CONFIG_TOUCHSCREEN_CYTTSP_SPI) += cyttsp_spi.o
+obj-$(CONFIG_TOUCHSCREEN_CYTTSP4_CORE) += cyttsp4_core.o
+obj-$(CONFIG_TOUCHSCREEN_CYTTSP4_I2C) += cyttsp4_i2c.o cyttsp_i2c_common.o
+obj-$(CONFIG_TOUCHSCREEN_CYTTSP4_SPI) += cyttsp4_spi.o
+obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o
+obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o
+obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o
+obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06) += edt-ft5x06.o
+obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o
+obj-$(CONFIG_TOUCHSCREEN_HYCON_HY46XX) += hycon-hy46xx.o
+obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o
+obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_EKTF2127) += ektf2127.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN) += elants_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o
+obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o
+obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o
+obj-$(CONFIG_TOUCHSCREEN_EXC3000) += exc3000.o
+obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o
+obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix_ts.o
+obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o
+obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o
+obj-$(CONFIG_TOUCHSCREEN_ILITEK) += ilitek_ts_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_IMAGIS) += imagis.o
+obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o
+obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o
+obj-$(CONFIG_TOUCHSCREEN_IPROC) += bcm_iproc_tsc.o
+obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o
+obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o
+obj-$(CONFIG_TOUCHSCREEN_MXS_LRADC) += mxs-lradc-ts.o
+obj-$(CONFIG_TOUCHSCREEN_MX25) += fsl-imx25-tcq.o
+obj-$(CONFIG_TOUCHSCREEN_MC13783) += mc13783_ts.o
+obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o
+obj-$(CONFIG_TOUCHSCREEN_MELFAS_MIP4) += melfas_mip4.o
+obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o
+obj-$(CONFIG_TOUCHSCREEN_MMS114) += mms114.o
+obj-$(CONFIG_TOUCHSCREEN_MSG2638) += msg2638.o
+obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o
+obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o
+obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o
+obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o
+obj-$(CONFIG_TOUCHSCREEN_IPAQ_MICRO) += ipaq-micro-ts.o
+obj-$(CONFIG_TOUCHSCREEN_HTCPEN) += htcpen.o
+obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o
+obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o
+obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o
+obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o
+obj-$(CONFIG_TOUCHSCREEN_RM_TS) += raydium_i2c_ts.o
+obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o
+obj-$(CONFIG_TOUCHSCREEN_S6SY761) += s6sy761.o
+obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o
+obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o
+obj-$(CONFIG_TOUCHSCREEN_STMFTS) += stmfts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o
+obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o
+obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
+obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI) += surface3_spi.o
+obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
+obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o
+obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
+obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
+obj-$(CONFIG_TOUCHSCREEN_TS4800) += ts4800-ts.o
+obj-$(CONFIG_TOUCHSCREEN_TSC_SERIO) += tsc40.o
+obj-$(CONFIG_TOUCHSCREEN_TSC200X_CORE) += tsc200x-core.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2004) += tsc2004.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.o
+tsc2007-y := tsc2007_core.o
+tsc2007-$(CONFIG_TOUCHSCREEN_TSC2007_IIO) += tsc2007_iio.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
+obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
+obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
+obj-$(CONFIG_TOUCHSCREEN_WACOM_I2C) += wacom_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_WDT87XX_I2C) += wdt87xx_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_WM831X) += wm831x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o
+wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
+wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
+wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o
+obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
+obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
+obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o
+obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_ZET6223) += zet6223.o
+obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o
+obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o
+obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o
+obj-$(CONFIG_TOUCHSCREEN_RASPBERRYPI_FW) += raspberrypi-ts.o
+obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o
+obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o
diff --git a/drivers/input/touchscreen/ad7877.c b/drivers/input/touchscreen/ad7877.c
new file mode 100644
index 000000000..08f5372f0
--- /dev/null
+++ b/drivers/input/touchscreen/ad7877.c
@@ -0,0 +1,824 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2006-2008 Michael Hennerich, Analog Devices Inc.
+ *
+ * Description: AD7877 based touchscreen, sensor (ADCs), DAC and GPIO driver
+ * Based on: ads7846.c
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * History:
+ * Copyright (c) 2005 David Brownell
+ * Copyright (c) 2006 Nokia Corporation
+ * Various changes: Imre Deak <imre.deak@nokia.com>
+ *
+ * Using code from:
+ * - corgi_ts.c
+ * Copyright (C) 2004-2005 Richard Purdie
+ * - omap_ts.[hc], ads7846.h, ts_osk.c
+ * Copyright (C) 2002 MontaVista Software
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2005 Dirk Behme
+ */
+
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/ad7877.h>
+#include <linux/module.h>
+#include <asm/irq.h>
+
+#define TS_PEN_UP_TIMEOUT msecs_to_jiffies(100)
+
+#define MAX_SPI_FREQ_HZ 20000000
+#define MAX_12BIT ((1<<12)-1)
+
+#define AD7877_REG_ZEROS 0
+#define AD7877_REG_CTRL1 1
+#define AD7877_REG_CTRL2 2
+#define AD7877_REG_ALERT 3
+#define AD7877_REG_AUX1HIGH 4
+#define AD7877_REG_AUX1LOW 5
+#define AD7877_REG_BAT1HIGH 6
+#define AD7877_REG_BAT1LOW 7
+#define AD7877_REG_BAT2HIGH 8
+#define AD7877_REG_BAT2LOW 9
+#define AD7877_REG_TEMP1HIGH 10
+#define AD7877_REG_TEMP1LOW 11
+#define AD7877_REG_SEQ0 12
+#define AD7877_REG_SEQ1 13
+#define AD7877_REG_DAC 14
+#define AD7877_REG_NONE1 15
+#define AD7877_REG_EXTWRITE 15
+#define AD7877_REG_XPLUS 16
+#define AD7877_REG_YPLUS 17
+#define AD7877_REG_Z2 18
+#define AD7877_REG_aux1 19
+#define AD7877_REG_aux2 20
+#define AD7877_REG_aux3 21
+#define AD7877_REG_bat1 22
+#define AD7877_REG_bat2 23
+#define AD7877_REG_temp1 24
+#define AD7877_REG_temp2 25
+#define AD7877_REG_Z1 26
+#define AD7877_REG_GPIOCTRL1 27
+#define AD7877_REG_GPIOCTRL2 28
+#define AD7877_REG_GPIODATA 29
+#define AD7877_REG_NONE2 30
+#define AD7877_REG_NONE3 31
+
+#define AD7877_SEQ_YPLUS_BIT (1<<11)
+#define AD7877_SEQ_XPLUS_BIT (1<<10)
+#define AD7877_SEQ_Z2_BIT (1<<9)
+#define AD7877_SEQ_AUX1_BIT (1<<8)
+#define AD7877_SEQ_AUX2_BIT (1<<7)
+#define AD7877_SEQ_AUX3_BIT (1<<6)
+#define AD7877_SEQ_BAT1_BIT (1<<5)
+#define AD7877_SEQ_BAT2_BIT (1<<4)
+#define AD7877_SEQ_TEMP1_BIT (1<<3)
+#define AD7877_SEQ_TEMP2_BIT (1<<2)
+#define AD7877_SEQ_Z1_BIT (1<<1)
+
+enum {
+ AD7877_SEQ_YPOS = 0,
+ AD7877_SEQ_XPOS = 1,
+ AD7877_SEQ_Z2 = 2,
+ AD7877_SEQ_AUX1 = 3,
+ AD7877_SEQ_AUX2 = 4,
+ AD7877_SEQ_AUX3 = 5,
+ AD7877_SEQ_BAT1 = 6,
+ AD7877_SEQ_BAT2 = 7,
+ AD7877_SEQ_TEMP1 = 8,
+ AD7877_SEQ_TEMP2 = 9,
+ AD7877_SEQ_Z1 = 10,
+ AD7877_NR_SENSE = 11,
+};
+
+/* DAC Register Default RANGE 0 to Vcc, Volatge Mode, DAC On */
+#define AD7877_DAC_CONF 0x1
+
+/* If gpio3 is set AUX3/GPIO3 acts as GPIO Output */
+#define AD7877_EXTW_GPIO_3_CONF 0x1C4
+#define AD7877_EXTW_GPIO_DATA 0x200
+
+/* Control REG 2 */
+#define AD7877_TMR(x) ((x & 0x3) << 0)
+#define AD7877_REF(x) ((x & 0x1) << 2)
+#define AD7877_POL(x) ((x & 0x1) << 3)
+#define AD7877_FCD(x) ((x & 0x3) << 4)
+#define AD7877_PM(x) ((x & 0x3) << 6)
+#define AD7877_ACQ(x) ((x & 0x3) << 8)
+#define AD7877_AVG(x) ((x & 0x3) << 10)
+
+/* Control REG 1 */
+#define AD7877_SER (1 << 11) /* non-differential */
+#define AD7877_DFR (0 << 11) /* differential */
+
+#define AD7877_MODE_NOC (0) /* Do not convert */
+#define AD7877_MODE_SCC (1) /* Single channel conversion */
+#define AD7877_MODE_SEQ0 (2) /* Sequence 0 in Slave Mode */
+#define AD7877_MODE_SEQ1 (3) /* Sequence 1 in Master Mode */
+
+#define AD7877_CHANADD(x) ((x&0xF)<<7)
+#define AD7877_READADD(x) ((x)<<2)
+#define AD7877_WRITEADD(x) ((x)<<12)
+
+#define AD7877_READ_CHAN(x) (AD7877_WRITEADD(AD7877_REG_CTRL1) | AD7877_SER | \
+ AD7877_MODE_SCC | AD7877_CHANADD(AD7877_REG_ ## x) | \
+ AD7877_READADD(AD7877_REG_ ## x))
+
+#define AD7877_MM_SEQUENCE (AD7877_SEQ_YPLUS_BIT | AD7877_SEQ_XPLUS_BIT | \
+ AD7877_SEQ_Z2_BIT | AD7877_SEQ_Z1_BIT)
+
+/*
+ * Non-touchscreen sensors only use single-ended conversions.
+ */
+
+struct ser_req {
+ u16 reset;
+ u16 ref_on;
+ u16 command;
+ struct spi_message msg;
+ struct spi_transfer xfer[6];
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ u16 sample ____cacheline_aligned;
+};
+
+struct ad7877 {
+ struct input_dev *input;
+ char phys[32];
+
+ struct spi_device *spi;
+ u16 model;
+ u16 vref_delay_usecs;
+ u16 x_plate_ohms;
+ u16 pressure_max;
+
+ u16 cmd_crtl1;
+ u16 cmd_crtl2;
+ u16 cmd_dummy;
+ u16 dac;
+
+ u8 stopacq_polarity;
+ u8 first_conversion_delay;
+ u8 acquisition_time;
+ u8 averaging;
+ u8 pen_down_acc_interval;
+
+ struct spi_transfer xfer[AD7877_NR_SENSE + 2];
+ struct spi_message msg;
+
+ struct mutex mutex;
+ bool disabled; /* P: mutex */
+ bool gpio3; /* P: mutex */
+ bool gpio4; /* P: mutex */
+
+ spinlock_t lock;
+ struct timer_list timer; /* P: lock */
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ u16 conversion_data[AD7877_NR_SENSE] ____cacheline_aligned;
+};
+
+static bool gpio3;
+module_param(gpio3, bool, 0);
+MODULE_PARM_DESC(gpio3, "If gpio3 is set to 1 AUX3 acts as GPIO3");
+
+static int ad7877_read(struct spi_device *spi, u16 reg)
+{
+ struct ser_req *req;
+ int status, ret;
+
+ req = kzalloc(sizeof *req, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ spi_message_init(&req->msg);
+
+ req->command = (u16) (AD7877_WRITEADD(AD7877_REG_CTRL1) |
+ AD7877_READADD(reg));
+ req->xfer[0].tx_buf = &req->command;
+ req->xfer[0].len = 2;
+ req->xfer[0].cs_change = 1;
+
+ req->xfer[1].rx_buf = &req->sample;
+ req->xfer[1].len = 2;
+
+ spi_message_add_tail(&req->xfer[0], &req->msg);
+ spi_message_add_tail(&req->xfer[1], &req->msg);
+
+ status = spi_sync(spi, &req->msg);
+ ret = status ? : req->sample;
+
+ kfree(req);
+
+ return ret;
+}
+
+static int ad7877_write(struct spi_device *spi, u16 reg, u16 val)
+{
+ struct ser_req *req;
+ int status;
+
+ req = kzalloc(sizeof *req, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ spi_message_init(&req->msg);
+
+ req->command = (u16) (AD7877_WRITEADD(reg) | (val & MAX_12BIT));
+ req->xfer[0].tx_buf = &req->command;
+ req->xfer[0].len = 2;
+
+ spi_message_add_tail(&req->xfer[0], &req->msg);
+
+ status = spi_sync(spi, &req->msg);
+
+ kfree(req);
+
+ return status;
+}
+
+static int ad7877_read_adc(struct spi_device *spi, unsigned command)
+{
+ struct ad7877 *ts = spi_get_drvdata(spi);
+ struct ser_req *req;
+ int status;
+ int sample;
+ int i;
+
+ req = kzalloc(sizeof *req, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ spi_message_init(&req->msg);
+
+ /* activate reference, so it has time to settle; */
+ req->ref_on = AD7877_WRITEADD(AD7877_REG_CTRL2) |
+ AD7877_POL(ts->stopacq_polarity) |
+ AD7877_AVG(0) | AD7877_PM(2) | AD7877_TMR(0) |
+ AD7877_ACQ(ts->acquisition_time) | AD7877_FCD(0);
+
+ req->reset = AD7877_WRITEADD(AD7877_REG_CTRL1) | AD7877_MODE_NOC;
+
+ req->command = (u16) command;
+
+ req->xfer[0].tx_buf = &req->reset;
+ req->xfer[0].len = 2;
+ req->xfer[0].cs_change = 1;
+
+ req->xfer[1].tx_buf = &req->ref_on;
+ req->xfer[1].len = 2;
+ req->xfer[1].delay.value = ts->vref_delay_usecs;
+ req->xfer[1].delay.unit = SPI_DELAY_UNIT_USECS;
+ req->xfer[1].cs_change = 1;
+
+ req->xfer[2].tx_buf = &req->command;
+ req->xfer[2].len = 2;
+ req->xfer[2].delay.value = ts->vref_delay_usecs;
+ req->xfer[2].delay.unit = SPI_DELAY_UNIT_USECS;
+ req->xfer[2].cs_change = 1;
+
+ req->xfer[3].rx_buf = &req->sample;
+ req->xfer[3].len = 2;
+ req->xfer[3].cs_change = 1;
+
+ req->xfer[4].tx_buf = &ts->cmd_crtl2; /*REF OFF*/
+ req->xfer[4].len = 2;
+ req->xfer[4].cs_change = 1;
+
+ req->xfer[5].tx_buf = &ts->cmd_crtl1; /*DEFAULT*/
+ req->xfer[5].len = 2;
+
+ /* group all the transfers together, so we can't interfere with
+ * reading touchscreen state; disable penirq while sampling
+ */
+ for (i = 0; i < 6; i++)
+ spi_message_add_tail(&req->xfer[i], &req->msg);
+
+ status = spi_sync(spi, &req->msg);
+ sample = req->sample;
+
+ kfree(req);
+
+ return status ? : sample;
+}
+
+static int ad7877_process_data(struct ad7877 *ts)
+{
+ struct input_dev *input_dev = ts->input;
+ unsigned Rt;
+ u16 x, y, z1, z2;
+
+ x = ts->conversion_data[AD7877_SEQ_XPOS] & MAX_12BIT;
+ y = ts->conversion_data[AD7877_SEQ_YPOS] & MAX_12BIT;
+ z1 = ts->conversion_data[AD7877_SEQ_Z1] & MAX_12BIT;
+ z2 = ts->conversion_data[AD7877_SEQ_Z2] & MAX_12BIT;
+
+ /*
+ * The samples processed here are already preprocessed by the AD7877.
+ * The preprocessing function consists of an averaging filter.
+ * The combination of 'first conversion delay' and averaging provides a robust solution,
+ * discarding the spurious noise in the signal and keeping only the data of interest.
+ * The size of the averaging filter is programmable. (dev.platform_data, see linux/spi/ad7877.h)
+ * Other user-programmable conversion controls include variable acquisition time,
+ * and first conversion delay. Up to 16 averages can be taken per conversion.
+ */
+
+ if (likely(x && z1)) {
+ /* compute touch pressure resistance using equation #1 */
+ Rt = (z2 - z1) * x * ts->x_plate_ohms;
+ Rt /= z1;
+ Rt = (Rt + 2047) >> 12;
+
+ /*
+ * Sample found inconsistent, pressure is beyond
+ * the maximum. Don't report it to user space.
+ */
+ if (Rt > ts->pressure_max)
+ return -EINVAL;
+
+ if (!timer_pending(&ts->timer))
+ input_report_key(input_dev, BTN_TOUCH, 1);
+
+ input_report_abs(input_dev, ABS_X, x);
+ input_report_abs(input_dev, ABS_Y, y);
+ input_report_abs(input_dev, ABS_PRESSURE, Rt);
+ input_sync(input_dev);
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static inline void ad7877_ts_event_release(struct ad7877 *ts)
+{
+ struct input_dev *input_dev = ts->input;
+
+ input_report_abs(input_dev, ABS_PRESSURE, 0);
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_sync(input_dev);
+}
+
+static void ad7877_timer(struct timer_list *t)
+{
+ struct ad7877 *ts = from_timer(ts, t, timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ts->lock, flags);
+ ad7877_ts_event_release(ts);
+ spin_unlock_irqrestore(&ts->lock, flags);
+}
+
+static irqreturn_t ad7877_irq(int irq, void *handle)
+{
+ struct ad7877 *ts = handle;
+ unsigned long flags;
+ int error;
+
+ error = spi_sync(ts->spi, &ts->msg);
+ if (error) {
+ dev_err(&ts->spi->dev, "spi_sync --> %d\n", error);
+ goto out;
+ }
+
+ spin_lock_irqsave(&ts->lock, flags);
+ error = ad7877_process_data(ts);
+ if (!error)
+ mod_timer(&ts->timer, jiffies + TS_PEN_UP_TIMEOUT);
+ spin_unlock_irqrestore(&ts->lock, flags);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static void ad7877_disable(void *data)
+{
+ struct ad7877 *ts = data;
+
+ mutex_lock(&ts->mutex);
+
+ if (!ts->disabled) {
+ ts->disabled = true;
+ disable_irq(ts->spi->irq);
+
+ if (del_timer_sync(&ts->timer))
+ ad7877_ts_event_release(ts);
+ }
+
+ /*
+ * We know the chip's in lowpower mode since we always
+ * leave it that way after every request
+ */
+
+ mutex_unlock(&ts->mutex);
+}
+
+static void ad7877_enable(struct ad7877 *ts)
+{
+ mutex_lock(&ts->mutex);
+
+ if (ts->disabled) {
+ ts->disabled = false;
+ enable_irq(ts->spi->irq);
+ }
+
+ mutex_unlock(&ts->mutex);
+}
+
+#define SHOW(name) static ssize_t \
+name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ struct ad7877 *ts = dev_get_drvdata(dev); \
+ ssize_t v = ad7877_read_adc(ts->spi, \
+ AD7877_READ_CHAN(name)); \
+ if (v < 0) \
+ return v; \
+ return sprintf(buf, "%u\n", (unsigned) v); \
+} \
+static DEVICE_ATTR(name, S_IRUGO, name ## _show, NULL);
+
+SHOW(aux1)
+SHOW(aux2)
+SHOW(aux3)
+SHOW(bat1)
+SHOW(bat2)
+SHOW(temp1)
+SHOW(temp2)
+
+static ssize_t ad7877_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t ad7877_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ if (val)
+ ad7877_disable(ts);
+ else
+ ad7877_enable(ts);
+
+ return count;
+}
+
+static DEVICE_ATTR(disable, 0664, ad7877_disable_show, ad7877_disable_store);
+
+static ssize_t ad7877_dac_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->dac);
+}
+
+static ssize_t ad7877_dac_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ts->mutex);
+ ts->dac = val & 0xFF;
+ ad7877_write(ts->spi, AD7877_REG_DAC, (ts->dac << 4) | AD7877_DAC_CONF);
+ mutex_unlock(&ts->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(dac, 0664, ad7877_dac_show, ad7877_dac_store);
+
+static ssize_t ad7877_gpio3_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->gpio3);
+}
+
+static ssize_t ad7877_gpio3_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ts->mutex);
+ ts->gpio3 = !!val;
+ ad7877_write(ts->spi, AD7877_REG_EXTWRITE, AD7877_EXTW_GPIO_DATA |
+ (ts->gpio4 << 4) | (ts->gpio3 << 5));
+ mutex_unlock(&ts->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(gpio3, 0664, ad7877_gpio3_show, ad7877_gpio3_store);
+
+static ssize_t ad7877_gpio4_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->gpio4);
+}
+
+static ssize_t ad7877_gpio4_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ts->mutex);
+ ts->gpio4 = !!val;
+ ad7877_write(ts->spi, AD7877_REG_EXTWRITE, AD7877_EXTW_GPIO_DATA |
+ (ts->gpio4 << 4) | (ts->gpio3 << 5));
+ mutex_unlock(&ts->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(gpio4, 0664, ad7877_gpio4_show, ad7877_gpio4_store);
+
+static struct attribute *ad7877_attributes[] = {
+ &dev_attr_temp1.attr,
+ &dev_attr_temp2.attr,
+ &dev_attr_aux1.attr,
+ &dev_attr_aux2.attr,
+ &dev_attr_aux3.attr,
+ &dev_attr_bat1.attr,
+ &dev_attr_bat2.attr,
+ &dev_attr_disable.attr,
+ &dev_attr_dac.attr,
+ &dev_attr_gpio3.attr,
+ &dev_attr_gpio4.attr,
+ NULL
+};
+
+static umode_t ad7877_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ umode_t mode = attr->mode;
+
+ if (attr == &dev_attr_aux3.attr) {
+ if (gpio3)
+ mode = 0;
+ } else if (attr == &dev_attr_gpio3.attr) {
+ if (!gpio3)
+ mode = 0;
+ }
+
+ return mode;
+}
+
+static const struct attribute_group ad7877_attr_group = {
+ .is_visible = ad7877_attr_is_visible,
+ .attrs = ad7877_attributes,
+};
+
+static void ad7877_setup_ts_def_msg(struct spi_device *spi, struct ad7877 *ts)
+{
+ struct spi_message *m;
+ int i;
+
+ ts->cmd_crtl2 = AD7877_WRITEADD(AD7877_REG_CTRL2) |
+ AD7877_POL(ts->stopacq_polarity) |
+ AD7877_AVG(ts->averaging) | AD7877_PM(1) |
+ AD7877_TMR(ts->pen_down_acc_interval) |
+ AD7877_ACQ(ts->acquisition_time) |
+ AD7877_FCD(ts->first_conversion_delay);
+
+ ad7877_write(spi, AD7877_REG_CTRL2, ts->cmd_crtl2);
+
+ ts->cmd_crtl1 = AD7877_WRITEADD(AD7877_REG_CTRL1) |
+ AD7877_READADD(AD7877_REG_XPLUS-1) |
+ AD7877_MODE_SEQ1 | AD7877_DFR;
+
+ ad7877_write(spi, AD7877_REG_CTRL1, ts->cmd_crtl1);
+
+ ts->cmd_dummy = 0;
+
+ m = &ts->msg;
+
+ spi_message_init(m);
+
+ m->context = ts;
+
+ ts->xfer[0].tx_buf = &ts->cmd_crtl1;
+ ts->xfer[0].len = 2;
+ ts->xfer[0].cs_change = 1;
+
+ spi_message_add_tail(&ts->xfer[0], m);
+
+ ts->xfer[1].tx_buf = &ts->cmd_dummy; /* Send ZERO */
+ ts->xfer[1].len = 2;
+ ts->xfer[1].cs_change = 1;
+
+ spi_message_add_tail(&ts->xfer[1], m);
+
+ for (i = 0; i < AD7877_NR_SENSE; i++) {
+ ts->xfer[i + 2].rx_buf = &ts->conversion_data[AD7877_SEQ_YPOS + i];
+ ts->xfer[i + 2].len = 2;
+ if (i < (AD7877_NR_SENSE - 1))
+ ts->xfer[i + 2].cs_change = 1;
+ spi_message_add_tail(&ts->xfer[i + 2], m);
+ }
+}
+
+static int ad7877_probe(struct spi_device *spi)
+{
+ struct ad7877 *ts;
+ struct input_dev *input_dev;
+ struct ad7877_platform_data *pdata = dev_get_platdata(&spi->dev);
+ int err;
+ u16 verify;
+
+ if (!spi->irq) {
+ dev_dbg(&spi->dev, "no IRQ?\n");
+ return -ENODEV;
+ }
+
+ if (!pdata) {
+ dev_dbg(&spi->dev, "no platform data?\n");
+ return -ENODEV;
+ }
+
+ /* don't exceed max specified SPI CLK frequency */
+ if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) {
+ dev_dbg(&spi->dev, "SPI CLK %d Hz?\n",spi->max_speed_hz);
+ return -EINVAL;
+ }
+
+ spi->bits_per_word = 16;
+ err = spi_setup(spi);
+ if (err) {
+ dev_dbg(&spi->dev, "spi master doesn't support 16 bits/word\n");
+ return err;
+ }
+
+ ts = devm_kzalloc(&spi->dev, sizeof(struct ad7877), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&spi->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ err = devm_add_action_or_reset(&spi->dev, ad7877_disable, ts);
+ if (err)
+ return err;
+
+ spi_set_drvdata(spi, ts);
+ ts->spi = spi;
+ ts->input = input_dev;
+
+ timer_setup(&ts->timer, ad7877_timer, 0);
+ mutex_init(&ts->mutex);
+ spin_lock_init(&ts->lock);
+
+ ts->model = pdata->model ? : 7877;
+ ts->vref_delay_usecs = pdata->vref_delay_usecs ? : 100;
+ ts->x_plate_ohms = pdata->x_plate_ohms ? : 400;
+ ts->pressure_max = pdata->pressure_max ? : ~0;
+
+ ts->stopacq_polarity = pdata->stopacq_polarity;
+ ts->first_conversion_delay = pdata->first_conversion_delay;
+ ts->acquisition_time = pdata->acquisition_time;
+ ts->averaging = pdata->averaging;
+ ts->pen_down_acc_interval = pdata->pen_down_acc_interval;
+
+ snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&spi->dev));
+
+ input_dev->name = "AD7877 Touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->dev.parent = &spi->dev;
+
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(ABS_X, input_dev->absbit);
+ __set_bit(ABS_Y, input_dev->absbit);
+ __set_bit(ABS_PRESSURE, input_dev->absbit);
+
+ input_set_abs_params(input_dev, ABS_X,
+ pdata->x_min ? : 0,
+ pdata->x_max ? : MAX_12BIT,
+ 0, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ pdata->y_min ? : 0,
+ pdata->y_max ? : MAX_12BIT,
+ 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE,
+ pdata->pressure_min, pdata->pressure_max, 0, 0);
+
+ ad7877_write(spi, AD7877_REG_SEQ1, AD7877_MM_SEQUENCE);
+
+ verify = ad7877_read(spi, AD7877_REG_SEQ1);
+
+ if (verify != AD7877_MM_SEQUENCE) {
+ dev_err(&spi->dev, "%s: Failed to probe %s\n",
+ dev_name(&spi->dev), input_dev->name);
+ return -ENODEV;
+ }
+
+ if (gpio3)
+ ad7877_write(spi, AD7877_REG_EXTWRITE, AD7877_EXTW_GPIO_3_CONF);
+
+ ad7877_setup_ts_def_msg(spi, ts);
+
+ /* Request AD7877 /DAV GPIO interrupt */
+
+ err = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, ad7877_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ spi->dev.driver->name, ts);
+ if (err) {
+ dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq);
+ return err;
+ }
+
+ err = devm_device_add_group(&spi->dev, &ad7877_attr_group);
+ if (err)
+ return err;
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int __maybe_unused ad7877_suspend(struct device *dev)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+
+ ad7877_disable(ts);
+
+ return 0;
+}
+
+static int __maybe_unused ad7877_resume(struct device *dev)
+{
+ struct ad7877 *ts = dev_get_drvdata(dev);
+
+ ad7877_enable(ts);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ad7877_pm, ad7877_suspend, ad7877_resume);
+
+static struct spi_driver ad7877_driver = {
+ .driver = {
+ .name = "ad7877",
+ .pm = &ad7877_pm,
+ },
+ .probe = ad7877_probe,
+};
+
+module_spi_driver(ad7877_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("AD7877 touchscreen Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:ad7877");
diff --git a/drivers/input/touchscreen/ad7879-i2c.c b/drivers/input/touchscreen/ad7879-i2c.c
new file mode 100644
index 000000000..0f20a1fdc
--- /dev/null
+++ b/drivers/input/touchscreen/ad7879-i2c.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AD7879-1/AD7889-1 touchscreen (I2C bus)
+ *
+ * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc.
+ */
+
+#include <linux/input.h> /* BUS_I2C */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+#include "ad7879.h"
+
+#define AD7879_DEVID 0x79 /* AD7879-1/AD7889-1 */
+
+static const struct regmap_config ad7879_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = 15,
+};
+
+static int ad7879_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(&client->dev, "SMBUS Word Data not Supported\n");
+ return -EIO;
+ }
+
+ regmap = devm_regmap_init_i2c(client, &ad7879_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return ad7879_probe(&client->dev, regmap, client->irq,
+ BUS_I2C, AD7879_DEVID);
+}
+
+static const struct i2c_device_id ad7879_id[] = {
+ { "ad7879", 0 },
+ { "ad7889", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ad7879_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id ad7879_i2c_dt_ids[] = {
+ { .compatible = "adi,ad7879-1", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7879_i2c_dt_ids);
+#endif
+
+static struct i2c_driver ad7879_i2c_driver = {
+ .driver = {
+ .name = "ad7879",
+ .pm = &ad7879_pm_ops,
+ .of_match_table = of_match_ptr(ad7879_i2c_dt_ids),
+ },
+ .probe = ad7879_i2c_probe,
+ .id_table = ad7879_id,
+};
+
+module_i2c_driver(ad7879_i2c_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("AD7879(-1) touchscreen I2C bus driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/ad7879-spi.c b/drivers/input/touchscreen/ad7879-spi.c
new file mode 100644
index 000000000..50e889846
--- /dev/null
+++ b/drivers/input/touchscreen/ad7879-spi.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AD7879/AD7889 touchscreen (SPI bus)
+ *
+ * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc.
+ */
+
+#include <linux/input.h> /* BUS_SPI */
+#include <linux/pm.h>
+#include <linux/spi/spi.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include "ad7879.h"
+
+#define AD7879_DEVID 0x7A /* AD7879/AD7889 */
+
+#define MAX_SPI_FREQ_HZ 5000000
+
+#define AD7879_CMD_MAGIC 0xE0
+#define AD7879_CMD_READ BIT(2)
+
+static const struct regmap_config ad7879_spi_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .max_register = 15,
+ .read_flag_mask = AD7879_CMD_MAGIC | AD7879_CMD_READ,
+ .write_flag_mask = AD7879_CMD_MAGIC,
+};
+
+static int ad7879_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+
+ /* don't exceed max specified SPI CLK frequency */
+ if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) {
+ dev_err(&spi->dev, "SPI CLK %d Hz?\n", spi->max_speed_hz);
+ return -EINVAL;
+ }
+
+ regmap = devm_regmap_init_spi(spi, &ad7879_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return ad7879_probe(&spi->dev, regmap, spi->irq, BUS_SPI, AD7879_DEVID);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ad7879_spi_dt_ids[] = {
+ { .compatible = "adi,ad7879", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7879_spi_dt_ids);
+#endif
+
+static struct spi_driver ad7879_spi_driver = {
+ .driver = {
+ .name = "ad7879",
+ .pm = &ad7879_pm_ops,
+ .of_match_table = of_match_ptr(ad7879_spi_dt_ids),
+ },
+ .probe = ad7879_spi_probe,
+};
+
+module_spi_driver(ad7879_spi_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("AD7879(-1) touchscreen SPI bus driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:ad7879");
diff --git a/drivers/input/touchscreen/ad7879.c b/drivers/input/touchscreen/ad7879.c
new file mode 100644
index 000000000..e85085332
--- /dev/null
+++ b/drivers/input/touchscreen/ad7879.c
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AD7879/AD7889 based touchscreen and GPIO driver
+ *
+ * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc.
+ *
+ * History:
+ * Copyright (c) 2005 David Brownell
+ * Copyright (c) 2006 Nokia Corporation
+ * Various changes: Imre Deak <imre.deak@nokia.com>
+ *
+ * Using code from:
+ * - corgi_ts.c
+ * Copyright (C) 2004-2005 Richard Purdie
+ * - omap_ts.[hc], ads7846.h, ts_osk.c
+ * Copyright (C) 2002 MontaVista Software
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2005 Dirk Behme
+ * - ad7877.c
+ * Copyright (C) 2006-2008 Analog Devices Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+#include "ad7879.h"
+
+#define AD7879_REG_ZEROS 0
+#define AD7879_REG_CTRL1 1
+#define AD7879_REG_CTRL2 2
+#define AD7879_REG_CTRL3 3
+#define AD7879_REG_AUX1HIGH 4
+#define AD7879_REG_AUX1LOW 5
+#define AD7879_REG_TEMP1HIGH 6
+#define AD7879_REG_TEMP1LOW 7
+#define AD7879_REG_XPLUS 8
+#define AD7879_REG_YPLUS 9
+#define AD7879_REG_Z1 10
+#define AD7879_REG_Z2 11
+#define AD7879_REG_AUXVBAT 12
+#define AD7879_REG_TEMP 13
+#define AD7879_REG_REVID 14
+
+/* Control REG 1 */
+#define AD7879_TMR(x) ((x & 0xFF) << 0)
+#define AD7879_ACQ(x) ((x & 0x3) << 8)
+#define AD7879_MODE_NOC (0 << 10) /* Do not convert */
+#define AD7879_MODE_SCC (1 << 10) /* Single channel conversion */
+#define AD7879_MODE_SEQ0 (2 << 10) /* Sequence 0 in Slave Mode */
+#define AD7879_MODE_SEQ1 (3 << 10) /* Sequence 1 in Master Mode */
+#define AD7879_MODE_INT (1 << 15) /* PENIRQ disabled INT enabled */
+
+/* Control REG 2 */
+#define AD7879_FCD(x) ((x & 0x3) << 0)
+#define AD7879_RESET (1 << 4)
+#define AD7879_MFS(x) ((x & 0x3) << 5)
+#define AD7879_AVG(x) ((x & 0x3) << 7)
+#define AD7879_SER (1 << 9) /* non-differential */
+#define AD7879_DFR (0 << 9) /* differential */
+#define AD7879_GPIOPOL (1 << 10)
+#define AD7879_GPIODIR (1 << 11)
+#define AD7879_GPIO_DATA (1 << 12)
+#define AD7879_GPIO_EN (1 << 13)
+#define AD7879_PM(x) ((x & 0x3) << 14)
+#define AD7879_PM_SHUTDOWN (0)
+#define AD7879_PM_DYN (1)
+#define AD7879_PM_FULLON (2)
+
+/* Control REG 3 */
+#define AD7879_TEMPMASK_BIT (1<<15)
+#define AD7879_AUXVBATMASK_BIT (1<<14)
+#define AD7879_INTMODE_BIT (1<<13)
+#define AD7879_GPIOALERTMASK_BIT (1<<12)
+#define AD7879_AUXLOW_BIT (1<<11)
+#define AD7879_AUXHIGH_BIT (1<<10)
+#define AD7879_TEMPLOW_BIT (1<<9)
+#define AD7879_TEMPHIGH_BIT (1<<8)
+#define AD7879_YPLUS_BIT (1<<7)
+#define AD7879_XPLUS_BIT (1<<6)
+#define AD7879_Z1_BIT (1<<5)
+#define AD7879_Z2_BIT (1<<4)
+#define AD7879_AUX_BIT (1<<3)
+#define AD7879_VBAT_BIT (1<<2)
+#define AD7879_TEMP_BIT (1<<1)
+
+enum {
+ AD7879_SEQ_YPOS = 0,
+ AD7879_SEQ_XPOS = 1,
+ AD7879_SEQ_Z1 = 2,
+ AD7879_SEQ_Z2 = 3,
+ AD7879_NR_SENSE = 4,
+};
+
+#define MAX_12BIT ((1<<12)-1)
+#define TS_PEN_UP_TIMEOUT msecs_to_jiffies(50)
+
+struct ad7879 {
+ struct regmap *regmap;
+ struct device *dev;
+ struct input_dev *input;
+ struct timer_list timer;
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gc;
+ struct mutex mutex;
+#endif
+ unsigned int irq;
+ bool disabled; /* P: input->mutex */
+ bool suspended; /* P: input->mutex */
+ bool swap_xy;
+ u16 conversion_data[AD7879_NR_SENSE];
+ char phys[32];
+ u8 first_conversion_delay;
+ u8 acquisition_time;
+ u8 averaging;
+ u8 pen_down_acc_interval;
+ u8 median;
+ u16 x_plate_ohms;
+ u16 cmd_crtl1;
+ u16 cmd_crtl2;
+ u16 cmd_crtl3;
+ int x;
+ int y;
+ int Rt;
+};
+
+static int ad7879_read(struct ad7879 *ts, u8 reg)
+{
+ unsigned int val;
+ int error;
+
+ error = regmap_read(ts->regmap, reg, &val);
+ if (error) {
+ dev_err(ts->dev, "failed to read register %#02x: %d\n",
+ reg, error);
+ return error;
+ }
+
+ return val;
+}
+
+static int ad7879_write(struct ad7879 *ts, u8 reg, u16 val)
+{
+ int error;
+
+ error = regmap_write(ts->regmap, reg, val);
+ if (error) {
+ dev_err(ts->dev,
+ "failed to write %#04x to register %#02x: %d\n",
+ val, reg, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ad7879_report(struct ad7879 *ts)
+{
+ struct input_dev *input_dev = ts->input;
+ unsigned Rt;
+ u16 x, y, z1, z2;
+
+ x = ts->conversion_data[AD7879_SEQ_XPOS] & MAX_12BIT;
+ y = ts->conversion_data[AD7879_SEQ_YPOS] & MAX_12BIT;
+ z1 = ts->conversion_data[AD7879_SEQ_Z1] & MAX_12BIT;
+ z2 = ts->conversion_data[AD7879_SEQ_Z2] & MAX_12BIT;
+
+ if (ts->swap_xy)
+ swap(x, y);
+
+ /*
+ * The samples processed here are already preprocessed by the AD7879.
+ * The preprocessing function consists of a median and an averaging
+ * filter. The combination of these two techniques provides a robust
+ * solution, discarding the spurious noise in the signal and keeping
+ * only the data of interest. The size of both filters is
+ * programmable. (dev.platform_data, see linux/platform_data/ad7879.h)
+ * Other user-programmable conversion controls include variable
+ * acquisition time, and first conversion delay. Up to 16 averages can
+ * be taken per conversion.
+ */
+
+ if (likely(x && z1)) {
+ /* compute touch pressure resistance using equation #1 */
+ Rt = (z2 - z1) * x * ts->x_plate_ohms;
+ Rt /= z1;
+ Rt = (Rt + 2047) >> 12;
+
+ /*
+ * Sample found inconsistent, pressure is beyond
+ * the maximum. Don't report it to user space.
+ */
+ if (Rt > input_abs_get_max(input_dev, ABS_PRESSURE))
+ return -EINVAL;
+
+ /*
+ * Note that we delay reporting events by one sample.
+ * This is done to avoid reporting last sample of the
+ * touch sequence, which may be incomplete if finger
+ * leaves the surface before last reading is taken.
+ */
+ if (timer_pending(&ts->timer)) {
+ /* Touch continues */
+ input_report_key(input_dev, BTN_TOUCH, 1);
+ input_report_abs(input_dev, ABS_X, ts->x);
+ input_report_abs(input_dev, ABS_Y, ts->y);
+ input_report_abs(input_dev, ABS_PRESSURE, ts->Rt);
+ input_sync(input_dev);
+ }
+
+ ts->x = x;
+ ts->y = y;
+ ts->Rt = Rt;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static void ad7879_ts_event_release(struct ad7879 *ts)
+{
+ struct input_dev *input_dev = ts->input;
+
+ input_report_abs(input_dev, ABS_PRESSURE, 0);
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_sync(input_dev);
+}
+
+static void ad7879_timer(struct timer_list *t)
+{
+ struct ad7879 *ts = from_timer(ts, t, timer);
+
+ ad7879_ts_event_release(ts);
+}
+
+static irqreturn_t ad7879_irq(int irq, void *handle)
+{
+ struct ad7879 *ts = handle;
+ int error;
+
+ error = regmap_bulk_read(ts->regmap, AD7879_REG_XPLUS,
+ ts->conversion_data, AD7879_NR_SENSE);
+ if (error)
+ dev_err_ratelimited(ts->dev, "failed to read %#02x: %d\n",
+ AD7879_REG_XPLUS, error);
+ else if (!ad7879_report(ts))
+ mod_timer(&ts->timer, jiffies + TS_PEN_UP_TIMEOUT);
+
+ return IRQ_HANDLED;
+}
+
+static void __ad7879_enable(struct ad7879 *ts)
+{
+ ad7879_write(ts, AD7879_REG_CTRL2, ts->cmd_crtl2);
+ ad7879_write(ts, AD7879_REG_CTRL3, ts->cmd_crtl3);
+ ad7879_write(ts, AD7879_REG_CTRL1, ts->cmd_crtl1);
+
+ enable_irq(ts->irq);
+}
+
+static void __ad7879_disable(struct ad7879 *ts)
+{
+ u16 reg = (ts->cmd_crtl2 & ~AD7879_PM(-1)) |
+ AD7879_PM(AD7879_PM_SHUTDOWN);
+ disable_irq(ts->irq);
+
+ if (del_timer_sync(&ts->timer))
+ ad7879_ts_event_release(ts);
+
+ ad7879_write(ts, AD7879_REG_CTRL2, reg);
+}
+
+
+static int ad7879_open(struct input_dev *input)
+{
+ struct ad7879 *ts = input_get_drvdata(input);
+
+ /* protected by input->mutex */
+ if (!ts->disabled && !ts->suspended)
+ __ad7879_enable(ts);
+
+ return 0;
+}
+
+static void ad7879_close(struct input_dev *input)
+{
+ struct ad7879 *ts = input_get_drvdata(input);
+
+ /* protected by input->mutex */
+ if (!ts->disabled && !ts->suspended)
+ __ad7879_disable(ts);
+}
+
+static int __maybe_unused ad7879_suspend(struct device *dev)
+{
+ struct ad7879 *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->input->mutex);
+
+ if (!ts->suspended && !ts->disabled && input_device_enabled(ts->input))
+ __ad7879_disable(ts);
+
+ ts->suspended = true;
+
+ mutex_unlock(&ts->input->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused ad7879_resume(struct device *dev)
+{
+ struct ad7879 *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->input->mutex);
+
+ if (ts->suspended && !ts->disabled && input_device_enabled(ts->input))
+ __ad7879_enable(ts);
+
+ ts->suspended = false;
+
+ mutex_unlock(&ts->input->mutex);
+
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(ad7879_pm_ops, ad7879_suspend, ad7879_resume);
+EXPORT_SYMBOL(ad7879_pm_ops);
+
+static void ad7879_toggle(struct ad7879 *ts, bool disable)
+{
+ mutex_lock(&ts->input->mutex);
+
+ if (!ts->suspended && input_device_enabled(ts->input)) {
+
+ if (disable) {
+ if (ts->disabled)
+ __ad7879_enable(ts);
+ } else {
+ if (!ts->disabled)
+ __ad7879_disable(ts);
+ }
+ }
+
+ ts->disabled = disable;
+
+ mutex_unlock(&ts->input->mutex);
+}
+
+static ssize_t ad7879_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ad7879 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t ad7879_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ad7879 *ts = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ ad7879_toggle(ts, val);
+
+ return count;
+}
+
+static DEVICE_ATTR(disable, 0664, ad7879_disable_show, ad7879_disable_store);
+
+static struct attribute *ad7879_attributes[] = {
+ &dev_attr_disable.attr,
+ NULL
+};
+
+static const struct attribute_group ad7879_attr_group = {
+ .attrs = ad7879_attributes,
+};
+
+#ifdef CONFIG_GPIOLIB
+static int ad7879_gpio_direction_input(struct gpio_chip *chip,
+ unsigned gpio)
+{
+ struct ad7879 *ts = gpiochip_get_data(chip);
+ int err;
+
+ mutex_lock(&ts->mutex);
+ ts->cmd_crtl2 |= AD7879_GPIO_EN | AD7879_GPIODIR | AD7879_GPIOPOL;
+ err = ad7879_write(ts, AD7879_REG_CTRL2, ts->cmd_crtl2);
+ mutex_unlock(&ts->mutex);
+
+ return err;
+}
+
+static int ad7879_gpio_direction_output(struct gpio_chip *chip,
+ unsigned gpio, int level)
+{
+ struct ad7879 *ts = gpiochip_get_data(chip);
+ int err;
+
+ mutex_lock(&ts->mutex);
+ ts->cmd_crtl2 &= ~AD7879_GPIODIR;
+ ts->cmd_crtl2 |= AD7879_GPIO_EN | AD7879_GPIOPOL;
+ if (level)
+ ts->cmd_crtl2 |= AD7879_GPIO_DATA;
+ else
+ ts->cmd_crtl2 &= ~AD7879_GPIO_DATA;
+
+ err = ad7879_write(ts, AD7879_REG_CTRL2, ts->cmd_crtl2);
+ mutex_unlock(&ts->mutex);
+
+ return err;
+}
+
+static int ad7879_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+ struct ad7879 *ts = gpiochip_get_data(chip);
+ u16 val;
+
+ mutex_lock(&ts->mutex);
+ val = ad7879_read(ts, AD7879_REG_CTRL2);
+ mutex_unlock(&ts->mutex);
+
+ return !!(val & AD7879_GPIO_DATA);
+}
+
+static void ad7879_gpio_set_value(struct gpio_chip *chip,
+ unsigned gpio, int value)
+{
+ struct ad7879 *ts = gpiochip_get_data(chip);
+
+ mutex_lock(&ts->mutex);
+ if (value)
+ ts->cmd_crtl2 |= AD7879_GPIO_DATA;
+ else
+ ts->cmd_crtl2 &= ~AD7879_GPIO_DATA;
+
+ ad7879_write(ts, AD7879_REG_CTRL2, ts->cmd_crtl2);
+ mutex_unlock(&ts->mutex);
+}
+
+static int ad7879_gpio_add(struct ad7879 *ts)
+{
+ int ret = 0;
+
+ mutex_init(&ts->mutex);
+
+ /* Do not create a chip unless flagged for it */
+ if (!device_property_read_bool(ts->dev, "gpio-controller"))
+ return 0;
+
+ ts->gc.direction_input = ad7879_gpio_direction_input;
+ ts->gc.direction_output = ad7879_gpio_direction_output;
+ ts->gc.get = ad7879_gpio_get_value;
+ ts->gc.set = ad7879_gpio_set_value;
+ ts->gc.can_sleep = 1;
+ ts->gc.base = -1;
+ ts->gc.ngpio = 1;
+ ts->gc.label = "AD7879-GPIO";
+ ts->gc.owner = THIS_MODULE;
+ ts->gc.parent = ts->dev;
+
+ ret = devm_gpiochip_add_data(ts->dev, &ts->gc, ts);
+ if (ret)
+ dev_err(ts->dev, "failed to register gpio %d\n",
+ ts->gc.base);
+
+ return ret;
+}
+#else
+static int ad7879_gpio_add(struct ad7879 *ts)
+{
+ return 0;
+}
+#endif
+
+static int ad7879_parse_dt(struct device *dev, struct ad7879 *ts)
+{
+ int err;
+ u32 tmp;
+
+ err = device_property_read_u32(dev, "adi,resistance-plate-x", &tmp);
+ if (err) {
+ dev_err(dev, "failed to get resistance-plate-x property\n");
+ return err;
+ }
+ ts->x_plate_ohms = (u16)tmp;
+
+ device_property_read_u8(dev, "adi,first-conversion-delay",
+ &ts->first_conversion_delay);
+ device_property_read_u8(dev, "adi,acquisition-time",
+ &ts->acquisition_time);
+ device_property_read_u8(dev, "adi,median-filter-size", &ts->median);
+ device_property_read_u8(dev, "adi,averaging", &ts->averaging);
+ device_property_read_u8(dev, "adi,conversion-interval",
+ &ts->pen_down_acc_interval);
+
+ ts->swap_xy = device_property_read_bool(dev, "touchscreen-swapped-x-y");
+
+ return 0;
+}
+
+int ad7879_probe(struct device *dev, struct regmap *regmap,
+ int irq, u16 bustype, u8 devid)
+{
+ struct ad7879 *ts;
+ struct input_dev *input_dev;
+ int err;
+ u16 revid;
+
+ if (irq <= 0) {
+ dev_err(dev, "No IRQ specified\n");
+ return -EINVAL;
+ }
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ err = ad7879_parse_dt(dev, ts);
+ if (err)
+ return err;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->dev = dev;
+ ts->input = input_dev;
+ ts->irq = irq;
+ ts->regmap = regmap;
+
+ timer_setup(&ts->timer, ad7879_timer, 0);
+ snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev));
+
+ input_dev->name = "AD7879 Touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->dev.parent = dev;
+ input_dev->id.bustype = bustype;
+
+ input_dev->open = ad7879_open;
+ input_dev->close = ad7879_close;
+
+ input_set_drvdata(input_dev, ts);
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+
+ input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0);
+ input_set_capability(input_dev, EV_ABS, ABS_PRESSURE);
+ touchscreen_parse_properties(input_dev, false, NULL);
+ if (!input_abs_get_max(input_dev, ABS_PRESSURE)) {
+ dev_err(dev, "Touchscreen pressure is not specified\n");
+ return -EINVAL;
+ }
+
+ err = ad7879_write(ts, AD7879_REG_CTRL2, AD7879_RESET);
+ if (err < 0) {
+ dev_err(dev, "Failed to write %s\n", input_dev->name);
+ return err;
+ }
+
+ revid = ad7879_read(ts, AD7879_REG_REVID);
+ input_dev->id.product = (revid & 0xff);
+ input_dev->id.version = revid >> 8;
+ if (input_dev->id.product != devid) {
+ dev_err(dev, "Failed to probe %s (%x vs %x)\n",
+ input_dev->name, devid, revid);
+ return -ENODEV;
+ }
+
+ ts->cmd_crtl3 = AD7879_YPLUS_BIT |
+ AD7879_XPLUS_BIT |
+ AD7879_Z2_BIT |
+ AD7879_Z1_BIT |
+ AD7879_TEMPMASK_BIT |
+ AD7879_AUXVBATMASK_BIT |
+ AD7879_GPIOALERTMASK_BIT;
+
+ ts->cmd_crtl2 = AD7879_PM(AD7879_PM_DYN) | AD7879_DFR |
+ AD7879_AVG(ts->averaging) |
+ AD7879_MFS(ts->median) |
+ AD7879_FCD(ts->first_conversion_delay);
+
+ ts->cmd_crtl1 = AD7879_MODE_INT | AD7879_MODE_SEQ1 |
+ AD7879_ACQ(ts->acquisition_time) |
+ AD7879_TMR(ts->pen_down_acc_interval);
+
+ err = devm_request_threaded_irq(dev, ts->irq, NULL, ad7879_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ dev_name(dev), ts);
+ if (err) {
+ dev_err(dev, "Failed to request IRQ: %d\n", err);
+ return err;
+ }
+
+ __ad7879_disable(ts);
+
+ err = devm_device_add_group(dev, &ad7879_attr_group);
+ if (err)
+ return err;
+
+ err = ad7879_gpio_add(ts);
+ if (err)
+ return err;
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ dev_set_drvdata(dev, ts);
+
+ return 0;
+}
+EXPORT_SYMBOL(ad7879_probe);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("AD7879(-1) touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/ad7879.h b/drivers/input/touchscreen/ad7879.h
new file mode 100644
index 000000000..ae8aa1428
--- /dev/null
+++ b/drivers/input/touchscreen/ad7879.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AD7879/AD7889 touchscreen (bus interfaces)
+ *
+ * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc.
+ */
+
+#ifndef _AD7879_H_
+#define _AD7879_H_
+
+#include <linux/types.h>
+
+struct device;
+struct regmap;
+
+extern const struct dev_pm_ops ad7879_pm_ops;
+
+int ad7879_probe(struct device *dev, struct regmap *regmap,
+ int irq, u16 bustype, u8 devid);
+
+#endif
diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c
new file mode 100644
index 000000000..bed68a68f
--- /dev/null
+++ b/drivers/input/touchscreen/ads7846.c
@@ -0,0 +1,1435 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADS7846 based touchscreen and sensor driver
+ *
+ * Copyright (c) 2005 David Brownell
+ * Copyright (c) 2006 Nokia Corporation
+ * Various changes: Imre Deak <imre.deak@nokia.com>
+ *
+ * Using code from:
+ * - corgi_ts.c
+ * Copyright (C) 2004-2005 Richard Purdie
+ * - omap_ts.[hc], ads7846.h, ts_osk.c
+ * Copyright (C) 2002 MontaVista Software
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2005 Dirk Behme
+ */
+#include <linux/types.h>
+#include <linux/hwmon.h>
+#include <linux/err.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/ads7846.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+#include <asm/unaligned.h>
+
+/*
+ * This code has been heavily tested on a Nokia 770, and lightly
+ * tested on other ads7846 devices (OSK/Mistral, Lubbock, Spitz).
+ * TSC2046 is just newer ads7846 silicon.
+ * Support for ads7843 tested on Atmel at91sam926x-EK.
+ * Support for ads7845 has only been stubbed in.
+ * Support for Analog Devices AD7873 and AD7843 tested.
+ *
+ * IRQ handling needs a workaround because of a shortcoming in handling
+ * edge triggered IRQs on some platforms like the OMAP1/2. These
+ * platforms don't handle the ARM lazy IRQ disabling properly, thus we
+ * have to maintain our own SW IRQ disabled status. This should be
+ * removed as soon as the affected platform's IRQ handling is fixed.
+ *
+ * App note sbaa036 talks in more detail about accurate sampling...
+ * that ought to help in situations like LCDs inducing noise (which
+ * can also be helped by using synch signals) and more generally.
+ * This driver tries to utilize the measures described in the app
+ * note. The strength of filtering can be set in the board-* specific
+ * files.
+ */
+
+#define TS_POLL_DELAY 1 /* ms delay before the first sample */
+#define TS_POLL_PERIOD 5 /* ms delay between samples */
+
+/* this driver doesn't aim at the peak continuous sample rate */
+#define SAMPLE_BITS (8 /*cmd*/ + 16 /*sample*/ + 2 /* before, after */)
+
+struct ads7846_buf {
+ u8 cmd;
+ __be16 data;
+} __packed;
+
+struct ads7846_buf_layout {
+ unsigned int offset;
+ unsigned int count;
+ unsigned int skip;
+};
+
+/*
+ * We allocate this separately to avoid cache line sharing issues when
+ * driver is used with DMA-based SPI controllers (like atmel_spi) on
+ * systems where main memory is not DMA-coherent (most non-x86 boards).
+ */
+struct ads7846_packet {
+ unsigned int count;
+ unsigned int count_skip;
+ unsigned int cmds;
+ unsigned int last_cmd_idx;
+ struct ads7846_buf_layout l[5];
+ struct ads7846_buf *rx;
+ struct ads7846_buf *tx;
+
+ struct ads7846_buf pwrdown_cmd;
+
+ bool ignore;
+ u16 x, y, z1, z2;
+};
+
+struct ads7846 {
+ struct input_dev *input;
+ char phys[32];
+ char name[32];
+
+ struct spi_device *spi;
+ struct regulator *reg;
+
+ u16 model;
+ u16 vref_mv;
+ u16 vref_delay_usecs;
+ u16 x_plate_ohms;
+ u16 pressure_max;
+
+ bool swap_xy;
+ bool use_internal;
+
+ struct ads7846_packet *packet;
+
+ struct spi_transfer xfer[18];
+ struct spi_message msg[5];
+ int msg_count;
+ wait_queue_head_t wait;
+
+ bool pendown;
+
+ int read_cnt;
+ int read_rep;
+ int last_read;
+
+ u16 debounce_max;
+ u16 debounce_tol;
+ u16 debounce_rep;
+
+ u16 penirq_recheck_delay_usecs;
+
+ struct touchscreen_properties core_prop;
+
+ struct mutex lock;
+ bool stopped; /* P: lock */
+ bool disabled; /* P: lock */
+ bool suspended; /* P: lock */
+
+ int (*filter)(void *data, int data_idx, int *val);
+ void *filter_data;
+ int (*get_pendown_state)(void);
+ int gpio_pendown;
+
+ void (*wait_for_sync)(void);
+};
+
+enum ads7846_filter {
+ ADS7846_FILTER_OK,
+ ADS7846_FILTER_REPEAT,
+ ADS7846_FILTER_IGNORE,
+};
+
+/* leave chip selected when we're done, for quicker re-select? */
+#if 0
+#define CS_CHANGE(xfer) ((xfer).cs_change = 1)
+#else
+#define CS_CHANGE(xfer) ((xfer).cs_change = 0)
+#endif
+
+/*--------------------------------------------------------------------------*/
+
+/* The ADS7846 has touchscreen and other sensors.
+ * Earlier ads784x chips are somewhat compatible.
+ */
+#define ADS_START (1 << 7)
+#define ADS_A2A1A0_d_y (1 << 4) /* differential */
+#define ADS_A2A1A0_d_z1 (3 << 4) /* differential */
+#define ADS_A2A1A0_d_z2 (4 << 4) /* differential */
+#define ADS_A2A1A0_d_x (5 << 4) /* differential */
+#define ADS_A2A1A0_temp0 (0 << 4) /* non-differential */
+#define ADS_A2A1A0_vbatt (2 << 4) /* non-differential */
+#define ADS_A2A1A0_vaux (6 << 4) /* non-differential */
+#define ADS_A2A1A0_temp1 (7 << 4) /* non-differential */
+#define ADS_8_BIT (1 << 3)
+#define ADS_12_BIT (0 << 3)
+#define ADS_SER (1 << 2) /* non-differential */
+#define ADS_DFR (0 << 2) /* differential */
+#define ADS_PD10_PDOWN (0 << 0) /* low power mode + penirq */
+#define ADS_PD10_ADC_ON (1 << 0) /* ADC on */
+#define ADS_PD10_REF_ON (2 << 0) /* vREF on + penirq */
+#define ADS_PD10_ALL_ON (3 << 0) /* ADC + vREF on */
+
+#define MAX_12BIT ((1<<12)-1)
+
+/* leave ADC powered up (disables penirq) between differential samples */
+#define READ_12BIT_DFR(x, adc, vref) (ADS_START | ADS_A2A1A0_d_ ## x \
+ | ADS_12_BIT | ADS_DFR | \
+ (adc ? ADS_PD10_ADC_ON : 0) | (vref ? ADS_PD10_REF_ON : 0))
+
+#define READ_Y(vref) (READ_12BIT_DFR(y, 1, vref))
+#define READ_Z1(vref) (READ_12BIT_DFR(z1, 1, vref))
+#define READ_Z2(vref) (READ_12BIT_DFR(z2, 1, vref))
+#define READ_X(vref) (READ_12BIT_DFR(x, 1, vref))
+#define PWRDOWN (READ_12BIT_DFR(y, 0, 0)) /* LAST */
+
+/* single-ended samples need to first power up reference voltage;
+ * we leave both ADC and VREF powered
+ */
+#define READ_12BIT_SER(x) (ADS_START | ADS_A2A1A0_ ## x \
+ | ADS_12_BIT | ADS_SER)
+
+#define REF_ON (READ_12BIT_DFR(x, 1, 1))
+#define REF_OFF (READ_12BIT_DFR(y, 0, 0))
+
+/* Order commands in the most optimal way to reduce Vref switching and
+ * settling time:
+ * Measure: X; Vref: X+, X-; IN: Y+
+ * Measure: Y; Vref: Y+, Y-; IN: X+
+ * Measure: Z1; Vref: Y+, X-; IN: X+
+ * Measure: Z2; Vref: Y+, X-; IN: Y-
+ */
+enum ads7846_cmds {
+ ADS7846_X,
+ ADS7846_Y,
+ ADS7846_Z1,
+ ADS7846_Z2,
+ ADS7846_PWDOWN,
+};
+
+static int get_pendown_state(struct ads7846 *ts)
+{
+ if (ts->get_pendown_state)
+ return ts->get_pendown_state();
+
+ return !gpio_get_value(ts->gpio_pendown);
+}
+
+static void ads7846_report_pen_up(struct ads7846 *ts)
+{
+ struct input_dev *input = ts->input;
+
+ input_report_key(input, BTN_TOUCH, 0);
+ input_report_abs(input, ABS_PRESSURE, 0);
+ input_sync(input);
+
+ ts->pendown = false;
+ dev_vdbg(&ts->spi->dev, "UP\n");
+}
+
+/* Must be called with ts->lock held */
+static void ads7846_stop(struct ads7846 *ts)
+{
+ if (!ts->disabled && !ts->suspended) {
+ /* Signal IRQ thread to stop polling and disable the handler. */
+ ts->stopped = true;
+ mb();
+ wake_up(&ts->wait);
+ disable_irq(ts->spi->irq);
+ }
+}
+
+/* Must be called with ts->lock held */
+static void ads7846_restart(struct ads7846 *ts)
+{
+ if (!ts->disabled && !ts->suspended) {
+ /* Check if pen was released since last stop */
+ if (ts->pendown && !get_pendown_state(ts))
+ ads7846_report_pen_up(ts);
+
+ /* Tell IRQ thread that it may poll the device. */
+ ts->stopped = false;
+ mb();
+ enable_irq(ts->spi->irq);
+ }
+}
+
+/* Must be called with ts->lock held */
+static void __ads7846_disable(struct ads7846 *ts)
+{
+ ads7846_stop(ts);
+ regulator_disable(ts->reg);
+
+ /*
+ * We know the chip's in low power mode since we always
+ * leave it that way after every request
+ */
+}
+
+/* Must be called with ts->lock held */
+static void __ads7846_enable(struct ads7846 *ts)
+{
+ int error;
+
+ error = regulator_enable(ts->reg);
+ if (error != 0)
+ dev_err(&ts->spi->dev, "Failed to enable supply: %d\n", error);
+
+ ads7846_restart(ts);
+}
+
+static void ads7846_disable(struct ads7846 *ts)
+{
+ mutex_lock(&ts->lock);
+
+ if (!ts->disabled) {
+
+ if (!ts->suspended)
+ __ads7846_disable(ts);
+
+ ts->disabled = true;
+ }
+
+ mutex_unlock(&ts->lock);
+}
+
+static void ads7846_enable(struct ads7846 *ts)
+{
+ mutex_lock(&ts->lock);
+
+ if (ts->disabled) {
+
+ ts->disabled = false;
+
+ if (!ts->suspended)
+ __ads7846_enable(ts);
+ }
+
+ mutex_unlock(&ts->lock);
+}
+
+/*--------------------------------------------------------------------------*/
+
+/*
+ * Non-touchscreen sensors only use single-ended conversions.
+ * The range is GND..vREF. The ads7843 and ads7835 must use external vREF;
+ * ads7846 lets that pin be unconnected, to use internal vREF.
+ */
+
+struct ser_req {
+ u8 ref_on;
+ u8 command;
+ u8 ref_off;
+ u16 scratch;
+ struct spi_message msg;
+ struct spi_transfer xfer[6];
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ __be16 sample ____cacheline_aligned;
+};
+
+struct ads7845_ser_req {
+ u8 command[3];
+ struct spi_message msg;
+ struct spi_transfer xfer[2];
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ u8 sample[3] ____cacheline_aligned;
+};
+
+static int ads7846_read12_ser(struct device *dev, unsigned command)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct ads7846 *ts = dev_get_drvdata(dev);
+ struct ser_req *req;
+ int status;
+
+ req = kzalloc(sizeof *req, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ spi_message_init(&req->msg);
+
+ /* maybe turn on internal vREF, and let it settle */
+ if (ts->use_internal) {
+ req->ref_on = REF_ON;
+ req->xfer[0].tx_buf = &req->ref_on;
+ req->xfer[0].len = 1;
+ spi_message_add_tail(&req->xfer[0], &req->msg);
+
+ req->xfer[1].rx_buf = &req->scratch;
+ req->xfer[1].len = 2;
+
+ /* for 1uF, settle for 800 usec; no cap, 100 usec. */
+ req->xfer[1].delay.value = ts->vref_delay_usecs;
+ req->xfer[1].delay.unit = SPI_DELAY_UNIT_USECS;
+ spi_message_add_tail(&req->xfer[1], &req->msg);
+
+ /* Enable reference voltage */
+ command |= ADS_PD10_REF_ON;
+ }
+
+ /* Enable ADC in every case */
+ command |= ADS_PD10_ADC_ON;
+
+ /* take sample */
+ req->command = (u8) command;
+ req->xfer[2].tx_buf = &req->command;
+ req->xfer[2].len = 1;
+ spi_message_add_tail(&req->xfer[2], &req->msg);
+
+ req->xfer[3].rx_buf = &req->sample;
+ req->xfer[3].len = 2;
+ spi_message_add_tail(&req->xfer[3], &req->msg);
+
+ /* REVISIT: take a few more samples, and compare ... */
+
+ /* converter in low power mode & enable PENIRQ */
+ req->ref_off = PWRDOWN;
+ req->xfer[4].tx_buf = &req->ref_off;
+ req->xfer[4].len = 1;
+ spi_message_add_tail(&req->xfer[4], &req->msg);
+
+ req->xfer[5].rx_buf = &req->scratch;
+ req->xfer[5].len = 2;
+ CS_CHANGE(req->xfer[5]);
+ spi_message_add_tail(&req->xfer[5], &req->msg);
+
+ mutex_lock(&ts->lock);
+ ads7846_stop(ts);
+ status = spi_sync(spi, &req->msg);
+ ads7846_restart(ts);
+ mutex_unlock(&ts->lock);
+
+ if (status == 0) {
+ /* on-wire is a must-ignore bit, a BE12 value, then padding */
+ status = be16_to_cpu(req->sample);
+ status = status >> 3;
+ status &= 0x0fff;
+ }
+
+ kfree(req);
+ return status;
+}
+
+static int ads7845_read12_ser(struct device *dev, unsigned command)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct ads7846 *ts = dev_get_drvdata(dev);
+ struct ads7845_ser_req *req;
+ int status;
+
+ req = kzalloc(sizeof *req, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ spi_message_init(&req->msg);
+
+ req->command[0] = (u8) command;
+ req->xfer[0].tx_buf = req->command;
+ req->xfer[0].rx_buf = req->sample;
+ req->xfer[0].len = 3;
+ spi_message_add_tail(&req->xfer[0], &req->msg);
+
+ mutex_lock(&ts->lock);
+ ads7846_stop(ts);
+ status = spi_sync(spi, &req->msg);
+ ads7846_restart(ts);
+ mutex_unlock(&ts->lock);
+
+ if (status == 0) {
+ /* BE12 value, then padding */
+ status = get_unaligned_be16(&req->sample[1]);
+ status = status >> 3;
+ status &= 0x0fff;
+ }
+
+ kfree(req);
+ return status;
+}
+
+#if IS_ENABLED(CONFIG_HWMON)
+
+#define SHOW(name, var, adjust) static ssize_t \
+name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ struct ads7846 *ts = dev_get_drvdata(dev); \
+ ssize_t v = ads7846_read12_ser(&ts->spi->dev, \
+ READ_12BIT_SER(var)); \
+ if (v < 0) \
+ return v; \
+ return sprintf(buf, "%u\n", adjust(ts, v)); \
+} \
+static DEVICE_ATTR(name, S_IRUGO, name ## _show, NULL);
+
+
+/* Sysfs conventions report temperatures in millidegrees Celsius.
+ * ADS7846 could use the low-accuracy two-sample scheme, but can't do the high
+ * accuracy scheme without calibration data. For now we won't try either;
+ * userspace sees raw sensor values, and must scale/calibrate appropriately.
+ */
+static inline unsigned null_adjust(struct ads7846 *ts, ssize_t v)
+{
+ return v;
+}
+
+SHOW(temp0, temp0, null_adjust) /* temp1_input */
+SHOW(temp1, temp1, null_adjust) /* temp2_input */
+
+
+/* sysfs conventions report voltages in millivolts. We can convert voltages
+ * if we know vREF. userspace may need to scale vAUX to match the board's
+ * external resistors; we assume that vBATT only uses the internal ones.
+ */
+static inline unsigned vaux_adjust(struct ads7846 *ts, ssize_t v)
+{
+ unsigned retval = v;
+
+ /* external resistors may scale vAUX into 0..vREF */
+ retval *= ts->vref_mv;
+ retval = retval >> 12;
+
+ return retval;
+}
+
+static inline unsigned vbatt_adjust(struct ads7846 *ts, ssize_t v)
+{
+ unsigned retval = vaux_adjust(ts, v);
+
+ /* ads7846 has a resistor ladder to scale this signal down */
+ if (ts->model == 7846)
+ retval *= 4;
+
+ return retval;
+}
+
+SHOW(in0_input, vaux, vaux_adjust)
+SHOW(in1_input, vbatt, vbatt_adjust)
+
+static umode_t ads7846_is_visible(struct kobject *kobj, struct attribute *attr,
+ int index)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct ads7846 *ts = dev_get_drvdata(dev);
+
+ if (ts->model == 7843 && index < 2) /* in0, in1 */
+ return 0;
+ if (ts->model == 7845 && index != 2) /* in0 */
+ return 0;
+
+ return attr->mode;
+}
+
+static struct attribute *ads7846_attributes[] = {
+ &dev_attr_temp0.attr, /* 0 */
+ &dev_attr_temp1.attr, /* 1 */
+ &dev_attr_in0_input.attr, /* 2 */
+ &dev_attr_in1_input.attr, /* 3 */
+ NULL,
+};
+
+static const struct attribute_group ads7846_attr_group = {
+ .attrs = ads7846_attributes,
+ .is_visible = ads7846_is_visible,
+};
+__ATTRIBUTE_GROUPS(ads7846_attr);
+
+static int ads784x_hwmon_register(struct spi_device *spi, struct ads7846 *ts)
+{
+ struct device *hwmon;
+
+ /* hwmon sensors need a reference voltage */
+ switch (ts->model) {
+ case 7846:
+ if (!ts->vref_mv) {
+ dev_dbg(&spi->dev, "assuming 2.5V internal vREF\n");
+ ts->vref_mv = 2500;
+ ts->use_internal = true;
+ }
+ break;
+ case 7845:
+ case 7843:
+ if (!ts->vref_mv) {
+ dev_warn(&spi->dev,
+ "external vREF for ADS%d not specified\n",
+ ts->model);
+ return 0;
+ }
+ break;
+ }
+
+ hwmon = devm_hwmon_device_register_with_groups(&spi->dev,
+ spi->modalias, ts,
+ ads7846_attr_groups);
+
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+
+#else
+static inline int ads784x_hwmon_register(struct spi_device *spi,
+ struct ads7846 *ts)
+{
+ return 0;
+}
+#endif
+
+static ssize_t ads7846_pen_down_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ads7846 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->pendown);
+}
+
+static DEVICE_ATTR(pen_down, S_IRUGO, ads7846_pen_down_show, NULL);
+
+static ssize_t ads7846_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ads7846 *ts = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t ads7846_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ads7846 *ts = dev_get_drvdata(dev);
+ unsigned int i;
+ int err;
+
+ err = kstrtouint(buf, 10, &i);
+ if (err)
+ return err;
+
+ if (i)
+ ads7846_disable(ts);
+ else
+ ads7846_enable(ts);
+
+ return count;
+}
+
+static DEVICE_ATTR(disable, 0664, ads7846_disable_show, ads7846_disable_store);
+
+static struct attribute *ads784x_attributes[] = {
+ &dev_attr_pen_down.attr,
+ &dev_attr_disable.attr,
+ NULL,
+};
+
+static const struct attribute_group ads784x_attr_group = {
+ .attrs = ads784x_attributes,
+};
+
+/*--------------------------------------------------------------------------*/
+
+static void null_wait_for_sync(void)
+{
+}
+
+static int ads7846_debounce_filter(void *ads, int data_idx, int *val)
+{
+ struct ads7846 *ts = ads;
+
+ if (!ts->read_cnt || (abs(ts->last_read - *val) > ts->debounce_tol)) {
+ /* Start over collecting consistent readings. */
+ ts->read_rep = 0;
+ /*
+ * Repeat it, if this was the first read or the read
+ * wasn't consistent enough.
+ */
+ if (ts->read_cnt < ts->debounce_max) {
+ ts->last_read = *val;
+ ts->read_cnt++;
+ return ADS7846_FILTER_REPEAT;
+ } else {
+ /*
+ * Maximum number of debouncing reached and still
+ * not enough number of consistent readings. Abort
+ * the whole sample, repeat it in the next sampling
+ * period.
+ */
+ ts->read_cnt = 0;
+ return ADS7846_FILTER_IGNORE;
+ }
+ } else {
+ if (++ts->read_rep > ts->debounce_rep) {
+ /*
+ * Got a good reading for this coordinate,
+ * go for the next one.
+ */
+ ts->read_cnt = 0;
+ ts->read_rep = 0;
+ return ADS7846_FILTER_OK;
+ } else {
+ /* Read more values that are consistent. */
+ ts->read_cnt++;
+ return ADS7846_FILTER_REPEAT;
+ }
+ }
+}
+
+static int ads7846_no_filter(void *ads, int data_idx, int *val)
+{
+ return ADS7846_FILTER_OK;
+}
+
+static int ads7846_get_value(struct ads7846_buf *buf)
+{
+ int value;
+
+ value = be16_to_cpup(&buf->data);
+
+ /* enforce ADC output is 12 bits width */
+ return (value >> 3) & 0xfff;
+}
+
+static void ads7846_set_cmd_val(struct ads7846 *ts, enum ads7846_cmds cmd_idx,
+ u16 val)
+{
+ struct ads7846_packet *packet = ts->packet;
+
+ switch (cmd_idx) {
+ case ADS7846_Y:
+ packet->y = val;
+ break;
+ case ADS7846_X:
+ packet->x = val;
+ break;
+ case ADS7846_Z1:
+ packet->z1 = val;
+ break;
+ case ADS7846_Z2:
+ packet->z2 = val;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ }
+}
+
+static u8 ads7846_get_cmd(enum ads7846_cmds cmd_idx, int vref)
+{
+ switch (cmd_idx) {
+ case ADS7846_Y:
+ return READ_Y(vref);
+ case ADS7846_X:
+ return READ_X(vref);
+
+ /* 7846 specific commands */
+ case ADS7846_Z1:
+ return READ_Z1(vref);
+ case ADS7846_Z2:
+ return READ_Z2(vref);
+ case ADS7846_PWDOWN:
+ return PWRDOWN;
+ default:
+ WARN_ON_ONCE(1);
+ }
+
+ return 0;
+}
+
+static bool ads7846_cmd_need_settle(enum ads7846_cmds cmd_idx)
+{
+ switch (cmd_idx) {
+ case ADS7846_X:
+ case ADS7846_Y:
+ case ADS7846_Z1:
+ case ADS7846_Z2:
+ return true;
+ case ADS7846_PWDOWN:
+ return false;
+ default:
+ WARN_ON_ONCE(1);
+ }
+
+ return false;
+}
+
+static int ads7846_filter(struct ads7846 *ts)
+{
+ struct ads7846_packet *packet = ts->packet;
+ int action;
+ int val;
+ unsigned int cmd_idx, b;
+
+ packet->ignore = false;
+ for (cmd_idx = packet->last_cmd_idx; cmd_idx < packet->cmds - 1; cmd_idx++) {
+ struct ads7846_buf_layout *l = &packet->l[cmd_idx];
+
+ packet->last_cmd_idx = cmd_idx;
+
+ for (b = l->skip; b < l->count; b++) {
+ val = ads7846_get_value(&packet->rx[l->offset + b]);
+
+ action = ts->filter(ts->filter_data, cmd_idx, &val);
+ if (action == ADS7846_FILTER_REPEAT) {
+ if (b == l->count - 1)
+ return -EAGAIN;
+ } else if (action == ADS7846_FILTER_OK) {
+ ads7846_set_cmd_val(ts, cmd_idx, val);
+ break;
+ } else {
+ packet->ignore = true;
+ return 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void ads7846_read_state(struct ads7846 *ts)
+{
+ struct ads7846_packet *packet = ts->packet;
+ struct spi_message *m;
+ int msg_idx = 0;
+ int error;
+
+ packet->last_cmd_idx = 0;
+
+ while (true) {
+ ts->wait_for_sync();
+
+ m = &ts->msg[msg_idx];
+ error = spi_sync(ts->spi, m);
+ if (error) {
+ dev_err(&ts->spi->dev, "spi_sync --> %d\n", error);
+ packet->ignore = true;
+ return;
+ }
+
+ error = ads7846_filter(ts);
+ if (error)
+ continue;
+
+ return;
+ }
+}
+
+static void ads7846_report_state(struct ads7846 *ts)
+{
+ struct ads7846_packet *packet = ts->packet;
+ unsigned int Rt;
+ u16 x, y, z1, z2;
+
+ x = packet->x;
+ y = packet->y;
+ if (ts->model == 7845) {
+ z1 = 0;
+ z2 = 0;
+ } else {
+ z1 = packet->z1;
+ z2 = packet->z2;
+ }
+
+ /* range filtering */
+ if (x == MAX_12BIT)
+ x = 0;
+
+ if (ts->model == 7843) {
+ Rt = ts->pressure_max / 2;
+ } else if (ts->model == 7845) {
+ if (get_pendown_state(ts))
+ Rt = ts->pressure_max / 2;
+ else
+ Rt = 0;
+ dev_vdbg(&ts->spi->dev, "x/y: %d/%d, PD %d\n", x, y, Rt);
+ } else if (likely(x && z1)) {
+ /* compute touch pressure resistance using equation #2 */
+ Rt = z2;
+ Rt -= z1;
+ Rt *= ts->x_plate_ohms;
+ Rt = DIV_ROUND_CLOSEST(Rt, 16);
+ Rt *= x;
+ Rt /= z1;
+ Rt = DIV_ROUND_CLOSEST(Rt, 256);
+ } else {
+ Rt = 0;
+ }
+
+ /*
+ * Sample found inconsistent by debouncing or pressure is beyond
+ * the maximum. Don't report it to user space, repeat at least
+ * once more the measurement
+ */
+ if (packet->ignore || Rt > ts->pressure_max) {
+ dev_vdbg(&ts->spi->dev, "ignored %d pressure %d\n",
+ packet->ignore, Rt);
+ return;
+ }
+
+ /*
+ * Maybe check the pendown state before reporting. This discards
+ * false readings when the pen is lifted.
+ */
+ if (ts->penirq_recheck_delay_usecs) {
+ udelay(ts->penirq_recheck_delay_usecs);
+ if (!get_pendown_state(ts))
+ Rt = 0;
+ }
+
+ /*
+ * NOTE: We can't rely on the pressure to determine the pen down
+ * state, even this controller has a pressure sensor. The pressure
+ * value can fluctuate for quite a while after lifting the pen and
+ * in some cases may not even settle at the expected value.
+ *
+ * The only safe way to check for the pen up condition is in the
+ * timer by reading the pen signal state (it's a GPIO _and_ IRQ).
+ */
+ if (Rt) {
+ struct input_dev *input = ts->input;
+
+ if (!ts->pendown) {
+ input_report_key(input, BTN_TOUCH, 1);
+ ts->pendown = true;
+ dev_vdbg(&ts->spi->dev, "DOWN\n");
+ }
+
+ touchscreen_report_pos(input, &ts->core_prop, x, y, false);
+ input_report_abs(input, ABS_PRESSURE, ts->pressure_max - Rt);
+
+ input_sync(input);
+ dev_vdbg(&ts->spi->dev, "%4d/%4d/%4d\n", x, y, Rt);
+ }
+}
+
+static irqreturn_t ads7846_hard_irq(int irq, void *handle)
+{
+ struct ads7846 *ts = handle;
+
+ return get_pendown_state(ts) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+
+static irqreturn_t ads7846_irq(int irq, void *handle)
+{
+ struct ads7846 *ts = handle;
+
+ /* Start with a small delay before checking pendown state */
+ msleep(TS_POLL_DELAY);
+
+ while (!ts->stopped && get_pendown_state(ts)) {
+
+ /* pen is down, continue with the measurement */
+ ads7846_read_state(ts);
+
+ if (!ts->stopped)
+ ads7846_report_state(ts);
+
+ wait_event_timeout(ts->wait, ts->stopped,
+ msecs_to_jiffies(TS_POLL_PERIOD));
+ }
+
+ if (ts->pendown && !ts->stopped)
+ ads7846_report_pen_up(ts);
+
+ return IRQ_HANDLED;
+}
+
+static int __maybe_unused ads7846_suspend(struct device *dev)
+{
+ struct ads7846 *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->lock);
+
+ if (!ts->suspended) {
+
+ if (!ts->disabled)
+ __ads7846_disable(ts);
+
+ if (device_may_wakeup(&ts->spi->dev))
+ enable_irq_wake(ts->spi->irq);
+
+ ts->suspended = true;
+ }
+
+ mutex_unlock(&ts->lock);
+
+ return 0;
+}
+
+static int __maybe_unused ads7846_resume(struct device *dev)
+{
+ struct ads7846 *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->lock);
+
+ if (ts->suspended) {
+
+ ts->suspended = false;
+
+ if (device_may_wakeup(&ts->spi->dev))
+ disable_irq_wake(ts->spi->irq);
+
+ if (!ts->disabled)
+ __ads7846_enable(ts);
+ }
+
+ mutex_unlock(&ts->lock);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ads7846_pm, ads7846_suspend, ads7846_resume);
+
+static int ads7846_setup_pendown(struct spi_device *spi,
+ struct ads7846 *ts,
+ const struct ads7846_platform_data *pdata)
+{
+ int err;
+
+ /*
+ * REVISIT when the irq can be triggered active-low, or if for some
+ * reason the touchscreen isn't hooked up, we don't need to access
+ * the pendown state.
+ */
+
+ if (pdata->get_pendown_state) {
+ ts->get_pendown_state = pdata->get_pendown_state;
+ } else if (gpio_is_valid(pdata->gpio_pendown)) {
+
+ err = devm_gpio_request_one(&spi->dev, pdata->gpio_pendown,
+ GPIOF_IN, "ads7846_pendown");
+ if (err) {
+ dev_err(&spi->dev,
+ "failed to request/setup pendown GPIO%d: %d\n",
+ pdata->gpio_pendown, err);
+ return err;
+ }
+
+ ts->gpio_pendown = pdata->gpio_pendown;
+
+ if (pdata->gpio_pendown_debounce)
+ gpio_set_debounce(pdata->gpio_pendown,
+ pdata->gpio_pendown_debounce);
+ } else {
+ dev_err(&spi->dev, "no get_pendown_state nor gpio_pendown?\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set up the transfers to read touchscreen state; this assumes we
+ * use formula #2 for pressure, not #3.
+ */
+static int ads7846_setup_spi_msg(struct ads7846 *ts,
+ const struct ads7846_platform_data *pdata)
+{
+ struct spi_message *m = &ts->msg[0];
+ struct spi_transfer *x = ts->xfer;
+ struct ads7846_packet *packet = ts->packet;
+ int vref = pdata->keep_vref_on;
+ unsigned int count, offset = 0;
+ unsigned int cmd_idx, b;
+ unsigned long time;
+ size_t size = 0;
+
+ /* time per bit */
+ time = NSEC_PER_SEC / ts->spi->max_speed_hz;
+
+ count = pdata->settle_delay_usecs * NSEC_PER_USEC / time;
+ packet->count_skip = DIV_ROUND_UP(count, 24);
+
+ if (ts->debounce_max && ts->debounce_rep)
+ /* ads7846_debounce_filter() is making ts->debounce_rep + 2
+ * reads. So we need to get all samples for normal case. */
+ packet->count = ts->debounce_rep + 2;
+ else
+ packet->count = 1;
+
+ if (ts->model == 7846)
+ packet->cmds = 5; /* x, y, z1, z2, pwdown */
+ else
+ packet->cmds = 3; /* x, y, pwdown */
+
+ for (cmd_idx = 0; cmd_idx < packet->cmds; cmd_idx++) {
+ struct ads7846_buf_layout *l = &packet->l[cmd_idx];
+ unsigned int max_count;
+
+ if (ads7846_cmd_need_settle(cmd_idx))
+ max_count = packet->count + packet->count_skip;
+ else
+ max_count = packet->count;
+
+ l->offset = offset;
+ offset += max_count;
+ l->count = max_count;
+ l->skip = packet->count_skip;
+ size += sizeof(*packet->tx) * max_count;
+ }
+
+ packet->tx = devm_kzalloc(&ts->spi->dev, size, GFP_KERNEL);
+ if (!packet->tx)
+ return -ENOMEM;
+
+ packet->rx = devm_kzalloc(&ts->spi->dev, size, GFP_KERNEL);
+ if (!packet->rx)
+ return -ENOMEM;
+
+ if (ts->model == 7873) {
+ /*
+ * The AD7873 is almost identical to the ADS7846
+ * keep VREF off during differential/ratiometric
+ * conversion modes.
+ */
+ ts->model = 7846;
+ vref = 0;
+ }
+
+ ts->msg_count = 1;
+ spi_message_init(m);
+ m->context = ts;
+
+ for (cmd_idx = 0; cmd_idx < packet->cmds; cmd_idx++) {
+ struct ads7846_buf_layout *l = &packet->l[cmd_idx];
+ u8 cmd = ads7846_get_cmd(cmd_idx, vref);
+
+ for (b = 0; b < l->count; b++)
+ packet->tx[l->offset + b].cmd = cmd;
+ }
+
+ x->tx_buf = packet->tx;
+ x->rx_buf = packet->rx;
+ x->len = size;
+ spi_message_add_tail(x, m);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ads7846_dt_ids[] = {
+ { .compatible = "ti,tsc2046", .data = (void *) 7846 },
+ { .compatible = "ti,ads7843", .data = (void *) 7843 },
+ { .compatible = "ti,ads7845", .data = (void *) 7845 },
+ { .compatible = "ti,ads7846", .data = (void *) 7846 },
+ { .compatible = "ti,ads7873", .data = (void *) 7873 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads7846_dt_ids);
+
+static const struct ads7846_platform_data *ads7846_probe_dt(struct device *dev)
+{
+ struct ads7846_platform_data *pdata;
+ struct device_node *node = dev->of_node;
+ const struct of_device_id *match;
+ u32 value;
+
+ if (!node) {
+ dev_err(dev, "Device does not have associated DT data\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ match = of_match_device(ads7846_dt_ids, dev);
+ if (!match) {
+ dev_err(dev, "Unknown device model\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->model = (unsigned long)match->data;
+
+ of_property_read_u16(node, "ti,vref-delay-usecs",
+ &pdata->vref_delay_usecs);
+ of_property_read_u16(node, "ti,vref-mv", &pdata->vref_mv);
+ pdata->keep_vref_on = of_property_read_bool(node, "ti,keep-vref-on");
+
+ pdata->swap_xy = of_property_read_bool(node, "ti,swap-xy");
+
+ of_property_read_u16(node, "ti,settle-delay-usec",
+ &pdata->settle_delay_usecs);
+ of_property_read_u16(node, "ti,penirq-recheck-delay-usecs",
+ &pdata->penirq_recheck_delay_usecs);
+
+ of_property_read_u16(node, "ti,x-plate-ohms", &pdata->x_plate_ohms);
+ of_property_read_u16(node, "ti,y-plate-ohms", &pdata->y_plate_ohms);
+
+ of_property_read_u16(node, "ti,x-min", &pdata->x_min);
+ of_property_read_u16(node, "ti,y-min", &pdata->y_min);
+ of_property_read_u16(node, "ti,x-max", &pdata->x_max);
+ of_property_read_u16(node, "ti,y-max", &pdata->y_max);
+
+ /*
+ * touchscreen-max-pressure gets parsed during
+ * touchscreen_parse_properties()
+ */
+ of_property_read_u16(node, "ti,pressure-min", &pdata->pressure_min);
+ if (!of_property_read_u32(node, "touchscreen-min-pressure", &value))
+ pdata->pressure_min = (u16) value;
+ of_property_read_u16(node, "ti,pressure-max", &pdata->pressure_max);
+
+ of_property_read_u16(node, "ti,debounce-max", &pdata->debounce_max);
+ if (!of_property_read_u32(node, "touchscreen-average-samples", &value))
+ pdata->debounce_max = (u16) value;
+ of_property_read_u16(node, "ti,debounce-tol", &pdata->debounce_tol);
+ of_property_read_u16(node, "ti,debounce-rep", &pdata->debounce_rep);
+
+ of_property_read_u32(node, "ti,pendown-gpio-debounce",
+ &pdata->gpio_pendown_debounce);
+
+ pdata->wakeup = of_property_read_bool(node, "wakeup-source") ||
+ of_property_read_bool(node, "linux,wakeup");
+
+ pdata->gpio_pendown = of_get_named_gpio(dev->of_node, "pendown-gpio", 0);
+
+ return pdata;
+}
+#else
+static const struct ads7846_platform_data *ads7846_probe_dt(struct device *dev)
+{
+ dev_err(dev, "no platform data defined\n");
+ return ERR_PTR(-EINVAL);
+}
+#endif
+
+static void ads7846_regulator_disable(void *regulator)
+{
+ regulator_disable(regulator);
+}
+
+static int ads7846_probe(struct spi_device *spi)
+{
+ const struct ads7846_platform_data *pdata;
+ struct ads7846 *ts;
+ struct device *dev = &spi->dev;
+ struct ads7846_packet *packet;
+ struct input_dev *input_dev;
+ unsigned long irq_flags;
+ int err;
+
+ if (!spi->irq) {
+ dev_dbg(dev, "no IRQ?\n");
+ return -EINVAL;
+ }
+
+ /* don't exceed max specified sample rate */
+ if (spi->max_speed_hz > (125000 * SAMPLE_BITS)) {
+ dev_err(dev, "f(sample) %d KHz?\n",
+ (spi->max_speed_hz/SAMPLE_BITS)/1000);
+ return -EINVAL;
+ }
+
+ /*
+ * We'd set TX word size 8 bits and RX word size to 13 bits ... except
+ * that even if the hardware can do that, the SPI controller driver
+ * may not. So we stick to very-portable 8 bit words, both RX and TX.
+ */
+ spi->bits_per_word = 8;
+ spi->mode &= ~SPI_MODE_X_MASK;
+ spi->mode |= SPI_MODE_0;
+ err = spi_setup(spi);
+ if (err < 0)
+ return err;
+
+ ts = devm_kzalloc(dev, sizeof(struct ads7846), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ packet = devm_kzalloc(dev, sizeof(struct ads7846_packet), GFP_KERNEL);
+ if (!packet)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ts);
+
+ ts->packet = packet;
+ ts->spi = spi;
+ ts->input = input_dev;
+
+ mutex_init(&ts->lock);
+ init_waitqueue_head(&ts->wait);
+
+ pdata = dev_get_platdata(dev);
+ if (!pdata) {
+ pdata = ads7846_probe_dt(dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ }
+
+ ts->model = pdata->model ? : 7846;
+ ts->vref_delay_usecs = pdata->vref_delay_usecs ? : 100;
+ ts->x_plate_ohms = pdata->x_plate_ohms ? : 400;
+ ts->vref_mv = pdata->vref_mv;
+
+ if (pdata->debounce_max) {
+ ts->debounce_max = pdata->debounce_max;
+ if (ts->debounce_max < 2)
+ ts->debounce_max = 2;
+ ts->debounce_tol = pdata->debounce_tol;
+ ts->debounce_rep = pdata->debounce_rep;
+ ts->filter = ads7846_debounce_filter;
+ ts->filter_data = ts;
+ } else {
+ ts->filter = ads7846_no_filter;
+ }
+
+ err = ads7846_setup_pendown(spi, ts, pdata);
+ if (err)
+ return err;
+
+ if (pdata->penirq_recheck_delay_usecs)
+ ts->penirq_recheck_delay_usecs =
+ pdata->penirq_recheck_delay_usecs;
+
+ ts->wait_for_sync = pdata->wait_for_sync ? : null_wait_for_sync;
+
+ snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev));
+ snprintf(ts->name, sizeof(ts->name), "ADS%d Touchscreen", ts->model);
+
+ input_dev->name = ts->name;
+ input_dev->phys = ts->phys;
+
+ input_dev->id.bustype = BUS_SPI;
+ input_dev->id.product = pdata->model;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X,
+ pdata->x_min ? : 0,
+ pdata->x_max ? : MAX_12BIT,
+ 0, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ pdata->y_min ? : 0,
+ pdata->y_max ? : MAX_12BIT,
+ 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE,
+ pdata->pressure_min, pdata->pressure_max, 0, 0);
+
+ /*
+ * Parse common framework properties. Must be done here to ensure the
+ * correct behaviour in case of using the legacy vendor bindings. The
+ * general binding value overrides the vendor specific one.
+ */
+ touchscreen_parse_properties(ts->input, false, &ts->core_prop);
+ ts->pressure_max = input_abs_get_max(input_dev, ABS_PRESSURE) ? : ~0;
+
+ /*
+ * Check if legacy ti,swap-xy binding is used instead of
+ * touchscreen-swapped-x-y
+ */
+ if (!ts->core_prop.swap_x_y && pdata->swap_xy) {
+ swap(input_dev->absinfo[ABS_X], input_dev->absinfo[ABS_Y]);
+ ts->core_prop.swap_x_y = true;
+ }
+
+ ads7846_setup_spi_msg(ts, pdata);
+
+ ts->reg = devm_regulator_get(dev, "vcc");
+ if (IS_ERR(ts->reg)) {
+ err = PTR_ERR(ts->reg);
+ dev_err(dev, "unable to get regulator: %d\n", err);
+ return err;
+ }
+
+ err = regulator_enable(ts->reg);
+ if (err) {
+ dev_err(dev, "unable to enable regulator: %d\n", err);
+ return err;
+ }
+
+ err = devm_add_action_or_reset(dev, ads7846_regulator_disable, ts->reg);
+ if (err)
+ return err;
+
+ irq_flags = pdata->irq_flags ? : IRQF_TRIGGER_FALLING;
+ irq_flags |= IRQF_ONESHOT;
+
+ err = devm_request_threaded_irq(dev, spi->irq,
+ ads7846_hard_irq, ads7846_irq,
+ irq_flags, dev->driver->name, ts);
+ if (err && err != -EPROBE_DEFER && !pdata->irq_flags) {
+ dev_info(dev,
+ "trying pin change workaround on irq %d\n", spi->irq);
+ irq_flags |= IRQF_TRIGGER_RISING;
+ err = devm_request_threaded_irq(dev, spi->irq,
+ ads7846_hard_irq, ads7846_irq,
+ irq_flags, dev->driver->name,
+ ts);
+ }
+
+ if (err) {
+ dev_dbg(dev, "irq %d busy?\n", spi->irq);
+ return err;
+ }
+
+ err = ads784x_hwmon_register(spi, ts);
+ if (err)
+ return err;
+
+ dev_info(dev, "touchscreen, irq %d\n", spi->irq);
+
+ /*
+ * Take a first sample, leaving nPENIRQ active and vREF off; avoid
+ * the touchscreen, in case it's not connected.
+ */
+ if (ts->model == 7845)
+ ads7845_read12_ser(dev, PWRDOWN);
+ else
+ (void) ads7846_read12_ser(dev, READ_12BIT_SER(vaux));
+
+ err = devm_device_add_group(dev, &ads784x_attr_group);
+ if (err)
+ return err;
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ device_init_wakeup(dev, pdata->wakeup);
+
+ /*
+ * If device does not carry platform data we must have allocated it
+ * when parsing DT data.
+ */
+ if (!dev_get_platdata(dev))
+ devm_kfree(dev, (void *)pdata);
+
+ return 0;
+}
+
+static void ads7846_remove(struct spi_device *spi)
+{
+ struct ads7846 *ts = spi_get_drvdata(spi);
+
+ ads7846_stop(ts);
+}
+
+static struct spi_driver ads7846_driver = {
+ .driver = {
+ .name = "ads7846",
+ .pm = &ads7846_pm,
+ .of_match_table = of_match_ptr(ads7846_dt_ids),
+ },
+ .probe = ads7846_probe,
+ .remove = ads7846_remove,
+};
+
+module_spi_driver(ads7846_driver);
+
+MODULE_DESCRIPTION("ADS7846 TouchScreen Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:ads7846");
diff --git a/drivers/input/touchscreen/ar1021_i2c.c b/drivers/input/touchscreen/ar1021_i2c.c
new file mode 100644
index 000000000..dc6a85362
--- /dev/null
+++ b/drivers/input/touchscreen/ar1021_i2c.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip AR1020 and AR1021 driver for I2C
+ *
+ * Author: Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+#define AR1021_TOUCH_PKG_SIZE 5
+
+#define AR1021_MAX_X 4095
+#define AR1021_MAX_Y 4095
+
+#define AR1021_CMD 0x55
+
+#define AR1021_CMD_ENABLE_TOUCH 0x12
+
+struct ar1021_i2c {
+ struct i2c_client *client;
+ struct input_dev *input;
+ u8 data[AR1021_TOUCH_PKG_SIZE];
+};
+
+static irqreturn_t ar1021_i2c_irq(int irq, void *dev_id)
+{
+ struct ar1021_i2c *ar1021 = dev_id;
+ struct input_dev *input = ar1021->input;
+ u8 *data = ar1021->data;
+ unsigned int x, y, button;
+ int retval;
+
+ retval = i2c_master_recv(ar1021->client,
+ ar1021->data, sizeof(ar1021->data));
+ if (retval != sizeof(ar1021->data))
+ goto out;
+
+ /* sync bit set ? */
+ if (!(data[0] & BIT(7)))
+ goto out;
+
+ button = data[0] & BIT(0);
+ x = ((data[2] & 0x1f) << 7) | (data[1] & 0x7f);
+ y = ((data[4] & 0x1f) << 7) | (data[3] & 0x7f);
+
+ input_report_abs(input, ABS_X, x);
+ input_report_abs(input, ABS_Y, y);
+ input_report_key(input, BTN_TOUCH, button);
+ input_sync(input);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int ar1021_i2c_open(struct input_dev *dev)
+{
+ static const u8 cmd_enable_touch[] = {
+ AR1021_CMD,
+ 0x01, /* number of bytes after this */
+ AR1021_CMD_ENABLE_TOUCH
+ };
+ struct ar1021_i2c *ar1021 = input_get_drvdata(dev);
+ struct i2c_client *client = ar1021->client;
+ int error;
+
+ error = i2c_master_send(ar1021->client, cmd_enable_touch,
+ sizeof(cmd_enable_touch));
+ if (error < 0)
+ return error;
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static void ar1021_i2c_close(struct input_dev *dev)
+{
+ struct ar1021_i2c *ar1021 = input_get_drvdata(dev);
+ struct i2c_client *client = ar1021->client;
+
+ disable_irq(client->irq);
+}
+
+static int ar1021_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ar1021_i2c *ar1021;
+ struct input_dev *input;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "i2c_check_functionality error\n");
+ return -ENXIO;
+ }
+
+ ar1021 = devm_kzalloc(&client->dev, sizeof(*ar1021), GFP_KERNEL);
+ if (!ar1021)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(&client->dev);
+ if (!input)
+ return -ENOMEM;
+
+ ar1021->client = client;
+ ar1021->input = input;
+
+ input->name = "ar1021 I2C Touchscreen";
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &client->dev;
+ input->open = ar1021_i2c_open;
+ input->close = ar1021_i2c_close;
+
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input, ABS_X, 0, AR1021_MAX_X, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, AR1021_MAX_Y, 0, 0);
+
+ input_set_drvdata(input, ar1021);
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, ar1021_i2c_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "ar1021_i2c", ar1021);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to enable IRQ, error: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(ar1021->input);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register input device, error: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused ar1021_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ disable_irq(client->irq);
+
+ return 0;
+}
+
+static int __maybe_unused ar1021_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ar1021_i2c_pm, ar1021_i2c_suspend, ar1021_i2c_resume);
+
+static const struct i2c_device_id ar1021_i2c_id[] = {
+ { "ar1021", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ar1021_i2c_id);
+
+static const struct of_device_id ar1021_i2c_of_match[] = {
+ { .compatible = "microchip,ar1021-i2c", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ar1021_i2c_of_match);
+
+static struct i2c_driver ar1021_i2c_driver = {
+ .driver = {
+ .name = "ar1021_i2c",
+ .pm = &ar1021_i2c_pm,
+ .of_match_table = ar1021_i2c_of_match,
+ },
+
+ .probe = ar1021_i2c_probe,
+ .id_table = ar1021_i2c_id,
+};
+module_i2c_driver(ar1021_i2c_driver);
+
+MODULE_AUTHOR("Christian Gmeiner <christian.gmeiner@gmail.com>");
+MODULE_DESCRIPTION("Microchip AR1020 and AR1021 I2C Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
new file mode 100644
index 000000000..ccecd1441
--- /dev/null
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -0,0 +1,3390 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Atmel maXTouch Touchscreen driver
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ * Copyright (C) 2011-2014 Atmel Corporation
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ *
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <asm/unaligned.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <dt-bindings/input/atmel-maxtouch.h>
+
+/* Firmware files */
+#define MXT_FW_NAME "maxtouch.fw"
+#define MXT_CFG_NAME "maxtouch.cfg"
+#define MXT_CFG_MAGIC "OBP_RAW V1"
+
+/* Registers */
+#define MXT_OBJECT_START 0x07
+#define MXT_OBJECT_SIZE 6
+#define MXT_INFO_CHECKSUM_SIZE 3
+#define MXT_MAX_BLOCK_WRITE 256
+
+/* Object types */
+#define MXT_DEBUG_DIAGNOSTIC_T37 37
+#define MXT_GEN_MESSAGE_T5 5
+#define MXT_GEN_COMMAND_T6 6
+#define MXT_GEN_POWER_T7 7
+#define MXT_GEN_ACQUIRE_T8 8
+#define MXT_GEN_DATASOURCE_T53 53
+#define MXT_TOUCH_MULTI_T9 9
+#define MXT_TOUCH_KEYARRAY_T15 15
+#define MXT_TOUCH_PROXIMITY_T23 23
+#define MXT_TOUCH_PROXKEY_T52 52
+#define MXT_PROCI_GRIPFACE_T20 20
+#define MXT_PROCG_NOISE_T22 22
+#define MXT_PROCI_ONETOUCH_T24 24
+#define MXT_PROCI_TWOTOUCH_T27 27
+#define MXT_PROCI_GRIP_T40 40
+#define MXT_PROCI_PALM_T41 41
+#define MXT_PROCI_TOUCHSUPPRESSION_T42 42
+#define MXT_PROCI_STYLUS_T47 47
+#define MXT_PROCG_NOISESUPPRESSION_T48 48
+#define MXT_SPT_COMMSCONFIG_T18 18
+#define MXT_SPT_GPIOPWM_T19 19
+#define MXT_SPT_SELFTEST_T25 25
+#define MXT_SPT_CTECONFIG_T28 28
+#define MXT_SPT_USERDATA_T38 38
+#define MXT_SPT_DIGITIZER_T43 43
+#define MXT_SPT_MESSAGECOUNT_T44 44
+#define MXT_SPT_CTECONFIG_T46 46
+#define MXT_SPT_DYNAMICCONFIGURATIONCONTAINER_T71 71
+#define MXT_TOUCH_MULTITOUCHSCREEN_T100 100
+
+/* MXT_GEN_MESSAGE_T5 object */
+#define MXT_RPTID_NOMSG 0xff
+
+/* MXT_GEN_COMMAND_T6 field */
+#define MXT_COMMAND_RESET 0
+#define MXT_COMMAND_BACKUPNV 1
+#define MXT_COMMAND_CALIBRATE 2
+#define MXT_COMMAND_REPORTALL 3
+#define MXT_COMMAND_DIAGNOSTIC 5
+
+/* Define for T6 status byte */
+#define MXT_T6_STATUS_RESET BIT(7)
+#define MXT_T6_STATUS_OFL BIT(6)
+#define MXT_T6_STATUS_SIGERR BIT(5)
+#define MXT_T6_STATUS_CAL BIT(4)
+#define MXT_T6_STATUS_CFGERR BIT(3)
+#define MXT_T6_STATUS_COMSERR BIT(2)
+
+/* MXT_GEN_POWER_T7 field */
+struct t7_config {
+ u8 idle;
+ u8 active;
+} __packed;
+
+#define MXT_POWER_CFG_RUN 0
+#define MXT_POWER_CFG_DEEPSLEEP 1
+
+/* MXT_TOUCH_MULTI_T9 field */
+#define MXT_T9_CTRL 0
+#define MXT_T9_XSIZE 3
+#define MXT_T9_YSIZE 4
+#define MXT_T9_ORIENT 9
+#define MXT_T9_RANGE 18
+
+/* MXT_TOUCH_MULTI_T9 status */
+#define MXT_T9_UNGRIP BIT(0)
+#define MXT_T9_SUPPRESS BIT(1)
+#define MXT_T9_AMP BIT(2)
+#define MXT_T9_VECTOR BIT(3)
+#define MXT_T9_MOVE BIT(4)
+#define MXT_T9_RELEASE BIT(5)
+#define MXT_T9_PRESS BIT(6)
+#define MXT_T9_DETECT BIT(7)
+
+struct t9_range {
+ __le16 x;
+ __le16 y;
+} __packed;
+
+/* MXT_TOUCH_MULTI_T9 orient */
+#define MXT_T9_ORIENT_SWITCH BIT(0)
+#define MXT_T9_ORIENT_INVERTX BIT(1)
+#define MXT_T9_ORIENT_INVERTY BIT(2)
+
+/* MXT_SPT_COMMSCONFIG_T18 */
+#define MXT_COMMS_CTRL 0
+#define MXT_COMMS_CMD 1
+#define MXT_COMMS_RETRIGEN BIT(6)
+
+/* MXT_DEBUG_DIAGNOSTIC_T37 */
+#define MXT_DIAGNOSTIC_PAGEUP 0x01
+#define MXT_DIAGNOSTIC_DELTAS 0x10
+#define MXT_DIAGNOSTIC_REFS 0x11
+#define MXT_DIAGNOSTIC_SIZE 128
+
+#define MXT_FAMILY_1386 160
+#define MXT1386_COLUMNS 3
+#define MXT1386_PAGES_PER_COLUMN 8
+
+struct t37_debug {
+#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37
+ u8 mode;
+ u8 page;
+ u8 data[MXT_DIAGNOSTIC_SIZE];
+#endif
+};
+
+/* Define for MXT_GEN_COMMAND_T6 */
+#define MXT_BOOT_VALUE 0xa5
+#define MXT_RESET_VALUE 0x01
+#define MXT_BACKUP_VALUE 0x55
+
+/* T100 Multiple Touch Touchscreen */
+#define MXT_T100_CTRL 0
+#define MXT_T100_CFG1 1
+#define MXT_T100_TCHAUX 3
+#define MXT_T100_XSIZE 9
+#define MXT_T100_XRANGE 13
+#define MXT_T100_YSIZE 20
+#define MXT_T100_YRANGE 24
+
+#define MXT_T100_CFG_SWITCHXY BIT(5)
+#define MXT_T100_CFG_INVERTY BIT(6)
+#define MXT_T100_CFG_INVERTX BIT(7)
+
+#define MXT_T100_TCHAUX_VECT BIT(0)
+#define MXT_T100_TCHAUX_AMPL BIT(1)
+#define MXT_T100_TCHAUX_AREA BIT(2)
+
+#define MXT_T100_DETECT BIT(7)
+#define MXT_T100_TYPE_MASK 0x70
+
+enum t100_type {
+ MXT_T100_TYPE_FINGER = 1,
+ MXT_T100_TYPE_PASSIVE_STYLUS = 2,
+ MXT_T100_TYPE_HOVERING_FINGER = 4,
+ MXT_T100_TYPE_GLOVE = 5,
+ MXT_T100_TYPE_LARGE_TOUCH = 6,
+};
+
+#define MXT_DISTANCE_ACTIVE_TOUCH 0
+#define MXT_DISTANCE_HOVERING 1
+
+#define MXT_TOUCH_MAJOR_DEFAULT 1
+#define MXT_PRESSURE_DEFAULT 1
+
+/* Delay times */
+#define MXT_BACKUP_TIME 50 /* msec */
+#define MXT_RESET_GPIO_TIME 20 /* msec */
+#define MXT_RESET_INVALID_CHG 100 /* msec */
+#define MXT_RESET_TIME 200 /* msec */
+#define MXT_RESET_TIMEOUT 3000 /* msec */
+#define MXT_CRC_TIMEOUT 1000 /* msec */
+#define MXT_FW_RESET_TIME 3000 /* msec */
+#define MXT_FW_CHG_TIMEOUT 300 /* msec */
+#define MXT_WAKEUP_TIME 25 /* msec */
+
+/* Command to unlock bootloader */
+#define MXT_UNLOCK_CMD_MSB 0xaa
+#define MXT_UNLOCK_CMD_LSB 0xdc
+
+/* Bootloader mode status */
+#define MXT_WAITING_BOOTLOAD_CMD 0xc0 /* valid 7 6 bit only */
+#define MXT_WAITING_FRAME_DATA 0x80 /* valid 7 6 bit only */
+#define MXT_FRAME_CRC_CHECK 0x02
+#define MXT_FRAME_CRC_FAIL 0x03
+#define MXT_FRAME_CRC_PASS 0x04
+#define MXT_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */
+#define MXT_BOOT_STATUS_MASK 0x3f
+#define MXT_BOOT_EXTENDED_ID BIT(5)
+#define MXT_BOOT_ID_MASK 0x1f
+
+/* Touchscreen absolute values */
+#define MXT_MAX_AREA 0xff
+
+#define MXT_PIXELS_PER_MM 20
+
+struct mxt_info {
+ u8 family_id;
+ u8 variant_id;
+ u8 version;
+ u8 build;
+ u8 matrix_xsize;
+ u8 matrix_ysize;
+ u8 object_num;
+};
+
+struct mxt_object {
+ u8 type;
+ u16 start_address;
+ u8 size_minus_one;
+ u8 instances_minus_one;
+ u8 num_report_ids;
+} __packed;
+
+struct mxt_dbg {
+ u16 t37_address;
+ u16 diag_cmd_address;
+ struct t37_debug *t37_buf;
+ unsigned int t37_pages;
+ unsigned int t37_nodes;
+
+ struct v4l2_device v4l2;
+ struct v4l2_pix_format format;
+ struct video_device vdev;
+ struct vb2_queue queue;
+ struct mutex lock;
+ int input;
+};
+
+enum v4l_dbg_inputs {
+ MXT_V4L_INPUT_DELTAS,
+ MXT_V4L_INPUT_REFS,
+ MXT_V4L_INPUT_MAX,
+};
+
+enum mxt_suspend_mode {
+ MXT_SUSPEND_DEEP_SLEEP = 0,
+ MXT_SUSPEND_T9_CTRL = 1,
+};
+
+/* Config update context */
+struct mxt_cfg {
+ u8 *raw;
+ size_t raw_size;
+ off_t raw_pos;
+
+ u8 *mem;
+ size_t mem_size;
+ int start_ofs;
+
+ struct mxt_info info;
+};
+
+/* Each client has this additional data */
+struct mxt_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ char phys[64]; /* device physical location */
+ struct mxt_object *object_table;
+ struct mxt_info *info;
+ void *raw_info_block;
+ unsigned int irq;
+ unsigned int max_x;
+ unsigned int max_y;
+ bool invertx;
+ bool inverty;
+ bool xy_switch;
+ u8 xsize;
+ u8 ysize;
+ bool in_bootloader;
+ u16 mem_size;
+ u8 t100_aux_ampl;
+ u8 t100_aux_area;
+ u8 t100_aux_vect;
+ u8 max_reportid;
+ u32 config_crc;
+ u32 info_crc;
+ u8 bootloader_addr;
+ u8 *msg_buf;
+ u8 t6_status;
+ bool update_input;
+ u8 last_message_count;
+ u8 num_touchids;
+ u8 multitouch;
+ struct t7_config t7_cfg;
+ struct mxt_dbg dbg;
+ struct regulator_bulk_data regulators[2];
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *wake_gpio;
+ bool use_retrigen_workaround;
+
+ /* Cached parameters from object table */
+ u16 T5_address;
+ u8 T5_msg_size;
+ u8 T6_reportid;
+ u16 T6_address;
+ u16 T7_address;
+ u16 T71_address;
+ u8 T9_reportid_min;
+ u8 T9_reportid_max;
+ u16 T18_address;
+ u8 T19_reportid;
+ u16 T44_address;
+ u8 T100_reportid_min;
+ u8 T100_reportid_max;
+
+ /* for fw update in bootloader */
+ struct completion bl_completion;
+
+ /* for reset handling */
+ struct completion reset_completion;
+
+ /* for config update handling */
+ struct completion crc_completion;
+
+ u32 *t19_keymap;
+ unsigned int t19_num_keys;
+
+ enum mxt_suspend_mode suspend_mode;
+
+ u32 wakeup_method;
+};
+
+struct mxt_vb2_buffer {
+ struct vb2_buffer vb;
+ struct list_head list;
+};
+
+static size_t mxt_obj_size(const struct mxt_object *obj)
+{
+ return obj->size_minus_one + 1;
+}
+
+static size_t mxt_obj_instances(const struct mxt_object *obj)
+{
+ return obj->instances_minus_one + 1;
+}
+
+static bool mxt_object_readable(unsigned int type)
+{
+ switch (type) {
+ case MXT_GEN_COMMAND_T6:
+ case MXT_GEN_POWER_T7:
+ case MXT_GEN_ACQUIRE_T8:
+ case MXT_GEN_DATASOURCE_T53:
+ case MXT_TOUCH_MULTI_T9:
+ case MXT_TOUCH_KEYARRAY_T15:
+ case MXT_TOUCH_PROXIMITY_T23:
+ case MXT_TOUCH_PROXKEY_T52:
+ case MXT_TOUCH_MULTITOUCHSCREEN_T100:
+ case MXT_PROCI_GRIPFACE_T20:
+ case MXT_PROCG_NOISE_T22:
+ case MXT_PROCI_ONETOUCH_T24:
+ case MXT_PROCI_TWOTOUCH_T27:
+ case MXT_PROCI_GRIP_T40:
+ case MXT_PROCI_PALM_T41:
+ case MXT_PROCI_TOUCHSUPPRESSION_T42:
+ case MXT_PROCI_STYLUS_T47:
+ case MXT_PROCG_NOISESUPPRESSION_T48:
+ case MXT_SPT_COMMSCONFIG_T18:
+ case MXT_SPT_GPIOPWM_T19:
+ case MXT_SPT_SELFTEST_T25:
+ case MXT_SPT_CTECONFIG_T28:
+ case MXT_SPT_USERDATA_T38:
+ case MXT_SPT_DIGITIZER_T43:
+ case MXT_SPT_CTECONFIG_T46:
+ case MXT_SPT_DYNAMICCONFIGURATIONCONTAINER_T71:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void mxt_dump_message(struct mxt_data *data, u8 *message)
+{
+ dev_dbg(&data->client->dev, "message: %*ph\n",
+ data->T5_msg_size, message);
+}
+
+static int mxt_wait_for_completion(struct mxt_data *data,
+ struct completion *comp,
+ unsigned int timeout_ms)
+{
+ struct device *dev = &data->client->dev;
+ unsigned long timeout = msecs_to_jiffies(timeout_ms);
+ long ret;
+
+ ret = wait_for_completion_interruptible_timeout(comp, timeout);
+ if (ret < 0) {
+ return ret;
+ } else if (ret == 0) {
+ dev_err(dev, "Wait for completion timed out.\n");
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+static int mxt_bootloader_read(struct mxt_data *data,
+ u8 *val, unsigned int count)
+{
+ int ret;
+ struct i2c_msg msg;
+
+ msg.addr = data->bootloader_addr;
+ msg.flags = data->client->flags & I2C_M_TEN;
+ msg.flags |= I2C_M_RD;
+ msg.len = count;
+ msg.buf = val;
+
+ ret = i2c_transfer(data->client->adapter, &msg, 1);
+ if (ret == 1) {
+ ret = 0;
+ } else {
+ ret = ret < 0 ? ret : -EIO;
+ dev_err(&data->client->dev, "%s: i2c recv failed (%d)\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static int mxt_bootloader_write(struct mxt_data *data,
+ const u8 * const val, unsigned int count)
+{
+ int ret;
+ struct i2c_msg msg;
+
+ msg.addr = data->bootloader_addr;
+ msg.flags = data->client->flags & I2C_M_TEN;
+ msg.len = count;
+ msg.buf = (u8 *)val;
+
+ ret = i2c_transfer(data->client->adapter, &msg, 1);
+ if (ret == 1) {
+ ret = 0;
+ } else {
+ ret = ret < 0 ? ret : -EIO;
+ dev_err(&data->client->dev, "%s: i2c send failed (%d)\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static int mxt_lookup_bootloader_address(struct mxt_data *data, bool retry)
+{
+ u8 appmode = data->client->addr;
+ u8 bootloader;
+ u8 family_id = data->info ? data->info->family_id : 0;
+
+ switch (appmode) {
+ case 0x4a:
+ case 0x4b:
+ /* Chips after 1664S use different scheme */
+ if (retry || family_id >= 0xa2) {
+ bootloader = appmode - 0x24;
+ break;
+ }
+ fallthrough; /* for normal case */
+ case 0x4c:
+ case 0x4d:
+ case 0x5a:
+ case 0x5b:
+ bootloader = appmode - 0x26;
+ break;
+
+ default:
+ dev_err(&data->client->dev,
+ "Appmode i2c address 0x%02x not found\n",
+ appmode);
+ return -EINVAL;
+ }
+
+ data->bootloader_addr = bootloader;
+ return 0;
+}
+
+static int mxt_probe_bootloader(struct mxt_data *data, bool alt_address)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+ u8 val;
+ bool crc_failure;
+
+ error = mxt_lookup_bootloader_address(data, alt_address);
+ if (error)
+ return error;
+
+ error = mxt_bootloader_read(data, &val, 1);
+ if (error)
+ return error;
+
+ /* Check app crc fail mode */
+ crc_failure = (val & ~MXT_BOOT_STATUS_MASK) == MXT_APP_CRC_FAIL;
+
+ dev_err(dev, "Detected bootloader, status:%02X%s\n",
+ val, crc_failure ? ", APP_CRC_FAIL" : "");
+
+ return 0;
+}
+
+static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val)
+{
+ struct device *dev = &data->client->dev;
+ u8 buf[3];
+
+ if (val & MXT_BOOT_EXTENDED_ID) {
+ if (mxt_bootloader_read(data, &buf[0], 3) != 0) {
+ dev_err(dev, "%s: i2c failure\n", __func__);
+ return val;
+ }
+
+ dev_dbg(dev, "Bootloader ID:%d Version:%d\n", buf[1], buf[2]);
+
+ return buf[0];
+ } else {
+ dev_dbg(dev, "Bootloader ID:%d\n", val & MXT_BOOT_ID_MASK);
+
+ return val;
+ }
+}
+
+static int mxt_check_bootloader(struct mxt_data *data, unsigned int state,
+ bool wait)
+{
+ struct device *dev = &data->client->dev;
+ u8 val;
+ int ret;
+
+recheck:
+ if (wait) {
+ /*
+ * In application update mode, the interrupt
+ * line signals state transitions. We must wait for the
+ * CHG assertion before reading the status byte.
+ * Once the status byte has been read, the line is deasserted.
+ */
+ ret = mxt_wait_for_completion(data, &data->bl_completion,
+ MXT_FW_CHG_TIMEOUT);
+ if (ret) {
+ /*
+ * TODO: handle -ERESTARTSYS better by terminating
+ * fw update process before returning to userspace
+ * by writing length 0x000 to device (iff we are in
+ * WAITING_FRAME_DATA state).
+ */
+ dev_err(dev, "Update wait error %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = mxt_bootloader_read(data, &val, 1);
+ if (ret)
+ return ret;
+
+ if (state == MXT_WAITING_BOOTLOAD_CMD)
+ val = mxt_get_bootloader_version(data, val);
+
+ switch (state) {
+ case MXT_WAITING_BOOTLOAD_CMD:
+ case MXT_WAITING_FRAME_DATA:
+ case MXT_APP_CRC_FAIL:
+ val &= ~MXT_BOOT_STATUS_MASK;
+ break;
+ case MXT_FRAME_CRC_PASS:
+ if (val == MXT_FRAME_CRC_CHECK) {
+ goto recheck;
+ } else if (val == MXT_FRAME_CRC_FAIL) {
+ dev_err(dev, "Bootloader CRC fail\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (val != state) {
+ dev_err(dev, "Invalid bootloader state %02X != %02X\n",
+ val, state);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mxt_send_bootloader_cmd(struct mxt_data *data, bool unlock)
+{
+ u8 buf[2];
+
+ if (unlock) {
+ buf[0] = MXT_UNLOCK_CMD_LSB;
+ buf[1] = MXT_UNLOCK_CMD_MSB;
+ } else {
+ buf[0] = 0x01;
+ buf[1] = 0x01;
+ }
+
+ return mxt_bootloader_write(data, buf, sizeof(buf));
+}
+
+static bool mxt_wakeup_toggle(struct i2c_client *client,
+ bool wake_up, bool in_i2c)
+{
+ struct mxt_data *data = i2c_get_clientdata(client);
+
+ switch (data->wakeup_method) {
+ case ATMEL_MXT_WAKEUP_I2C_SCL:
+ if (!in_i2c)
+ return false;
+ break;
+
+ case ATMEL_MXT_WAKEUP_GPIO:
+ if (in_i2c)
+ return false;
+
+ gpiod_set_value(data->wake_gpio, wake_up);
+ break;
+
+ default:
+ return false;
+ }
+
+ if (wake_up) {
+ dev_dbg(&client->dev, "waking up controller\n");
+
+ msleep(MXT_WAKEUP_TIME);
+ }
+
+ return true;
+}
+
+static int __mxt_read_reg(struct i2c_client *client,
+ u16 reg, u16 len, void *val)
+{
+ struct i2c_msg xfer[2];
+ bool retried = false;
+ u8 buf[2];
+ int ret;
+
+ buf[0] = reg & 0xff;
+ buf[1] = (reg >> 8) & 0xff;
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 2;
+ xfer[0].buf = buf;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = len;
+ xfer[1].buf = val;
+
+retry:
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret == 2) {
+ ret = 0;
+ } else if (!retried && mxt_wakeup_toggle(client, true, true)) {
+ retried = true;
+ goto retry;
+ } else {
+ if (ret >= 0)
+ ret = -EIO;
+ dev_err(&client->dev, "%s: i2c transfer failed (%d)\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static int __mxt_write_reg(struct i2c_client *client, u16 reg, u16 len,
+ const void *val)
+{
+ bool retried = false;
+ u8 *buf;
+ size_t count;
+ int ret;
+
+ count = len + 2;
+ buf = kmalloc(count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = reg & 0xff;
+ buf[1] = (reg >> 8) & 0xff;
+ memcpy(&buf[2], val, len);
+
+retry:
+ ret = i2c_master_send(client, buf, count);
+ if (ret == count) {
+ ret = 0;
+ } else if (!retried && mxt_wakeup_toggle(client, true, true)) {
+ retried = true;
+ goto retry;
+ } else {
+ if (ret >= 0)
+ ret = -EIO;
+ dev_err(&client->dev, "%s: i2c send failed (%d)\n",
+ __func__, ret);
+ }
+
+ kfree(buf);
+ return ret;
+}
+
+static int mxt_write_reg(struct i2c_client *client, u16 reg, u8 val)
+{
+ return __mxt_write_reg(client, reg, 1, &val);
+}
+
+static struct mxt_object *
+mxt_get_object(struct mxt_data *data, u8 type)
+{
+ struct mxt_object *object;
+ int i;
+
+ for (i = 0; i < data->info->object_num; i++) {
+ object = data->object_table + i;
+ if (object->type == type)
+ return object;
+ }
+
+ dev_warn(&data->client->dev, "Invalid object type T%u\n", type);
+ return NULL;
+}
+
+static void mxt_proc_t6_messages(struct mxt_data *data, u8 *msg)
+{
+ struct device *dev = &data->client->dev;
+ u8 status = msg[1];
+ u32 crc = msg[2] | (msg[3] << 8) | (msg[4] << 16);
+
+ if (crc != data->config_crc) {
+ data->config_crc = crc;
+ dev_dbg(dev, "T6 Config Checksum: 0x%06X\n", crc);
+ }
+
+ complete(&data->crc_completion);
+
+ /* Detect reset */
+ if (status & MXT_T6_STATUS_RESET)
+ complete(&data->reset_completion);
+
+ /* Output debug if status has changed */
+ if (status != data->t6_status)
+ dev_dbg(dev, "T6 Status 0x%02X%s%s%s%s%s%s%s\n",
+ status,
+ status == 0 ? " OK" : "",
+ status & MXT_T6_STATUS_RESET ? " RESET" : "",
+ status & MXT_T6_STATUS_OFL ? " OFL" : "",
+ status & MXT_T6_STATUS_SIGERR ? " SIGERR" : "",
+ status & MXT_T6_STATUS_CAL ? " CAL" : "",
+ status & MXT_T6_STATUS_CFGERR ? " CFGERR" : "",
+ status & MXT_T6_STATUS_COMSERR ? " COMSERR" : "");
+
+ /* Save current status */
+ data->t6_status = status;
+}
+
+static int mxt_write_object(struct mxt_data *data,
+ u8 type, u8 offset, u8 val)
+{
+ struct mxt_object *object;
+ u16 reg;
+
+ object = mxt_get_object(data, type);
+ if (!object || offset >= mxt_obj_size(object))
+ return -EINVAL;
+
+ reg = object->start_address;
+ return mxt_write_reg(data->client, reg + offset, val);
+}
+
+static void mxt_input_button(struct mxt_data *data, u8 *message)
+{
+ struct input_dev *input = data->input_dev;
+ int i;
+
+ for (i = 0; i < data->t19_num_keys; i++) {
+ if (data->t19_keymap[i] == KEY_RESERVED)
+ continue;
+
+ /* Active-low switch */
+ input_report_key(input, data->t19_keymap[i],
+ !(message[1] & BIT(i)));
+ }
+}
+
+static void mxt_input_sync(struct mxt_data *data)
+{
+ input_mt_report_pointer_emulation(data->input_dev,
+ data->t19_num_keys);
+ input_sync(data->input_dev);
+}
+
+static void mxt_proc_t9_message(struct mxt_data *data, u8 *message)
+{
+ struct device *dev = &data->client->dev;
+ struct input_dev *input_dev = data->input_dev;
+ int id;
+ u8 status;
+ int x;
+ int y;
+ int area;
+ int amplitude;
+
+ id = message[0] - data->T9_reportid_min;
+ status = message[1];
+ x = (message[2] << 4) | ((message[4] >> 4) & 0xf);
+ y = (message[3] << 4) | ((message[4] & 0xf));
+
+ /* Handle 10/12 bit switching */
+ if (data->max_x < 1024)
+ x >>= 2;
+ if (data->max_y < 1024)
+ y >>= 2;
+
+ area = message[5];
+ amplitude = message[6];
+
+ dev_dbg(dev,
+ "[%u] %c%c%c%c%c%c%c%c x: %5u y: %5u area: %3u amp: %3u\n",
+ id,
+ (status & MXT_T9_DETECT) ? 'D' : '.',
+ (status & MXT_T9_PRESS) ? 'P' : '.',
+ (status & MXT_T9_RELEASE) ? 'R' : '.',
+ (status & MXT_T9_MOVE) ? 'M' : '.',
+ (status & MXT_T9_VECTOR) ? 'V' : '.',
+ (status & MXT_T9_AMP) ? 'A' : '.',
+ (status & MXT_T9_SUPPRESS) ? 'S' : '.',
+ (status & MXT_T9_UNGRIP) ? 'U' : '.',
+ x, y, area, amplitude);
+
+ input_mt_slot(input_dev, id);
+
+ if (status & MXT_T9_DETECT) {
+ /*
+ * Multiple bits may be set if the host is slow to read
+ * the status messages, indicating all the events that
+ * have happened.
+ */
+ if (status & MXT_T9_RELEASE) {
+ input_mt_report_slot_inactive(input_dev);
+ mxt_input_sync(data);
+ }
+
+ /* if active, pressure must be non-zero */
+ if (!amplitude)
+ amplitude = MXT_PRESSURE_DEFAULT;
+
+ /* Touch active */
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 1);
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+ input_report_abs(input_dev, ABS_MT_PRESSURE, amplitude);
+ input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, area);
+ } else {
+ /* Touch no longer active, close out slot */
+ input_mt_report_slot_inactive(input_dev);
+ }
+
+ data->update_input = true;
+}
+
+static void mxt_proc_t100_message(struct mxt_data *data, u8 *message)
+{
+ struct device *dev = &data->client->dev;
+ struct input_dev *input_dev = data->input_dev;
+ int id;
+ u8 status;
+ u8 type = 0;
+ u16 x;
+ u16 y;
+ int distance = 0;
+ int tool = 0;
+ u8 major = 0;
+ u8 pressure = 0;
+ u8 orientation = 0;
+
+ id = message[0] - data->T100_reportid_min - 2;
+
+ /* ignore SCRSTATUS events */
+ if (id < 0)
+ return;
+
+ status = message[1];
+ x = get_unaligned_le16(&message[2]);
+ y = get_unaligned_le16(&message[4]);
+
+ if (status & MXT_T100_DETECT) {
+ type = (status & MXT_T100_TYPE_MASK) >> 4;
+
+ switch (type) {
+ case MXT_T100_TYPE_HOVERING_FINGER:
+ tool = MT_TOOL_FINGER;
+ distance = MXT_DISTANCE_HOVERING;
+
+ if (data->t100_aux_vect)
+ orientation = message[data->t100_aux_vect];
+
+ break;
+
+ case MXT_T100_TYPE_FINGER:
+ case MXT_T100_TYPE_GLOVE:
+ tool = MT_TOOL_FINGER;
+ distance = MXT_DISTANCE_ACTIVE_TOUCH;
+
+ if (data->t100_aux_area)
+ major = message[data->t100_aux_area];
+
+ if (data->t100_aux_ampl)
+ pressure = message[data->t100_aux_ampl];
+
+ if (data->t100_aux_vect)
+ orientation = message[data->t100_aux_vect];
+
+ break;
+
+ case MXT_T100_TYPE_PASSIVE_STYLUS:
+ tool = MT_TOOL_PEN;
+
+ /*
+ * Passive stylus is reported with size zero so
+ * hardcode.
+ */
+ major = MXT_TOUCH_MAJOR_DEFAULT;
+
+ if (data->t100_aux_ampl)
+ pressure = message[data->t100_aux_ampl];
+
+ break;
+
+ case MXT_T100_TYPE_LARGE_TOUCH:
+ /* Ignore suppressed touch */
+ break;
+
+ default:
+ dev_dbg(dev, "Unexpected T100 type\n");
+ return;
+ }
+ }
+
+ /*
+ * Values reported should be non-zero if tool is touching the
+ * device
+ */
+ if (!pressure && type != MXT_T100_TYPE_HOVERING_FINGER)
+ pressure = MXT_PRESSURE_DEFAULT;
+
+ input_mt_slot(input_dev, id);
+
+ if (status & MXT_T100_DETECT) {
+ dev_dbg(dev, "[%u] type:%u x:%u y:%u a:%02X p:%02X v:%02X\n",
+ id, type, x, y, major, pressure, orientation);
+
+ input_mt_report_slot_state(input_dev, tool, 1);
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+ input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, major);
+ input_report_abs(input_dev, ABS_MT_PRESSURE, pressure);
+ input_report_abs(input_dev, ABS_MT_DISTANCE, distance);
+ input_report_abs(input_dev, ABS_MT_ORIENTATION, orientation);
+ } else {
+ dev_dbg(dev, "[%u] release\n", id);
+
+ /* close out slot */
+ input_mt_report_slot_inactive(input_dev);
+ }
+
+ data->update_input = true;
+}
+
+static int mxt_proc_message(struct mxt_data *data, u8 *message)
+{
+ u8 report_id = message[0];
+
+ if (report_id == MXT_RPTID_NOMSG)
+ return 0;
+
+ if (report_id == data->T6_reportid) {
+ mxt_proc_t6_messages(data, message);
+ } else if (!data->input_dev) {
+ /*
+ * Do not report events if input device
+ * is not yet registered.
+ */
+ mxt_dump_message(data, message);
+ } else if (report_id >= data->T9_reportid_min &&
+ report_id <= data->T9_reportid_max) {
+ mxt_proc_t9_message(data, message);
+ } else if (report_id >= data->T100_reportid_min &&
+ report_id <= data->T100_reportid_max) {
+ mxt_proc_t100_message(data, message);
+ } else if (report_id == data->T19_reportid) {
+ mxt_input_button(data, message);
+ data->update_input = true;
+ } else {
+ mxt_dump_message(data, message);
+ }
+
+ return 1;
+}
+
+static int mxt_read_and_process_messages(struct mxt_data *data, u8 count)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ int i;
+ u8 num_valid = 0;
+
+ /* Safety check for msg_buf */
+ if (count > data->max_reportid)
+ return -EINVAL;
+
+ /* Process remaining messages if necessary */
+ ret = __mxt_read_reg(data->client, data->T5_address,
+ data->T5_msg_size * count, data->msg_buf);
+ if (ret) {
+ dev_err(dev, "Failed to read %u messages (%d)\n", count, ret);
+ return ret;
+ }
+
+ for (i = 0; i < count; i++) {
+ ret = mxt_proc_message(data,
+ data->msg_buf + data->T5_msg_size * i);
+
+ if (ret == 1)
+ num_valid++;
+ }
+
+ /* return number of messages read */
+ return num_valid;
+}
+
+static irqreturn_t mxt_process_messages_t44(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ u8 count, num_left;
+
+ /* Read T44 and T5 together */
+ ret = __mxt_read_reg(data->client, data->T44_address,
+ data->T5_msg_size + 1, data->msg_buf);
+ if (ret) {
+ dev_err(dev, "Failed to read T44 and T5 (%d)\n", ret);
+ return IRQ_NONE;
+ }
+
+ count = data->msg_buf[0];
+
+ /*
+ * This condition may be caused by the CHG line being configured in
+ * Mode 0. It results in unnecessary I2C operations but it is benign.
+ */
+ if (count == 0)
+ return IRQ_NONE;
+
+ if (count > data->max_reportid) {
+ dev_warn(dev, "T44 count %d exceeded max report id\n", count);
+ count = data->max_reportid;
+ }
+
+ /* Process first message */
+ ret = mxt_proc_message(data, data->msg_buf + 1);
+ if (ret < 0) {
+ dev_warn(dev, "Unexpected invalid message\n");
+ return IRQ_NONE;
+ }
+
+ num_left = count - 1;
+
+ /* Process remaining messages if necessary */
+ if (num_left) {
+ ret = mxt_read_and_process_messages(data, num_left);
+ if (ret < 0)
+ goto end;
+ else if (ret != num_left)
+ dev_warn(dev, "Unexpected invalid message\n");
+ }
+
+end:
+ if (data->update_input) {
+ mxt_input_sync(data);
+ data->update_input = false;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mxt_process_messages_until_invalid(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int count, read;
+ u8 tries = 2;
+
+ count = data->max_reportid;
+
+ /* Read messages until we force an invalid */
+ do {
+ read = mxt_read_and_process_messages(data, count);
+ if (read < count)
+ return 0;
+ } while (--tries);
+
+ if (data->update_input) {
+ mxt_input_sync(data);
+ data->update_input = false;
+ }
+
+ dev_err(dev, "CHG pin isn't cleared\n");
+ return -EBUSY;
+}
+
+static irqreturn_t mxt_process_messages(struct mxt_data *data)
+{
+ int total_handled, num_handled;
+ u8 count = data->last_message_count;
+
+ if (count < 1 || count > data->max_reportid)
+ count = 1;
+
+ /* include final invalid message */
+ total_handled = mxt_read_and_process_messages(data, count + 1);
+ if (total_handled < 0)
+ return IRQ_NONE;
+ /* if there were invalid messages, then we are done */
+ else if (total_handled <= count)
+ goto update_count;
+
+ /* keep reading two msgs until one is invalid or reportid limit */
+ do {
+ num_handled = mxt_read_and_process_messages(data, 2);
+ if (num_handled < 0)
+ return IRQ_NONE;
+
+ total_handled += num_handled;
+
+ if (num_handled < 2)
+ break;
+ } while (total_handled < data->num_touchids);
+
+update_count:
+ data->last_message_count = total_handled;
+
+ if (data->update_input) {
+ mxt_input_sync(data);
+ data->update_input = false;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxt_interrupt(int irq, void *dev_id)
+{
+ struct mxt_data *data = dev_id;
+
+ if (data->in_bootloader) {
+ /* bootloader state transition completion */
+ complete(&data->bl_completion);
+ return IRQ_HANDLED;
+ }
+
+ if (!data->object_table)
+ return IRQ_HANDLED;
+
+ if (data->T44_address) {
+ return mxt_process_messages_t44(data);
+ } else {
+ return mxt_process_messages(data);
+ }
+}
+
+static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset,
+ u8 value, bool wait)
+{
+ u16 reg;
+ u8 command_register;
+ int timeout_counter = 0;
+ int ret;
+
+ reg = data->T6_address + cmd_offset;
+
+ ret = mxt_write_reg(data->client, reg, value);
+ if (ret)
+ return ret;
+
+ if (!wait)
+ return 0;
+
+ do {
+ msleep(20);
+ ret = __mxt_read_reg(data->client, reg, 1, &command_register);
+ if (ret)
+ return ret;
+ } while (command_register != 0 && timeout_counter++ <= 100);
+
+ if (timeout_counter > 100) {
+ dev_err(&data->client->dev, "Command failed!\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mxt_acquire_irq(struct mxt_data *data)
+{
+ int error;
+
+ enable_irq(data->irq);
+
+ if (data->use_retrigen_workaround) {
+ error = mxt_process_messages_until_invalid(data);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static int mxt_soft_reset(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int ret = 0;
+
+ dev_info(dev, "Resetting device\n");
+
+ disable_irq(data->irq);
+
+ reinit_completion(&data->reset_completion);
+
+ ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_RESET_VALUE, false);
+ if (ret)
+ return ret;
+
+ /* Ignore CHG line for 100ms after reset */
+ msleep(MXT_RESET_INVALID_CHG);
+
+ mxt_acquire_irq(data);
+
+ ret = mxt_wait_for_completion(data, &data->reset_completion,
+ MXT_RESET_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value)
+{
+ /*
+ * On failure, CRC is set to 0 and config will always be
+ * downloaded.
+ */
+ data->config_crc = 0;
+ reinit_completion(&data->crc_completion);
+
+ mxt_t6_command(data, cmd, value, true);
+
+ /*
+ * Wait for crc message. On failure, CRC is set to 0 and config will
+ * always be downloaded.
+ */
+ mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT);
+}
+
+static void mxt_calc_crc24(u32 *crc, u8 firstbyte, u8 secondbyte)
+{
+ static const unsigned int crcpoly = 0x80001B;
+ u32 result;
+ u32 data_word;
+
+ data_word = (secondbyte << 8) | firstbyte;
+ result = ((*crc << 1) ^ data_word);
+
+ if (result & 0x1000000)
+ result ^= crcpoly;
+
+ *crc = result;
+}
+
+static u32 mxt_calculate_crc(u8 *base, off_t start_off, off_t end_off)
+{
+ u32 crc = 0;
+ u8 *ptr = base + start_off;
+ u8 *last_val = base + end_off - 1;
+
+ if (end_off < start_off)
+ return -EINVAL;
+
+ while (ptr < last_val) {
+ mxt_calc_crc24(&crc, *ptr, *(ptr + 1));
+ ptr += 2;
+ }
+
+ /* if len is odd, fill the last byte with 0 */
+ if (ptr == last_val)
+ mxt_calc_crc24(&crc, *ptr, 0);
+
+ /* Mask to 24-bit */
+ crc &= 0x00FFFFFF;
+
+ return crc;
+}
+
+static int mxt_check_retrigen(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ int val;
+ struct irq_data *irqd;
+
+ data->use_retrigen_workaround = false;
+
+ irqd = irq_get_irq_data(data->irq);
+ if (!irqd)
+ return -EINVAL;
+
+ if (irqd_is_level_type(irqd))
+ return 0;
+
+ if (data->T18_address) {
+ error = __mxt_read_reg(client,
+ data->T18_address + MXT_COMMS_CTRL,
+ 1, &val);
+ if (error)
+ return error;
+
+ if (val & MXT_COMMS_RETRIGEN)
+ return 0;
+ }
+
+ dev_warn(&client->dev, "Enabling RETRIGEN workaround\n");
+ data->use_retrigen_workaround = true;
+ return 0;
+}
+
+static int mxt_prepare_cfg_mem(struct mxt_data *data, struct mxt_cfg *cfg)
+{
+ struct device *dev = &data->client->dev;
+ struct mxt_object *object;
+ unsigned int type, instance, size, byte_offset;
+ int offset;
+ int ret;
+ int i;
+ u16 reg;
+ u8 val;
+
+ while (cfg->raw_pos < cfg->raw_size) {
+ /* Read type, instance, length */
+ ret = sscanf(cfg->raw + cfg->raw_pos, "%x %x %x%n",
+ &type, &instance, &size, &offset);
+ if (ret == 0) {
+ /* EOF */
+ break;
+ } else if (ret != 3) {
+ dev_err(dev, "Bad format: failed to parse object\n");
+ return -EINVAL;
+ }
+ cfg->raw_pos += offset;
+
+ object = mxt_get_object(data, type);
+ if (!object) {
+ /* Skip object */
+ for (i = 0; i < size; i++) {
+ ret = sscanf(cfg->raw + cfg->raw_pos, "%hhx%n",
+ &val, &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format in T%d at %d\n",
+ type, i);
+ return -EINVAL;
+ }
+ cfg->raw_pos += offset;
+ }
+ continue;
+ }
+
+ if (size > mxt_obj_size(object)) {
+ /*
+ * Either we are in fallback mode due to wrong
+ * config or config from a later fw version,
+ * or the file is corrupt or hand-edited.
+ */
+ dev_warn(dev, "Discarding %zu byte(s) in T%u\n",
+ size - mxt_obj_size(object), type);
+ } else if (mxt_obj_size(object) > size) {
+ /*
+ * If firmware is upgraded, new bytes may be added to
+ * end of objects. It is generally forward compatible
+ * to zero these bytes - previous behaviour will be
+ * retained. However this does invalidate the CRC and
+ * will force fallback mode until the configuration is
+ * updated. We warn here but do nothing else - the
+ * malloc has zeroed the entire configuration.
+ */
+ dev_warn(dev, "Zeroing %zu byte(s) in T%d\n",
+ mxt_obj_size(object) - size, type);
+ }
+
+ if (instance >= mxt_obj_instances(object)) {
+ dev_err(dev, "Object instances exceeded!\n");
+ return -EINVAL;
+ }
+
+ reg = object->start_address + mxt_obj_size(object) * instance;
+
+ for (i = 0; i < size; i++) {
+ ret = sscanf(cfg->raw + cfg->raw_pos, "%hhx%n",
+ &val,
+ &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format in T%d at %d\n",
+ type, i);
+ return -EINVAL;
+ }
+ cfg->raw_pos += offset;
+
+ if (i > mxt_obj_size(object))
+ continue;
+
+ byte_offset = reg + i - cfg->start_ofs;
+
+ if (byte_offset >= 0 && byte_offset < cfg->mem_size) {
+ *(cfg->mem + byte_offset) = val;
+ } else {
+ dev_err(dev, "Bad object: reg:%d, T%d, ofs=%d\n",
+ reg, object->type, byte_offset);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mxt_upload_cfg_mem(struct mxt_data *data, struct mxt_cfg *cfg)
+{
+ unsigned int byte_offset = 0;
+ int error;
+
+ /* Write configuration as blocks */
+ while (byte_offset < cfg->mem_size) {
+ unsigned int size = cfg->mem_size - byte_offset;
+
+ if (size > MXT_MAX_BLOCK_WRITE)
+ size = MXT_MAX_BLOCK_WRITE;
+
+ error = __mxt_write_reg(data->client,
+ cfg->start_ofs + byte_offset,
+ size, cfg->mem + byte_offset);
+ if (error) {
+ dev_err(&data->client->dev,
+ "Config write error, ret=%d\n", error);
+ return error;
+ }
+
+ byte_offset += size;
+ }
+
+ return 0;
+}
+
+static int mxt_init_t7_power_cfg(struct mxt_data *data);
+
+/*
+ * mxt_update_cfg - download configuration to chip
+ *
+ * Atmel Raw Config File Format
+ *
+ * The first four lines of the raw config file contain:
+ * 1) Version
+ * 2) Chip ID Information (first 7 bytes of device memory)
+ * 3) Chip Information Block 24-bit CRC Checksum
+ * 4) Chip Configuration 24-bit CRC Checksum
+ *
+ * The rest of the file consists of one line per object instance:
+ * <TYPE> <INSTANCE> <SIZE> <CONTENTS>
+ *
+ * <TYPE> - 2-byte object type as hex
+ * <INSTANCE> - 2-byte object instance number as hex
+ * <SIZE> - 2-byte object size as hex
+ * <CONTENTS> - array of <SIZE> 1-byte hex values
+ */
+static int mxt_update_cfg(struct mxt_data *data, const struct firmware *fw)
+{
+ struct device *dev = &data->client->dev;
+ struct mxt_cfg cfg;
+ int ret;
+ int offset;
+ int i;
+ u32 info_crc, config_crc, calculated_crc;
+ u16 crc_start = 0;
+
+ /* Make zero terminated copy of the OBP_RAW file */
+ cfg.raw = kmemdup_nul(fw->data, fw->size, GFP_KERNEL);
+ if (!cfg.raw)
+ return -ENOMEM;
+
+ cfg.raw_size = fw->size;
+
+ mxt_update_crc(data, MXT_COMMAND_REPORTALL, 1);
+
+ if (strncmp(cfg.raw, MXT_CFG_MAGIC, strlen(MXT_CFG_MAGIC))) {
+ dev_err(dev, "Unrecognised config file\n");
+ ret = -EINVAL;
+ goto release_raw;
+ }
+
+ cfg.raw_pos = strlen(MXT_CFG_MAGIC);
+
+ /* Load information block and check */
+ for (i = 0; i < sizeof(struct mxt_info); i++) {
+ ret = sscanf(cfg.raw + cfg.raw_pos, "%hhx%n",
+ (unsigned char *)&cfg.info + i,
+ &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format\n");
+ ret = -EINVAL;
+ goto release_raw;
+ }
+
+ cfg.raw_pos += offset;
+ }
+
+ if (cfg.info.family_id != data->info->family_id) {
+ dev_err(dev, "Family ID mismatch!\n");
+ ret = -EINVAL;
+ goto release_raw;
+ }
+
+ if (cfg.info.variant_id != data->info->variant_id) {
+ dev_err(dev, "Variant ID mismatch!\n");
+ ret = -EINVAL;
+ goto release_raw;
+ }
+
+ /* Read CRCs */
+ ret = sscanf(cfg.raw + cfg.raw_pos, "%x%n", &info_crc, &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format: failed to parse Info CRC\n");
+ ret = -EINVAL;
+ goto release_raw;
+ }
+ cfg.raw_pos += offset;
+
+ ret = sscanf(cfg.raw + cfg.raw_pos, "%x%n", &config_crc, &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format: failed to parse Config CRC\n");
+ ret = -EINVAL;
+ goto release_raw;
+ }
+ cfg.raw_pos += offset;
+
+ /*
+ * The Info Block CRC is calculated over mxt_info and the object
+ * table. If it does not match then we are trying to load the
+ * configuration from a different chip or firmware version, so
+ * the configuration CRC is invalid anyway.
+ */
+ if (info_crc == data->info_crc) {
+ if (config_crc == 0 || data->config_crc == 0) {
+ dev_info(dev, "CRC zero, attempting to apply config\n");
+ } else if (config_crc == data->config_crc) {
+ dev_dbg(dev, "Config CRC 0x%06X: OK\n",
+ data->config_crc);
+ ret = 0;
+ goto release_raw;
+ } else {
+ dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n",
+ data->config_crc, config_crc);
+ }
+ } else {
+ dev_warn(dev,
+ "Warning: Info CRC error - device=0x%06X file=0x%06X\n",
+ data->info_crc, info_crc);
+ }
+
+ /* Malloc memory to store configuration */
+ cfg.start_ofs = MXT_OBJECT_START +
+ data->info->object_num * sizeof(struct mxt_object) +
+ MXT_INFO_CHECKSUM_SIZE;
+ cfg.mem_size = data->mem_size - cfg.start_ofs;
+ cfg.mem = kzalloc(cfg.mem_size, GFP_KERNEL);
+ if (!cfg.mem) {
+ ret = -ENOMEM;
+ goto release_raw;
+ }
+
+ ret = mxt_prepare_cfg_mem(data, &cfg);
+ if (ret)
+ goto release_mem;
+
+ /* Calculate crc of the received configs (not the raw config file) */
+ if (data->T71_address)
+ crc_start = data->T71_address;
+ else if (data->T7_address)
+ crc_start = data->T7_address;
+ else
+ dev_warn(dev, "Could not find CRC start\n");
+
+ if (crc_start > cfg.start_ofs) {
+ calculated_crc = mxt_calculate_crc(cfg.mem,
+ crc_start - cfg.start_ofs,
+ cfg.mem_size);
+
+ if (config_crc > 0 && config_crc != calculated_crc)
+ dev_warn(dev, "Config CRC in file inconsistent, calculated=%06X, file=%06X\n",
+ calculated_crc, config_crc);
+ }
+
+ ret = mxt_upload_cfg_mem(data, &cfg);
+ if (ret)
+ goto release_mem;
+
+ mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE);
+
+ ret = mxt_check_retrigen(data);
+ if (ret)
+ goto release_mem;
+
+ ret = mxt_soft_reset(data);
+ if (ret)
+ goto release_mem;
+
+ dev_info(dev, "Config successfully updated\n");
+
+ /* T7 config may have changed */
+ mxt_init_t7_power_cfg(data);
+
+release_mem:
+ kfree(cfg.mem);
+release_raw:
+ kfree(cfg.raw);
+ return ret;
+}
+
+static void mxt_free_input_device(struct mxt_data *data)
+{
+ if (data->input_dev) {
+ input_unregister_device(data->input_dev);
+ data->input_dev = NULL;
+ }
+}
+
+static void mxt_free_object_table(struct mxt_data *data)
+{
+#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37
+ video_unregister_device(&data->dbg.vdev);
+ v4l2_device_unregister(&data->dbg.v4l2);
+#endif
+ data->object_table = NULL;
+ data->info = NULL;
+ kfree(data->raw_info_block);
+ data->raw_info_block = NULL;
+ kfree(data->msg_buf);
+ data->msg_buf = NULL;
+ data->T5_address = 0;
+ data->T5_msg_size = 0;
+ data->T6_reportid = 0;
+ data->T7_address = 0;
+ data->T71_address = 0;
+ data->T9_reportid_min = 0;
+ data->T9_reportid_max = 0;
+ data->T18_address = 0;
+ data->T19_reportid = 0;
+ data->T44_address = 0;
+ data->T100_reportid_min = 0;
+ data->T100_reportid_max = 0;
+ data->max_reportid = 0;
+}
+
+static int mxt_parse_object_table(struct mxt_data *data,
+ struct mxt_object *object_table)
+{
+ struct i2c_client *client = data->client;
+ int i;
+ u8 reportid;
+ u16 end_address;
+
+ /* Valid Report IDs start counting from 1 */
+ reportid = 1;
+ data->mem_size = 0;
+ for (i = 0; i < data->info->object_num; i++) {
+ struct mxt_object *object = object_table + i;
+ u8 min_id, max_id;
+
+ le16_to_cpus(&object->start_address);
+
+ if (object->num_report_ids) {
+ min_id = reportid;
+ reportid += object->num_report_ids *
+ mxt_obj_instances(object);
+ max_id = reportid - 1;
+ } else {
+ min_id = 0;
+ max_id = 0;
+ }
+
+ dev_dbg(&data->client->dev,
+ "T%u Start:%u Size:%zu Instances:%zu Report IDs:%u-%u\n",
+ object->type, object->start_address,
+ mxt_obj_size(object), mxt_obj_instances(object),
+ min_id, max_id);
+
+ switch (object->type) {
+ case MXT_GEN_MESSAGE_T5:
+ if (data->info->family_id == 0x80 &&
+ data->info->version < 0x20) {
+ /*
+ * On mXT224 firmware versions prior to V2.0
+ * read and discard unused CRC byte otherwise
+ * DMA reads are misaligned.
+ */
+ data->T5_msg_size = mxt_obj_size(object);
+ } else {
+ /* CRC not enabled, so skip last byte */
+ data->T5_msg_size = mxt_obj_size(object) - 1;
+ }
+ data->T5_address = object->start_address;
+ break;
+ case MXT_GEN_COMMAND_T6:
+ data->T6_reportid = min_id;
+ data->T6_address = object->start_address;
+ break;
+ case MXT_GEN_POWER_T7:
+ data->T7_address = object->start_address;
+ break;
+ case MXT_SPT_DYNAMICCONFIGURATIONCONTAINER_T71:
+ data->T71_address = object->start_address;
+ break;
+ case MXT_TOUCH_MULTI_T9:
+ data->multitouch = MXT_TOUCH_MULTI_T9;
+ /* Only handle messages from first T9 instance */
+ data->T9_reportid_min = min_id;
+ data->T9_reportid_max = min_id +
+ object->num_report_ids - 1;
+ data->num_touchids = object->num_report_ids;
+ break;
+ case MXT_SPT_COMMSCONFIG_T18:
+ data->T18_address = object->start_address;
+ break;
+ case MXT_SPT_MESSAGECOUNT_T44:
+ data->T44_address = object->start_address;
+ break;
+ case MXT_SPT_GPIOPWM_T19:
+ data->T19_reportid = min_id;
+ break;
+ case MXT_TOUCH_MULTITOUCHSCREEN_T100:
+ data->multitouch = MXT_TOUCH_MULTITOUCHSCREEN_T100;
+ data->T100_reportid_min = min_id;
+ data->T100_reportid_max = max_id;
+ /* first two report IDs reserved */
+ data->num_touchids = object->num_report_ids - 2;
+ break;
+ }
+
+ end_address = object->start_address
+ + mxt_obj_size(object) * mxt_obj_instances(object) - 1;
+
+ if (end_address >= data->mem_size)
+ data->mem_size = end_address + 1;
+ }
+
+ /* Store maximum reportid */
+ data->max_reportid = reportid;
+
+ /* If T44 exists, T5 position has to be directly after */
+ if (data->T44_address && (data->T5_address != data->T44_address + 1)) {
+ dev_err(&client->dev, "Invalid T44 position\n");
+ return -EINVAL;
+ }
+
+ data->msg_buf = kcalloc(data->max_reportid,
+ data->T5_msg_size, GFP_KERNEL);
+ if (!data->msg_buf)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int mxt_read_info_block(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ size_t size;
+ void *id_buf, *buf;
+ uint8_t num_objects;
+ u32 calculated_crc;
+ u8 *crc_ptr;
+
+ /* If info block already allocated, free it */
+ if (data->raw_info_block)
+ mxt_free_object_table(data);
+
+ /* Read 7-byte ID information block starting at address 0 */
+ size = sizeof(struct mxt_info);
+ id_buf = kzalloc(size, GFP_KERNEL);
+ if (!id_buf)
+ return -ENOMEM;
+
+ error = __mxt_read_reg(client, 0, size, id_buf);
+ if (error)
+ goto err_free_mem;
+
+ /* Resize buffer to give space for rest of info block */
+ num_objects = ((struct mxt_info *)id_buf)->object_num;
+ size += (num_objects * sizeof(struct mxt_object))
+ + MXT_INFO_CHECKSUM_SIZE;
+
+ buf = krealloc(id_buf, size, GFP_KERNEL);
+ if (!buf) {
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+ id_buf = buf;
+
+ /* Read rest of info block */
+ error = __mxt_read_reg(client, MXT_OBJECT_START,
+ size - MXT_OBJECT_START,
+ id_buf + MXT_OBJECT_START);
+ if (error)
+ goto err_free_mem;
+
+ /* Extract & calculate checksum */
+ crc_ptr = id_buf + size - MXT_INFO_CHECKSUM_SIZE;
+ data->info_crc = crc_ptr[0] | (crc_ptr[1] << 8) | (crc_ptr[2] << 16);
+
+ calculated_crc = mxt_calculate_crc(id_buf, 0,
+ size - MXT_INFO_CHECKSUM_SIZE);
+
+ /*
+ * CRC mismatch can be caused by data corruption due to I2C comms
+ * issue or else device is not using Object Based Protocol (eg i2c-hid)
+ */
+ if ((data->info_crc == 0) || (data->info_crc != calculated_crc)) {
+ dev_err(&client->dev,
+ "Info Block CRC error calculated=0x%06X read=0x%06X\n",
+ calculated_crc, data->info_crc);
+ error = -EIO;
+ goto err_free_mem;
+ }
+
+ data->raw_info_block = id_buf;
+ data->info = (struct mxt_info *)id_buf;
+
+ dev_info(&client->dev,
+ "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u\n",
+ data->info->family_id, data->info->variant_id,
+ data->info->version >> 4, data->info->version & 0xf,
+ data->info->build, data->info->object_num);
+
+ /* Parse object table information */
+ error = mxt_parse_object_table(data, id_buf + MXT_OBJECT_START);
+ if (error) {
+ dev_err(&client->dev, "Error %d parsing object table\n", error);
+ mxt_free_object_table(data);
+ return error;
+ }
+
+ data->object_table = (struct mxt_object *)(id_buf + MXT_OBJECT_START);
+
+ return 0;
+
+err_free_mem:
+ kfree(id_buf);
+ return error;
+}
+
+static int mxt_read_t9_resolution(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ struct t9_range range;
+ unsigned char orient;
+ struct mxt_object *object;
+
+ object = mxt_get_object(data, MXT_TOUCH_MULTI_T9);
+ if (!object)
+ return -EINVAL;
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T9_XSIZE,
+ sizeof(data->xsize), &data->xsize);
+ if (error)
+ return error;
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T9_YSIZE,
+ sizeof(data->ysize), &data->ysize);
+ if (error)
+ return error;
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T9_RANGE,
+ sizeof(range), &range);
+ if (error)
+ return error;
+
+ data->max_x = get_unaligned_le16(&range.x);
+ data->max_y = get_unaligned_le16(&range.y);
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T9_ORIENT,
+ 1, &orient);
+ if (error)
+ return error;
+
+ data->xy_switch = orient & MXT_T9_ORIENT_SWITCH;
+ data->invertx = orient & MXT_T9_ORIENT_INVERTX;
+ data->inverty = orient & MXT_T9_ORIENT_INVERTY;
+
+ return 0;
+}
+
+static int mxt_read_t100_config(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ struct mxt_object *object;
+ u16 range_x, range_y;
+ u8 cfg, tchaux;
+ u8 aux;
+
+ object = mxt_get_object(data, MXT_TOUCH_MULTITOUCHSCREEN_T100);
+ if (!object)
+ return -EINVAL;
+
+ /* read touchscreen dimensions */
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_XRANGE,
+ sizeof(range_x), &range_x);
+ if (error)
+ return error;
+
+ data->max_x = get_unaligned_le16(&range_x);
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_YRANGE,
+ sizeof(range_y), &range_y);
+ if (error)
+ return error;
+
+ data->max_y = get_unaligned_le16(&range_y);
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_XSIZE,
+ sizeof(data->xsize), &data->xsize);
+ if (error)
+ return error;
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_YSIZE,
+ sizeof(data->ysize), &data->ysize);
+ if (error)
+ return error;
+
+ /* read orientation config */
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_CFG1,
+ 1, &cfg);
+ if (error)
+ return error;
+
+ data->xy_switch = cfg & MXT_T100_CFG_SWITCHXY;
+ data->invertx = cfg & MXT_T100_CFG_INVERTX;
+ data->inverty = cfg & MXT_T100_CFG_INVERTY;
+
+ /* allocate aux bytes */
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_TCHAUX,
+ 1, &tchaux);
+ if (error)
+ return error;
+
+ aux = 6;
+
+ if (tchaux & MXT_T100_TCHAUX_VECT)
+ data->t100_aux_vect = aux++;
+
+ if (tchaux & MXT_T100_TCHAUX_AMPL)
+ data->t100_aux_ampl = aux++;
+
+ if (tchaux & MXT_T100_TCHAUX_AREA)
+ data->t100_aux_area = aux++;
+
+ dev_dbg(&client->dev,
+ "T100 aux mappings vect:%u ampl:%u area:%u\n",
+ data->t100_aux_vect, data->t100_aux_ampl, data->t100_aux_area);
+
+ return 0;
+}
+
+static int mxt_input_open(struct input_dev *dev);
+static void mxt_input_close(struct input_dev *dev);
+
+static void mxt_set_up_as_touchpad(struct input_dev *input_dev,
+ struct mxt_data *data)
+{
+ int i;
+
+ input_dev->name = "Atmel maXTouch Touchpad";
+
+ __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit);
+
+ input_abs_set_res(input_dev, ABS_X, MXT_PIXELS_PER_MM);
+ input_abs_set_res(input_dev, ABS_Y, MXT_PIXELS_PER_MM);
+ input_abs_set_res(input_dev, ABS_MT_POSITION_X,
+ MXT_PIXELS_PER_MM);
+ input_abs_set_res(input_dev, ABS_MT_POSITION_Y,
+ MXT_PIXELS_PER_MM);
+
+ for (i = 0; i < data->t19_num_keys; i++)
+ if (data->t19_keymap[i] != KEY_RESERVED)
+ input_set_capability(input_dev, EV_KEY,
+ data->t19_keymap[i]);
+}
+
+static int mxt_initialize_input_device(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ struct input_dev *input_dev;
+ int error;
+ unsigned int num_mt_slots;
+ unsigned int mt_flags = 0;
+
+ switch (data->multitouch) {
+ case MXT_TOUCH_MULTI_T9:
+ num_mt_slots = data->T9_reportid_max - data->T9_reportid_min + 1;
+ error = mxt_read_t9_resolution(data);
+ if (error)
+ dev_warn(dev, "Failed to initialize T9 resolution\n");
+ break;
+
+ case MXT_TOUCH_MULTITOUCHSCREEN_T100:
+ num_mt_slots = data->num_touchids;
+ error = mxt_read_t100_config(data);
+ if (error)
+ dev_warn(dev, "Failed to read T100 config\n");
+ break;
+
+ default:
+ dev_err(dev, "Invalid multitouch object\n");
+ return -EINVAL;
+ }
+
+ /* Handle default values and orientation switch */
+ if (data->max_x == 0)
+ data->max_x = 1023;
+
+ if (data->max_y == 0)
+ data->max_y = 1023;
+
+ if (data->xy_switch)
+ swap(data->max_x, data->max_y);
+
+ dev_info(dev, "Touchscreen size X%uY%u\n", data->max_x, data->max_y);
+
+ /* Register input device */
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Atmel maXTouch Touchscreen";
+ input_dev->phys = data->phys;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = dev;
+ input_dev->open = mxt_input_open;
+ input_dev->close = mxt_input_close;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+
+ /* For single touch */
+ input_set_abs_params(input_dev, ABS_X, 0, data->max_x, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, data->max_y, 0, 0);
+
+ if (data->multitouch == MXT_TOUCH_MULTI_T9 ||
+ (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 &&
+ data->t100_aux_ampl)) {
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0);
+ }
+
+ /* If device has buttons we assume it is a touchpad */
+ if (data->t19_num_keys) {
+ mxt_set_up_as_touchpad(input_dev, data);
+ mt_flags |= INPUT_MT_POINTER;
+ } else {
+ mt_flags |= INPUT_MT_DIRECT;
+ }
+
+ /* For multi touch */
+ error = input_mt_init_slots(input_dev, num_mt_slots, mt_flags);
+ if (error) {
+ dev_err(dev, "Error %d initialising slots\n", error);
+ goto err_free_mem;
+ }
+
+ if (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100) {
+ input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE,
+ 0, MT_TOOL_MAX, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_DISTANCE,
+ MXT_DISTANCE_ACTIVE_TOUCH,
+ MXT_DISTANCE_HOVERING,
+ 0, 0);
+ }
+
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+ 0, data->max_x, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+ 0, data->max_y, 0, 0);
+
+ if (data->multitouch == MXT_TOUCH_MULTI_T9 ||
+ (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 &&
+ data->t100_aux_area)) {
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
+ 0, MXT_MAX_AREA, 0, 0);
+ }
+
+ if (data->multitouch == MXT_TOUCH_MULTI_T9 ||
+ (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 &&
+ data->t100_aux_ampl)) {
+ input_set_abs_params(input_dev, ABS_MT_PRESSURE,
+ 0, 255, 0, 0);
+ }
+
+ if (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 &&
+ data->t100_aux_vect) {
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION,
+ 0, 255, 0, 0);
+ }
+
+ if (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 &&
+ data->t100_aux_vect) {
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION,
+ 0, 255, 0, 0);
+ }
+
+ input_set_drvdata(input_dev, data);
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(dev, "Error %d registering input device\n", error);
+ goto err_free_mem;
+ }
+
+ data->input_dev = input_dev;
+
+ return 0;
+
+err_free_mem:
+ input_free_device(input_dev);
+ return error;
+}
+
+static int mxt_configure_objects(struct mxt_data *data,
+ const struct firmware *cfg);
+
+static void mxt_config_cb(const struct firmware *cfg, void *ctx)
+{
+ mxt_configure_objects(ctx, cfg);
+ release_firmware(cfg);
+}
+
+static int mxt_initialize(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int recovery_attempts = 0;
+ int error;
+
+ while (1) {
+ error = mxt_read_info_block(data);
+ if (!error)
+ break;
+
+ /* Check bootloader state */
+ error = mxt_probe_bootloader(data, false);
+ if (error) {
+ dev_info(&client->dev, "Trying alternate bootloader address\n");
+ error = mxt_probe_bootloader(data, true);
+ if (error) {
+ /* Chip is not in appmode or bootloader mode */
+ return error;
+ }
+ }
+
+ /* OK, we are in bootloader, see if we can recover */
+ if (++recovery_attempts > 1) {
+ dev_err(&client->dev, "Could not recover from bootloader mode\n");
+ /*
+ * We can reflash from this state, so do not
+ * abort initialization.
+ */
+ data->in_bootloader = true;
+ return 0;
+ }
+
+ /* Attempt to exit bootloader into app mode */
+ mxt_send_bootloader_cmd(data, false);
+ msleep(MXT_FW_RESET_TIME);
+ }
+
+ error = mxt_check_retrigen(data);
+ if (error)
+ return error;
+
+ error = mxt_acquire_irq(data);
+ if (error)
+ return error;
+
+ error = request_firmware_nowait(THIS_MODULE, true, MXT_CFG_NAME,
+ &client->dev, GFP_KERNEL, data,
+ mxt_config_cb);
+ if (error) {
+ dev_err(&client->dev, "Failed to invoke firmware loader: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int mxt_set_t7_power_cfg(struct mxt_data *data, u8 sleep)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+ struct t7_config *new_config;
+ struct t7_config deepsleep = { .active = 0, .idle = 0 };
+
+ if (sleep == MXT_POWER_CFG_DEEPSLEEP)
+ new_config = &deepsleep;
+ else
+ new_config = &data->t7_cfg;
+
+ error = __mxt_write_reg(data->client, data->T7_address,
+ sizeof(data->t7_cfg), new_config);
+ if (error)
+ return error;
+
+ dev_dbg(dev, "Set T7 ACTV:%d IDLE:%d\n",
+ new_config->active, new_config->idle);
+
+ return 0;
+}
+
+static int mxt_init_t7_power_cfg(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+ bool retry = false;
+
+recheck:
+ error = __mxt_read_reg(data->client, data->T7_address,
+ sizeof(data->t7_cfg), &data->t7_cfg);
+ if (error)
+ return error;
+
+ if (data->t7_cfg.active == 0 || data->t7_cfg.idle == 0) {
+ if (!retry) {
+ dev_dbg(dev, "T7 cfg zero, resetting\n");
+ mxt_soft_reset(data);
+ retry = true;
+ goto recheck;
+ } else {
+ dev_dbg(dev, "T7 cfg zero after reset, overriding\n");
+ data->t7_cfg.active = 20;
+ data->t7_cfg.idle = 100;
+ return mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN);
+ }
+ }
+
+ dev_dbg(dev, "Initialized power cfg: ACTV %d, IDLE %d\n",
+ data->t7_cfg.active, data->t7_cfg.idle);
+ return 0;
+}
+
+#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37
+static const struct v4l2_file_operations mxt_video_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .unlocked_ioctl = video_ioctl2,
+ .read = vb2_fop_read,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll,
+};
+
+static u16 mxt_get_debug_value(struct mxt_data *data, unsigned int x,
+ unsigned int y)
+{
+ struct mxt_info *info = data->info;
+ struct mxt_dbg *dbg = &data->dbg;
+ unsigned int ofs, page;
+ unsigned int col = 0;
+ unsigned int col_width;
+
+ if (info->family_id == MXT_FAMILY_1386) {
+ col_width = info->matrix_ysize / MXT1386_COLUMNS;
+ col = y / col_width;
+ y = y % col_width;
+ } else {
+ col_width = info->matrix_ysize;
+ }
+
+ ofs = (y + (x * col_width)) * sizeof(u16);
+ page = ofs / MXT_DIAGNOSTIC_SIZE;
+ ofs %= MXT_DIAGNOSTIC_SIZE;
+
+ if (info->family_id == MXT_FAMILY_1386)
+ page += col * MXT1386_PAGES_PER_COLUMN;
+
+ return get_unaligned_le16(&dbg->t37_buf[page].data[ofs]);
+}
+
+static int mxt_convert_debug_pages(struct mxt_data *data, u16 *outbuf)
+{
+ struct mxt_dbg *dbg = &data->dbg;
+ unsigned int x = 0;
+ unsigned int y = 0;
+ unsigned int i, rx, ry;
+
+ for (i = 0; i < dbg->t37_nodes; i++) {
+ /* Handle orientation */
+ rx = data->xy_switch ? y : x;
+ ry = data->xy_switch ? x : y;
+ rx = data->invertx ? (data->xsize - 1 - rx) : rx;
+ ry = data->inverty ? (data->ysize - 1 - ry) : ry;
+
+ outbuf[i] = mxt_get_debug_value(data, rx, ry);
+
+ /* Next value */
+ if (++x >= (data->xy_switch ? data->ysize : data->xsize)) {
+ x = 0;
+ y++;
+ }
+ }
+
+ return 0;
+}
+
+static int mxt_read_diagnostic_debug(struct mxt_data *data, u8 mode,
+ u16 *outbuf)
+{
+ struct mxt_dbg *dbg = &data->dbg;
+ int retries = 0;
+ int page;
+ int ret;
+ u8 cmd = mode;
+ struct t37_debug *p;
+ u8 cmd_poll;
+
+ for (page = 0; page < dbg->t37_pages; page++) {
+ p = dbg->t37_buf + page;
+
+ ret = mxt_write_reg(data->client, dbg->diag_cmd_address,
+ cmd);
+ if (ret)
+ return ret;
+
+ retries = 0;
+ msleep(20);
+wait_cmd:
+ /* Read back command byte */
+ ret = __mxt_read_reg(data->client, dbg->diag_cmd_address,
+ sizeof(cmd_poll), &cmd_poll);
+ if (ret)
+ return ret;
+
+ /* Field is cleared once the command has been processed */
+ if (cmd_poll) {
+ if (retries++ > 100)
+ return -EINVAL;
+
+ msleep(20);
+ goto wait_cmd;
+ }
+
+ /* Read T37 page */
+ ret = __mxt_read_reg(data->client, dbg->t37_address,
+ sizeof(struct t37_debug), p);
+ if (ret)
+ return ret;
+
+ if (p->mode != mode || p->page != page) {
+ dev_err(&data->client->dev, "T37 page mismatch\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(&data->client->dev, "%s page:%d retries:%d\n",
+ __func__, page, retries);
+
+ /* For remaining pages, write PAGEUP rather than mode */
+ cmd = MXT_DIAGNOSTIC_PAGEUP;
+ }
+
+ return mxt_convert_debug_pages(data, outbuf);
+}
+
+static int mxt_queue_setup(struct vb2_queue *q,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct mxt_data *data = q->drv_priv;
+ size_t size = data->dbg.t37_nodes * sizeof(u16);
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static void mxt_buffer_queue(struct vb2_buffer *vb)
+{
+ struct mxt_data *data = vb2_get_drv_priv(vb->vb2_queue);
+ u16 *ptr;
+ int ret;
+ u8 mode;
+
+ ptr = vb2_plane_vaddr(vb, 0);
+ if (!ptr) {
+ dev_err(&data->client->dev, "Error acquiring frame ptr\n");
+ goto fault;
+ }
+
+ switch (data->dbg.input) {
+ case MXT_V4L_INPUT_DELTAS:
+ default:
+ mode = MXT_DIAGNOSTIC_DELTAS;
+ break;
+
+ case MXT_V4L_INPUT_REFS:
+ mode = MXT_DIAGNOSTIC_REFS;
+ break;
+ }
+
+ ret = mxt_read_diagnostic_debug(data, mode, ptr);
+ if (ret)
+ goto fault;
+
+ vb2_set_plane_payload(vb, 0, data->dbg.t37_nodes * sizeof(u16));
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ return;
+
+fault:
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+}
+
+/* V4L2 structures */
+static const struct vb2_ops mxt_queue_ops = {
+ .queue_setup = mxt_queue_setup,
+ .buf_queue = mxt_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static const struct vb2_queue mxt_queue = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ,
+ .buf_struct_size = sizeof(struct mxt_vb2_buffer),
+ .ops = &mxt_queue_ops,
+ .mem_ops = &vb2_vmalloc_memops,
+ .timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC,
+ .min_buffers_needed = 1,
+};
+
+static int mxt_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct mxt_data *data = video_drvdata(file);
+
+ strscpy(cap->driver, "atmel_mxt_ts", sizeof(cap->driver));
+ strscpy(cap->card, "atmel_mxt_ts touch", sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "I2C:%s", dev_name(&data->client->dev));
+ return 0;
+}
+
+static int mxt_vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ if (i->index >= MXT_V4L_INPUT_MAX)
+ return -EINVAL;
+
+ i->type = V4L2_INPUT_TYPE_TOUCH;
+
+ switch (i->index) {
+ case MXT_V4L_INPUT_REFS:
+ strscpy(i->name, "Mutual Capacitance References",
+ sizeof(i->name));
+ break;
+ case MXT_V4L_INPUT_DELTAS:
+ strscpy(i->name, "Mutual Capacitance Deltas", sizeof(i->name));
+ break;
+ }
+
+ return 0;
+}
+
+static int mxt_set_input(struct mxt_data *data, unsigned int i)
+{
+ struct v4l2_pix_format *f = &data->dbg.format;
+
+ if (i >= MXT_V4L_INPUT_MAX)
+ return -EINVAL;
+
+ if (i == MXT_V4L_INPUT_DELTAS)
+ f->pixelformat = V4L2_TCH_FMT_DELTA_TD16;
+ else
+ f->pixelformat = V4L2_TCH_FMT_TU16;
+
+ f->width = data->xy_switch ? data->ysize : data->xsize;
+ f->height = data->xy_switch ? data->xsize : data->ysize;
+ f->field = V4L2_FIELD_NONE;
+ f->colorspace = V4L2_COLORSPACE_RAW;
+ f->bytesperline = f->width * sizeof(u16);
+ f->sizeimage = f->width * f->height * sizeof(u16);
+
+ data->dbg.input = i;
+
+ return 0;
+}
+
+static int mxt_vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ return mxt_set_input(video_drvdata(file), i);
+}
+
+static int mxt_vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ struct mxt_data *data = video_drvdata(file);
+
+ *i = data->dbg.input;
+
+ return 0;
+}
+
+static int mxt_vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct mxt_data *data = video_drvdata(file);
+
+ f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ f->fmt.pix = data->dbg.format;
+
+ return 0;
+}
+
+static int mxt_vidioc_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *fmt)
+{
+ if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ switch (fmt->index) {
+ case 0:
+ fmt->pixelformat = V4L2_TCH_FMT_TU16;
+ break;
+
+ case 1:
+ fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD16;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mxt_vidioc_g_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ a->parm.capture.readbuffers = 1;
+ a->parm.capture.timeperframe.numerator = 1;
+ a->parm.capture.timeperframe.denominator = 10;
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops mxt_video_ioctl_ops = {
+ .vidioc_querycap = mxt_vidioc_querycap,
+
+ .vidioc_enum_fmt_vid_cap = mxt_vidioc_enum_fmt,
+ .vidioc_s_fmt_vid_cap = mxt_vidioc_fmt,
+ .vidioc_g_fmt_vid_cap = mxt_vidioc_fmt,
+ .vidioc_try_fmt_vid_cap = mxt_vidioc_fmt,
+ .vidioc_g_parm = mxt_vidioc_g_parm,
+
+ .vidioc_enum_input = mxt_vidioc_enum_input,
+ .vidioc_g_input = mxt_vidioc_g_input,
+ .vidioc_s_input = mxt_vidioc_s_input,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+static const struct video_device mxt_video_device = {
+ .name = "Atmel maxTouch",
+ .fops = &mxt_video_fops,
+ .ioctl_ops = &mxt_video_ioctl_ops,
+ .release = video_device_release_empty,
+ .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH |
+ V4L2_CAP_READWRITE | V4L2_CAP_STREAMING,
+};
+
+static void mxt_debug_init(struct mxt_data *data)
+{
+ struct mxt_info *info = data->info;
+ struct mxt_dbg *dbg = &data->dbg;
+ struct mxt_object *object;
+ int error;
+
+ object = mxt_get_object(data, MXT_GEN_COMMAND_T6);
+ if (!object)
+ goto error;
+
+ dbg->diag_cmd_address = object->start_address + MXT_COMMAND_DIAGNOSTIC;
+
+ object = mxt_get_object(data, MXT_DEBUG_DIAGNOSTIC_T37);
+ if (!object)
+ goto error;
+
+ if (mxt_obj_size(object) != sizeof(struct t37_debug)) {
+ dev_warn(&data->client->dev, "Bad T37 size");
+ goto error;
+ }
+
+ dbg->t37_address = object->start_address;
+
+ /* Calculate size of data and allocate buffer */
+ dbg->t37_nodes = data->xsize * data->ysize;
+
+ if (info->family_id == MXT_FAMILY_1386)
+ dbg->t37_pages = MXT1386_COLUMNS * MXT1386_PAGES_PER_COLUMN;
+ else
+ dbg->t37_pages = DIV_ROUND_UP(data->xsize *
+ info->matrix_ysize *
+ sizeof(u16),
+ sizeof(dbg->t37_buf->data));
+
+ dbg->t37_buf = devm_kmalloc_array(&data->client->dev, dbg->t37_pages,
+ sizeof(struct t37_debug), GFP_KERNEL);
+ if (!dbg->t37_buf)
+ goto error;
+
+ /* init channel to zero */
+ mxt_set_input(data, 0);
+
+ /* register video device */
+ snprintf(dbg->v4l2.name, sizeof(dbg->v4l2.name), "%s", "atmel_mxt_ts");
+ error = v4l2_device_register(&data->client->dev, &dbg->v4l2);
+ if (error)
+ goto error;
+
+ /* initialize the queue */
+ mutex_init(&dbg->lock);
+ dbg->queue = mxt_queue;
+ dbg->queue.drv_priv = data;
+ dbg->queue.lock = &dbg->lock;
+ dbg->queue.dev = &data->client->dev;
+
+ error = vb2_queue_init(&dbg->queue);
+ if (error)
+ goto error_unreg_v4l2;
+
+ dbg->vdev = mxt_video_device;
+ dbg->vdev.v4l2_dev = &dbg->v4l2;
+ dbg->vdev.lock = &dbg->lock;
+ dbg->vdev.vfl_dir = VFL_DIR_RX;
+ dbg->vdev.queue = &dbg->queue;
+ video_set_drvdata(&dbg->vdev, data);
+
+ error = video_register_device(&dbg->vdev, VFL_TYPE_TOUCH, -1);
+ if (error)
+ goto error_unreg_v4l2;
+
+ return;
+
+error_unreg_v4l2:
+ v4l2_device_unregister(&dbg->v4l2);
+error:
+ dev_warn(&data->client->dev, "Error initializing T37\n");
+}
+#else
+static void mxt_debug_init(struct mxt_data *data)
+{
+}
+#endif
+
+static int mxt_configure_objects(struct mxt_data *data,
+ const struct firmware *cfg)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+
+ error = mxt_init_t7_power_cfg(data);
+ if (error) {
+ dev_err(dev, "Failed to initialize power cfg\n");
+ return error;
+ }
+
+ if (cfg) {
+ error = mxt_update_cfg(data, cfg);
+ if (error)
+ dev_warn(dev, "Error %d updating config\n", error);
+ }
+
+ if (data->multitouch) {
+ error = mxt_initialize_input_device(data);
+ if (error)
+ return error;
+ } else {
+ dev_warn(dev, "No touch object detected\n");
+ }
+
+ mxt_debug_init(data);
+
+ return 0;
+}
+
+/* Firmware Version is returned as Major.Minor.Build */
+static ssize_t mxt_fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ struct mxt_info *info = data->info;
+ return scnprintf(buf, PAGE_SIZE, "%u.%u.%02X\n",
+ info->version >> 4, info->version & 0xf, info->build);
+}
+
+/* Hardware Version is returned as FamilyID.VariantID */
+static ssize_t mxt_hw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ struct mxt_info *info = data->info;
+ return scnprintf(buf, PAGE_SIZE, "%u.%u\n",
+ info->family_id, info->variant_id);
+}
+
+static ssize_t mxt_show_instance(char *buf, int count,
+ struct mxt_object *object, int instance,
+ const u8 *val)
+{
+ int i;
+
+ if (mxt_obj_instances(object) > 1)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "Instance %u\n", instance);
+
+ for (i = 0; i < mxt_obj_size(object); i++)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "\t[%2u]: %02x (%d)\n", i, val[i], val[i]);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ return count;
+}
+
+static ssize_t mxt_object_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ struct mxt_object *object;
+ int count = 0;
+ int i, j;
+ int error;
+ u8 *obuf;
+
+ /* Pre-allocate buffer large enough to hold max sized object. */
+ obuf = kmalloc(256, GFP_KERNEL);
+ if (!obuf)
+ return -ENOMEM;
+
+ error = 0;
+ for (i = 0; i < data->info->object_num; i++) {
+ object = data->object_table + i;
+
+ if (!mxt_object_readable(object->type))
+ continue;
+
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "T%u:\n", object->type);
+
+ for (j = 0; j < mxt_obj_instances(object); j++) {
+ u16 size = mxt_obj_size(object);
+ u16 addr = object->start_address + j * size;
+
+ error = __mxt_read_reg(data->client, addr, size, obuf);
+ if (error)
+ goto done;
+
+ count = mxt_show_instance(buf, count, object, j, obuf);
+ }
+ }
+
+done:
+ kfree(obuf);
+ return error ?: count;
+}
+
+static int mxt_check_firmware_format(struct device *dev,
+ const struct firmware *fw)
+{
+ unsigned int pos = 0;
+ char c;
+
+ while (pos < fw->size) {
+ c = *(fw->data + pos);
+
+ if (c < '0' || (c > '9' && c < 'A') || c > 'F')
+ return 0;
+
+ pos++;
+ }
+
+ /*
+ * To convert file try:
+ * xxd -r -p mXTXXX__APP_VX-X-XX.enc > maxtouch.fw
+ */
+ dev_err(dev, "Aborting: firmware file must be in binary format\n");
+
+ return -EINVAL;
+}
+
+static int mxt_load_fw(struct device *dev, const char *fn)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ const struct firmware *fw = NULL;
+ unsigned int frame_size;
+ unsigned int pos = 0;
+ unsigned int retry = 0;
+ unsigned int frame = 0;
+ int ret;
+
+ ret = request_firmware(&fw, fn, dev);
+ if (ret) {
+ dev_err(dev, "Unable to open firmware %s\n", fn);
+ return ret;
+ }
+
+ /* Check for incorrect enc file */
+ ret = mxt_check_firmware_format(dev, fw);
+ if (ret)
+ goto release_firmware;
+
+ if (!data->in_bootloader) {
+ /* Change to the bootloader mode */
+ data->in_bootloader = true;
+
+ ret = mxt_t6_command(data, MXT_COMMAND_RESET,
+ MXT_BOOT_VALUE, false);
+ if (ret)
+ goto release_firmware;
+
+ msleep(MXT_RESET_TIME);
+
+ /* Do not need to scan since we know family ID */
+ ret = mxt_lookup_bootloader_address(data, 0);
+ if (ret)
+ goto release_firmware;
+
+ mxt_free_input_device(data);
+ mxt_free_object_table(data);
+ } else {
+ enable_irq(data->irq);
+ }
+
+ reinit_completion(&data->bl_completion);
+
+ ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD, false);
+ if (ret) {
+ /* Bootloader may still be unlocked from previous attempt */
+ ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, false);
+ if (ret)
+ goto disable_irq;
+ } else {
+ dev_info(dev, "Unlocking bootloader\n");
+
+ /* Unlock bootloader */
+ ret = mxt_send_bootloader_cmd(data, true);
+ if (ret)
+ goto disable_irq;
+ }
+
+ while (pos < fw->size) {
+ ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, true);
+ if (ret)
+ goto disable_irq;
+
+ frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));
+
+ /* Take account of CRC bytes */
+ frame_size += 2;
+
+ /* Write one frame to device */
+ ret = mxt_bootloader_write(data, fw->data + pos, frame_size);
+ if (ret)
+ goto disable_irq;
+
+ ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS, true);
+ if (ret) {
+ retry++;
+
+ /* Back off by 20ms per retry */
+ msleep(retry * 20);
+
+ if (retry > 20) {
+ dev_err(dev, "Retry count exceeded\n");
+ goto disable_irq;
+ }
+ } else {
+ retry = 0;
+ pos += frame_size;
+ frame++;
+ }
+
+ if (frame % 50 == 0)
+ dev_dbg(dev, "Sent %d frames, %d/%zd bytes\n",
+ frame, pos, fw->size);
+ }
+
+ /* Wait for flash. */
+ ret = mxt_wait_for_completion(data, &data->bl_completion,
+ MXT_FW_RESET_TIME);
+ if (ret)
+ goto disable_irq;
+
+ dev_dbg(dev, "Sent %d frames, %d bytes\n", frame, pos);
+
+ /*
+ * Wait for device to reset. Some bootloader versions do not assert
+ * the CHG line after bootloading has finished, so ignore potential
+ * errors.
+ */
+ mxt_wait_for_completion(data, &data->bl_completion, MXT_FW_RESET_TIME);
+
+ data->in_bootloader = false;
+
+disable_irq:
+ disable_irq(data->irq);
+release_firmware:
+ release_firmware(fw);
+ return ret;
+}
+
+static ssize_t mxt_update_fw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int error;
+
+ error = mxt_load_fw(dev, MXT_FW_NAME);
+ if (error) {
+ dev_err(dev, "The firmware update failed(%d)\n", error);
+ count = error;
+ } else {
+ dev_info(dev, "The firmware update succeeded\n");
+
+ error = mxt_initialize(data);
+ if (error)
+ return error;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(fw_version, S_IRUGO, mxt_fw_version_show, NULL);
+static DEVICE_ATTR(hw_version, S_IRUGO, mxt_hw_version_show, NULL);
+static DEVICE_ATTR(object, S_IRUGO, mxt_object_show, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mxt_update_fw_store);
+
+static struct attribute *mxt_attrs[] = {
+ &dev_attr_fw_version.attr,
+ &dev_attr_hw_version.attr,
+ &dev_attr_object.attr,
+ &dev_attr_update_fw.attr,
+ NULL
+};
+
+static const struct attribute_group mxt_attr_group = {
+ .attrs = mxt_attrs,
+};
+
+static void mxt_start(struct mxt_data *data)
+{
+ mxt_wakeup_toggle(data->client, true, false);
+
+ switch (data->suspend_mode) {
+ case MXT_SUSPEND_T9_CTRL:
+ mxt_soft_reset(data);
+
+ /* Touch enable */
+ /* 0x83 = SCANEN | RPTEN | ENABLE */
+ mxt_write_object(data,
+ MXT_TOUCH_MULTI_T9, MXT_T9_CTRL, 0x83);
+ break;
+
+ case MXT_SUSPEND_DEEP_SLEEP:
+ default:
+ mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN);
+
+ /* Recalibrate since chip has been in deep sleep */
+ mxt_t6_command(data, MXT_COMMAND_CALIBRATE, 1, false);
+ break;
+ }
+}
+
+static void mxt_stop(struct mxt_data *data)
+{
+ switch (data->suspend_mode) {
+ case MXT_SUSPEND_T9_CTRL:
+ /* Touch disable */
+ mxt_write_object(data,
+ MXT_TOUCH_MULTI_T9, MXT_T9_CTRL, 0);
+ break;
+
+ case MXT_SUSPEND_DEEP_SLEEP:
+ default:
+ mxt_set_t7_power_cfg(data, MXT_POWER_CFG_DEEPSLEEP);
+ break;
+ }
+
+ mxt_wakeup_toggle(data->client, false, false);
+}
+
+static int mxt_input_open(struct input_dev *dev)
+{
+ struct mxt_data *data = input_get_drvdata(dev);
+
+ mxt_start(data);
+
+ return 0;
+}
+
+static void mxt_input_close(struct input_dev *dev)
+{
+ struct mxt_data *data = input_get_drvdata(dev);
+
+ mxt_stop(data);
+}
+
+static int mxt_parse_device_properties(struct mxt_data *data)
+{
+ static const char keymap_property[] = "linux,gpio-keymap";
+ struct device *dev = &data->client->dev;
+ u32 *keymap;
+ int n_keys;
+ int error;
+
+ if (device_property_present(dev, keymap_property)) {
+ n_keys = device_property_count_u32(dev, keymap_property);
+ if (n_keys <= 0) {
+ error = n_keys < 0 ? n_keys : -EINVAL;
+ dev_err(dev, "invalid/malformed '%s' property: %d\n",
+ keymap_property, error);
+ return error;
+ }
+
+ keymap = devm_kmalloc_array(dev, n_keys, sizeof(*keymap),
+ GFP_KERNEL);
+ if (!keymap)
+ return -ENOMEM;
+
+ error = device_property_read_u32_array(dev, keymap_property,
+ keymap, n_keys);
+ if (error) {
+ dev_err(dev, "failed to parse '%s' property: %d\n",
+ keymap_property, error);
+ return error;
+ }
+
+ data->t19_keymap = keymap;
+ data->t19_num_keys = n_keys;
+ }
+
+ return 0;
+}
+
+static const struct dmi_system_id chromebook_T9_suspend_dmi[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"),
+ },
+ },
+ { }
+};
+
+static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct mxt_data *data;
+ int error;
+
+ /*
+ * Ignore devices that do not have device properties attached to
+ * them, as we need help determining whether we are dealing with
+ * touch screen or touchpad.
+ *
+ * So far on x86 the only users of Atmel touch controllers are
+ * Chromebooks, and chromeos_laptop driver will ensure that
+ * necessary properties are provided (if firmware does not do that).
+ */
+ if (!device_property_present(&client->dev, "compatible"))
+ return -ENXIO;
+
+ /*
+ * Ignore ACPI devices representing bootloader mode.
+ *
+ * This is a bit of a hack: Google Chromebook BIOS creates ACPI
+ * devices for both application and bootloader modes, but we are
+ * interested in application mode only (if device is in bootloader
+ * mode we'll end up switching into application anyway). So far
+ * application mode addresses were all above 0x40, so we'll use it
+ * as a threshold.
+ */
+ if (ACPI_COMPANION(&client->dev) && client->addr < 0x40)
+ return -ENXIO;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mxt_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0",
+ client->adapter->nr, client->addr);
+
+ data->client = client;
+ data->irq = client->irq;
+ i2c_set_clientdata(client, data);
+
+ init_completion(&data->bl_completion);
+ init_completion(&data->reset_completion);
+ init_completion(&data->crc_completion);
+
+ data->suspend_mode = dmi_check_system(chromebook_T9_suspend_dmi) ?
+ MXT_SUSPEND_T9_CTRL : MXT_SUSPEND_DEEP_SLEEP;
+
+ error = mxt_parse_device_properties(data);
+ if (error)
+ return error;
+
+ /*
+ * VDDA is the analog voltage supply 2.57..3.47 V
+ * VDD is the digital voltage supply 1.71..3.47 V
+ */
+ data->regulators[0].supply = "vdda";
+ data->regulators[1].supply = "vdd";
+ error = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (error) {
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev, "Failed to get regulators %d\n",
+ error);
+ return error;
+ }
+
+ /* Request the RESET line as asserted so we go into reset */
+ data->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(data->reset_gpio)) {
+ error = PTR_ERR(data->reset_gpio);
+ dev_err(&client->dev, "Failed to get reset gpio: %d\n", error);
+ return error;
+ }
+
+ /* Request the WAKE line as asserted so we go out of sleep */
+ data->wake_gpio = devm_gpiod_get_optional(&client->dev,
+ "wake", GPIOD_OUT_HIGH);
+ if (IS_ERR(data->wake_gpio)) {
+ error = PTR_ERR(data->wake_gpio);
+ dev_err(&client->dev, "Failed to get wake gpio: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, mxt_interrupt,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ client->name, data);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = regulator_bulk_enable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (error) {
+ dev_err(&client->dev, "failed to enable regulators: %d\n",
+ error);
+ return error;
+ }
+ /*
+ * The device takes 40ms to come up after power-on according
+ * to the mXT224 datasheet, page 13.
+ */
+ msleep(MXT_BACKUP_TIME);
+
+ if (data->reset_gpio) {
+ /* Wait a while and then de-assert the RESET GPIO line */
+ msleep(MXT_RESET_GPIO_TIME);
+ gpiod_set_value(data->reset_gpio, 0);
+ msleep(MXT_RESET_INVALID_CHG);
+ }
+
+ /*
+ * Controllers like mXT1386 have a dedicated WAKE line that could be
+ * connected to a GPIO or to I2C SCL pin, or permanently asserted low.
+ *
+ * This WAKE line is used for waking controller from a deep-sleep and
+ * it needs to be asserted low for 25 milliseconds before I2C transfers
+ * could be accepted by controller if it was in a deep-sleep mode.
+ * Controller will go into sleep automatically after 2 seconds of
+ * inactivity if WAKE line is deasserted and deep sleep is activated.
+ *
+ * If WAKE line is connected to I2C SCL pin, then the first I2C transfer
+ * will get an instant NAK and transfer needs to be retried after 25ms.
+ *
+ * If WAKE line is connected to a GPIO line, the line must be asserted
+ * 25ms before the host attempts to communicate with the controller.
+ */
+ device_property_read_u32(&client->dev, "atmel,wakeup-method",
+ &data->wakeup_method);
+
+ error = mxt_initialize(data);
+ if (error)
+ goto err_disable_regulators;
+
+ error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group);
+ if (error) {
+ dev_err(&client->dev, "Failure %d creating sysfs group\n",
+ error);
+ goto err_free_object;
+ }
+
+ return 0;
+
+err_free_object:
+ mxt_free_input_device(data);
+ mxt_free_object_table(data);
+err_disable_regulators:
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+ return error;
+}
+
+static void mxt_remove(struct i2c_client *client)
+{
+ struct mxt_data *data = i2c_get_clientdata(client);
+
+ disable_irq(data->irq);
+ sysfs_remove_group(&client->dev.kobj, &mxt_attr_group);
+ mxt_free_input_device(data);
+ mxt_free_object_table(data);
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+}
+
+static int __maybe_unused mxt_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mxt_data *data = i2c_get_clientdata(client);
+ struct input_dev *input_dev = data->input_dev;
+
+ if (!input_dev)
+ return 0;
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ mxt_stop(data);
+
+ mutex_unlock(&input_dev->mutex);
+
+ disable_irq(data->irq);
+
+ return 0;
+}
+
+static int __maybe_unused mxt_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mxt_data *data = i2c_get_clientdata(client);
+ struct input_dev *input_dev = data->input_dev;
+
+ if (!input_dev)
+ return 0;
+
+ enable_irq(data->irq);
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ mxt_start(data);
+
+ mutex_unlock(&input_dev->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mxt_pm_ops, mxt_suspend, mxt_resume);
+
+static const struct of_device_id mxt_of_match[] = {
+ { .compatible = "atmel,maxtouch", },
+ /* Compatibles listed below are deprecated */
+ { .compatible = "atmel,qt602240_ts", },
+ { .compatible = "atmel,atmel_mxt_ts", },
+ { .compatible = "atmel,atmel_mxt_tp", },
+ { .compatible = "atmel,mXT224", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mxt_of_match);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id mxt_acpi_id[] = {
+ { "ATML0000", 0 }, /* Touchpad */
+ { "ATML0001", 0 }, /* Touchscreen */
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, mxt_acpi_id);
+#endif
+
+static const struct i2c_device_id mxt_id[] = {
+ { "qt602240_ts", 0 },
+ { "atmel_mxt_ts", 0 },
+ { "atmel_mxt_tp", 0 },
+ { "maxtouch", 0 },
+ { "mXT224", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mxt_id);
+
+static struct i2c_driver mxt_driver = {
+ .driver = {
+ .name = "atmel_mxt_ts",
+ .of_match_table = mxt_of_match,
+ .acpi_match_table = ACPI_PTR(mxt_acpi_id),
+ .pm = &mxt_pm_ops,
+ },
+ .probe = mxt_probe,
+ .remove = mxt_remove,
+ .id_table = mxt_id,
+};
+
+module_i2c_driver(mxt_driver);
+
+/* Module information */
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_DESCRIPTION("Atmel maXTouch Touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c
new file mode 100644
index 000000000..2deae5a68
--- /dev/null
+++ b/drivers/input/touchscreen/auo-pixcir-ts.c
@@ -0,0 +1,648 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for AUO in-cell touchscreens
+ *
+ * Copyright (c) 2011 Heiko Stuebner <heiko@sntech.de>
+ *
+ * loosely based on auo_touch.c from Dell Streak vendor-kernel
+ *
+ * Copyright (c) 2008 QUALCOMM Incorporated.
+ * Copyright (c) 2008 QUALCOMM USA, INC.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/property.h>
+
+/*
+ * Coordinate calculation:
+ * X1 = X1_LSB + X1_MSB*256
+ * Y1 = Y1_LSB + Y1_MSB*256
+ * X2 = X2_LSB + X2_MSB*256
+ * Y2 = Y2_LSB + Y2_MSB*256
+ */
+#define AUO_PIXCIR_REG_X1_LSB 0x00
+#define AUO_PIXCIR_REG_X1_MSB 0x01
+#define AUO_PIXCIR_REG_Y1_LSB 0x02
+#define AUO_PIXCIR_REG_Y1_MSB 0x03
+#define AUO_PIXCIR_REG_X2_LSB 0x04
+#define AUO_PIXCIR_REG_X2_MSB 0x05
+#define AUO_PIXCIR_REG_Y2_LSB 0x06
+#define AUO_PIXCIR_REG_Y2_MSB 0x07
+
+#define AUO_PIXCIR_REG_STRENGTH 0x0d
+#define AUO_PIXCIR_REG_STRENGTH_X1_LSB 0x0e
+#define AUO_PIXCIR_REG_STRENGTH_X1_MSB 0x0f
+
+#define AUO_PIXCIR_REG_RAW_DATA_X 0x2b
+#define AUO_PIXCIR_REG_RAW_DATA_Y 0x4f
+
+#define AUO_PIXCIR_REG_X_SENSITIVITY 0x6f
+#define AUO_PIXCIR_REG_Y_SENSITIVITY 0x70
+#define AUO_PIXCIR_REG_INT_SETTING 0x71
+#define AUO_PIXCIR_REG_INT_WIDTH 0x72
+#define AUO_PIXCIR_REG_POWER_MODE 0x73
+
+#define AUO_PIXCIR_REG_VERSION 0x77
+#define AUO_PIXCIR_REG_CALIBRATE 0x78
+
+#define AUO_PIXCIR_REG_TOUCHAREA_X1 0x1e
+#define AUO_PIXCIR_REG_TOUCHAREA_Y1 0x1f
+#define AUO_PIXCIR_REG_TOUCHAREA_X2 0x20
+#define AUO_PIXCIR_REG_TOUCHAREA_Y2 0x21
+
+#define AUO_PIXCIR_REG_EEPROM_CALIB_X 0x42
+#define AUO_PIXCIR_REG_EEPROM_CALIB_Y 0xad
+
+#define AUO_PIXCIR_INT_TPNUM_MASK 0xe0
+#define AUO_PIXCIR_INT_TPNUM_SHIFT 5
+#define AUO_PIXCIR_INT_RELEASE (1 << 4)
+#define AUO_PIXCIR_INT_ENABLE (1 << 3)
+#define AUO_PIXCIR_INT_POL_HIGH (1 << 2)
+
+/*
+ * Interrupt modes:
+ * periodical: interrupt is asserted periodicaly
+ * compare coordinates: interrupt is asserted when coordinates change
+ * indicate touch: interrupt is asserted during touch
+ */
+#define AUO_PIXCIR_INT_PERIODICAL 0x00
+#define AUO_PIXCIR_INT_COMP_COORD 0x01
+#define AUO_PIXCIR_INT_TOUCH_IND 0x02
+#define AUO_PIXCIR_INT_MODE_MASK 0x03
+
+/*
+ * Power modes:
+ * active: scan speed 60Hz
+ * sleep: scan speed 10Hz can be auto-activated, wakeup on 1st touch
+ * deep sleep: scan speed 1Hz can only be entered or left manually.
+ */
+#define AUO_PIXCIR_POWER_ACTIVE 0x00
+#define AUO_PIXCIR_POWER_SLEEP 0x01
+#define AUO_PIXCIR_POWER_DEEP_SLEEP 0x02
+#define AUO_PIXCIR_POWER_MASK 0x03
+
+#define AUO_PIXCIR_POWER_ALLOW_SLEEP (1 << 2)
+#define AUO_PIXCIR_POWER_IDLE_TIME(ms) ((ms & 0xf) << 4)
+
+#define AUO_PIXCIR_CALIBRATE 0x03
+
+#define AUO_PIXCIR_EEPROM_CALIB_X_LEN 62
+#define AUO_PIXCIR_EEPROM_CALIB_Y_LEN 36
+
+#define AUO_PIXCIR_RAW_DATA_X_LEN 18
+#define AUO_PIXCIR_RAW_DATA_Y_LEN 11
+
+#define AUO_PIXCIR_STRENGTH_ENABLE (1 << 0)
+
+/* Touchscreen absolute values */
+#define AUO_PIXCIR_REPORT_POINTS 2
+#define AUO_PIXCIR_MAX_AREA 0xff
+#define AUO_PIXCIR_PENUP_TIMEOUT_MS 10
+
+struct auo_pixcir_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *gpio_int;
+ struct gpio_desc *gpio_rst;
+ char phys[32];
+
+ unsigned int x_max;
+ unsigned int y_max;
+
+ /* special handling for touch_indicate interrupt mode */
+ bool touch_ind_mode;
+
+ wait_queue_head_t wait;
+ bool stopped;
+};
+
+struct auo_point_t {
+ int coord_x;
+ int coord_y;
+ int area_major;
+ int area_minor;
+ int orientation;
+};
+
+static int auo_pixcir_collect_data(struct auo_pixcir_ts *ts,
+ struct auo_point_t *point)
+{
+ struct i2c_client *client = ts->client;
+ uint8_t raw_coord[8];
+ uint8_t raw_area[4];
+ int i, ret;
+
+ /* touch coordinates */
+ ret = i2c_smbus_read_i2c_block_data(client, AUO_PIXCIR_REG_X1_LSB,
+ 8, raw_coord);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to read coordinate, %d\n", ret);
+ return ret;
+ }
+
+ /* touch area */
+ ret = i2c_smbus_read_i2c_block_data(client, AUO_PIXCIR_REG_TOUCHAREA_X1,
+ 4, raw_area);
+ if (ret < 0) {
+ dev_err(&client->dev, "could not read touch area, %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < AUO_PIXCIR_REPORT_POINTS; i++) {
+ point[i].coord_x =
+ raw_coord[4 * i + 1] << 8 | raw_coord[4 * i];
+ point[i].coord_y =
+ raw_coord[4 * i + 3] << 8 | raw_coord[4 * i + 2];
+
+ if (point[i].coord_x > ts->x_max ||
+ point[i].coord_y > ts->y_max) {
+ dev_warn(&client->dev, "coordinates (%d,%d) invalid\n",
+ point[i].coord_x, point[i].coord_y);
+ point[i].coord_x = point[i].coord_y = 0;
+ }
+
+ /* determine touch major, minor and orientation */
+ point[i].area_major = max(raw_area[2 * i], raw_area[2 * i + 1]);
+ point[i].area_minor = min(raw_area[2 * i], raw_area[2 * i + 1]);
+ point[i].orientation = raw_area[2 * i] > raw_area[2 * i + 1];
+ }
+
+ return 0;
+}
+
+static irqreturn_t auo_pixcir_interrupt(int irq, void *dev_id)
+{
+ struct auo_pixcir_ts *ts = dev_id;
+ struct auo_point_t point[AUO_PIXCIR_REPORT_POINTS];
+ int i;
+ int ret;
+ int fingers = 0;
+ int abs = -1;
+
+ while (!ts->stopped) {
+
+ /* check for up event in touch touch_ind_mode */
+ if (ts->touch_ind_mode) {
+ if (gpiod_get_value_cansleep(ts->gpio_int) == 0) {
+ input_mt_sync(ts->input);
+ input_report_key(ts->input, BTN_TOUCH, 0);
+ input_sync(ts->input);
+ break;
+ }
+ }
+
+ ret = auo_pixcir_collect_data(ts, point);
+ if (ret < 0) {
+ /* we want to loop only in touch_ind_mode */
+ if (!ts->touch_ind_mode)
+ break;
+
+ wait_event_timeout(ts->wait, ts->stopped,
+ msecs_to_jiffies(AUO_PIXCIR_PENUP_TIMEOUT_MS));
+ continue;
+ }
+
+ for (i = 0; i < AUO_PIXCIR_REPORT_POINTS; i++) {
+ if (point[i].coord_x > 0 || point[i].coord_y > 0) {
+ input_report_abs(ts->input, ABS_MT_POSITION_X,
+ point[i].coord_x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y,
+ point[i].coord_y);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR,
+ point[i].area_major);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MINOR,
+ point[i].area_minor);
+ input_report_abs(ts->input, ABS_MT_ORIENTATION,
+ point[i].orientation);
+ input_mt_sync(ts->input);
+
+ /* use first finger as source for singletouch */
+ if (fingers == 0)
+ abs = i;
+
+ /* number of touch points could also be queried
+ * via i2c but would require an additional call
+ */
+ fingers++;
+ }
+ }
+
+ input_report_key(ts->input, BTN_TOUCH, fingers > 0);
+
+ if (abs > -1) {
+ input_report_abs(ts->input, ABS_X, point[abs].coord_x);
+ input_report_abs(ts->input, ABS_Y, point[abs].coord_y);
+ }
+
+ input_sync(ts->input);
+
+ /* we want to loop only in touch_ind_mode */
+ if (!ts->touch_ind_mode)
+ break;
+
+ wait_event_timeout(ts->wait, ts->stopped,
+ msecs_to_jiffies(AUO_PIXCIR_PENUP_TIMEOUT_MS));
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Set the power mode of the device.
+ * Valid modes are
+ * - AUO_PIXCIR_POWER_ACTIVE
+ * - AUO_PIXCIR_POWER_SLEEP - automatically left on first touch
+ * - AUO_PIXCIR_POWER_DEEP_SLEEP
+ */
+static int auo_pixcir_power_mode(struct auo_pixcir_ts *ts, int mode)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_POWER_MODE);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to read reg %Xh, %d\n",
+ AUO_PIXCIR_REG_POWER_MODE, ret);
+ return ret;
+ }
+
+ ret &= ~AUO_PIXCIR_POWER_MASK;
+ ret |= mode;
+
+ ret = i2c_smbus_write_byte_data(client, AUO_PIXCIR_REG_POWER_MODE, ret);
+ if (ret) {
+ dev_err(&client->dev, "unable to write reg %Xh, %d\n",
+ AUO_PIXCIR_REG_POWER_MODE, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int auo_pixcir_int_config(struct auo_pixcir_ts *ts, int int_setting)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_INT_SETTING);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to read reg %Xh, %d\n",
+ AUO_PIXCIR_REG_INT_SETTING, ret);
+ return ret;
+ }
+
+ ret &= ~AUO_PIXCIR_INT_MODE_MASK;
+ ret |= int_setting;
+ ret |= AUO_PIXCIR_INT_POL_HIGH; /* always use high for interrupts */
+
+ ret = i2c_smbus_write_byte_data(client, AUO_PIXCIR_REG_INT_SETTING,
+ ret);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to write reg %Xh, %d\n",
+ AUO_PIXCIR_REG_INT_SETTING, ret);
+ return ret;
+ }
+
+ ts->touch_ind_mode = int_setting == AUO_PIXCIR_INT_TOUCH_IND;
+
+ return 0;
+}
+
+/* control the generation of interrupts on the device side */
+static int auo_pixcir_int_toggle(struct auo_pixcir_ts *ts, bool enable)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_INT_SETTING);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to read reg %Xh, %d\n",
+ AUO_PIXCIR_REG_INT_SETTING, ret);
+ return ret;
+ }
+
+ if (enable)
+ ret |= AUO_PIXCIR_INT_ENABLE;
+ else
+ ret &= ~AUO_PIXCIR_INT_ENABLE;
+
+ ret = i2c_smbus_write_byte_data(client, AUO_PIXCIR_REG_INT_SETTING,
+ ret);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to write reg %Xh, %d\n",
+ AUO_PIXCIR_REG_INT_SETTING, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int auo_pixcir_start(struct auo_pixcir_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = auo_pixcir_power_mode(ts, AUO_PIXCIR_POWER_ACTIVE);
+ if (ret < 0) {
+ dev_err(&client->dev, "could not set power mode, %d\n",
+ ret);
+ return ret;
+ }
+
+ ts->stopped = false;
+ mb();
+ enable_irq(client->irq);
+
+ ret = auo_pixcir_int_toggle(ts, 1);
+ if (ret < 0) {
+ dev_err(&client->dev, "could not enable interrupt, %d\n",
+ ret);
+ disable_irq(client->irq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int auo_pixcir_stop(struct auo_pixcir_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = auo_pixcir_int_toggle(ts, 0);
+ if (ret < 0) {
+ dev_err(&client->dev, "could not disable interrupt, %d\n",
+ ret);
+ return ret;
+ }
+
+ /* disable receiving of interrupts */
+ disable_irq(client->irq);
+ ts->stopped = true;
+ mb();
+ wake_up(&ts->wait);
+
+ return auo_pixcir_power_mode(ts, AUO_PIXCIR_POWER_DEEP_SLEEP);
+}
+
+static int auo_pixcir_input_open(struct input_dev *dev)
+{
+ struct auo_pixcir_ts *ts = input_get_drvdata(dev);
+
+ return auo_pixcir_start(ts);
+}
+
+static void auo_pixcir_input_close(struct input_dev *dev)
+{
+ struct auo_pixcir_ts *ts = input_get_drvdata(dev);
+
+ auo_pixcir_stop(ts);
+}
+
+static int __maybe_unused auo_pixcir_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct auo_pixcir_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+
+ /* when configured as wakeup source, device should always wake system
+ * therefore start device if necessary
+ */
+ if (device_may_wakeup(&client->dev)) {
+ /* need to start device if not open, to be wakeup source */
+ if (!input_device_enabled(input)) {
+ ret = auo_pixcir_start(ts);
+ if (ret)
+ goto unlock;
+ }
+
+ enable_irq_wake(client->irq);
+ ret = auo_pixcir_power_mode(ts, AUO_PIXCIR_POWER_SLEEP);
+ } else if (input_device_enabled(input)) {
+ ret = auo_pixcir_stop(ts);
+ }
+
+unlock:
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static int __maybe_unused auo_pixcir_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct auo_pixcir_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+
+ if (device_may_wakeup(&client->dev)) {
+ disable_irq_wake(client->irq);
+
+ /* need to stop device if it was not open on suspend */
+ if (!input_device_enabled(input)) {
+ ret = auo_pixcir_stop(ts);
+ if (ret)
+ goto unlock;
+ }
+
+ /* device wakes automatically from SLEEP */
+ } else if (input_device_enabled(input)) {
+ ret = auo_pixcir_start(ts);
+ }
+
+unlock:
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(auo_pixcir_pm_ops,
+ auo_pixcir_suspend, auo_pixcir_resume);
+
+static void auo_pixcir_reset(void *data)
+{
+ struct auo_pixcir_ts *ts = data;
+
+ gpiod_set_value_cansleep(ts->gpio_rst, 1);
+}
+
+static int auo_pixcir_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct auo_pixcir_ts *ts;
+ struct input_dev *input_dev;
+ int version;
+ int error;
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev) {
+ dev_err(&client->dev, "could not allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->client = client;
+ ts->input = input_dev;
+ ts->touch_ind_mode = 0;
+ ts->stopped = true;
+ init_waitqueue_head(&ts->wait);
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input0", dev_name(&client->dev));
+
+ if (device_property_read_u32(&client->dev, "x-size", &ts->x_max)) {
+ dev_err(&client->dev, "failed to get x-size property\n");
+ return -EINVAL;
+ }
+
+ if (device_property_read_u32(&client->dev, "y-size", &ts->y_max)) {
+ dev_err(&client->dev, "failed to get y-size property\n");
+ return -EINVAL;
+ }
+
+ input_dev->name = "AUO-Pixcir touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->id.bustype = BUS_I2C;
+
+ input_dev->open = auo_pixcir_input_open;
+ input_dev->close = auo_pixcir_input_close;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+
+ /* For single touch */
+ input_set_abs_params(input_dev, ABS_X, 0, ts->x_max, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, ts->y_max, 0, 0);
+
+ /* For multi touch */
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
+ 0, AUO_PIXCIR_MAX_AREA, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR,
+ 0, AUO_PIXCIR_MAX_AREA, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+
+ input_set_drvdata(ts->input, ts);
+
+ ts->gpio_int = devm_gpiod_get_index(&client->dev, NULL, 0, GPIOD_IN);
+ error = PTR_ERR_OR_ZERO(ts->gpio_int);
+ if (error) {
+ dev_err(&client->dev,
+ "request of int gpio failed: %d\n", error);
+ return error;
+ }
+
+ gpiod_set_consumer_name(ts->gpio_int, "auo_pixcir_ts_int");
+
+ /* Take the chip out of reset */
+ ts->gpio_rst = devm_gpiod_get_index(&client->dev, NULL, 1,
+ GPIOD_OUT_LOW);
+ error = PTR_ERR_OR_ZERO(ts->gpio_rst);
+ if (error) {
+ dev_err(&client->dev,
+ "request of reset gpio failed: %d\n", error);
+ return error;
+ }
+
+ gpiod_set_consumer_name(ts->gpio_rst, "auo_pixcir_ts_rst");
+
+ error = devm_add_action_or_reset(&client->dev, auo_pixcir_reset, ts);
+ if (error) {
+ dev_err(&client->dev, "failed to register reset action, %d\n",
+ error);
+ return error;
+ }
+
+ msleep(200);
+
+ version = i2c_smbus_read_byte_data(client, AUO_PIXCIR_REG_VERSION);
+ if (version < 0) {
+ error = version;
+ return error;
+ }
+
+ dev_info(&client->dev, "firmware version 0x%X\n", version);
+
+ /* default to asserting the interrupt when the screen is touched */
+ error = auo_pixcir_int_config(ts, AUO_PIXCIR_INT_TOUCH_IND);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, auo_pixcir_interrupt,
+ IRQF_ONESHOT,
+ input_dev->name, ts);
+ if (error) {
+ dev_err(&client->dev, "irq %d requested failed, %d\n",
+ client->irq, error);
+ return error;
+ }
+
+ /* stop device and put it into deep sleep until it is opened */
+ error = auo_pixcir_stop(ts);
+ if (error)
+ return error;
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&client->dev, "could not register input device, %d\n",
+ error);
+ return error;
+ }
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+}
+
+static const struct i2c_device_id auo_pixcir_idtable[] = {
+ { "auo_pixcir_ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, auo_pixcir_idtable);
+
+#ifdef CONFIG_OF
+static const struct of_device_id auo_pixcir_ts_dt_idtable[] = {
+ { .compatible = "auo,auo_pixcir_ts" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, auo_pixcir_ts_dt_idtable);
+#endif
+
+static struct i2c_driver auo_pixcir_driver = {
+ .driver = {
+ .name = "auo_pixcir_ts",
+ .pm = &auo_pixcir_pm_ops,
+ .of_match_table = of_match_ptr(auo_pixcir_ts_dt_idtable),
+ },
+ .probe = auo_pixcir_probe,
+ .id_table = auo_pixcir_idtable,
+};
+
+module_i2c_driver(auo_pixcir_driver);
+
+MODULE_DESCRIPTION("AUO-PIXCIR touchscreen driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
diff --git a/drivers/input/touchscreen/bcm_iproc_tsc.c b/drivers/input/touchscreen/bcm_iproc_tsc.c
new file mode 100644
index 000000000..35e2fe991
--- /dev/null
+++ b/drivers/input/touchscreen/bcm_iproc_tsc.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+* Copyright (C) 2015 Broadcom Corporation
+*
+*/
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/keyboard.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <asm/irq.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/serio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#define IPROC_TS_NAME "iproc-ts"
+
+#define PEN_DOWN_STATUS 1
+#define PEN_UP_STATUS 0
+
+#define X_MIN 0
+#define Y_MIN 0
+#define X_MAX 0xFFF
+#define Y_MAX 0xFFF
+
+/* Value given by controller for invalid coordinate. */
+#define INVALID_COORD 0xFFFFFFFF
+
+/* Register offsets */
+#define REGCTL1 0x00
+#define REGCTL2 0x04
+#define INTERRUPT_THRES 0x08
+#define INTERRUPT_MASK 0x0c
+
+#define INTERRUPT_STATUS 0x10
+#define CONTROLLER_STATUS 0x14
+#define FIFO_DATA 0x18
+#define FIFO_DATA_X_Y_MASK 0xFFFF
+#define ANALOG_CONTROL 0x1c
+
+#define AUX_DATA 0x20
+#define DEBOUNCE_CNTR_STAT 0x24
+#define SCAN_CNTR_STAT 0x28
+#define REM_CNTR_STAT 0x2c
+
+#define SETTLING_TIMER_STAT 0x30
+#define SPARE_REG 0x34
+#define SOFT_BYPASS_CONTROL 0x38
+#define SOFT_BYPASS_DATA 0x3c
+
+
+/* Bit values for INTERRUPT_MASK and INTERRUPT_STATUS regs */
+#define TS_PEN_INTR_MASK BIT(0)
+#define TS_FIFO_INTR_MASK BIT(2)
+
+/* Bit values for CONTROLLER_STATUS reg1 */
+#define TS_PEN_DOWN BIT(0)
+
+/* Shift values for control reg1 */
+#define SCANNING_PERIOD_SHIFT 24
+#define DEBOUNCE_TIMEOUT_SHIFT 16
+#define SETTLING_TIMEOUT_SHIFT 8
+#define TOUCH_TIMEOUT_SHIFT 0
+
+/* Shift values for coordinates from fifo */
+#define X_COORD_SHIFT 0
+#define Y_COORD_SHIFT 16
+
+/* Bit values for REGCTL2 */
+#define TS_CONTROLLER_EN_BIT BIT(16)
+#define TS_CONTROLLER_AVGDATA_SHIFT 8
+#define TS_CONTROLLER_AVGDATA_MASK (0x7 << TS_CONTROLLER_AVGDATA_SHIFT)
+#define TS_CONTROLLER_PWR_LDO BIT(5)
+#define TS_CONTROLLER_PWR_ADC BIT(4)
+#define TS_CONTROLLER_PWR_BGP BIT(3)
+#define TS_CONTROLLER_PWR_TS BIT(2)
+#define TS_WIRE_MODE_BIT BIT(1)
+
+#define dbg_reg(dev, priv, reg) \
+do { \
+ u32 val; \
+ regmap_read(priv->regmap, reg, &val); \
+ dev_dbg(dev, "%20s= 0x%08x\n", #reg, val); \
+} while (0)
+
+struct tsc_param {
+ /* Each step is 1024 us. Valid 1-256 */
+ u32 scanning_period;
+
+ /* Each step is 512 us. Valid 0-255 */
+ u32 debounce_timeout;
+
+ /*
+ * The settling duration (in ms) is the amount of time the tsc
+ * waits to allow the voltage to settle after turning on the
+ * drivers in detection mode. Valid values: 0-11
+ * 0 = 0.008 ms
+ * 1 = 0.01 ms
+ * 2 = 0.02 ms
+ * 3 = 0.04 ms
+ * 4 = 0.08 ms
+ * 5 = 0.16 ms
+ * 6 = 0.32 ms
+ * 7 = 0.64 ms
+ * 8 = 1.28 ms
+ * 9 = 2.56 ms
+ * 10 = 5.12 ms
+ * 11 = 10.24 ms
+ */
+ u32 settling_timeout;
+
+ /* touch timeout in sample counts */
+ u32 touch_timeout;
+
+ /*
+ * Number of data samples which are averaged before a final data point
+ * is placed into the FIFO
+ */
+ u32 average_data;
+
+ /* FIFO threshold */
+ u32 fifo_threshold;
+
+ /* Optional standard touchscreen properties. */
+ u32 max_x;
+ u32 max_y;
+ u32 fuzz_x;
+ u32 fuzz_y;
+ bool invert_x;
+ bool invert_y;
+};
+
+struct iproc_ts_priv {
+ struct platform_device *pdev;
+ struct input_dev *idev;
+
+ struct regmap *regmap;
+ struct clk *tsc_clk;
+
+ int pen_status;
+ struct tsc_param cfg_params;
+};
+
+/*
+ * Set default values the same as hardware reset values
+ * except for fifo_threshold with is set to 1.
+ */
+static const struct tsc_param iproc_default_config = {
+ .scanning_period = 0x5, /* 1 to 256 */
+ .debounce_timeout = 0x28, /* 0 to 255 */
+ .settling_timeout = 0x7, /* 0 to 11 */
+ .touch_timeout = 0xa, /* 0 to 255 */
+ .average_data = 5, /* entry 5 = 32 pts */
+ .fifo_threshold = 1, /* 0 to 31 */
+ .max_x = X_MAX,
+ .max_y = Y_MAX,
+};
+
+static void ts_reg_dump(struct iproc_ts_priv *priv)
+{
+ struct device *dev = &priv->pdev->dev;
+
+ dbg_reg(dev, priv, REGCTL1);
+ dbg_reg(dev, priv, REGCTL2);
+ dbg_reg(dev, priv, INTERRUPT_THRES);
+ dbg_reg(dev, priv, INTERRUPT_MASK);
+ dbg_reg(dev, priv, INTERRUPT_STATUS);
+ dbg_reg(dev, priv, CONTROLLER_STATUS);
+ dbg_reg(dev, priv, FIFO_DATA);
+ dbg_reg(dev, priv, ANALOG_CONTROL);
+ dbg_reg(dev, priv, AUX_DATA);
+ dbg_reg(dev, priv, DEBOUNCE_CNTR_STAT);
+ dbg_reg(dev, priv, SCAN_CNTR_STAT);
+ dbg_reg(dev, priv, REM_CNTR_STAT);
+ dbg_reg(dev, priv, SETTLING_TIMER_STAT);
+ dbg_reg(dev, priv, SPARE_REG);
+ dbg_reg(dev, priv, SOFT_BYPASS_CONTROL);
+ dbg_reg(dev, priv, SOFT_BYPASS_DATA);
+}
+
+static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data)
+{
+ struct platform_device *pdev = data;
+ struct iproc_ts_priv *priv = platform_get_drvdata(pdev);
+ u32 intr_status;
+ u32 raw_coordinate;
+ u16 x;
+ u16 y;
+ int i;
+ bool needs_sync = false;
+
+ regmap_read(priv->regmap, INTERRUPT_STATUS, &intr_status);
+ intr_status &= TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK;
+ if (intr_status == 0)
+ return IRQ_NONE;
+
+ /* Clear all interrupt status bits, write-1-clear */
+ regmap_write(priv->regmap, INTERRUPT_STATUS, intr_status);
+ /* Pen up/down */
+ if (intr_status & TS_PEN_INTR_MASK) {
+ regmap_read(priv->regmap, CONTROLLER_STATUS, &priv->pen_status);
+ if (priv->pen_status & TS_PEN_DOWN)
+ priv->pen_status = PEN_DOWN_STATUS;
+ else
+ priv->pen_status = PEN_UP_STATUS;
+
+ input_report_key(priv->idev, BTN_TOUCH, priv->pen_status);
+ needs_sync = true;
+
+ dev_dbg(&priv->pdev->dev,
+ "pen up-down (%d)\n", priv->pen_status);
+ }
+
+ /* coordinates in FIFO exceed the theshold */
+ if (intr_status & TS_FIFO_INTR_MASK) {
+ for (i = 0; i < priv->cfg_params.fifo_threshold; i++) {
+ regmap_read(priv->regmap, FIFO_DATA, &raw_coordinate);
+ if (raw_coordinate == INVALID_COORD)
+ continue;
+
+ /*
+ * The x and y coordinate are 16 bits each
+ * with the x in the lower 16 bits and y in the
+ * upper 16 bits.
+ */
+ x = (raw_coordinate >> X_COORD_SHIFT) &
+ FIFO_DATA_X_Y_MASK;
+ y = (raw_coordinate >> Y_COORD_SHIFT) &
+ FIFO_DATA_X_Y_MASK;
+
+ /* We only want to retain the 12 msb of the 16 */
+ x = (x >> 4) & 0x0FFF;
+ y = (y >> 4) & 0x0FFF;
+
+ /* Adjust x y according to LCD tsc mount angle. */
+ if (priv->cfg_params.invert_x)
+ x = priv->cfg_params.max_x - x;
+
+ if (priv->cfg_params.invert_y)
+ y = priv->cfg_params.max_y - y;
+
+ input_report_abs(priv->idev, ABS_X, x);
+ input_report_abs(priv->idev, ABS_Y, y);
+ needs_sync = true;
+
+ dev_dbg(&priv->pdev->dev, "xy (0x%x 0x%x)\n", x, y);
+ }
+ }
+
+ if (needs_sync)
+ input_sync(priv->idev);
+
+ return IRQ_HANDLED;
+}
+
+static int iproc_ts_start(struct input_dev *idev)
+{
+ u32 val;
+ u32 mask;
+ int error;
+ struct iproc_ts_priv *priv = input_get_drvdata(idev);
+
+ /* Enable clock */
+ error = clk_prepare_enable(priv->tsc_clk);
+ if (error) {
+ dev_err(&priv->pdev->dev, "%s clk_prepare_enable failed %d\n",
+ __func__, error);
+ return error;
+ }
+
+ /*
+ * Interrupt is generated when:
+ * FIFO reaches the int_th value, and pen event(up/down)
+ */
+ val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK;
+ regmap_update_bits(priv->regmap, INTERRUPT_MASK, val, val);
+
+ val = priv->cfg_params.fifo_threshold;
+ regmap_write(priv->regmap, INTERRUPT_THRES, val);
+
+ /* Initialize control reg1 */
+ val = 0;
+ val |= priv->cfg_params.scanning_period << SCANNING_PERIOD_SHIFT;
+ val |= priv->cfg_params.debounce_timeout << DEBOUNCE_TIMEOUT_SHIFT;
+ val |= priv->cfg_params.settling_timeout << SETTLING_TIMEOUT_SHIFT;
+ val |= priv->cfg_params.touch_timeout << TOUCH_TIMEOUT_SHIFT;
+ regmap_write(priv->regmap, REGCTL1, val);
+
+ /* Try to clear all interrupt status */
+ val = TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK;
+ regmap_update_bits(priv->regmap, INTERRUPT_STATUS, val, val);
+
+ /* Initialize control reg2 */
+ val = TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT;
+ val |= priv->cfg_params.average_data << TS_CONTROLLER_AVGDATA_SHIFT;
+
+ mask = (TS_CONTROLLER_AVGDATA_MASK);
+ mask |= (TS_CONTROLLER_PWR_LDO | /* PWR up LDO */
+ TS_CONTROLLER_PWR_ADC | /* PWR up ADC */
+ TS_CONTROLLER_PWR_BGP | /* PWR up BGP */
+ TS_CONTROLLER_PWR_TS); /* PWR up TS */
+ mask |= val;
+ regmap_update_bits(priv->regmap, REGCTL2, mask, val);
+
+ ts_reg_dump(priv);
+
+ return 0;
+}
+
+static void iproc_ts_stop(struct input_dev *dev)
+{
+ u32 val;
+ struct iproc_ts_priv *priv = input_get_drvdata(dev);
+
+ /*
+ * Disable FIFO int_th and pen event(up/down)Interrupts only
+ * as the interrupt mask register is shared between ADC, TS and
+ * flextimer.
+ */
+ val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK;
+ regmap_update_bits(priv->regmap, INTERRUPT_MASK, val, 0);
+
+ /* Only power down touch screen controller */
+ val = TS_CONTROLLER_PWR_TS;
+ regmap_update_bits(priv->regmap, REGCTL2, val, val);
+
+ clk_disable(priv->tsc_clk);
+}
+
+static int iproc_get_tsc_config(struct device *dev, struct iproc_ts_priv *priv)
+{
+ struct device_node *np = dev->of_node;
+ u32 val;
+
+ priv->cfg_params = iproc_default_config;
+
+ if (!np)
+ return 0;
+
+ if (of_property_read_u32(np, "scanning_period", &val) >= 0) {
+ if (val < 1 || val > 256) {
+ dev_err(dev, "scanning_period (%u) must be [1-256]\n",
+ val);
+ return -EINVAL;
+ }
+ priv->cfg_params.scanning_period = val;
+ }
+
+ if (of_property_read_u32(np, "debounce_timeout", &val) >= 0) {
+ if (val > 255) {
+ dev_err(dev, "debounce_timeout (%u) must be [0-255]\n",
+ val);
+ return -EINVAL;
+ }
+ priv->cfg_params.debounce_timeout = val;
+ }
+
+ if (of_property_read_u32(np, "settling_timeout", &val) >= 0) {
+ if (val > 11) {
+ dev_err(dev, "settling_timeout (%u) must be [0-11]\n",
+ val);
+ return -EINVAL;
+ }
+ priv->cfg_params.settling_timeout = val;
+ }
+
+ if (of_property_read_u32(np, "touch_timeout", &val) >= 0) {
+ if (val > 255) {
+ dev_err(dev, "touch_timeout (%u) must be [0-255]\n",
+ val);
+ return -EINVAL;
+ }
+ priv->cfg_params.touch_timeout = val;
+ }
+
+ if (of_property_read_u32(np, "average_data", &val) >= 0) {
+ if (val > 8) {
+ dev_err(dev, "average_data (%u) must be [0-8]\n", val);
+ return -EINVAL;
+ }
+ priv->cfg_params.average_data = val;
+ }
+
+ if (of_property_read_u32(np, "fifo_threshold", &val) >= 0) {
+ if (val > 31) {
+ dev_err(dev, "fifo_threshold (%u)) must be [0-31]\n",
+ val);
+ return -EINVAL;
+ }
+ priv->cfg_params.fifo_threshold = val;
+ }
+
+ /* Parse optional properties. */
+ of_property_read_u32(np, "touchscreen-size-x", &priv->cfg_params.max_x);
+ of_property_read_u32(np, "touchscreen-size-y", &priv->cfg_params.max_y);
+
+ of_property_read_u32(np, "touchscreen-fuzz-x",
+ &priv->cfg_params.fuzz_x);
+ of_property_read_u32(np, "touchscreen-fuzz-y",
+ &priv->cfg_params.fuzz_y);
+
+ priv->cfg_params.invert_x =
+ of_property_read_bool(np, "touchscreen-inverted-x");
+ priv->cfg_params.invert_y =
+ of_property_read_bool(np, "touchscreen-inverted-y");
+
+ return 0;
+}
+
+static int iproc_ts_probe(struct platform_device *pdev)
+{
+ struct iproc_ts_priv *priv;
+ struct input_dev *idev;
+ int irq;
+ int error;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* touchscreen controller memory mapped regs via syscon*/
+ priv->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "ts_syscon");
+ if (IS_ERR(priv->regmap)) {
+ error = PTR_ERR(priv->regmap);
+ dev_err(&pdev->dev, "unable to map I/O memory:%d\n", error);
+ return error;
+ }
+
+ priv->tsc_clk = devm_clk_get(&pdev->dev, "tsc_clk");
+ if (IS_ERR(priv->tsc_clk)) {
+ error = PTR_ERR(priv->tsc_clk);
+ dev_err(&pdev->dev,
+ "failed getting clock tsc_clk: %d\n", error);
+ return error;
+ }
+
+ priv->pdev = pdev;
+ error = iproc_get_tsc_config(&pdev->dev, priv);
+ if (error) {
+ dev_err(&pdev->dev, "get_tsc_config failed: %d\n", error);
+ return error;
+ }
+
+ idev = devm_input_allocate_device(&pdev->dev);
+ if (!idev) {
+ dev_err(&pdev->dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ priv->idev = idev;
+ priv->pen_status = PEN_UP_STATUS;
+
+ /* Set input device info */
+ idev->name = IPROC_TS_NAME;
+ idev->dev.parent = &pdev->dev;
+
+ idev->id.bustype = BUS_HOST;
+ idev->id.vendor = SERIO_UNKNOWN;
+ idev->id.product = 0;
+ idev->id.version = 0;
+
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ __set_bit(BTN_TOUCH, idev->keybit);
+
+ input_set_abs_params(idev, ABS_X, X_MIN, priv->cfg_params.max_x,
+ priv->cfg_params.fuzz_x, 0);
+ input_set_abs_params(idev, ABS_Y, Y_MIN, priv->cfg_params.max_y,
+ priv->cfg_params.fuzz_y, 0);
+
+ idev->open = iproc_ts_start;
+ idev->close = iproc_ts_stop;
+
+ input_set_drvdata(idev, priv);
+ platform_set_drvdata(pdev, priv);
+
+ /* get interrupt */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ error = devm_request_irq(&pdev->dev, irq,
+ iproc_touchscreen_interrupt,
+ IRQF_SHARED, IPROC_TS_NAME, pdev);
+ if (error)
+ return error;
+
+ error = input_register_device(priv->idev);
+ if (error) {
+ dev_err(&pdev->dev,
+ "failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id iproc_ts_of_match[] = {
+ {.compatible = "brcm,iproc-touchscreen", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, iproc_ts_of_match);
+
+static struct platform_driver iproc_ts_driver = {
+ .probe = iproc_ts_probe,
+ .driver = {
+ .name = IPROC_TS_NAME,
+ .of_match_table = of_match_ptr(iproc_ts_of_match),
+ },
+};
+
+module_platform_driver(iproc_ts_driver);
+
+MODULE_DESCRIPTION("IPROC Touchscreen driver");
+MODULE_AUTHOR("Broadcom");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/bu21013_ts.c b/drivers/input/touchscreen/bu21013_ts.c
new file mode 100644
index 000000000..34f422e24
--- /dev/null
+++ b/drivers/input/touchscreen/bu21013_ts.c
@@ -0,0 +1,630 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define MAX_FINGERS 2
+#define RESET_DELAY 30
+#define PENUP_TIMEOUT (10)
+#define DELTA_MIN 16
+#define MASK_BITS 0x03
+#define SHIFT_8 8
+#define SHIFT_2 2
+#define LENGTH_OF_BUFFER 11
+#define I2C_RETRY_COUNT 5
+
+#define BU21013_SENSORS_BTN_0_7_REG 0x70
+#define BU21013_SENSORS_BTN_8_15_REG 0x71
+#define BU21013_SENSORS_BTN_16_23_REG 0x72
+#define BU21013_X1_POS_MSB_REG 0x73
+#define BU21013_X1_POS_LSB_REG 0x74
+#define BU21013_Y1_POS_MSB_REG 0x75
+#define BU21013_Y1_POS_LSB_REG 0x76
+#define BU21013_X2_POS_MSB_REG 0x77
+#define BU21013_X2_POS_LSB_REG 0x78
+#define BU21013_Y2_POS_MSB_REG 0x79
+#define BU21013_Y2_POS_LSB_REG 0x7A
+#define BU21013_INT_CLR_REG 0xE8
+#define BU21013_INT_MODE_REG 0xE9
+#define BU21013_GAIN_REG 0xEA
+#define BU21013_OFFSET_MODE_REG 0xEB
+#define BU21013_XY_EDGE_REG 0xEC
+#define BU21013_RESET_REG 0xED
+#define BU21013_CALIB_REG 0xEE
+#define BU21013_DONE_REG 0xEF
+#define BU21013_SENSOR_0_7_REG 0xF0
+#define BU21013_SENSOR_8_15_REG 0xF1
+#define BU21013_SENSOR_16_23_REG 0xF2
+#define BU21013_POS_MODE1_REG 0xF3
+#define BU21013_POS_MODE2_REG 0xF4
+#define BU21013_CLK_MODE_REG 0xF5
+#define BU21013_IDLE_REG 0xFA
+#define BU21013_FILTER_REG 0xFB
+#define BU21013_TH_ON_REG 0xFC
+#define BU21013_TH_OFF_REG 0xFD
+
+
+#define BU21013_RESET_ENABLE 0x01
+
+#define BU21013_SENSORS_EN_0_7 0x3F
+#define BU21013_SENSORS_EN_8_15 0xFC
+#define BU21013_SENSORS_EN_16_23 0x1F
+
+#define BU21013_POS_MODE1_0 0x02
+#define BU21013_POS_MODE1_1 0x04
+#define BU21013_POS_MODE1_2 0x08
+
+#define BU21013_POS_MODE2_ZERO 0x01
+#define BU21013_POS_MODE2_AVG1 0x02
+#define BU21013_POS_MODE2_AVG2 0x04
+#define BU21013_POS_MODE2_EN_XY 0x08
+#define BU21013_POS_MODE2_EN_RAW 0x10
+#define BU21013_POS_MODE2_MULTI 0x80
+
+#define BU21013_CLK_MODE_DIV 0x01
+#define BU21013_CLK_MODE_EXT 0x02
+#define BU21013_CLK_MODE_CALIB 0x80
+
+#define BU21013_IDLET_0 0x01
+#define BU21013_IDLET_1 0x02
+#define BU21013_IDLET_2 0x04
+#define BU21013_IDLET_3 0x08
+#define BU21013_IDLE_INTERMIT_EN 0x10
+
+#define BU21013_DELTA_0_6 0x7F
+#define BU21013_FILTER_EN 0x80
+
+#define BU21013_INT_MODE_LEVEL 0x00
+#define BU21013_INT_MODE_EDGE 0x01
+
+#define BU21013_GAIN_0 0x01
+#define BU21013_GAIN_1 0x02
+#define BU21013_GAIN_2 0x04
+
+#define BU21013_OFFSET_MODE_DEFAULT 0x00
+#define BU21013_OFFSET_MODE_MOVE 0x01
+#define BU21013_OFFSET_MODE_DISABLE 0x02
+
+#define BU21013_TH_ON_0 0x01
+#define BU21013_TH_ON_1 0x02
+#define BU21013_TH_ON_2 0x04
+#define BU21013_TH_ON_3 0x08
+#define BU21013_TH_ON_4 0x10
+#define BU21013_TH_ON_5 0x20
+#define BU21013_TH_ON_6 0x40
+#define BU21013_TH_ON_7 0x80
+#define BU21013_TH_ON_MAX 0xFF
+
+#define BU21013_TH_OFF_0 0x01
+#define BU21013_TH_OFF_1 0x02
+#define BU21013_TH_OFF_2 0x04
+#define BU21013_TH_OFF_3 0x08
+#define BU21013_TH_OFF_4 0x10
+#define BU21013_TH_OFF_5 0x20
+#define BU21013_TH_OFF_6 0x40
+#define BU21013_TH_OFF_7 0x80
+#define BU21013_TH_OFF_MAX 0xFF
+
+#define BU21013_X_EDGE_0 0x01
+#define BU21013_X_EDGE_1 0x02
+#define BU21013_X_EDGE_2 0x04
+#define BU21013_X_EDGE_3 0x08
+#define BU21013_Y_EDGE_0 0x10
+#define BU21013_Y_EDGE_1 0x20
+#define BU21013_Y_EDGE_2 0x40
+#define BU21013_Y_EDGE_3 0x80
+
+#define BU21013_DONE 0x01
+#define BU21013_NUMBER_OF_X_SENSORS (6)
+#define BU21013_NUMBER_OF_Y_SENSORS (11)
+
+#define DRIVER_TP "bu21013_tp"
+
+/**
+ * struct bu21013_ts - touch panel data structure
+ * @client: pointer to the i2c client
+ * @in_dev: pointer to the input device structure
+ * @props: the device coordinate transformation properties
+ * @regulator: pointer to the Regulator used for touch screen
+ * @cs_gpiod: chip select GPIO line
+ * @int_gpiod: touch interrupt GPIO line
+ * @touch_x_max: maximum X coordinate reported by the device
+ * @touch_y_max: maximum Y coordinate reported by the device
+ * @x_flip: indicates that the driver should invert X coordinate before
+ * reporting
+ * @y_flip: indicates that the driver should invert Y coordinate before
+ * reporting
+ * @touch_stopped: touch stop flag
+ *
+ * Touch panel device data structure
+ */
+struct bu21013_ts {
+ struct i2c_client *client;
+ struct input_dev *in_dev;
+ struct touchscreen_properties props;
+ struct regulator *regulator;
+ struct gpio_desc *cs_gpiod;
+ struct gpio_desc *int_gpiod;
+ u32 touch_x_max;
+ u32 touch_y_max;
+ bool x_flip;
+ bool y_flip;
+ bool touch_stopped;
+};
+
+static int bu21013_read_block_data(struct bu21013_ts *ts, u8 *buf)
+{
+ int ret, i;
+
+ for (i = 0; i < I2C_RETRY_COUNT; i++) {
+ ret = i2c_smbus_read_i2c_block_data(ts->client,
+ BU21013_SENSORS_BTN_0_7_REG,
+ LENGTH_OF_BUFFER, buf);
+ if (ret == LENGTH_OF_BUFFER)
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int bu21013_do_touch_report(struct bu21013_ts *ts)
+{
+ struct input_dev *input = ts->in_dev;
+ struct input_mt_pos pos[MAX_FINGERS];
+ int slots[MAX_FINGERS];
+ u8 buf[LENGTH_OF_BUFFER];
+ bool has_x_sensors, has_y_sensors;
+ int finger_down_count = 0;
+ int i;
+
+ if (bu21013_read_block_data(ts, buf) < 0)
+ return -EINVAL;
+
+ has_x_sensors = hweight32(buf[0] & BU21013_SENSORS_EN_0_7);
+ has_y_sensors = hweight32(((buf[1] & BU21013_SENSORS_EN_8_15) |
+ ((buf[2] & BU21013_SENSORS_EN_16_23) << SHIFT_8)) >> SHIFT_2);
+ if (!has_x_sensors || !has_y_sensors)
+ return 0;
+
+ for (i = 0; i < MAX_FINGERS; i++) {
+ const u8 *data = &buf[4 * i + 3];
+ unsigned int x, y;
+
+ x = data[0] << SHIFT_2 | (data[1] & MASK_BITS);
+ y = data[2] << SHIFT_2 | (data[3] & MASK_BITS);
+ if (x != 0 && y != 0)
+ touchscreen_set_mt_pos(&pos[finger_down_count++],
+ &ts->props, x, y);
+ }
+
+ if (finger_down_count == 2 &&
+ (abs(pos[0].x - pos[1].x) < DELTA_MIN ||
+ abs(pos[0].y - pos[1].y) < DELTA_MIN)) {
+ return 0;
+ }
+
+ input_mt_assign_slots(input, slots, pos, finger_down_count, DELTA_MIN);
+ for (i = 0; i < finger_down_count; i++) {
+ input_mt_slot(input, slots[i]);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ input_report_abs(input, ABS_MT_POSITION_X, pos[i].x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos[i].y);
+ }
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+
+ return 0;
+}
+
+static irqreturn_t bu21013_gpio_irq(int irq, void *device_data)
+{
+ struct bu21013_ts *ts = device_data;
+ int keep_polling;
+ int error;
+
+ do {
+ error = bu21013_do_touch_report(ts);
+ if (error) {
+ dev_err(&ts->client->dev, "%s failed\n", __func__);
+ break;
+ }
+
+ if (unlikely(ts->touch_stopped))
+ break;
+
+ keep_polling = ts->int_gpiod ?
+ gpiod_get_value(ts->int_gpiod) : false;
+ if (keep_polling)
+ usleep_range(2000, 2500);
+ } while (keep_polling);
+
+ return IRQ_HANDLED;
+}
+
+static int bu21013_init_chip(struct bu21013_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ int error;
+
+ error = i2c_smbus_write_byte_data(client, BU21013_RESET_REG,
+ BU21013_RESET_ENABLE);
+ if (error) {
+ dev_err(&client->dev, "BU21013_RESET reg write failed\n");
+ return error;
+ }
+ msleep(RESET_DELAY);
+
+ error = i2c_smbus_write_byte_data(client, BU21013_SENSOR_0_7_REG,
+ BU21013_SENSORS_EN_0_7);
+ if (error) {
+ dev_err(&client->dev, "BU21013_SENSOR_0_7 reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_SENSOR_8_15_REG,
+ BU21013_SENSORS_EN_8_15);
+ if (error) {
+ dev_err(&client->dev, "BU21013_SENSOR_8_15 reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_SENSOR_16_23_REG,
+ BU21013_SENSORS_EN_16_23);
+ if (error) {
+ dev_err(&client->dev, "BU21013_SENSOR_16_23 reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_POS_MODE1_REG,
+ BU21013_POS_MODE1_0 |
+ BU21013_POS_MODE1_1);
+ if (error) {
+ dev_err(&client->dev, "BU21013_POS_MODE1 reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_POS_MODE2_REG,
+ BU21013_POS_MODE2_ZERO |
+ BU21013_POS_MODE2_AVG1 |
+ BU21013_POS_MODE2_AVG2 |
+ BU21013_POS_MODE2_EN_RAW |
+ BU21013_POS_MODE2_MULTI);
+ if (error) {
+ dev_err(&client->dev, "BU21013_POS_MODE2 reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_CLK_MODE_REG,
+ BU21013_CLK_MODE_DIV |
+ BU21013_CLK_MODE_CALIB);
+ if (error) {
+ dev_err(&client->dev, "BU21013_CLK_MODE reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_IDLE_REG,
+ BU21013_IDLET_0 |
+ BU21013_IDLE_INTERMIT_EN);
+ if (error) {
+ dev_err(&client->dev, "BU21013_IDLE reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_INT_MODE_REG,
+ BU21013_INT_MODE_LEVEL);
+ if (error) {
+ dev_err(&client->dev, "BU21013_INT_MODE reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_FILTER_REG,
+ BU21013_DELTA_0_6 |
+ BU21013_FILTER_EN);
+ if (error) {
+ dev_err(&client->dev, "BU21013_FILTER reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_TH_ON_REG,
+ BU21013_TH_ON_5);
+ if (error) {
+ dev_err(&client->dev, "BU21013_TH_ON reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_TH_OFF_REG,
+ BU21013_TH_OFF_4 | BU21013_TH_OFF_3);
+ if (error) {
+ dev_err(&client->dev, "BU21013_TH_OFF reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_GAIN_REG,
+ BU21013_GAIN_0 | BU21013_GAIN_1);
+ if (error) {
+ dev_err(&client->dev, "BU21013_GAIN reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_OFFSET_MODE_REG,
+ BU21013_OFFSET_MODE_DEFAULT);
+ if (error) {
+ dev_err(&client->dev, "BU21013_OFFSET_MODE reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_XY_EDGE_REG,
+ BU21013_X_EDGE_0 |
+ BU21013_X_EDGE_2 |
+ BU21013_Y_EDGE_1 |
+ BU21013_Y_EDGE_3);
+ if (error) {
+ dev_err(&client->dev, "BU21013_XY_EDGE reg write failed\n");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, BU21013_DONE_REG,
+ BU21013_DONE);
+ if (error) {
+ dev_err(&client->dev, "BU21013_REG_DONE reg write failed\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static void bu21013_power_off(void *_ts)
+{
+ struct bu21013_ts *ts = _ts;
+
+ regulator_disable(ts->regulator);
+}
+
+static void bu21013_disable_chip(void *_ts)
+{
+ struct bu21013_ts *ts = _ts;
+
+ gpiod_set_value(ts->cs_gpiod, 0);
+}
+
+static int bu21013_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bu21013_ts *ts;
+ struct input_dev *in_dev;
+ struct input_absinfo *info;
+ u32 max_x = 0, max_y = 0;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(&client->dev, "i2c smbus byte data not supported\n");
+ return -EIO;
+ }
+
+ if (!client->irq) {
+ dev_err(&client->dev, "No IRQ set up\n");
+ return -EINVAL;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+
+ ts->x_flip = device_property_read_bool(&client->dev, "rohm,flip-x");
+ ts->y_flip = device_property_read_bool(&client->dev, "rohm,flip-y");
+
+ in_dev = devm_input_allocate_device(&client->dev);
+ if (!in_dev) {
+ dev_err(&client->dev, "device memory alloc failed\n");
+ return -ENOMEM;
+ }
+ ts->in_dev = in_dev;
+ input_set_drvdata(in_dev, ts);
+
+ /* register the device to input subsystem */
+ in_dev->name = DRIVER_TP;
+ in_dev->id.bustype = BUS_I2C;
+
+ device_property_read_u32(&client->dev, "rohm,touch-max-x", &max_x);
+ device_property_read_u32(&client->dev, "rohm,touch-max-y", &max_y);
+
+ input_set_abs_params(in_dev, ABS_MT_POSITION_X, 0, max_x, 0, 0);
+ input_set_abs_params(in_dev, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
+
+ touchscreen_parse_properties(in_dev, true, &ts->props);
+
+ /* Adjust for the legacy "flip" properties, if present */
+ if (!ts->props.invert_x &&
+ device_property_read_bool(&client->dev, "rohm,flip-x")) {
+ info = &in_dev->absinfo[ABS_MT_POSITION_X];
+ info->maximum -= info->minimum;
+ info->minimum = 0;
+ }
+
+ if (!ts->props.invert_y &&
+ device_property_read_bool(&client->dev, "rohm,flip-y")) {
+ info = &in_dev->absinfo[ABS_MT_POSITION_Y];
+ info->maximum -= info->minimum;
+ info->minimum = 0;
+ }
+
+ error = input_mt_init_slots(in_dev, MAX_FINGERS,
+ INPUT_MT_DIRECT | INPUT_MT_TRACK |
+ INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&client->dev, "failed to initialize MT slots");
+ return error;
+ }
+
+ ts->regulator = devm_regulator_get(&client->dev, "avdd");
+ if (IS_ERR(ts->regulator)) {
+ dev_err(&client->dev, "regulator_get failed\n");
+ return PTR_ERR(ts->regulator);
+ }
+
+ error = regulator_enable(ts->regulator);
+ if (error) {
+ dev_err(&client->dev, "regulator enable failed\n");
+ return error;
+ }
+
+ error = devm_add_action_or_reset(&client->dev, bu21013_power_off, ts);
+ if (error) {
+ dev_err(&client->dev, "failed to install power off handler\n");
+ return error;
+ }
+
+ /* Named "CS" on the chip, DT binding is "reset" */
+ ts->cs_gpiod = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH);
+ error = PTR_ERR_OR_ZERO(ts->cs_gpiod);
+ if (error) {
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev, "failed to get CS GPIO\n");
+ return error;
+ }
+ gpiod_set_consumer_name(ts->cs_gpiod, "BU21013 CS");
+
+ error = devm_add_action_or_reset(&client->dev,
+ bu21013_disable_chip, ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to install chip disable handler\n");
+ return error;
+ }
+
+ /* Named "INT" on the chip, DT binding is "touch" */
+ ts->int_gpiod = devm_gpiod_get_optional(&client->dev,
+ "touch", GPIOD_IN);
+ error = PTR_ERR_OR_ZERO(ts->int_gpiod);
+ if (error) {
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev, "failed to get INT GPIO\n");
+ return error;
+ }
+
+ if (ts->int_gpiod)
+ gpiod_set_consumer_name(ts->int_gpiod, "BU21013 INT");
+
+ /* configure the touch panel controller */
+ error = bu21013_init_chip(ts);
+ if (error) {
+ dev_err(&client->dev, "error in bu21013 config\n");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, bu21013_gpio_irq,
+ IRQF_ONESHOT, DRIVER_TP, ts);
+ if (error) {
+ dev_err(&client->dev, "request irq %d failed\n",
+ client->irq);
+ return error;
+ }
+
+ error = input_register_device(in_dev);
+ if (error) {
+ dev_err(&client->dev, "failed to register input device\n");
+ return error;
+ }
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+}
+
+static void bu21013_remove(struct i2c_client *client)
+{
+ struct bu21013_ts *ts = i2c_get_clientdata(client);
+
+ /* Make sure IRQ will exit quickly even if there is contact */
+ ts->touch_stopped = true;
+ /* The resources will be freed by devm */
+}
+
+static int __maybe_unused bu21013_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bu21013_ts *ts = i2c_get_clientdata(client);
+
+ ts->touch_stopped = true;
+ mb();
+ disable_irq(client->irq);
+
+ if (!device_may_wakeup(&client->dev))
+ regulator_disable(ts->regulator);
+
+ return 0;
+}
+
+static int __maybe_unused bu21013_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bu21013_ts *ts = i2c_get_clientdata(client);
+ int error;
+
+ if (!device_may_wakeup(&client->dev)) {
+ error = regulator_enable(ts->regulator);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to re-enable regulator when resuming\n");
+ return error;
+ }
+
+ error = bu21013_init_chip(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to reinitialize chip when resuming\n");
+ return error;
+ }
+ }
+
+ ts->touch_stopped = false;
+ mb();
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(bu21013_dev_pm_ops, bu21013_suspend, bu21013_resume);
+
+static const struct i2c_device_id bu21013_id[] = {
+ { DRIVER_TP, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, bu21013_id);
+
+static struct i2c_driver bu21013_driver = {
+ .driver = {
+ .name = DRIVER_TP,
+ .pm = &bu21013_dev_pm_ops,
+ },
+ .probe = bu21013_probe,
+ .remove = bu21013_remove,
+ .id_table = bu21013_id,
+};
+
+module_i2c_driver(bu21013_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Naveen Kumar G <naveen.gaddipati@stericsson.com>");
+MODULE_DESCRIPTION("bu21013 touch screen controller driver");
diff --git a/drivers/input/touchscreen/bu21029_ts.c b/drivers/input/touchscreen/bu21029_ts.c
new file mode 100644
index 000000000..392950aa7
--- /dev/null
+++ b/drivers/input/touchscreen/bu21029_ts.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rohm BU21029 touchscreen controller driver
+ *
+ * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/timer.h>
+
+/*
+ * HW_ID1 Register (PAGE=0, ADDR=0x0E, Reset value=0x02, Read only)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | HW_IDH |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * HW_ID2 Register (PAGE=0, ADDR=0x0F, Reset value=0x29, Read only)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | HW_IDL |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * HW_IDH: high 8bits of IC's ID
+ * HW_IDL: low 8bits of IC's ID
+ */
+#define BU21029_HWID_REG (0x0E << 3)
+#define SUPPORTED_HWID 0x0229
+
+/*
+ * CFR0 Register (PAGE=0, ADDR=0x00, Reset value=0x20)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | 0 | 0 | CALIB | INTRM | 0 | 0 | 0 | 0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * CALIB: 0 = not to use calibration result (*)
+ * 1 = use calibration result
+ * INTRM: 0 = INT output depend on "pen down" (*)
+ * 1 = INT output always "0"
+ */
+#define BU21029_CFR0_REG (0x00 << 3)
+#define CFR0_VALUE 0x00
+
+/*
+ * CFR1 Register (PAGE=0, ADDR=0x01, Reset value=0xA6)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | MAV | AVE[2:0] | 0 | SMPL[2:0] |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * MAV: 0 = median average filter off
+ * 1 = median average filter on (*)
+ * AVE: AVE+1 = number of average samples for MAV,
+ * if AVE>SMPL, then AVE=SMPL (=3)
+ * SMPL: SMPL+1 = number of conversion samples for MAV (=7)
+ */
+#define BU21029_CFR1_REG (0x01 << 3)
+#define CFR1_VALUE 0xA6
+
+/*
+ * CFR2 Register (PAGE=0, ADDR=0x02, Reset value=0x04)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | INTVL_TIME[3:0] | TIME_ST_ADC[3:0] |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * INTVL_TIME: waiting time between completion of conversion
+ * and start of next conversion, only usable in
+ * autoscan mode (=20.480ms)
+ * TIME_ST_ADC: waiting time between application of voltage
+ * to panel and start of A/D conversion (=100us)
+ */
+#define BU21029_CFR2_REG (0x02 << 3)
+#define CFR2_VALUE 0xC9
+
+/*
+ * CFR3 Register (PAGE=0, ADDR=0x0B, Reset value=0x72)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | RM8 | STRETCH| PU90K | DUAL | PIDAC_OFS[3:0] |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * RM8: 0 = coordinate resolution is 12bit (*)
+ * 1 = coordinate resolution is 8bit
+ * STRETCH: 0 = SCL_STRETCH function off
+ * 1 = SCL_STRETCH function on (*)
+ * PU90K: 0 = internal pull-up resistance for touch detection is ~50kohms (*)
+ * 1 = internal pull-up resistance for touch detection is ~90kohms
+ * DUAL: 0 = dual touch detection off (*)
+ * 1 = dual touch detection on
+ * PIDAC_OFS: dual touch detection circuit adjustment, it is not necessary
+ * to change this from initial value
+ */
+#define BU21029_CFR3_REG (0x0B << 3)
+#define CFR3_VALUE 0x42
+
+/*
+ * LDO Register (PAGE=0, ADDR=0x0C, Reset value=0x00)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | 0 | PVDD[2:0] | 0 | AVDD[2:0] |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * PVDD: output voltage of panel output regulator (=2.000V)
+ * AVDD: output voltage of analog circuit regulator (=2.000V)
+ */
+#define BU21029_LDO_REG (0x0C << 3)
+#define LDO_VALUE 0x77
+
+/*
+ * Serial Interface Command Byte 1 (CID=1)
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * | 1 | CF | CMSK | PDM | STP |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+
+ * CF: conversion function, see table 3 in datasheet p6 (=0000, automatic scan)
+ * CMSK: 0 = executes convert function (*)
+ * 1 = reads the convert result
+ * PDM: 0 = power down after convert function stops (*)
+ * 1 = keep power on after convert function stops
+ * STP: 1 = abort current conversion and power down, set to "0" automatically
+ */
+#define BU21029_AUTOSCAN 0x80
+
+/*
+ * The timeout value needs to be larger than INTVL_TIME + tConv4 (sample and
+ * conversion time), where tConv4 is calculated by formula:
+ * tPON + tDLY1 + (tTIME_ST_ADC + (tADC * tSMPL) * 2 + tDLY2) * 3
+ * see figure 8 in datasheet p15 for details of each field.
+ */
+#define PEN_UP_TIMEOUT_MS 50
+
+#define STOP_DELAY_MIN_US 50
+#define STOP_DELAY_MAX_US 1000
+#define START_DELAY_MS 2
+#define BUF_LEN 8
+#define SCALE_12BIT (1 << 12)
+#define MAX_12BIT ((1 << 12) - 1)
+#define DRIVER_NAME "bu21029"
+
+struct bu21029_ts_data {
+ struct i2c_client *client;
+ struct input_dev *in_dev;
+ struct timer_list timer;
+ struct regulator *vdd;
+ struct gpio_desc *reset_gpios;
+ u32 x_plate_ohms;
+ struct touchscreen_properties prop;
+};
+
+static void bu21029_touch_report(struct bu21029_ts_data *bu21029, const u8 *buf)
+{
+ u16 x, y, z1, z2;
+ u32 rz;
+ s32 max_pressure = input_abs_get_max(bu21029->in_dev, ABS_PRESSURE);
+
+ /*
+ * compose upper 8 and lower 4 bits into a 12bit value:
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * | ByteH | ByteL |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * |b07|b06|b05|b04|b03|b02|b01|b00|b07|b06|b05|b04|b03|b02|b01|b00|
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * |v11|v10|v09|v08|v07|v06|v05|v04|v03|v02|v01|v00| 0 | 0 | 0 | 0 |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ */
+ x = (buf[0] << 4) | (buf[1] >> 4);
+ y = (buf[2] << 4) | (buf[3] >> 4);
+ z1 = (buf[4] << 4) | (buf[5] >> 4);
+ z2 = (buf[6] << 4) | (buf[7] >> 4);
+
+ if (z1 && z2) {
+ /*
+ * calculate Rz (pressure resistance value) by equation:
+ * Rz = Rx * (x/Q) * ((z2/z1) - 1), where
+ * Rx is x-plate resistance,
+ * Q is the touch screen resolution (8bit = 256, 12bit = 4096)
+ * x, z1, z2 are the measured positions.
+ */
+ rz = z2 - z1;
+ rz *= x;
+ rz *= bu21029->x_plate_ohms;
+ rz /= z1;
+ rz = DIV_ROUND_CLOSEST(rz, SCALE_12BIT);
+ if (rz <= max_pressure) {
+ touchscreen_report_pos(bu21029->in_dev, &bu21029->prop,
+ x, y, false);
+ input_report_abs(bu21029->in_dev, ABS_PRESSURE,
+ max_pressure - rz);
+ input_report_key(bu21029->in_dev, BTN_TOUCH, 1);
+ input_sync(bu21029->in_dev);
+ }
+ }
+}
+
+static void bu21029_touch_release(struct timer_list *t)
+{
+ struct bu21029_ts_data *bu21029 = from_timer(bu21029, t, timer);
+
+ input_report_abs(bu21029->in_dev, ABS_PRESSURE, 0);
+ input_report_key(bu21029->in_dev, BTN_TOUCH, 0);
+ input_sync(bu21029->in_dev);
+}
+
+static irqreturn_t bu21029_touch_soft_irq(int irq, void *data)
+{
+ struct bu21029_ts_data *bu21029 = data;
+ u8 buf[BUF_LEN];
+ int error;
+
+ /*
+ * Read touch data and deassert interrupt (will assert again after
+ * INTVL_TIME + tConv4 for continuous touch)
+ */
+ error = i2c_smbus_read_i2c_block_data(bu21029->client, BU21029_AUTOSCAN,
+ sizeof(buf), buf);
+ if (error < 0)
+ goto out;
+
+ bu21029_touch_report(bu21029, buf);
+
+ /* reset timer for pen up detection */
+ mod_timer(&bu21029->timer,
+ jiffies + msecs_to_jiffies(PEN_UP_TIMEOUT_MS));
+
+out:
+ return IRQ_HANDLED;
+}
+
+static void bu21029_put_chip_in_reset(struct bu21029_ts_data *bu21029)
+{
+ if (bu21029->reset_gpios) {
+ gpiod_set_value_cansleep(bu21029->reset_gpios, 1);
+ usleep_range(STOP_DELAY_MIN_US, STOP_DELAY_MAX_US);
+ }
+}
+
+static int bu21029_start_chip(struct input_dev *dev)
+{
+ struct bu21029_ts_data *bu21029 = input_get_drvdata(dev);
+ struct i2c_client *i2c = bu21029->client;
+ struct {
+ u8 reg;
+ u8 value;
+ } init_table[] = {
+ {BU21029_CFR0_REG, CFR0_VALUE},
+ {BU21029_CFR1_REG, CFR1_VALUE},
+ {BU21029_CFR2_REG, CFR2_VALUE},
+ {BU21029_CFR3_REG, CFR3_VALUE},
+ {BU21029_LDO_REG, LDO_VALUE}
+ };
+ int error, i;
+ __be16 hwid;
+
+ error = regulator_enable(bu21029->vdd);
+ if (error) {
+ dev_err(&i2c->dev, "failed to power up chip: %d", error);
+ return error;
+ }
+
+ /* take chip out of reset */
+ if (bu21029->reset_gpios) {
+ gpiod_set_value_cansleep(bu21029->reset_gpios, 0);
+ msleep(START_DELAY_MS);
+ }
+
+ error = i2c_smbus_read_i2c_block_data(i2c, BU21029_HWID_REG,
+ sizeof(hwid), (u8 *)&hwid);
+ if (error < 0) {
+ dev_err(&i2c->dev, "failed to read HW ID\n");
+ goto err_out;
+ }
+
+ if (be16_to_cpu(hwid) != SUPPORTED_HWID) {
+ dev_err(&i2c->dev,
+ "unsupported HW ID 0x%x\n", be16_to_cpu(hwid));
+ error = -ENODEV;
+ goto err_out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(init_table); ++i) {
+ error = i2c_smbus_write_byte_data(i2c,
+ init_table[i].reg,
+ init_table[i].value);
+ if (error < 0) {
+ dev_err(&i2c->dev,
+ "failed to write %#02x to register %#02x: %d\n",
+ init_table[i].value, init_table[i].reg,
+ error);
+ goto err_out;
+ }
+ }
+
+ error = i2c_smbus_write_byte(i2c, BU21029_AUTOSCAN);
+ if (error < 0) {
+ dev_err(&i2c->dev, "failed to start autoscan\n");
+ goto err_out;
+ }
+
+ enable_irq(bu21029->client->irq);
+ return 0;
+
+err_out:
+ bu21029_put_chip_in_reset(bu21029);
+ regulator_disable(bu21029->vdd);
+ return error;
+}
+
+static void bu21029_stop_chip(struct input_dev *dev)
+{
+ struct bu21029_ts_data *bu21029 = input_get_drvdata(dev);
+
+ disable_irq(bu21029->client->irq);
+ del_timer_sync(&bu21029->timer);
+
+ bu21029_put_chip_in_reset(bu21029);
+ regulator_disable(bu21029->vdd);
+}
+
+static int bu21029_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bu21029_ts_data *bu21029;
+ struct input_dev *in_dev;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE |
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ dev_err(&client->dev,
+ "i2c functionality support is not sufficient\n");
+ return -EIO;
+ }
+
+ bu21029 = devm_kzalloc(&client->dev, sizeof(*bu21029), GFP_KERNEL);
+ if (!bu21029)
+ return -ENOMEM;
+
+ error = device_property_read_u32(&client->dev, "rohm,x-plate-ohms",
+ &bu21029->x_plate_ohms);
+ if (error) {
+ dev_err(&client->dev,
+ "invalid 'x-plate-ohms' supplied: %d\n", error);
+ return error;
+ }
+
+ bu21029->vdd = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(bu21029->vdd)) {
+ error = PTR_ERR(bu21029->vdd);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "failed to acquire 'vdd' supply: %d\n", error);
+ return error;
+ }
+
+ bu21029->reset_gpios = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(bu21029->reset_gpios)) {
+ error = PTR_ERR(bu21029->reset_gpios);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "failed to acquire 'reset' gpio: %d\n", error);
+ return error;
+ }
+
+ in_dev = devm_input_allocate_device(&client->dev);
+ if (!in_dev) {
+ dev_err(&client->dev, "unable to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ bu21029->client = client;
+ bu21029->in_dev = in_dev;
+ timer_setup(&bu21029->timer, bu21029_touch_release, 0);
+
+ in_dev->name = DRIVER_NAME;
+ in_dev->id.bustype = BUS_I2C;
+ in_dev->open = bu21029_start_chip;
+ in_dev->close = bu21029_stop_chip;
+
+ input_set_capability(in_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(in_dev, ABS_X, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(in_dev, ABS_Y, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(in_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0);
+ touchscreen_parse_properties(in_dev, false, &bu21029->prop);
+
+ input_set_drvdata(in_dev, bu21029);
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, bu21029_touch_soft_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ DRIVER_NAME, bu21029);
+ if (error) {
+ dev_err(&client->dev,
+ "unable to request touch irq: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(in_dev);
+ if (error) {
+ dev_err(&client->dev,
+ "unable to register input device: %d\n", error);
+ return error;
+ }
+
+ i2c_set_clientdata(client, bu21029);
+
+ return 0;
+}
+
+static int __maybe_unused bu21029_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct bu21029_ts_data *bu21029 = i2c_get_clientdata(i2c);
+
+ if (!device_may_wakeup(dev)) {
+ mutex_lock(&bu21029->in_dev->mutex);
+ if (input_device_enabled(bu21029->in_dev))
+ bu21029_stop_chip(bu21029->in_dev);
+ mutex_unlock(&bu21029->in_dev->mutex);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused bu21029_resume(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct bu21029_ts_data *bu21029 = i2c_get_clientdata(i2c);
+
+ if (!device_may_wakeup(dev)) {
+ mutex_lock(&bu21029->in_dev->mutex);
+ if (input_device_enabled(bu21029->in_dev))
+ bu21029_start_chip(bu21029->in_dev);
+ mutex_unlock(&bu21029->in_dev->mutex);
+ }
+
+ return 0;
+}
+static SIMPLE_DEV_PM_OPS(bu21029_pm_ops, bu21029_suspend, bu21029_resume);
+
+static const struct i2c_device_id bu21029_ids[] = {
+ { DRIVER_NAME, 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, bu21029_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id bu21029_of_ids[] = {
+ { .compatible = "rohm,bu21029" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, bu21029_of_ids);
+#endif
+
+static struct i2c_driver bu21029_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = of_match_ptr(bu21029_of_ids),
+ .pm = &bu21029_pm_ops,
+ },
+ .id_table = bu21029_ids,
+ .probe = bu21029_probe,
+};
+module_i2c_driver(bu21029_driver);
+
+MODULE_AUTHOR("Zhu Yi <yi.zhu5@cn.bosch.com>");
+MODULE_DESCRIPTION("Rohm BU21029 touchscreen controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/chipone_icn8318.c b/drivers/input/touchscreen/chipone_icn8318.c
new file mode 100644
index 000000000..f2fb41fb0
--- /dev/null
+++ b/drivers/input/touchscreen/chipone_icn8318.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for ChipOne icn8318 i2c touchscreen controller
+ *
+ * Copyright (c) 2015 Red Hat Inc.
+ *
+ * Red Hat authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#define ICN8318_REG_POWER 4
+#define ICN8318_REG_TOUCHDATA 16
+
+#define ICN8318_POWER_ACTIVE 0
+#define ICN8318_POWER_MONITOR 1
+#define ICN8318_POWER_HIBERNATE 2
+
+#define ICN8318_MAX_TOUCHES 5
+
+struct icn8318_touch {
+ __u8 slot;
+ __be16 x;
+ __be16 y;
+ __u8 pressure; /* Seems more like finger width then pressure really */
+ __u8 event;
+/* The difference between 2 and 3 is unclear */
+#define ICN8318_EVENT_NO_DATA 1 /* No finger seen yet since wakeup */
+#define ICN8318_EVENT_UPDATE1 2 /* New or updated coordinates */
+#define ICN8318_EVENT_UPDATE2 3 /* New or updated coordinates */
+#define ICN8318_EVENT_END 4 /* Finger lifted */
+} __packed;
+
+struct icn8318_touch_data {
+ __u8 softbutton;
+ __u8 touch_count;
+ struct icn8318_touch touches[ICN8318_MAX_TOUCHES];
+} __packed;
+
+struct icn8318_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *wake_gpio;
+ struct touchscreen_properties prop;
+};
+
+static int icn8318_read_touch_data(struct i2c_client *client,
+ struct icn8318_touch_data *touch_data)
+{
+ u8 reg = ICN8318_REG_TOUCHDATA;
+ struct i2c_msg msg[2] = {
+ {
+ .addr = client->addr,
+ .len = 1,
+ .buf = &reg
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(struct icn8318_touch_data),
+ .buf = (u8 *)touch_data
+ }
+ };
+
+ return i2c_transfer(client->adapter, msg, 2);
+}
+
+static inline bool icn8318_touch_active(u8 event)
+{
+ return (event == ICN8318_EVENT_UPDATE1) ||
+ (event == ICN8318_EVENT_UPDATE2);
+}
+
+static irqreturn_t icn8318_irq(int irq, void *dev_id)
+{
+ struct icn8318_data *data = dev_id;
+ struct device *dev = &data->client->dev;
+ struct icn8318_touch_data touch_data;
+ int i, ret;
+
+ ret = icn8318_read_touch_data(data->client, &touch_data);
+ if (ret < 0) {
+ dev_err(dev, "Error reading touch data: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ if (touch_data.softbutton) {
+ /*
+ * Other data is invalid when a softbutton is pressed.
+ * This needs some extra devicetree bindings to map the icn8318
+ * softbutton codes to evdev codes. Currently no known devices
+ * use this.
+ */
+ return IRQ_HANDLED;
+ }
+
+ if (touch_data.touch_count > ICN8318_MAX_TOUCHES) {
+ dev_warn(dev, "Too much touches %d > %d\n",
+ touch_data.touch_count, ICN8318_MAX_TOUCHES);
+ touch_data.touch_count = ICN8318_MAX_TOUCHES;
+ }
+
+ for (i = 0; i < touch_data.touch_count; i++) {
+ struct icn8318_touch *touch = &touch_data.touches[i];
+ bool act = icn8318_touch_active(touch->event);
+
+ input_mt_slot(data->input, touch->slot);
+ input_mt_report_slot_state(data->input, MT_TOOL_FINGER, act);
+ if (!act)
+ continue;
+
+ touchscreen_report_pos(data->input, &data->prop,
+ be16_to_cpu(touch->x),
+ be16_to_cpu(touch->y), true);
+ }
+
+ input_mt_sync_frame(data->input);
+ input_sync(data->input);
+
+ return IRQ_HANDLED;
+}
+
+static int icn8318_start(struct input_dev *dev)
+{
+ struct icn8318_data *data = input_get_drvdata(dev);
+
+ enable_irq(data->client->irq);
+ gpiod_set_value_cansleep(data->wake_gpio, 1);
+
+ return 0;
+}
+
+static void icn8318_stop(struct input_dev *dev)
+{
+ struct icn8318_data *data = input_get_drvdata(dev);
+
+ disable_irq(data->client->irq);
+ i2c_smbus_write_byte_data(data->client, ICN8318_REG_POWER,
+ ICN8318_POWER_HIBERNATE);
+ gpiod_set_value_cansleep(data->wake_gpio, 0);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int icn8318_suspend(struct device *dev)
+{
+ struct icn8318_data *data = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&data->input->mutex);
+ if (input_device_enabled(data->input))
+ icn8318_stop(data->input);
+ mutex_unlock(&data->input->mutex);
+
+ return 0;
+}
+
+static int icn8318_resume(struct device *dev)
+{
+ struct icn8318_data *data = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&data->input->mutex);
+ if (input_device_enabled(data->input))
+ icn8318_start(data->input);
+ mutex_unlock(&data->input->mutex);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(icn8318_pm_ops, icn8318_suspend, icn8318_resume);
+
+static int icn8318_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct icn8318_data *data;
+ struct input_dev *input;
+ int error;
+
+ if (!client->irq) {
+ dev_err(dev, "Error no irq specified\n");
+ return -EINVAL;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->wake_gpio = devm_gpiod_get(dev, "wake", GPIOD_OUT_LOW);
+ if (IS_ERR(data->wake_gpio)) {
+ error = PTR_ERR(data->wake_gpio);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Error getting wake gpio: %d\n", error);
+ return error;
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->open = icn8318_start;
+ input->close = icn8318_stop;
+ input->dev.parent = dev;
+
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y);
+
+ touchscreen_parse_properties(input, true, &data->prop);
+ if (!input_abs_get_max(input, ABS_MT_POSITION_X) ||
+ !input_abs_get_max(input, ABS_MT_POSITION_Y)) {
+ dev_err(dev, "Error touchscreen-size-x and/or -y missing\n");
+ return -EINVAL;
+ }
+
+ error = input_mt_init_slots(input, ICN8318_MAX_TOUCHES,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error)
+ return error;
+
+ data->client = client;
+ data->input = input;
+ input_set_drvdata(input, data);
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL, icn8318_irq,
+ IRQF_ONESHOT, client->name, data);
+ if (error) {
+ dev_err(dev, "Error requesting irq: %d\n", error);
+ return error;
+ }
+
+ /* Stop device till opened */
+ icn8318_stop(data->input);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, data);
+
+ return 0;
+}
+
+static const struct of_device_id icn8318_of_match[] = {
+ { .compatible = "chipone,icn8318" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, icn8318_of_match);
+
+/* This is useless for OF-enabled devices, but it is needed by I2C subsystem */
+static const struct i2c_device_id icn8318_i2c_id[] = {
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, icn8318_i2c_id);
+
+static struct i2c_driver icn8318_driver = {
+ .driver = {
+ .name = "chipone_icn8318",
+ .pm = &icn8318_pm_ops,
+ .of_match_table = icn8318_of_match,
+ },
+ .probe = icn8318_probe,
+ .id_table = icn8318_i2c_id,
+};
+
+module_i2c_driver(icn8318_driver);
+
+MODULE_DESCRIPTION("ChipOne icn8318 I2C Touchscreen Driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/chipone_icn8505.c b/drivers/input/touchscreen/chipone_icn8505.c
new file mode 100644
index 000000000..c421f4be2
--- /dev/null
+++ b/drivers/input/touchscreen/chipone_icn8505.c
@@ -0,0 +1,508 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for ChipOne icn8505 i2c touchscreen controller
+ *
+ * Copyright (c) 2015-2018 Red Hat Inc.
+ *
+ * Red Hat authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/acpi.h>
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+
+/* Normal operation mode defines */
+#define ICN8505_REG_ADDR_WIDTH 16
+
+#define ICN8505_REG_POWER 0x0004
+#define ICN8505_REG_TOUCHDATA 0x1000
+#define ICN8505_REG_CONFIGDATA 0x8000
+
+/* ICN8505_REG_POWER commands */
+#define ICN8505_POWER_ACTIVE 0x00
+#define ICN8505_POWER_MONITOR 0x01
+#define ICN8505_POWER_HIBERNATE 0x02
+/*
+ * The Android driver uses these to turn on/off the charger filter, but the
+ * filter is way too aggressive making e.g. onscreen keyboards unusable.
+ */
+#define ICN8505_POWER_ENA_CHARGER_MODE 0x55
+#define ICN8505_POWER_DIS_CHARGER_MODE 0x66
+
+#define ICN8505_MAX_TOUCHES 10
+
+/* Programming mode defines */
+#define ICN8505_PROG_I2C_ADDR 0x30
+#define ICN8505_PROG_REG_ADDR_WIDTH 24
+
+#define MAX_FW_UPLOAD_TRIES 3
+
+struct icn8505_touch {
+ u8 slot;
+ u8 x[2];
+ u8 y[2];
+ u8 pressure; /* Seems more like finger width then pressure really */
+ u8 event;
+/* The difference between 2 and 3 is unclear */
+#define ICN8505_EVENT_NO_DATA 1 /* No finger seen yet since wakeup */
+#define ICN8505_EVENT_UPDATE1 2 /* New or updated coordinates */
+#define ICN8505_EVENT_UPDATE2 3 /* New or updated coordinates */
+#define ICN8505_EVENT_END 4 /* Finger lifted */
+} __packed;
+
+struct icn8505_touch_data {
+ u8 softbutton;
+ u8 touch_count;
+ struct icn8505_touch touches[ICN8505_MAX_TOUCHES];
+} __packed;
+
+struct icn8505_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *wake_gpio;
+ struct touchscreen_properties prop;
+ char firmware_name[32];
+};
+
+static int icn8505_read_xfer(struct i2c_client *client, u16 i2c_addr,
+ int reg_addr, int reg_addr_width,
+ void *data, int len, bool silent)
+{
+ u8 buf[3];
+ int i, ret;
+ struct i2c_msg msg[2] = {
+ {
+ .addr = i2c_addr,
+ .buf = buf,
+ .len = reg_addr_width / 8,
+ },
+ {
+ .addr = i2c_addr,
+ .flags = I2C_M_RD,
+ .buf = data,
+ .len = len,
+ }
+ };
+
+ for (i = 0; i < (reg_addr_width / 8); i++)
+ buf[i] = (reg_addr >> (reg_addr_width - (i + 1) * 8)) & 0xff;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret != ARRAY_SIZE(msg)) {
+ if (ret >= 0)
+ ret = -EIO;
+ if (!silent)
+ dev_err(&client->dev,
+ "Error reading addr %#x reg %#x: %d\n",
+ i2c_addr, reg_addr, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int icn8505_write_xfer(struct i2c_client *client, u16 i2c_addr,
+ int reg_addr, int reg_addr_width,
+ const void *data, int len, bool silent)
+{
+ u8 buf[3 + 32]; /* 3 bytes for 24 bit reg-addr + 32 bytes max len */
+ int i, ret;
+ struct i2c_msg msg = {
+ .addr = i2c_addr,
+ .buf = buf,
+ .len = reg_addr_width / 8 + len,
+ };
+
+ if (WARN_ON(len > 32))
+ return -EINVAL;
+
+ for (i = 0; i < (reg_addr_width / 8); i++)
+ buf[i] = (reg_addr >> (reg_addr_width - (i + 1) * 8)) & 0xff;
+
+ memcpy(buf + reg_addr_width / 8, data, len);
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1) {
+ if (ret >= 0)
+ ret = -EIO;
+ if (!silent)
+ dev_err(&client->dev,
+ "Error writing addr %#x reg %#x: %d\n",
+ i2c_addr, reg_addr, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int icn8505_read_data(struct icn8505_data *icn8505, int reg,
+ void *buf, int len)
+{
+ return icn8505_read_xfer(icn8505->client, icn8505->client->addr, reg,
+ ICN8505_REG_ADDR_WIDTH, buf, len, false);
+}
+
+static int icn8505_read_reg_silent(struct icn8505_data *icn8505, int reg)
+{
+ u8 buf;
+ int error;
+
+ error = icn8505_read_xfer(icn8505->client, icn8505->client->addr, reg,
+ ICN8505_REG_ADDR_WIDTH, &buf, 1, true);
+ if (error)
+ return error;
+
+ return buf;
+}
+
+static int icn8505_write_reg(struct icn8505_data *icn8505, int reg, u8 val)
+{
+ return icn8505_write_xfer(icn8505->client, icn8505->client->addr, reg,
+ ICN8505_REG_ADDR_WIDTH, &val, 1, false);
+}
+
+static int icn8505_read_prog_data(struct icn8505_data *icn8505, int reg,
+ void *buf, int len)
+{
+ return icn8505_read_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg,
+ ICN8505_PROG_REG_ADDR_WIDTH, buf, len, false);
+}
+
+static int icn8505_write_prog_data(struct icn8505_data *icn8505, int reg,
+ const void *buf, int len)
+{
+ return icn8505_write_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg,
+ ICN8505_PROG_REG_ADDR_WIDTH, buf, len, false);
+}
+
+static int icn8505_write_prog_reg(struct icn8505_data *icn8505, int reg, u8 val)
+{
+ return icn8505_write_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg,
+ ICN8505_PROG_REG_ADDR_WIDTH, &val, 1, false);
+}
+
+/*
+ * Note this function uses a number of magic register addresses and values,
+ * there are deliberately no defines for these because the algorithm is taken
+ * from the icn85xx Android driver and I do not want to make up possibly wrong
+ * names for the addresses and/or values.
+ */
+static int icn8505_try_fw_upload(struct icn8505_data *icn8505,
+ const struct firmware *fw)
+{
+ struct device *dev = &icn8505->client->dev;
+ size_t offset, count;
+ int error;
+ u8 buf[4];
+ u32 crc;
+
+ /* Put the controller in programming mode */
+ error = icn8505_write_prog_reg(icn8505, 0xcc3355, 0x5a);
+ if (error)
+ return error;
+
+ usleep_range(2000, 5000);
+
+ error = icn8505_write_prog_reg(icn8505, 0x040400, 0x01);
+ if (error)
+ return error;
+
+ usleep_range(2000, 5000);
+
+ error = icn8505_read_prog_data(icn8505, 0x040002, buf, 1);
+ if (error)
+ return error;
+
+ if (buf[0] != 0x85) {
+ dev_err(dev, "Failed to enter programming mode\n");
+ return -ENODEV;
+ }
+
+ usleep_range(1000, 5000);
+
+ /* Enable CRC mode */
+ error = icn8505_write_prog_reg(icn8505, 0x40028, 1);
+ if (error)
+ return error;
+
+ /* Send the firmware to SRAM */
+ for (offset = 0; offset < fw->size; offset += count) {
+ count = min_t(size_t, fw->size - offset, 32);
+ error = icn8505_write_prog_data(icn8505, offset,
+ fw->data + offset, count);
+ if (error)
+ return error;
+ }
+
+ /* Disable CRC mode */
+ error = icn8505_write_prog_reg(icn8505, 0x40028, 0);
+ if (error)
+ return error;
+
+ /* Get and check length and CRC */
+ error = icn8505_read_prog_data(icn8505, 0x40034, buf, 2);
+ if (error)
+ return error;
+
+ if (get_unaligned_le16(buf) != fw->size) {
+ dev_warn(dev, "Length mismatch after uploading fw\n");
+ return -EIO;
+ }
+
+ error = icn8505_read_prog_data(icn8505, 0x4002c, buf, 4);
+ if (error)
+ return error;
+
+ crc = crc32_be(0, fw->data, fw->size);
+ if (get_unaligned_le32(buf) != crc) {
+ dev_warn(dev, "CRC mismatch after uploading fw\n");
+ return -EIO;
+ }
+
+ /* Boot controller from SRAM */
+ error = icn8505_write_prog_reg(icn8505, 0x40400, 0x03);
+ if (error)
+ return error;
+
+ usleep_range(2000, 5000);
+ return 0;
+}
+
+static int icn8505_upload_fw(struct icn8505_data *icn8505)
+{
+ struct device *dev = &icn8505->client->dev;
+ const struct firmware *fw;
+ int i, error;
+
+ /*
+ * Always load the firmware, even if we don't need it at boot, we
+ * we may need it at resume. Having loaded it once will make the
+ * firmware class code cache it at suspend/resume.
+ */
+ error = firmware_request_platform(&fw, icn8505->firmware_name, dev);
+ if (error) {
+ dev_err(dev, "Firmware request error %d\n", error);
+ return error;
+ }
+
+ /* Check if the controller is not already up and running */
+ if (icn8505_read_reg_silent(icn8505, 0x000a) == 0x85)
+ goto success;
+
+ for (i = 1; i <= MAX_FW_UPLOAD_TRIES; i++) {
+ error = icn8505_try_fw_upload(icn8505, fw);
+ if (!error)
+ goto success;
+
+ dev_err(dev, "Failed to upload firmware: %d (attempt %d/%d)\n",
+ error, i, MAX_FW_UPLOAD_TRIES);
+ usleep_range(2000, 5000);
+ }
+
+success:
+ release_firmware(fw);
+ return error;
+}
+
+static bool icn8505_touch_active(u8 event)
+{
+ return event == ICN8505_EVENT_UPDATE1 ||
+ event == ICN8505_EVENT_UPDATE2;
+}
+
+static irqreturn_t icn8505_irq(int irq, void *dev_id)
+{
+ struct icn8505_data *icn8505 = dev_id;
+ struct device *dev = &icn8505->client->dev;
+ struct icn8505_touch_data touch_data;
+ int i, error;
+
+ error = icn8505_read_data(icn8505, ICN8505_REG_TOUCHDATA,
+ &touch_data, sizeof(touch_data));
+ if (error) {
+ dev_err(dev, "Error reading touch data: %d\n", error);
+ return IRQ_HANDLED;
+ }
+
+ if (touch_data.touch_count > ICN8505_MAX_TOUCHES) {
+ dev_warn(dev, "Too many touches %d > %d\n",
+ touch_data.touch_count, ICN8505_MAX_TOUCHES);
+ touch_data.touch_count = ICN8505_MAX_TOUCHES;
+ }
+
+ for (i = 0; i < touch_data.touch_count; i++) {
+ struct icn8505_touch *touch = &touch_data.touches[i];
+ bool act = icn8505_touch_active(touch->event);
+
+ input_mt_slot(icn8505->input, touch->slot);
+ input_mt_report_slot_state(icn8505->input, MT_TOOL_FINGER, act);
+ if (!act)
+ continue;
+
+ touchscreen_report_pos(icn8505->input, &icn8505->prop,
+ get_unaligned_le16(touch->x),
+ get_unaligned_le16(touch->y),
+ true);
+ }
+
+ input_mt_sync_frame(icn8505->input);
+ input_report_key(icn8505->input, KEY_LEFTMETA,
+ touch_data.softbutton == 1);
+ input_sync(icn8505->input);
+
+ return IRQ_HANDLED;
+}
+
+static int icn8505_probe_acpi(struct icn8505_data *icn8505, struct device *dev)
+{
+ const char *subsys;
+ int error;
+
+ subsys = acpi_get_subsystem_id(ACPI_HANDLE(dev));
+ error = PTR_ERR_OR_ZERO(subsys);
+ if (error == -ENODATA)
+ subsys = "unknown";
+ else if (error)
+ return error;
+
+ snprintf(icn8505->firmware_name, sizeof(icn8505->firmware_name),
+ "chipone/icn8505-%s.fw", subsys);
+
+ kfree_const(subsys);
+ return 0;
+}
+
+static int icn8505_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct icn8505_data *icn8505;
+ struct input_dev *input;
+ __le16 resolution[2];
+ int error;
+
+ if (!client->irq) {
+ dev_err(dev, "No irq specified\n");
+ return -EINVAL;
+ }
+
+ icn8505 = devm_kzalloc(dev, sizeof(*icn8505), GFP_KERNEL);
+ if (!icn8505)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y);
+ input_set_capability(input, EV_KEY, KEY_LEFTMETA);
+
+ icn8505->client = client;
+ icn8505->input = input;
+ input_set_drvdata(input, icn8505);
+
+ error = icn8505_probe_acpi(icn8505, dev);
+ if (error)
+ return error;
+
+ error = icn8505_upload_fw(icn8505);
+ if (error)
+ return error;
+
+ error = icn8505_read_data(icn8505, ICN8505_REG_CONFIGDATA,
+ resolution, sizeof(resolution));
+ if (error) {
+ dev_err(dev, "Error reading resolution: %d\n", error);
+ return error;
+ }
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0,
+ le16_to_cpu(resolution[0]) - 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
+ le16_to_cpu(resolution[1]) - 1, 0, 0);
+
+ touchscreen_parse_properties(input, true, &icn8505->prop);
+ if (!input_abs_get_max(input, ABS_MT_POSITION_X) ||
+ !input_abs_get_max(input, ABS_MT_POSITION_Y)) {
+ dev_err(dev, "Error touchscreen-size-x and/or -y missing\n");
+ return -EINVAL;
+ }
+
+ error = input_mt_init_slots(input, ICN8505_MAX_TOUCHES,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL, icn8505_irq,
+ IRQF_ONESHOT, client->name, icn8505);
+ if (error) {
+ dev_err(dev, "Error requesting irq: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, icn8505);
+ return 0;
+}
+
+static int __maybe_unused icn8505_suspend(struct device *dev)
+{
+ struct icn8505_data *icn8505 = i2c_get_clientdata(to_i2c_client(dev));
+
+ disable_irq(icn8505->client->irq);
+
+ icn8505_write_reg(icn8505, ICN8505_REG_POWER, ICN8505_POWER_HIBERNATE);
+
+ return 0;
+}
+
+static int __maybe_unused icn8505_resume(struct device *dev)
+{
+ struct icn8505_data *icn8505 = i2c_get_clientdata(to_i2c_client(dev));
+ int error;
+
+ error = icn8505_upload_fw(icn8505);
+ if (error)
+ return error;
+
+ enable_irq(icn8505->client->irq);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(icn8505_pm_ops, icn8505_suspend, icn8505_resume);
+
+static const struct acpi_device_id icn8505_acpi_match[] = {
+ { "CHPN0001" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, icn8505_acpi_match);
+
+static struct i2c_driver icn8505_driver = {
+ .driver = {
+ .name = "chipone_icn8505",
+ .pm = &icn8505_pm_ops,
+ .acpi_match_table = icn8505_acpi_match,
+ },
+ .probe_new = icn8505_probe,
+};
+
+module_i2c_driver(icn8505_driver);
+
+MODULE_DESCRIPTION("ChipOne icn8505 I2C Touchscreen Driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c
new file mode 100644
index 000000000..aa829725d
--- /dev/null
+++ b/drivers/input/touchscreen/colibri-vf50-ts.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Toradex Colibri VF50 Touchscreen driver
+ *
+ * Copyright 2015 Toradex AG
+ *
+ * Originally authored by Stefan Agner for 3.0 kernel
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/types.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define DRIVER_NAME "colibri-vf50-ts"
+
+#define VF_ADC_MAX ((1 << 12) - 1)
+
+#define COLI_TOUCH_MIN_DELAY_US 1000
+#define COLI_TOUCH_MAX_DELAY_US 2000
+#define COLI_PULLUP_MIN_DELAY_US 10000
+#define COLI_PULLUP_MAX_DELAY_US 11000
+#define COLI_TOUCH_NO_OF_AVGS 5
+#define COLI_TOUCH_REQ_ADC_CHAN 4
+
+struct vf50_touch_device {
+ struct platform_device *pdev;
+ struct input_dev *ts_input;
+ struct iio_channel *channels;
+ struct gpio_desc *gpio_xp;
+ struct gpio_desc *gpio_xm;
+ struct gpio_desc *gpio_yp;
+ struct gpio_desc *gpio_ym;
+ int pen_irq;
+ int min_pressure;
+ bool stop_touchscreen;
+};
+
+/*
+ * Enables given plates and measures touch parameters using ADC
+ */
+static int adc_ts_measure(struct iio_channel *channel,
+ struct gpio_desc *plate_p, struct gpio_desc *plate_m)
+{
+ int i, value = 0, val = 0;
+ int error;
+
+ gpiod_set_value(plate_p, 1);
+ gpiod_set_value(plate_m, 1);
+
+ usleep_range(COLI_TOUCH_MIN_DELAY_US, COLI_TOUCH_MAX_DELAY_US);
+
+ for (i = 0; i < COLI_TOUCH_NO_OF_AVGS; i++) {
+ error = iio_read_channel_raw(channel, &val);
+ if (error < 0) {
+ value = error;
+ goto error_iio_read;
+ }
+
+ value += val;
+ }
+
+ value /= COLI_TOUCH_NO_OF_AVGS;
+
+error_iio_read:
+ gpiod_set_value(plate_p, 0);
+ gpiod_set_value(plate_m, 0);
+
+ return value;
+}
+
+/*
+ * Enable touch detection using falling edge detection on XM
+ */
+static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts)
+{
+ /* Enable plate YM (needs to be strong GND, high active) */
+ gpiod_set_value(vf50_ts->gpio_ym, 1);
+
+ /*
+ * Let the platform mux to idle state in order to enable
+ * Pull-Up on GPIO
+ */
+ pinctrl_pm_select_idle_state(&vf50_ts->pdev->dev);
+
+ /* Wait for the pull-up to be stable on high */
+ usleep_range(COLI_PULLUP_MIN_DELAY_US, COLI_PULLUP_MAX_DELAY_US);
+}
+
+/*
+ * ADC touch screen sampling bottom half irq handler
+ */
+static irqreturn_t vf50_ts_irq_bh(int irq, void *private)
+{
+ struct vf50_touch_device *vf50_ts = private;
+ struct device *dev = &vf50_ts->pdev->dev;
+ int val_x, val_y, val_z1, val_z2, val_p = 0;
+ bool discard_val_on_start = true;
+
+ /* Disable the touch detection plates */
+ gpiod_set_value(vf50_ts->gpio_ym, 0);
+
+ /* Let the platform mux to default state in order to mux as ADC */
+ pinctrl_pm_select_default_state(dev);
+
+ while (!vf50_ts->stop_touchscreen) {
+ /* X-Direction */
+ val_x = adc_ts_measure(&vf50_ts->channels[0],
+ vf50_ts->gpio_xp, vf50_ts->gpio_xm);
+ if (val_x < 0)
+ break;
+
+ /* Y-Direction */
+ val_y = adc_ts_measure(&vf50_ts->channels[1],
+ vf50_ts->gpio_yp, vf50_ts->gpio_ym);
+ if (val_y < 0)
+ break;
+
+ /*
+ * Touch pressure
+ * Measure on XP/YM
+ */
+ val_z1 = adc_ts_measure(&vf50_ts->channels[2],
+ vf50_ts->gpio_yp, vf50_ts->gpio_xm);
+ if (val_z1 < 0)
+ break;
+ val_z2 = adc_ts_measure(&vf50_ts->channels[3],
+ vf50_ts->gpio_yp, vf50_ts->gpio_xm);
+ if (val_z2 < 0)
+ break;
+
+ /* Validate signal (avoid calculation using noise) */
+ if (val_z1 > 64 && val_x > 64) {
+ /*
+ * Calculate resistance between the plates
+ * lower resistance means higher pressure
+ */
+ int r_x = (1000 * val_x) / VF_ADC_MAX;
+
+ val_p = (r_x * val_z2) / val_z1 - r_x;
+
+ } else {
+ val_p = 2000;
+ }
+
+ val_p = 2000 - val_p;
+ dev_dbg(dev,
+ "Measured values: x: %d, y: %d, z1: %d, z2: %d, p: %d\n",
+ val_x, val_y, val_z1, val_z2, val_p);
+
+ /*
+ * If touch pressure is too low, stop measuring and reenable
+ * touch detection
+ */
+ if (val_p < vf50_ts->min_pressure || val_p > 2000)
+ break;
+
+ /*
+ * The pressure may not be enough for the first x and the
+ * second y measurement, but, the pressure is ok when the
+ * driver is doing the third and fourth measurement. To
+ * take care of this, we drop the first measurement always.
+ */
+ if (discard_val_on_start) {
+ discard_val_on_start = false;
+ } else {
+ /*
+ * Report touch position and sleep for
+ * the next measurement.
+ */
+ input_report_abs(vf50_ts->ts_input,
+ ABS_X, VF_ADC_MAX - val_x);
+ input_report_abs(vf50_ts->ts_input,
+ ABS_Y, VF_ADC_MAX - val_y);
+ input_report_abs(vf50_ts->ts_input,
+ ABS_PRESSURE, val_p);
+ input_report_key(vf50_ts->ts_input, BTN_TOUCH, 1);
+ input_sync(vf50_ts->ts_input);
+ }
+
+ usleep_range(COLI_PULLUP_MIN_DELAY_US,
+ COLI_PULLUP_MAX_DELAY_US);
+ }
+
+ /* Report no more touch, re-enable touch detection */
+ input_report_abs(vf50_ts->ts_input, ABS_PRESSURE, 0);
+ input_report_key(vf50_ts->ts_input, BTN_TOUCH, 0);
+ input_sync(vf50_ts->ts_input);
+
+ vf50_ts_enable_touch_detection(vf50_ts);
+
+ return IRQ_HANDLED;
+}
+
+static int vf50_ts_open(struct input_dev *dev_input)
+{
+ struct vf50_touch_device *touchdev = input_get_drvdata(dev_input);
+ struct device *dev = &touchdev->pdev->dev;
+
+ dev_dbg(dev, "Input device %s opened, starting touch detection\n",
+ dev_input->name);
+
+ touchdev->stop_touchscreen = false;
+
+ /* Mux detection before request IRQ, wait for pull-up to settle */
+ vf50_ts_enable_touch_detection(touchdev);
+
+ return 0;
+}
+
+static void vf50_ts_close(struct input_dev *dev_input)
+{
+ struct vf50_touch_device *touchdev = input_get_drvdata(dev_input);
+ struct device *dev = &touchdev->pdev->dev;
+
+ touchdev->stop_touchscreen = true;
+
+ /* Make sure IRQ is not running past close */
+ mb();
+ synchronize_irq(touchdev->pen_irq);
+
+ gpiod_set_value(touchdev->gpio_ym, 0);
+ pinctrl_pm_select_default_state(dev);
+
+ dev_dbg(dev, "Input device %s closed, disable touch detection\n",
+ dev_input->name);
+}
+
+static int vf50_ts_get_gpiod(struct device *dev, struct gpio_desc **gpio_d,
+ const char *con_id, enum gpiod_flags flags)
+{
+ int error;
+
+ *gpio_d = devm_gpiod_get(dev, con_id, flags);
+ if (IS_ERR(*gpio_d)) {
+ error = PTR_ERR(*gpio_d);
+ dev_err(dev, "Could not get gpio_%s %d\n", con_id, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void vf50_ts_channel_release(void *data)
+{
+ struct iio_channel *channels = data;
+
+ iio_channel_release_all(channels);
+}
+
+static int vf50_ts_probe(struct platform_device *pdev)
+{
+ struct input_dev *input;
+ struct iio_channel *channels;
+ struct device *dev = &pdev->dev;
+ struct vf50_touch_device *touchdev;
+ int num_adc_channels;
+ int error;
+
+ channels = iio_channel_get_all(dev);
+ if (IS_ERR(channels))
+ return PTR_ERR(channels);
+
+ error = devm_add_action(dev, vf50_ts_channel_release, channels);
+ if (error) {
+ iio_channel_release_all(channels);
+ dev_err(dev, "Failed to register iio channel release action");
+ return error;
+ }
+
+ num_adc_channels = 0;
+ while (channels[num_adc_channels].indio_dev)
+ num_adc_channels++;
+
+ if (num_adc_channels != COLI_TOUCH_REQ_ADC_CHAN) {
+ dev_err(dev, "Inadequate ADC channels specified\n");
+ return -EINVAL;
+ }
+
+ touchdev = devm_kzalloc(dev, sizeof(*touchdev), GFP_KERNEL);
+ if (!touchdev)
+ return -ENOMEM;
+
+ touchdev->pdev = pdev;
+ touchdev->channels = channels;
+
+ error = of_property_read_u32(dev->of_node, "vf50-ts-min-pressure",
+ &touchdev->min_pressure);
+ if (error)
+ return error;
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "Failed to allocate TS input device\n");
+ return -ENOMEM;
+ }
+
+ input->name = DRIVER_NAME;
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = dev;
+ input->open = vf50_ts_open;
+ input->close = vf50_ts_close;
+
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input, ABS_X, 0, VF_ADC_MAX, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, VF_ADC_MAX, 0, 0);
+ input_set_abs_params(input, ABS_PRESSURE, 0, VF_ADC_MAX, 0, 0);
+
+ touchdev->ts_input = input;
+ input_set_drvdata(input, touchdev);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "Failed to register input device\n");
+ return error;
+ }
+
+ error = vf50_ts_get_gpiod(dev, &touchdev->gpio_xp, "xp", GPIOD_OUT_LOW);
+ if (error)
+ return error;
+
+ error = vf50_ts_get_gpiod(dev, &touchdev->gpio_xm,
+ "xm", GPIOD_OUT_LOW);
+ if (error)
+ return error;
+
+ error = vf50_ts_get_gpiod(dev, &touchdev->gpio_yp, "yp", GPIOD_OUT_LOW);
+ if (error)
+ return error;
+
+ error = vf50_ts_get_gpiod(dev, &touchdev->gpio_ym, "ym", GPIOD_OUT_LOW);
+ if (error)
+ return error;
+
+ touchdev->pen_irq = platform_get_irq(pdev, 0);
+ if (touchdev->pen_irq < 0)
+ return touchdev->pen_irq;
+
+ error = devm_request_threaded_irq(dev, touchdev->pen_irq,
+ NULL, vf50_ts_irq_bh, IRQF_ONESHOT,
+ "vf50 touch", touchdev);
+ if (error) {
+ dev_err(dev, "Failed to request IRQ %d: %d\n",
+ touchdev->pen_irq, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id vf50_touch_of_match[] = {
+ { .compatible = "toradex,vf50-touchscreen", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, vf50_touch_of_match);
+
+static struct platform_driver vf50_touch_driver = {
+ .driver = {
+ .name = "toradex,vf50_touchctrl",
+ .of_match_table = vf50_touch_of_match,
+ },
+ .probe = vf50_ts_probe,
+};
+module_platform_driver(vf50_touch_driver);
+
+MODULE_AUTHOR("Sanchayan Maity");
+MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/cy8ctma140.c b/drivers/input/touchscreen/cy8ctma140.c
new file mode 100644
index 000000000..a9be29139
--- /dev/null
+++ b/drivers/input/touchscreen/cy8ctma140.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Cypress CY8CTMA140 (TMA140) touchscreen
+ * (C) 2020 Linus Walleij <linus.walleij@linaro.org>
+ * (C) 2007 Cypress
+ * (C) 2007 Google, Inc.
+ *
+ * Inspired by the tma140_skomer.c driver in the Samsung GT-S7710 code
+ * drop. The GT-S7710 is codenamed "Skomer", the code also indicates
+ * that the same touchscreen was used in a product called "Lucas".
+ *
+ * The code drop for GT-S7710 also contains a firmware downloader and
+ * 15 (!) versions of the firmware drop from Cypress. But here we assume
+ * the firmware got downloaded to the touchscreen flash successfully and
+ * just use it to read the fingers. The shipped vendor driver does the
+ * same.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/input/mt.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+
+#define CY8CTMA140_NAME "cy8ctma140"
+
+#define CY8CTMA140_MAX_FINGERS 4
+
+#define CY8CTMA140_GET_FINGERS 0x00
+#define CY8CTMA140_GET_FW_INFO 0x19
+
+/* This message also fits some bytes for touchkeys, if used */
+#define CY8CTMA140_PACKET_SIZE 31
+
+#define CY8CTMA140_INVALID_BUFFER_BIT 5
+
+struct cy8ctma140 {
+ struct input_dev *input;
+ struct touchscreen_properties props;
+ struct device *dev;
+ struct i2c_client *client;
+ struct regulator_bulk_data regulators[2];
+ u8 prev_fingers;
+ u8 prev_f1id;
+ u8 prev_f2id;
+};
+
+static void cy8ctma140_report(struct cy8ctma140 *ts, u8 *data, int n_fingers)
+{
+ static const u8 contact_offsets[] = { 0x03, 0x09, 0x10, 0x16 };
+ u8 *buf;
+ u16 x, y;
+ u8 w;
+ u8 id;
+ int slot;
+ int i;
+
+ for (i = 0; i < n_fingers; i++) {
+ buf = &data[contact_offsets[i]];
+
+ /*
+ * Odd contacts have contact ID in the lower nibble of
+ * the preceding byte, whereas even contacts have it in
+ * the upper nibble of the following byte.
+ */
+ id = i % 2 ? buf[-1] & 0x0f : buf[5] >> 4;
+ slot = input_mt_get_slot_by_key(ts->input, id);
+ if (slot < 0)
+ continue;
+
+ x = get_unaligned_be16(buf);
+ y = get_unaligned_be16(buf + 2);
+ w = buf[4];
+
+ dev_dbg(ts->dev, "finger %d: ID %02x (%d, %d) w: %d\n",
+ slot, id, x, y, w);
+
+ input_mt_slot(ts->input, slot);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+ touchscreen_report_pos(ts->input, &ts->props, x, y, true);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, w);
+ }
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+}
+
+static irqreturn_t cy8ctma140_irq_thread(int irq, void *d)
+{
+ struct cy8ctma140 *ts = d;
+ u8 cmdbuf[] = { CY8CTMA140_GET_FINGERS };
+ u8 buf[CY8CTMA140_PACKET_SIZE];
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .len = sizeof(cmdbuf),
+ .buf = cmdbuf,
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(buf),
+ .buf = buf,
+ },
+ };
+ u8 n_fingers;
+ int ret;
+
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ if (ret < 0)
+ dev_err(ts->dev, "error reading message: %d\n", ret);
+ else
+ dev_err(ts->dev, "wrong number of messages\n");
+ goto out;
+ }
+
+ if (buf[1] & BIT(CY8CTMA140_INVALID_BUFFER_BIT)) {
+ dev_dbg(ts->dev, "invalid event\n");
+ goto out;
+ }
+
+ n_fingers = buf[2] & 0x0f;
+ if (n_fingers > CY8CTMA140_MAX_FINGERS) {
+ dev_err(ts->dev, "unexpected number of fingers: %d\n",
+ n_fingers);
+ goto out;
+ }
+
+ cy8ctma140_report(ts, buf, n_fingers);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int cy8ctma140_init(struct cy8ctma140 *ts)
+{
+ u8 addr[1];
+ u8 buf[5];
+ int ret;
+
+ addr[0] = CY8CTMA140_GET_FW_INFO;
+ ret = i2c_master_send(ts->client, addr, 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "error sending FW info message\n");
+ return ret;
+ }
+ ret = i2c_master_recv(ts->client, buf, 5);
+ if (ret < 0) {
+ dev_err(ts->dev, "error receiving FW info message\n");
+ return ret;
+ }
+ if (ret != 5) {
+ dev_err(ts->dev, "got only %d bytes\n", ret);
+ return -EIO;
+ }
+
+ dev_dbg(ts->dev, "vendor %c%c, HW ID %.2d, FW ver %.4d\n",
+ buf[0], buf[1], buf[3], buf[4]);
+
+ return 0;
+}
+
+static int cy8ctma140_power_up(struct cy8ctma140 *ts)
+{
+ int error;
+
+ error = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+ if (error) {
+ dev_err(ts->dev, "failed to enable regulators\n");
+ return error;
+ }
+
+ msleep(250);
+
+ return 0;
+}
+
+static void cy8ctma140_power_down(struct cy8ctma140 *ts)
+{
+ regulator_bulk_disable(ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+}
+
+/* Called from the registered devm action */
+static void cy8ctma140_power_off_action(void *d)
+{
+ struct cy8ctma140 *ts = d;
+
+ cy8ctma140_power_down(ts);
+}
+
+static int cy8ctma140_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cy8ctma140 *ts;
+ struct input_dev *input;
+ struct device *dev = &client->dev;
+ int error;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ ts->dev = dev;
+ ts->client = client;
+ ts->input = input;
+
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y);
+ /* One byte for width 0..255 so this is the limit */
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ /*
+ * This sets up event max/min capabilities and fuzz.
+ * Some DT properties are compulsory so we do not need
+ * to provide defaults for X/Y max or pressure max.
+ *
+ * We just initialize a very simple MT touchscreen here,
+ * some devices use the capability of this touchscreen to
+ * provide touchkeys, and in that case this needs to be
+ * extended to handle touchkey input.
+ *
+ * The firmware takes care of finger tracking and dropping
+ * invalid ranges.
+ */
+ touchscreen_parse_properties(input, true, &ts->props);
+ input_abs_set_fuzz(input, ABS_MT_POSITION_X, 0);
+ input_abs_set_fuzz(input, ABS_MT_POSITION_Y, 0);
+
+ error = input_mt_init_slots(input, CY8CTMA140_MAX_FINGERS,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error)
+ return error;
+
+ input->name = CY8CTMA140_NAME;
+ input->id.bustype = BUS_I2C;
+ input_set_drvdata(input, ts);
+
+ /*
+ * VCPIN is the analog voltage supply
+ * VDD is the digital voltage supply
+ * since the voltage range of VDD overlaps that of VCPIN,
+ * many designs to just supply both with a single voltage
+ * source of ~3.3 V.
+ */
+ ts->regulators[0].supply = "vcpin";
+ ts->regulators[1].supply = "vdd";
+ error = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+ if (error) {
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get regulators %d\n",
+ error);
+ return error;
+ }
+
+ error = cy8ctma140_power_up(ts);
+ if (error)
+ return error;
+
+ error = devm_add_action_or_reset(dev, cy8ctma140_power_off_action, ts);
+ if (error) {
+ dev_err(dev, "failed to install power off handler\n");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, cy8ctma140_irq_thread,
+ IRQF_ONESHOT, CY8CTMA140_NAME, ts);
+ if (error) {
+ dev_err(dev, "irq %d busy? error %d\n", client->irq, error);
+ return error;
+ }
+
+ error = cy8ctma140_init(ts);
+ if (error)
+ return error;
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+}
+
+static int __maybe_unused cy8ctma140_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cy8ctma140 *ts = i2c_get_clientdata(client);
+
+ if (!device_may_wakeup(&client->dev))
+ cy8ctma140_power_down(ts);
+
+ return 0;
+}
+
+static int __maybe_unused cy8ctma140_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cy8ctma140 *ts = i2c_get_clientdata(client);
+ int error;
+
+ if (!device_may_wakeup(&client->dev)) {
+ error = cy8ctma140_power_up(ts);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cy8ctma140_pm, cy8ctma140_suspend, cy8ctma140_resume);
+
+static const struct i2c_device_id cy8ctma140_idtable[] = {
+ { CY8CTMA140_NAME, 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, cy8ctma140_idtable);
+
+static const struct of_device_id cy8ctma140_of_match[] = {
+ { .compatible = "cypress,cy8ctma140", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cy8ctma140_of_match);
+
+static struct i2c_driver cy8ctma140_driver = {
+ .driver = {
+ .name = CY8CTMA140_NAME,
+ .pm = &cy8ctma140_pm,
+ .of_match_table = cy8ctma140_of_match,
+ },
+ .id_table = cy8ctma140_idtable,
+ .probe = cy8ctma140_probe,
+};
+module_i2c_driver(cy8ctma140_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("CY8CTMA140 TouchScreen Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/cy8ctmg110_ts.c b/drivers/input/touchscreen/cy8ctmg110_ts.c
new file mode 100644
index 000000000..495ef156c
--- /dev/null
+++ b/drivers/input/touchscreen/cy8ctmg110_ts.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for cypress touch screen controller
+ *
+ * Copyright (c) 2009 Aava Mobile
+ *
+ * Some cleanups by Alan Cox <alan@linux.intel.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/byteorder.h>
+
+#define CY8CTMG110_DRIVER_NAME "cy8ctmg110"
+
+/* Touch coordinates */
+#define CY8CTMG110_X_MIN 0
+#define CY8CTMG110_Y_MIN 0
+#define CY8CTMG110_X_MAX 759
+#define CY8CTMG110_Y_MAX 465
+
+
+/* cy8ctmg110 register definitions */
+#define CY8CTMG110_TOUCH_WAKEUP_TIME 0
+#define CY8CTMG110_TOUCH_SLEEP_TIME 2
+#define CY8CTMG110_TOUCH_X1 3
+#define CY8CTMG110_TOUCH_Y1 5
+#define CY8CTMG110_TOUCH_X2 7
+#define CY8CTMG110_TOUCH_Y2 9
+#define CY8CTMG110_FINGERS 11
+#define CY8CTMG110_GESTURE 12
+#define CY8CTMG110_REG_MAX 13
+
+
+/*
+ * The touch driver structure.
+ */
+struct cy8ctmg110 {
+ struct input_dev *input;
+ char phys[32];
+ struct i2c_client *client;
+ struct gpio_desc *reset_gpio;
+};
+
+/*
+ * cy8ctmg110_power is the routine that is called when touch hardware
+ * is being powered off or on. When powering on this routine de-asserts
+ * the RESET line, when powering off reset line is asserted.
+ */
+static void cy8ctmg110_power(struct cy8ctmg110 *ts, bool poweron)
+{
+ if (ts->reset_gpio)
+ gpiod_set_value_cansleep(ts->reset_gpio, !poweron);
+}
+
+static int cy8ctmg110_write_regs(struct cy8ctmg110 *tsc, unsigned char reg,
+ unsigned char len, unsigned char *value)
+{
+ struct i2c_client *client = tsc->client;
+ int ret;
+ unsigned char i2c_data[6];
+
+ BUG_ON(len > 5);
+
+ i2c_data[0] = reg;
+ memcpy(i2c_data + 1, value, len);
+
+ ret = i2c_master_send(client, i2c_data, len + 1);
+ if (ret != len + 1) {
+ dev_err(&client->dev, "i2c write data cmd failed\n");
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+
+static int cy8ctmg110_read_regs(struct cy8ctmg110 *tsc,
+ unsigned char *data, unsigned char len, unsigned char cmd)
+{
+ struct i2c_client *client = tsc->client;
+ int ret;
+ struct i2c_msg msg[2] = {
+ /* first write slave position to i2c devices */
+ {
+ .addr = client->addr,
+ .len = 1,
+ .buf = &cmd
+ },
+ /* Second read data from position */
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = data
+ }
+ };
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cy8ctmg110_touch_pos(struct cy8ctmg110 *tsc)
+{
+ struct input_dev *input = tsc->input;
+ unsigned char reg_p[CY8CTMG110_REG_MAX];
+
+ memset(reg_p, 0, CY8CTMG110_REG_MAX);
+
+ /* Reading coordinates */
+ if (cy8ctmg110_read_regs(tsc, reg_p, 9, CY8CTMG110_TOUCH_X1) != 0)
+ return -EIO;
+
+ /* Number of touch */
+ if (reg_p[8] == 0) {
+ input_report_key(input, BTN_TOUCH, 0);
+ } else {
+ input_report_key(input, BTN_TOUCH, 1);
+ input_report_abs(input, ABS_X,
+ be16_to_cpup((__be16 *)(reg_p + 0)));
+ input_report_abs(input, ABS_Y,
+ be16_to_cpup((__be16 *)(reg_p + 2)));
+ }
+
+ input_sync(input);
+
+ return 0;
+}
+
+static int cy8ctmg110_set_sleepmode(struct cy8ctmg110 *ts, bool sleep)
+{
+ unsigned char reg_p[3];
+
+ if (sleep) {
+ reg_p[0] = 0x00;
+ reg_p[1] = 0xff;
+ reg_p[2] = 5;
+ } else {
+ reg_p[0] = 0x10;
+ reg_p[1] = 0xff;
+ reg_p[2] = 0;
+ }
+
+ return cy8ctmg110_write_regs(ts, CY8CTMG110_TOUCH_WAKEUP_TIME, 3, reg_p);
+}
+
+static irqreturn_t cy8ctmg110_irq_thread(int irq, void *dev_id)
+{
+ struct cy8ctmg110 *tsc = dev_id;
+
+ cy8ctmg110_touch_pos(tsc);
+
+ return IRQ_HANDLED;
+}
+
+static void cy8ctmg110_shut_off(void *_ts)
+{
+ struct cy8ctmg110 *ts = _ts;
+
+ cy8ctmg110_set_sleepmode(ts, true);
+ cy8ctmg110_power(ts, false);
+}
+
+static int cy8ctmg110_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cy8ctmg110 *ts;
+ struct input_dev *input_dev;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ return -EIO;
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ ts->client = client;
+ ts->input = input_dev;
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input0", dev_name(&client->dev));
+
+ input_dev->name = CY8CTMG110_DRIVER_NAME " Touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->id.bustype = BUS_I2C;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X,
+ CY8CTMG110_X_MIN, CY8CTMG110_X_MAX, 4, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ CY8CTMG110_Y_MIN, CY8CTMG110_Y_MAX, 4, 0);
+
+ /* Request and assert reset line */
+ ts->reset_gpio = devm_gpiod_get_optional(&client->dev, NULL,
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->reset_gpio)) {
+ err = PTR_ERR(ts->reset_gpio);
+ dev_err(&client->dev,
+ "Unable to request reset GPIO: %d\n", err);
+ return err;
+ }
+
+ cy8ctmg110_power(ts, true);
+ cy8ctmg110_set_sleepmode(ts, false);
+
+ err = devm_add_action_or_reset(&client->dev, cy8ctmg110_shut_off, ts);
+ if (err)
+ return err;
+
+ err = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, cy8ctmg110_irq_thread,
+ IRQF_ONESHOT, "touch_reset_key", ts);
+ if (err) {
+ dev_err(&client->dev,
+ "irq %d busy? error %d\n", client->irq, err);
+ return err;
+ }
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+}
+
+static int __maybe_unused cy8ctmg110_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cy8ctmg110 *ts = i2c_get_clientdata(client);
+
+ if (!device_may_wakeup(&client->dev)) {
+ cy8ctmg110_set_sleepmode(ts, true);
+ cy8ctmg110_power(ts, false);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused cy8ctmg110_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cy8ctmg110 *ts = i2c_get_clientdata(client);
+
+ if (!device_may_wakeup(&client->dev)) {
+ cy8ctmg110_power(ts, true);
+ cy8ctmg110_set_sleepmode(ts, false);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cy8ctmg110_pm, cy8ctmg110_suspend, cy8ctmg110_resume);
+
+static const struct i2c_device_id cy8ctmg110_idtable[] = {
+ { CY8CTMG110_DRIVER_NAME, 1 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, cy8ctmg110_idtable);
+
+static struct i2c_driver cy8ctmg110_driver = {
+ .driver = {
+ .name = CY8CTMG110_DRIVER_NAME,
+ .pm = &cy8ctmg110_pm,
+ },
+ .id_table = cy8ctmg110_idtable,
+ .probe = cy8ctmg110_probe,
+};
+
+module_i2c_driver(cy8ctmg110_driver);
+
+MODULE_AUTHOR("Samuli Konttila <samuli.konttila@aavamobile.com>");
+MODULE_DESCRIPTION("cy8ctmg110 TouchScreen Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/cyttsp4_core.c b/drivers/input/touchscreen/cyttsp4_core.c
new file mode 100644
index 000000000..dccbcb942
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp4_core.c
@@ -0,0 +1,2180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cyttsp4_core.c
+ * Cypress TrueTouch(TM) Standard Product V4 Core driver module.
+ * For use with Cypress Txx4xx parts.
+ * Supported parts include:
+ * TMA4XX
+ * TMA1036
+ *
+ * Copyright (C) 2012 Cypress Semiconductor
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#include "cyttsp4_core.h"
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+/* Timeout in ms. */
+#define CY_CORE_REQUEST_EXCLUSIVE_TIMEOUT 500
+#define CY_CORE_SLEEP_REQUEST_EXCLUSIVE_TIMEOUT 5000
+#define CY_CORE_MODE_CHANGE_TIMEOUT 1000
+#define CY_CORE_RESET_AND_WAIT_TIMEOUT 500
+#define CY_CORE_WAKEUP_TIMEOUT 500
+
+#define CY_CORE_STARTUP_RETRY_COUNT 3
+
+static const char * const cyttsp4_tch_abs_string[] = {
+ [CY_TCH_X] = "X",
+ [CY_TCH_Y] = "Y",
+ [CY_TCH_P] = "P",
+ [CY_TCH_T] = "T",
+ [CY_TCH_E] = "E",
+ [CY_TCH_O] = "O",
+ [CY_TCH_W] = "W",
+ [CY_TCH_MAJ] = "MAJ",
+ [CY_TCH_MIN] = "MIN",
+ [CY_TCH_OR] = "OR",
+ [CY_TCH_NUM_ABS] = "INVALID"
+};
+
+static const u8 ldr_exit[] = {
+ 0xFF, 0x01, 0x3B, 0x00, 0x00, 0x4F, 0x6D, 0x17
+};
+
+static const u8 ldr_err_app[] = {
+ 0x01, 0x02, 0x00, 0x00, 0x55, 0xDD, 0x17
+};
+
+static inline size_t merge_bytes(u8 high, u8 low)
+{
+ return (high << 8) + low;
+}
+
+#ifdef VERBOSE_DEBUG
+static void cyttsp4_pr_buf(struct device *dev, u8 *pr_buf, u8 *dptr, int size,
+ const char *data_name)
+{
+ int i, k;
+ const char fmt[] = "%02X ";
+ int max;
+
+ if (!size)
+ return;
+
+ max = (CY_MAX_PRBUF_SIZE - 1) - sizeof(CY_PR_TRUNCATED);
+
+ pr_buf[0] = 0;
+ for (i = k = 0; i < size && k < max; i++, k += 3)
+ scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, dptr[i]);
+
+ dev_vdbg(dev, "%s: %s[0..%d]=%s%s\n", __func__, data_name, size - 1,
+ pr_buf, size <= max ? "" : CY_PR_TRUNCATED);
+}
+#else
+#define cyttsp4_pr_buf(dev, pr_buf, dptr, size, data_name) do { } while (0)
+#endif
+
+static int cyttsp4_load_status_regs(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ struct device *dev = cd->dev;
+ int rc;
+
+ rc = cyttsp4_adap_read(cd, CY_REG_BASE, si->si_ofs.mode_size,
+ si->xy_mode);
+ if (rc < 0)
+ dev_err(dev, "%s: fail read mode regs r=%d\n",
+ __func__, rc);
+ else
+ cyttsp4_pr_buf(dev, cd->pr_buf, si->xy_mode,
+ si->si_ofs.mode_size, "xy_mode");
+
+ return rc;
+}
+
+static int cyttsp4_handshake(struct cyttsp4 *cd, u8 mode)
+{
+ u8 cmd = mode ^ CY_HST_TOGGLE;
+ int rc;
+
+ /*
+ * Mode change issued, handshaking now will cause endless mode change
+ * requests, for sync mode modechange will do same with handshake
+ * */
+ if (mode & CY_HST_MODE_CHANGE)
+ return 0;
+
+ rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(cmd), &cmd);
+ if (rc < 0)
+ dev_err(cd->dev, "%s: bus write fail on handshake (ret=%d)\n",
+ __func__, rc);
+
+ return rc;
+}
+
+static int cyttsp4_hw_soft_reset(struct cyttsp4 *cd)
+{
+ u8 cmd = CY_HST_RESET;
+ int rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(cmd), &cmd);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: FAILED to execute SOFT reset\n",
+ __func__);
+ return rc;
+ }
+ return 0;
+}
+
+static int cyttsp4_hw_hard_reset(struct cyttsp4 *cd)
+{
+ if (cd->cpdata->xres) {
+ cd->cpdata->xres(cd->cpdata, cd->dev);
+ dev_dbg(cd->dev, "%s: execute HARD reset\n", __func__);
+ return 0;
+ }
+ dev_err(cd->dev, "%s: FAILED to execute HARD reset\n", __func__);
+ return -ENOSYS;
+}
+
+static int cyttsp4_hw_reset(struct cyttsp4 *cd)
+{
+ int rc = cyttsp4_hw_hard_reset(cd);
+ if (rc == -ENOSYS)
+ rc = cyttsp4_hw_soft_reset(cd);
+ return rc;
+}
+
+/*
+ * Gets number of bits for a touch filed as parameter,
+ * sets maximum value for field which is used as bit mask
+ * and returns number of bytes required for that field
+ */
+static int cyttsp4_bits_2_bytes(unsigned int nbits, size_t *max)
+{
+ *max = 1UL << nbits;
+ return (nbits + 7) / 8;
+}
+
+static int cyttsp4_si_data_offsets(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ int rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(si->si_data),
+ &si->si_data);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read sysinfo data offsets r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ /* Print sysinfo data offsets */
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf, (u8 *)&si->si_data,
+ sizeof(si->si_data), "sysinfo_data_offsets");
+
+ /* convert sysinfo data offset bytes into integers */
+
+ si->si_ofs.map_sz = merge_bytes(si->si_data.map_szh,
+ si->si_data.map_szl);
+ si->si_ofs.map_sz = merge_bytes(si->si_data.map_szh,
+ si->si_data.map_szl);
+ si->si_ofs.cydata_ofs = merge_bytes(si->si_data.cydata_ofsh,
+ si->si_data.cydata_ofsl);
+ si->si_ofs.test_ofs = merge_bytes(si->si_data.test_ofsh,
+ si->si_data.test_ofsl);
+ si->si_ofs.pcfg_ofs = merge_bytes(si->si_data.pcfg_ofsh,
+ si->si_data.pcfg_ofsl);
+ si->si_ofs.opcfg_ofs = merge_bytes(si->si_data.opcfg_ofsh,
+ si->si_data.opcfg_ofsl);
+ si->si_ofs.ddata_ofs = merge_bytes(si->si_data.ddata_ofsh,
+ si->si_data.ddata_ofsl);
+ si->si_ofs.mdata_ofs = merge_bytes(si->si_data.mdata_ofsh,
+ si->si_data.mdata_ofsl);
+ return rc;
+}
+
+static int cyttsp4_si_get_cydata(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ int read_offset;
+ int mfgid_sz, calc_mfgid_sz;
+ void *p;
+ int rc;
+
+ if (si->si_ofs.test_ofs <= si->si_ofs.cydata_ofs) {
+ dev_err(cd->dev,
+ "%s: invalid offset test_ofs: %zu, cydata_ofs: %zu\n",
+ __func__, si->si_ofs.test_ofs, si->si_ofs.cydata_ofs);
+ return -EINVAL;
+ }
+
+ si->si_ofs.cydata_size = si->si_ofs.test_ofs - si->si_ofs.cydata_ofs;
+ dev_dbg(cd->dev, "%s: cydata size: %zd\n", __func__,
+ si->si_ofs.cydata_size);
+
+ p = krealloc(si->si_ptrs.cydata, si->si_ofs.cydata_size, GFP_KERNEL);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: failed to allocate cydata memory\n",
+ __func__);
+ return -ENOMEM;
+ }
+ si->si_ptrs.cydata = p;
+
+ read_offset = si->si_ofs.cydata_ofs;
+
+ /* Read the CYDA registers up to MFGID field */
+ rc = cyttsp4_adap_read(cd, read_offset,
+ offsetof(struct cyttsp4_cydata, mfgid_sz)
+ + sizeof(si->si_ptrs.cydata->mfgid_sz),
+ si->si_ptrs.cydata);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read cydata r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ /* Check MFGID size */
+ mfgid_sz = si->si_ptrs.cydata->mfgid_sz;
+ calc_mfgid_sz = si->si_ofs.cydata_size - sizeof(struct cyttsp4_cydata);
+ if (mfgid_sz != calc_mfgid_sz) {
+ dev_err(cd->dev, "%s: mismatch in MFGID size, reported:%d calculated:%d\n",
+ __func__, mfgid_sz, calc_mfgid_sz);
+ return -EINVAL;
+ }
+
+ read_offset += offsetof(struct cyttsp4_cydata, mfgid_sz)
+ + sizeof(si->si_ptrs.cydata->mfgid_sz);
+
+ /* Read the CYDA registers for MFGID field */
+ rc = cyttsp4_adap_read(cd, read_offset, si->si_ptrs.cydata->mfgid_sz,
+ si->si_ptrs.cydata->mfg_id);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read cydata r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ read_offset += si->si_ptrs.cydata->mfgid_sz;
+
+ /* Read the rest of the CYDA registers */
+ rc = cyttsp4_adap_read(cd, read_offset,
+ sizeof(struct cyttsp4_cydata)
+ - offsetof(struct cyttsp4_cydata, cyito_idh),
+ &si->si_ptrs.cydata->cyito_idh);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read cydata r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf, (u8 *)si->si_ptrs.cydata,
+ si->si_ofs.cydata_size, "sysinfo_cydata");
+ return rc;
+}
+
+static int cyttsp4_si_get_test_data(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ void *p;
+ int rc;
+
+ if (si->si_ofs.pcfg_ofs <= si->si_ofs.test_ofs) {
+ dev_err(cd->dev,
+ "%s: invalid offset pcfg_ofs: %zu, test_ofs: %zu\n",
+ __func__, si->si_ofs.pcfg_ofs, si->si_ofs.test_ofs);
+ return -EINVAL;
+ }
+
+ si->si_ofs.test_size = si->si_ofs.pcfg_ofs - si->si_ofs.test_ofs;
+
+ p = krealloc(si->si_ptrs.test, si->si_ofs.test_size, GFP_KERNEL);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: failed to allocate test memory\n",
+ __func__);
+ return -ENOMEM;
+ }
+ si->si_ptrs.test = p;
+
+ rc = cyttsp4_adap_read(cd, si->si_ofs.test_ofs, si->si_ofs.test_size,
+ si->si_ptrs.test);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read test data r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf,
+ (u8 *)si->si_ptrs.test, si->si_ofs.test_size,
+ "sysinfo_test_data");
+ if (si->si_ptrs.test->post_codel &
+ CY_POST_CODEL_WDG_RST)
+ dev_info(cd->dev, "%s: %s codel=%02X\n",
+ __func__, "Reset was a WATCHDOG RESET",
+ si->si_ptrs.test->post_codel);
+
+ if (!(si->si_ptrs.test->post_codel &
+ CY_POST_CODEL_CFG_DATA_CRC_FAIL))
+ dev_info(cd->dev, "%s: %s codel=%02X\n", __func__,
+ "Config Data CRC FAIL",
+ si->si_ptrs.test->post_codel);
+
+ if (!(si->si_ptrs.test->post_codel &
+ CY_POST_CODEL_PANEL_TEST_FAIL))
+ dev_info(cd->dev, "%s: %s codel=%02X\n",
+ __func__, "PANEL TEST FAIL",
+ si->si_ptrs.test->post_codel);
+
+ dev_info(cd->dev, "%s: SCANNING is %s codel=%02X\n",
+ __func__, si->si_ptrs.test->post_codel & 0x08 ?
+ "ENABLED" : "DISABLED",
+ si->si_ptrs.test->post_codel);
+ return rc;
+}
+
+static int cyttsp4_si_get_pcfg_data(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ void *p;
+ int rc;
+
+ if (si->si_ofs.opcfg_ofs <= si->si_ofs.pcfg_ofs) {
+ dev_err(cd->dev,
+ "%s: invalid offset opcfg_ofs: %zu, pcfg_ofs: %zu\n",
+ __func__, si->si_ofs.opcfg_ofs, si->si_ofs.pcfg_ofs);
+ return -EINVAL;
+ }
+
+ si->si_ofs.pcfg_size = si->si_ofs.opcfg_ofs - si->si_ofs.pcfg_ofs;
+
+ p = krealloc(si->si_ptrs.pcfg, si->si_ofs.pcfg_size, GFP_KERNEL);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: failed to allocate pcfg memory\n",
+ __func__);
+ return -ENOMEM;
+ }
+ si->si_ptrs.pcfg = p;
+
+ rc = cyttsp4_adap_read(cd, si->si_ofs.pcfg_ofs, si->si_ofs.pcfg_size,
+ si->si_ptrs.pcfg);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read pcfg data r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ si->si_ofs.max_x = merge_bytes((si->si_ptrs.pcfg->res_xh
+ & CY_PCFG_RESOLUTION_X_MASK), si->si_ptrs.pcfg->res_xl);
+ si->si_ofs.x_origin = !!(si->si_ptrs.pcfg->res_xh
+ & CY_PCFG_ORIGIN_X_MASK);
+ si->si_ofs.max_y = merge_bytes((si->si_ptrs.pcfg->res_yh
+ & CY_PCFG_RESOLUTION_Y_MASK), si->si_ptrs.pcfg->res_yl);
+ si->si_ofs.y_origin = !!(si->si_ptrs.pcfg->res_yh
+ & CY_PCFG_ORIGIN_Y_MASK);
+ si->si_ofs.max_p = merge_bytes(si->si_ptrs.pcfg->max_zh,
+ si->si_ptrs.pcfg->max_zl);
+
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf,
+ (u8 *)si->si_ptrs.pcfg,
+ si->si_ofs.pcfg_size, "sysinfo_pcfg_data");
+ return rc;
+}
+
+static int cyttsp4_si_get_opcfg_data(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ struct cyttsp4_tch_abs_params *tch;
+ struct cyttsp4_tch_rec_params *tch_old, *tch_new;
+ enum cyttsp4_tch_abs abs;
+ int i;
+ void *p;
+ int rc;
+
+ if (si->si_ofs.ddata_ofs <= si->si_ofs.opcfg_ofs) {
+ dev_err(cd->dev,
+ "%s: invalid offset ddata_ofs: %zu, opcfg_ofs: %zu\n",
+ __func__, si->si_ofs.ddata_ofs, si->si_ofs.opcfg_ofs);
+ return -EINVAL;
+ }
+
+ si->si_ofs.opcfg_size = si->si_ofs.ddata_ofs - si->si_ofs.opcfg_ofs;
+
+ p = krealloc(si->si_ptrs.opcfg, si->si_ofs.opcfg_size, GFP_KERNEL);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: failed to allocate opcfg memory\n",
+ __func__);
+ return -ENOMEM;
+ }
+ si->si_ptrs.opcfg = p;
+
+ rc = cyttsp4_adap_read(cd, si->si_ofs.opcfg_ofs, si->si_ofs.opcfg_size,
+ si->si_ptrs.opcfg);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail read opcfg data r=%d\n",
+ __func__, rc);
+ return rc;
+ }
+ si->si_ofs.cmd_ofs = si->si_ptrs.opcfg->cmd_ofs;
+ si->si_ofs.rep_ofs = si->si_ptrs.opcfg->rep_ofs;
+ si->si_ofs.rep_sz = (si->si_ptrs.opcfg->rep_szh * 256) +
+ si->si_ptrs.opcfg->rep_szl;
+ si->si_ofs.num_btns = si->si_ptrs.opcfg->num_btns;
+ si->si_ofs.num_btn_regs = (si->si_ofs.num_btns +
+ CY_NUM_BTN_PER_REG - 1) / CY_NUM_BTN_PER_REG;
+ si->si_ofs.tt_stat_ofs = si->si_ptrs.opcfg->tt_stat_ofs;
+ si->si_ofs.obj_cfg0 = si->si_ptrs.opcfg->obj_cfg0;
+ si->si_ofs.max_tchs = si->si_ptrs.opcfg->max_tchs &
+ CY_BYTE_OFS_MASK;
+ si->si_ofs.tch_rec_size = si->si_ptrs.opcfg->tch_rec_size &
+ CY_BYTE_OFS_MASK;
+
+ /* Get the old touch fields */
+ for (abs = CY_TCH_X; abs < CY_NUM_TCH_FIELDS; abs++) {
+ tch = &si->si_ofs.tch_abs[abs];
+ tch_old = &si->si_ptrs.opcfg->tch_rec_old[abs];
+
+ tch->ofs = tch_old->loc & CY_BYTE_OFS_MASK;
+ tch->size = cyttsp4_bits_2_bytes(tch_old->size,
+ &tch->max);
+ tch->bofs = (tch_old->loc & CY_BOFS_MASK) >> CY_BOFS_SHIFT;
+ }
+
+ /* button fields */
+ si->si_ofs.btn_rec_size = si->si_ptrs.opcfg->btn_rec_size;
+ si->si_ofs.btn_diff_ofs = si->si_ptrs.opcfg->btn_diff_ofs;
+ si->si_ofs.btn_diff_size = si->si_ptrs.opcfg->btn_diff_size;
+
+ if (si->si_ofs.tch_rec_size > CY_TMA1036_TCH_REC_SIZE) {
+ /* Get the extended touch fields */
+ for (i = 0; i < CY_NUM_EXT_TCH_FIELDS; abs++, i++) {
+ tch = &si->si_ofs.tch_abs[abs];
+ tch_new = &si->si_ptrs.opcfg->tch_rec_new[i];
+
+ tch->ofs = tch_new->loc & CY_BYTE_OFS_MASK;
+ tch->size = cyttsp4_bits_2_bytes(tch_new->size,
+ &tch->max);
+ tch->bofs = (tch_new->loc & CY_BOFS_MASK) >> CY_BOFS_SHIFT;
+ }
+ }
+
+ for (abs = 0; abs < CY_TCH_NUM_ABS; abs++) {
+ dev_dbg(cd->dev, "%s: tch_rec_%s\n", __func__,
+ cyttsp4_tch_abs_string[abs]);
+ dev_dbg(cd->dev, "%s: ofs =%2zd\n", __func__,
+ si->si_ofs.tch_abs[abs].ofs);
+ dev_dbg(cd->dev, "%s: siz =%2zd\n", __func__,
+ si->si_ofs.tch_abs[abs].size);
+ dev_dbg(cd->dev, "%s: max =%2zd\n", __func__,
+ si->si_ofs.tch_abs[abs].max);
+ dev_dbg(cd->dev, "%s: bofs=%2zd\n", __func__,
+ si->si_ofs.tch_abs[abs].bofs);
+ }
+
+ si->si_ofs.mode_size = si->si_ofs.tt_stat_ofs + 1;
+ si->si_ofs.data_size = si->si_ofs.max_tchs *
+ si->si_ptrs.opcfg->tch_rec_size;
+
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf, (u8 *)si->si_ptrs.opcfg,
+ si->si_ofs.opcfg_size, "sysinfo_opcfg_data");
+
+ return 0;
+}
+
+static int cyttsp4_si_get_ddata(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ void *p;
+ int rc;
+
+ si->si_ofs.ddata_size = si->si_ofs.mdata_ofs - si->si_ofs.ddata_ofs;
+
+ p = krealloc(si->si_ptrs.ddata, si->si_ofs.ddata_size, GFP_KERNEL);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: fail alloc ddata memory\n", __func__);
+ return -ENOMEM;
+ }
+ si->si_ptrs.ddata = p;
+
+ rc = cyttsp4_adap_read(cd, si->si_ofs.ddata_ofs, si->si_ofs.ddata_size,
+ si->si_ptrs.ddata);
+ if (rc < 0)
+ dev_err(cd->dev, "%s: fail read ddata data r=%d\n",
+ __func__, rc);
+ else
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf,
+ (u8 *)si->si_ptrs.ddata,
+ si->si_ofs.ddata_size, "sysinfo_ddata");
+ return rc;
+}
+
+static int cyttsp4_si_get_mdata(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ void *p;
+ int rc;
+
+ si->si_ofs.mdata_size = si->si_ofs.map_sz - si->si_ofs.mdata_ofs;
+
+ p = krealloc(si->si_ptrs.mdata, si->si_ofs.mdata_size, GFP_KERNEL);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: fail alloc mdata memory\n", __func__);
+ return -ENOMEM;
+ }
+ si->si_ptrs.mdata = p;
+
+ rc = cyttsp4_adap_read(cd, si->si_ofs.mdata_ofs, si->si_ofs.mdata_size,
+ si->si_ptrs.mdata);
+ if (rc < 0)
+ dev_err(cd->dev, "%s: fail read mdata data r=%d\n",
+ __func__, rc);
+ else
+ cyttsp4_pr_buf(cd->dev, cd->pr_buf,
+ (u8 *)si->si_ptrs.mdata,
+ si->si_ofs.mdata_size, "sysinfo_mdata");
+ return rc;
+}
+
+static int cyttsp4_si_get_btn_data(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ int btn;
+ int num_defined_keys;
+ u16 *key_table;
+ void *p;
+ int rc = 0;
+
+ if (si->si_ofs.num_btns) {
+ si->si_ofs.btn_keys_size = si->si_ofs.num_btns *
+ sizeof(struct cyttsp4_btn);
+
+ p = krealloc(si->btn, si->si_ofs.btn_keys_size,
+ GFP_KERNEL|__GFP_ZERO);
+ if (p == NULL) {
+ dev_err(cd->dev, "%s: %s\n", __func__,
+ "fail alloc btn_keys memory");
+ return -ENOMEM;
+ }
+ si->btn = p;
+
+ if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS] == NULL)
+ num_defined_keys = 0;
+ else if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS]->data == NULL)
+ num_defined_keys = 0;
+ else
+ num_defined_keys = cd->cpdata->sett
+ [CY_IC_GRPNUM_BTN_KEYS]->size;
+
+ for (btn = 0; btn < si->si_ofs.num_btns &&
+ btn < num_defined_keys; btn++) {
+ key_table = (u16 *)cd->cpdata->sett
+ [CY_IC_GRPNUM_BTN_KEYS]->data;
+ si->btn[btn].key_code = key_table[btn];
+ si->btn[btn].state = CY_BTN_RELEASED;
+ si->btn[btn].enabled = true;
+ }
+ for (; btn < si->si_ofs.num_btns; btn++) {
+ si->btn[btn].key_code = KEY_RESERVED;
+ si->btn[btn].state = CY_BTN_RELEASED;
+ si->btn[btn].enabled = true;
+ }
+
+ return rc;
+ }
+
+ si->si_ofs.btn_keys_size = 0;
+ kfree(si->btn);
+ si->btn = NULL;
+ return rc;
+}
+
+static int cyttsp4_si_get_op_data_ptrs(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ void *p;
+
+ p = krealloc(si->xy_mode, si->si_ofs.mode_size, GFP_KERNEL|__GFP_ZERO);
+ if (p == NULL)
+ return -ENOMEM;
+ si->xy_mode = p;
+
+ p = krealloc(si->xy_data, si->si_ofs.data_size, GFP_KERNEL|__GFP_ZERO);
+ if (p == NULL)
+ return -ENOMEM;
+ si->xy_data = p;
+
+ p = krealloc(si->btn_rec_data,
+ si->si_ofs.btn_rec_size * si->si_ofs.num_btns,
+ GFP_KERNEL|__GFP_ZERO);
+ if (p == NULL)
+ return -ENOMEM;
+ si->btn_rec_data = p;
+
+ return 0;
+}
+
+static void cyttsp4_si_put_log_data(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ dev_dbg(cd->dev, "%s: cydata_ofs =%4zd siz=%4zd\n", __func__,
+ si->si_ofs.cydata_ofs, si->si_ofs.cydata_size);
+ dev_dbg(cd->dev, "%s: test_ofs =%4zd siz=%4zd\n", __func__,
+ si->si_ofs.test_ofs, si->si_ofs.test_size);
+ dev_dbg(cd->dev, "%s: pcfg_ofs =%4zd siz=%4zd\n", __func__,
+ si->si_ofs.pcfg_ofs, si->si_ofs.pcfg_size);
+ dev_dbg(cd->dev, "%s: opcfg_ofs =%4zd siz=%4zd\n", __func__,
+ si->si_ofs.opcfg_ofs, si->si_ofs.opcfg_size);
+ dev_dbg(cd->dev, "%s: ddata_ofs =%4zd siz=%4zd\n", __func__,
+ si->si_ofs.ddata_ofs, si->si_ofs.ddata_size);
+ dev_dbg(cd->dev, "%s: mdata_ofs =%4zd siz=%4zd\n", __func__,
+ si->si_ofs.mdata_ofs, si->si_ofs.mdata_size);
+
+ dev_dbg(cd->dev, "%s: cmd_ofs =%4zd\n", __func__,
+ si->si_ofs.cmd_ofs);
+ dev_dbg(cd->dev, "%s: rep_ofs =%4zd\n", __func__,
+ si->si_ofs.rep_ofs);
+ dev_dbg(cd->dev, "%s: rep_sz =%4zd\n", __func__,
+ si->si_ofs.rep_sz);
+ dev_dbg(cd->dev, "%s: num_btns =%4zd\n", __func__,
+ si->si_ofs.num_btns);
+ dev_dbg(cd->dev, "%s: num_btn_regs =%4zd\n", __func__,
+ si->si_ofs.num_btn_regs);
+ dev_dbg(cd->dev, "%s: tt_stat_ofs =%4zd\n", __func__,
+ si->si_ofs.tt_stat_ofs);
+ dev_dbg(cd->dev, "%s: tch_rec_size =%4zd\n", __func__,
+ si->si_ofs.tch_rec_size);
+ dev_dbg(cd->dev, "%s: max_tchs =%4zd\n", __func__,
+ si->si_ofs.max_tchs);
+ dev_dbg(cd->dev, "%s: mode_size =%4zd\n", __func__,
+ si->si_ofs.mode_size);
+ dev_dbg(cd->dev, "%s: data_size =%4zd\n", __func__,
+ si->si_ofs.data_size);
+ dev_dbg(cd->dev, "%s: map_sz =%4zd\n", __func__,
+ si->si_ofs.map_sz);
+
+ dev_dbg(cd->dev, "%s: btn_rec_size =%2zd\n", __func__,
+ si->si_ofs.btn_rec_size);
+ dev_dbg(cd->dev, "%s: btn_diff_ofs =%2zd\n", __func__,
+ si->si_ofs.btn_diff_ofs);
+ dev_dbg(cd->dev, "%s: btn_diff_size =%2zd\n", __func__,
+ si->si_ofs.btn_diff_size);
+
+ dev_dbg(cd->dev, "%s: max_x = 0x%04zX (%zd)\n", __func__,
+ si->si_ofs.max_x, si->si_ofs.max_x);
+ dev_dbg(cd->dev, "%s: x_origin = %zd (%s)\n", __func__,
+ si->si_ofs.x_origin,
+ si->si_ofs.x_origin == CY_NORMAL_ORIGIN ?
+ "left corner" : "right corner");
+ dev_dbg(cd->dev, "%s: max_y = 0x%04zX (%zd)\n", __func__,
+ si->si_ofs.max_y, si->si_ofs.max_y);
+ dev_dbg(cd->dev, "%s: y_origin = %zd (%s)\n", __func__,
+ si->si_ofs.y_origin,
+ si->si_ofs.y_origin == CY_NORMAL_ORIGIN ?
+ "upper corner" : "lower corner");
+ dev_dbg(cd->dev, "%s: max_p = 0x%04zX (%zd)\n", __func__,
+ si->si_ofs.max_p, si->si_ofs.max_p);
+
+ dev_dbg(cd->dev, "%s: xy_mode=%p xy_data=%p\n", __func__,
+ si->xy_mode, si->xy_data);
+}
+
+static int cyttsp4_get_sysinfo_regs(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+ int rc;
+
+ rc = cyttsp4_si_data_offsets(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_cydata(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_test_data(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_pcfg_data(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_opcfg_data(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_ddata(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_mdata(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_btn_data(cd);
+ if (rc < 0)
+ return rc;
+
+ rc = cyttsp4_si_get_op_data_ptrs(cd);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: failed to get_op_data\n",
+ __func__);
+ return rc;
+ }
+
+ cyttsp4_si_put_log_data(cd);
+
+ /* provide flow control handshake */
+ rc = cyttsp4_handshake(cd, si->si_data.hst_mode);
+ if (rc < 0)
+ dev_err(cd->dev, "%s: handshake fail on sysinfo reg\n",
+ __func__);
+
+ si->ready = true;
+ return rc;
+}
+
+static void cyttsp4_queue_startup_(struct cyttsp4 *cd)
+{
+ if (cd->startup_state == STARTUP_NONE) {
+ cd->startup_state = STARTUP_QUEUED;
+ schedule_work(&cd->startup_work);
+ dev_dbg(cd->dev, "%s: cyttsp4_startup queued\n", __func__);
+ } else {
+ dev_dbg(cd->dev, "%s: startup_state = %d\n", __func__,
+ cd->startup_state);
+ }
+}
+
+static void cyttsp4_report_slot_liftoff(struct cyttsp4_mt_data *md,
+ int max_slots)
+{
+ int t;
+
+ if (md->num_prv_tch == 0)
+ return;
+
+ for (t = 0; t < max_slots; t++) {
+ input_mt_slot(md->input, t);
+ input_mt_report_slot_inactive(md->input);
+ }
+}
+
+static void cyttsp4_lift_all(struct cyttsp4_mt_data *md)
+{
+ if (!md->si)
+ return;
+
+ if (md->num_prv_tch != 0) {
+ cyttsp4_report_slot_liftoff(md,
+ md->si->si_ofs.tch_abs[CY_TCH_T].max);
+ input_sync(md->input);
+ md->num_prv_tch = 0;
+ }
+}
+
+static void cyttsp4_get_touch_axis(struct cyttsp4_mt_data *md,
+ int *axis, int size, int max, u8 *xy_data, int bofs)
+{
+ int nbyte;
+ int next;
+
+ for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) {
+ dev_vdbg(&md->input->dev,
+ "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p"
+ " xy_data[%d]=%02X(%d) bofs=%d\n",
+ __func__, *axis, *axis, size, max, xy_data, next,
+ xy_data[next], xy_data[next], bofs);
+ *axis = (*axis * 256) + (xy_data[next] >> bofs);
+ next++;
+ }
+
+ *axis &= max - 1;
+
+ dev_vdbg(&md->input->dev,
+ "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p"
+ " xy_data[%d]=%02X(%d)\n",
+ __func__, *axis, *axis, size, max, xy_data, next,
+ xy_data[next], xy_data[next]);
+}
+
+static void cyttsp4_get_touch(struct cyttsp4_mt_data *md,
+ struct cyttsp4_touch *touch, u8 *xy_data)
+{
+ struct device *dev = &md->input->dev;
+ struct cyttsp4_sysinfo *si = md->si;
+ enum cyttsp4_tch_abs abs;
+ bool flipped;
+
+ for (abs = CY_TCH_X; abs < CY_TCH_NUM_ABS; abs++) {
+ cyttsp4_get_touch_axis(md, &touch->abs[abs],
+ si->si_ofs.tch_abs[abs].size,
+ si->si_ofs.tch_abs[abs].max,
+ xy_data + si->si_ofs.tch_abs[abs].ofs,
+ si->si_ofs.tch_abs[abs].bofs);
+ dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__,
+ cyttsp4_tch_abs_string[abs],
+ touch->abs[abs], touch->abs[abs]);
+ }
+
+ if (md->pdata->flags & CY_FLAG_FLIP) {
+ swap(touch->abs[CY_TCH_X], touch->abs[CY_TCH_Y]);
+ flipped = true;
+ } else
+ flipped = false;
+
+ if (md->pdata->flags & CY_FLAG_INV_X) {
+ if (flipped)
+ touch->abs[CY_TCH_X] = md->si->si_ofs.max_y -
+ touch->abs[CY_TCH_X];
+ else
+ touch->abs[CY_TCH_X] = md->si->si_ofs.max_x -
+ touch->abs[CY_TCH_X];
+ }
+ if (md->pdata->flags & CY_FLAG_INV_Y) {
+ if (flipped)
+ touch->abs[CY_TCH_Y] = md->si->si_ofs.max_x -
+ touch->abs[CY_TCH_Y];
+ else
+ touch->abs[CY_TCH_Y] = md->si->si_ofs.max_y -
+ touch->abs[CY_TCH_Y];
+ }
+
+ dev_vdbg(dev, "%s: flip=%s inv-x=%s inv-y=%s x=%04X(%d) y=%04X(%d)\n",
+ __func__, flipped ? "true" : "false",
+ md->pdata->flags & CY_FLAG_INV_X ? "true" : "false",
+ md->pdata->flags & CY_FLAG_INV_Y ? "true" : "false",
+ touch->abs[CY_TCH_X], touch->abs[CY_TCH_X],
+ touch->abs[CY_TCH_Y], touch->abs[CY_TCH_Y]);
+}
+
+static void cyttsp4_final_sync(struct input_dev *input, int max_slots, int *ids)
+{
+ int t;
+
+ for (t = 0; t < max_slots; t++) {
+ if (ids[t])
+ continue;
+ input_mt_slot(input, t);
+ input_mt_report_slot_inactive(input);
+ }
+
+ input_sync(input);
+}
+
+static void cyttsp4_get_mt_touches(struct cyttsp4_mt_data *md, int num_cur_tch)
+{
+ struct device *dev = &md->input->dev;
+ struct cyttsp4_sysinfo *si = md->si;
+ struct cyttsp4_touch tch;
+ int sig;
+ int i, j, t = 0;
+ int ids[max(CY_TMA1036_MAX_TCH, CY_TMA4XX_MAX_TCH)];
+
+ memset(ids, 0, si->si_ofs.tch_abs[CY_TCH_T].max * sizeof(int));
+ for (i = 0; i < num_cur_tch; i++) {
+ cyttsp4_get_touch(md, &tch, si->xy_data +
+ (i * si->si_ofs.tch_rec_size));
+ if ((tch.abs[CY_TCH_T] < md->pdata->frmwrk->abs
+ [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + CY_MIN_OST]) ||
+ (tch.abs[CY_TCH_T] > md->pdata->frmwrk->abs
+ [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + CY_MAX_OST])) {
+ dev_err(dev, "%s: tch=%d -> bad trk_id=%d max_id=%d\n",
+ __func__, i, tch.abs[CY_TCH_T],
+ md->pdata->frmwrk->abs[(CY_ABS_ID_OST *
+ CY_NUM_ABS_SET) + CY_MAX_OST]);
+ continue;
+ }
+
+ /* use 0 based track id's */
+ sig = md->pdata->frmwrk->abs
+ [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + 0];
+ if (sig != CY_IGNORE_VALUE) {
+ t = tch.abs[CY_TCH_T] - md->pdata->frmwrk->abs
+ [(CY_ABS_ID_OST * CY_NUM_ABS_SET) + CY_MIN_OST];
+ if (tch.abs[CY_TCH_E] == CY_EV_LIFTOFF) {
+ dev_dbg(dev, "%s: t=%d e=%d lift-off\n",
+ __func__, t, tch.abs[CY_TCH_E]);
+ goto cyttsp4_get_mt_touches_pr_tch;
+ }
+ input_mt_slot(md->input, t);
+ input_mt_report_slot_state(md->input, MT_TOOL_FINGER,
+ true);
+ ids[t] = true;
+ }
+
+ /* all devices: position and pressure fields */
+ for (j = 0; j <= CY_ABS_W_OST; j++) {
+ sig = md->pdata->frmwrk->abs[((CY_ABS_X_OST + j) *
+ CY_NUM_ABS_SET) + 0];
+ if (sig != CY_IGNORE_VALUE)
+ input_report_abs(md->input, sig,
+ tch.abs[CY_TCH_X + j]);
+ }
+ if (si->si_ofs.tch_rec_size > CY_TMA1036_TCH_REC_SIZE) {
+ /*
+ * TMA400 size and orientation fields:
+ * if pressure is non-zero and major touch
+ * signal is zero, then set major and minor touch
+ * signals to minimum non-zero value
+ */
+ if (tch.abs[CY_TCH_P] > 0 && tch.abs[CY_TCH_MAJ] == 0)
+ tch.abs[CY_TCH_MAJ] = tch.abs[CY_TCH_MIN] = 1;
+
+ /* Get the extended touch fields */
+ for (j = 0; j < CY_NUM_EXT_TCH_FIELDS; j++) {
+ sig = md->pdata->frmwrk->abs
+ [((CY_ABS_MAJ_OST + j) *
+ CY_NUM_ABS_SET) + 0];
+ if (sig != CY_IGNORE_VALUE)
+ input_report_abs(md->input, sig,
+ tch.abs[CY_TCH_MAJ + j]);
+ }
+ }
+
+cyttsp4_get_mt_touches_pr_tch:
+ if (si->si_ofs.tch_rec_size > CY_TMA1036_TCH_REC_SIZE)
+ dev_dbg(dev,
+ "%s: t=%d x=%d y=%d z=%d M=%d m=%d o=%d e=%d\n",
+ __func__, t,
+ tch.abs[CY_TCH_X],
+ tch.abs[CY_TCH_Y],
+ tch.abs[CY_TCH_P],
+ tch.abs[CY_TCH_MAJ],
+ tch.abs[CY_TCH_MIN],
+ tch.abs[CY_TCH_OR],
+ tch.abs[CY_TCH_E]);
+ else
+ dev_dbg(dev,
+ "%s: t=%d x=%d y=%d z=%d e=%d\n", __func__,
+ t,
+ tch.abs[CY_TCH_X],
+ tch.abs[CY_TCH_Y],
+ tch.abs[CY_TCH_P],
+ tch.abs[CY_TCH_E]);
+ }
+
+ cyttsp4_final_sync(md->input, si->si_ofs.tch_abs[CY_TCH_T].max, ids);
+
+ md->num_prv_tch = num_cur_tch;
+
+ return;
+}
+
+/* read xy_data for all current touches */
+static int cyttsp4_xy_worker(struct cyttsp4 *cd)
+{
+ struct cyttsp4_mt_data *md = &cd->md;
+ struct device *dev = &md->input->dev;
+ struct cyttsp4_sysinfo *si = md->si;
+ u8 num_cur_tch;
+ u8 hst_mode;
+ u8 rep_len;
+ u8 rep_stat;
+ u8 tt_stat;
+ int rc = 0;
+
+ /*
+ * Get event data from cyttsp4 device.
+ * The event data includes all data
+ * for all active touches.
+ * Event data also includes button data
+ */
+ /*
+ * Use 2 reads:
+ * 1st read to get mode + button bytes + touch count (core)
+ * 2nd read (optional) to get touch 1 - touch n data
+ */
+ hst_mode = si->xy_mode[CY_REG_BASE];
+ rep_len = si->xy_mode[si->si_ofs.rep_ofs];
+ rep_stat = si->xy_mode[si->si_ofs.rep_ofs + 1];
+ tt_stat = si->xy_mode[si->si_ofs.tt_stat_ofs];
+ dev_vdbg(dev, "%s: %s%02X %s%d %s%02X %s%02X\n", __func__,
+ "hst_mode=", hst_mode, "rep_len=", rep_len,
+ "rep_stat=", rep_stat, "tt_stat=", tt_stat);
+
+ num_cur_tch = GET_NUM_TOUCHES(tt_stat);
+ dev_vdbg(dev, "%s: num_cur_tch=%d\n", __func__, num_cur_tch);
+
+ if (rep_len == 0 && num_cur_tch > 0) {
+ dev_err(dev, "%s: report length error rep_len=%d num_tch=%d\n",
+ __func__, rep_len, num_cur_tch);
+ goto cyttsp4_xy_worker_exit;
+ }
+
+ /* read touches */
+ if (num_cur_tch > 0) {
+ rc = cyttsp4_adap_read(cd, si->si_ofs.tt_stat_ofs + 1,
+ num_cur_tch * si->si_ofs.tch_rec_size,
+ si->xy_data);
+ if (rc < 0) {
+ dev_err(dev, "%s: read fail on touch regs r=%d\n",
+ __func__, rc);
+ goto cyttsp4_xy_worker_exit;
+ }
+ }
+
+ /* print xy data */
+ cyttsp4_pr_buf(dev, cd->pr_buf, si->xy_data, num_cur_tch *
+ si->si_ofs.tch_rec_size, "xy_data");
+
+ /* check any error conditions */
+ if (IS_BAD_PKT(rep_stat)) {
+ dev_dbg(dev, "%s: Invalid buffer detected\n", __func__);
+ rc = 0;
+ goto cyttsp4_xy_worker_exit;
+ }
+
+ if (IS_LARGE_AREA(tt_stat))
+ dev_dbg(dev, "%s: Large area detected\n", __func__);
+
+ if (num_cur_tch > si->si_ofs.max_tchs) {
+ dev_err(dev, "%s: too many tch; set to max tch (n=%d c=%zd)\n",
+ __func__, num_cur_tch, si->si_ofs.max_tchs);
+ num_cur_tch = si->si_ofs.max_tchs;
+ }
+
+ /* extract xy_data for all currently reported touches */
+ dev_vdbg(dev, "%s: extract data num_cur_tch=%d\n", __func__,
+ num_cur_tch);
+ if (num_cur_tch)
+ cyttsp4_get_mt_touches(md, num_cur_tch);
+ else
+ cyttsp4_lift_all(md);
+
+ rc = 0;
+
+cyttsp4_xy_worker_exit:
+ return rc;
+}
+
+static int cyttsp4_mt_attention(struct cyttsp4 *cd)
+{
+ struct device *dev = cd->dev;
+ struct cyttsp4_mt_data *md = &cd->md;
+ int rc = 0;
+
+ if (!md->si)
+ return 0;
+
+ mutex_lock(&md->report_lock);
+ if (!md->is_suspended) {
+ /* core handles handshake */
+ rc = cyttsp4_xy_worker(cd);
+ } else {
+ dev_vdbg(dev, "%s: Ignoring report while suspended\n",
+ __func__);
+ }
+ mutex_unlock(&md->report_lock);
+ if (rc < 0)
+ dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc);
+
+ return rc;
+}
+
+static irqreturn_t cyttsp4_irq(int irq, void *handle)
+{
+ struct cyttsp4 *cd = handle;
+ struct device *dev = cd->dev;
+ enum cyttsp4_mode cur_mode;
+ u8 cmd_ofs = cd->sysinfo.si_ofs.cmd_ofs;
+ u8 mode[3];
+ int rc;
+
+ /*
+ * Check whether this IRQ should be ignored (external)
+ * This should be the very first thing to check since
+ * ignore_irq may be set for a very short period of time
+ */
+ if (atomic_read(&cd->ignore_irq)) {
+ dev_vdbg(dev, "%s: Ignoring IRQ\n", __func__);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(dev, "%s int:0x%x\n", __func__, cd->int_status);
+
+ mutex_lock(&cd->system_lock);
+
+ /* Just to debug */
+ if (cd->sleep_state == SS_SLEEP_ON || cd->sleep_state == SS_SLEEPING)
+ dev_vdbg(dev, "%s: Received IRQ while in sleep\n", __func__);
+
+ rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), mode);
+ if (rc) {
+ dev_err(cd->dev, "%s: Fail read adapter r=%d\n", __func__, rc);
+ goto cyttsp4_irq_exit;
+ }
+ dev_vdbg(dev, "%s mode[0-2]:0x%X 0x%X 0x%X\n", __func__,
+ mode[0], mode[1], mode[2]);
+
+ if (IS_BOOTLOADER(mode[0], mode[1])) {
+ cur_mode = CY_MODE_BOOTLOADER;
+ dev_vdbg(dev, "%s: bl running\n", __func__);
+ if (cd->mode == CY_MODE_BOOTLOADER) {
+ /* Signal bootloader heartbeat heard */
+ wake_up(&cd->wait_q);
+ goto cyttsp4_irq_exit;
+ }
+
+ /* switch to bootloader */
+ dev_dbg(dev, "%s: restart switch to bl m=%d -> m=%d\n",
+ __func__, cd->mode, cur_mode);
+
+ /* catch operation->bl glitch */
+ if (cd->mode != CY_MODE_UNKNOWN) {
+ /* Incase startup_state do not let startup_() */
+ cd->mode = CY_MODE_UNKNOWN;
+ cyttsp4_queue_startup_(cd);
+ goto cyttsp4_irq_exit;
+ }
+
+ /*
+ * do not wake thread on this switch since
+ * it is possible to get an early heartbeat
+ * prior to performing the reset
+ */
+ cd->mode = cur_mode;
+
+ goto cyttsp4_irq_exit;
+ }
+
+ switch (mode[0] & CY_HST_MODE) {
+ case CY_HST_OPERATE:
+ cur_mode = CY_MODE_OPERATIONAL;
+ dev_vdbg(dev, "%s: operational\n", __func__);
+ break;
+ case CY_HST_CAT:
+ cur_mode = CY_MODE_CAT;
+ dev_vdbg(dev, "%s: CaT\n", __func__);
+ break;
+ case CY_HST_SYSINFO:
+ cur_mode = CY_MODE_SYSINFO;
+ dev_vdbg(dev, "%s: sysinfo\n", __func__);
+ break;
+ default:
+ cur_mode = CY_MODE_UNKNOWN;
+ dev_err(dev, "%s: unknown HST mode 0x%02X\n", __func__,
+ mode[0]);
+ break;
+ }
+
+ /* Check whether this IRQ should be ignored (internal) */
+ if (cd->int_status & CY_INT_IGNORE) {
+ dev_vdbg(dev, "%s: Ignoring IRQ\n", __func__);
+ goto cyttsp4_irq_exit;
+ }
+
+ /* Check for wake up interrupt */
+ if (cd->int_status & CY_INT_AWAKE) {
+ cd->int_status &= ~CY_INT_AWAKE;
+ wake_up(&cd->wait_q);
+ dev_vdbg(dev, "%s: Received wake up interrupt\n", __func__);
+ goto cyttsp4_irq_handshake;
+ }
+
+ /* Expecting mode change interrupt */
+ if ((cd->int_status & CY_INT_MODE_CHANGE)
+ && (mode[0] & CY_HST_MODE_CHANGE) == 0) {
+ cd->int_status &= ~CY_INT_MODE_CHANGE;
+ dev_dbg(dev, "%s: finish mode switch m=%d -> m=%d\n",
+ __func__, cd->mode, cur_mode);
+ cd->mode = cur_mode;
+ wake_up(&cd->wait_q);
+ goto cyttsp4_irq_handshake;
+ }
+
+ /* compare current core mode to current device mode */
+ dev_vdbg(dev, "%s: cd->mode=%d cur_mode=%d\n",
+ __func__, cd->mode, cur_mode);
+ if ((mode[0] & CY_HST_MODE_CHANGE) == 0 && cd->mode != cur_mode) {
+ /* Unexpected mode change occurred */
+ dev_err(dev, "%s %d->%d 0x%x\n", __func__, cd->mode,
+ cur_mode, cd->int_status);
+ dev_dbg(dev, "%s: Unexpected mode change, startup\n",
+ __func__);
+ cyttsp4_queue_startup_(cd);
+ goto cyttsp4_irq_exit;
+ }
+
+ /* Expecting command complete interrupt */
+ dev_vdbg(dev, "%s: command byte:0x%x\n", __func__, mode[cmd_ofs]);
+ if ((cd->int_status & CY_INT_EXEC_CMD)
+ && mode[cmd_ofs] & CY_CMD_COMPLETE) {
+ cd->int_status &= ~CY_INT_EXEC_CMD;
+ dev_vdbg(dev, "%s: Received command complete interrupt\n",
+ __func__);
+ wake_up(&cd->wait_q);
+ /*
+ * It is possible to receive a single interrupt for
+ * command complete and touch/button status report.
+ * Continue processing for a possible status report.
+ */
+ }
+
+ /* This should be status report, read status regs */
+ if (cd->mode == CY_MODE_OPERATIONAL) {
+ dev_vdbg(dev, "%s: Read status registers\n", __func__);
+ rc = cyttsp4_load_status_regs(cd);
+ if (rc < 0)
+ dev_err(dev, "%s: fail read mode regs r=%d\n",
+ __func__, rc);
+ }
+
+ cyttsp4_mt_attention(cd);
+
+cyttsp4_irq_handshake:
+ /* handshake the event */
+ dev_vdbg(dev, "%s: Handshake mode=0x%02X r=%d\n",
+ __func__, mode[0], rc);
+ rc = cyttsp4_handshake(cd, mode[0]);
+ if (rc < 0)
+ dev_err(dev, "%s: Fail handshake mode=0x%02X r=%d\n",
+ __func__, mode[0], rc);
+
+ /*
+ * a non-zero udelay period is required for using
+ * IRQF_TRIGGER_LOW in order to delay until the
+ * device completes isr deassert
+ */
+ udelay(cd->cpdata->level_irq_udelay);
+
+cyttsp4_irq_exit:
+ mutex_unlock(&cd->system_lock);
+ return IRQ_HANDLED;
+}
+
+static void cyttsp4_start_wd_timer(struct cyttsp4 *cd)
+{
+ if (!CY_WATCHDOG_TIMEOUT)
+ return;
+
+ mod_timer(&cd->watchdog_timer, jiffies +
+ msecs_to_jiffies(CY_WATCHDOG_TIMEOUT));
+}
+
+static void cyttsp4_stop_wd_timer(struct cyttsp4 *cd)
+{
+ if (!CY_WATCHDOG_TIMEOUT)
+ return;
+
+ /*
+ * Ensure we wait until the watchdog timer
+ * running on a different CPU finishes
+ */
+ del_timer_sync(&cd->watchdog_timer);
+ cancel_work_sync(&cd->watchdog_work);
+ del_timer_sync(&cd->watchdog_timer);
+}
+
+static void cyttsp4_watchdog_timer(struct timer_list *t)
+{
+ struct cyttsp4 *cd = from_timer(cd, t, watchdog_timer);
+
+ dev_vdbg(cd->dev, "%s: Watchdog timer triggered\n", __func__);
+
+ schedule_work(&cd->watchdog_work);
+
+ return;
+}
+
+static int cyttsp4_request_exclusive(struct cyttsp4 *cd, void *ownptr,
+ int timeout_ms)
+{
+ int t = msecs_to_jiffies(timeout_ms);
+ bool with_timeout = (timeout_ms != 0);
+
+ mutex_lock(&cd->system_lock);
+ if (!cd->exclusive_dev && cd->exclusive_waits == 0) {
+ cd->exclusive_dev = ownptr;
+ goto exit;
+ }
+
+ cd->exclusive_waits++;
+wait:
+ mutex_unlock(&cd->system_lock);
+ if (with_timeout) {
+ t = wait_event_timeout(cd->wait_q, !cd->exclusive_dev, t);
+ if (IS_TMO(t)) {
+ dev_err(cd->dev, "%s: tmo waiting exclusive access\n",
+ __func__);
+ mutex_lock(&cd->system_lock);
+ cd->exclusive_waits--;
+ mutex_unlock(&cd->system_lock);
+ return -ETIME;
+ }
+ } else {
+ wait_event(cd->wait_q, !cd->exclusive_dev);
+ }
+ mutex_lock(&cd->system_lock);
+ if (cd->exclusive_dev)
+ goto wait;
+ cd->exclusive_dev = ownptr;
+ cd->exclusive_waits--;
+exit:
+ mutex_unlock(&cd->system_lock);
+
+ return 0;
+}
+
+/*
+ * returns error if was not owned
+ */
+static int cyttsp4_release_exclusive(struct cyttsp4 *cd, void *ownptr)
+{
+ mutex_lock(&cd->system_lock);
+ if (cd->exclusive_dev != ownptr) {
+ mutex_unlock(&cd->system_lock);
+ return -EINVAL;
+ }
+
+ dev_vdbg(cd->dev, "%s: exclusive_dev %p freed\n",
+ __func__, cd->exclusive_dev);
+ cd->exclusive_dev = NULL;
+ wake_up(&cd->wait_q);
+ mutex_unlock(&cd->system_lock);
+ return 0;
+}
+
+static int cyttsp4_wait_bl_heartbeat(struct cyttsp4 *cd)
+{
+ long t;
+ int rc = 0;
+
+ /* wait heartbeat */
+ dev_vdbg(cd->dev, "%s: wait heartbeat...\n", __func__);
+ t = wait_event_timeout(cd->wait_q, cd->mode == CY_MODE_BOOTLOADER,
+ msecs_to_jiffies(CY_CORE_RESET_AND_WAIT_TIMEOUT));
+ if (IS_TMO(t)) {
+ dev_err(cd->dev, "%s: tmo waiting bl heartbeat cd->mode=%d\n",
+ __func__, cd->mode);
+ rc = -ETIME;
+ }
+
+ return rc;
+}
+
+static int cyttsp4_wait_sysinfo_mode(struct cyttsp4 *cd)
+{
+ long t;
+
+ dev_vdbg(cd->dev, "%s: wait sysinfo...\n", __func__);
+
+ t = wait_event_timeout(cd->wait_q, cd->mode == CY_MODE_SYSINFO,
+ msecs_to_jiffies(CY_CORE_MODE_CHANGE_TIMEOUT));
+ if (IS_TMO(t)) {
+ dev_err(cd->dev, "%s: tmo waiting exit bl cd->mode=%d\n",
+ __func__, cd->mode);
+ mutex_lock(&cd->system_lock);
+ cd->int_status &= ~CY_INT_MODE_CHANGE;
+ mutex_unlock(&cd->system_lock);
+ return -ETIME;
+ }
+
+ return 0;
+}
+
+static int cyttsp4_reset_and_wait(struct cyttsp4 *cd)
+{
+ int rc;
+
+ /* reset hardware */
+ mutex_lock(&cd->system_lock);
+ dev_dbg(cd->dev, "%s: reset hw...\n", __func__);
+ rc = cyttsp4_hw_reset(cd);
+ cd->mode = CY_MODE_UNKNOWN;
+ mutex_unlock(&cd->system_lock);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s:Fail hw reset r=%d\n", __func__, rc);
+ return rc;
+ }
+
+ return cyttsp4_wait_bl_heartbeat(cd);
+}
+
+/*
+ * returns err if refused or timeout; block until mode change complete
+ * bit is set (mode change interrupt)
+ */
+static int cyttsp4_set_mode(struct cyttsp4 *cd, int new_mode)
+{
+ u8 new_dev_mode;
+ u8 mode;
+ long t;
+ int rc;
+
+ switch (new_mode) {
+ case CY_MODE_OPERATIONAL:
+ new_dev_mode = CY_HST_OPERATE;
+ break;
+ case CY_MODE_SYSINFO:
+ new_dev_mode = CY_HST_SYSINFO;
+ break;
+ case CY_MODE_CAT:
+ new_dev_mode = CY_HST_CAT;
+ break;
+ default:
+ dev_err(cd->dev, "%s: invalid mode: %02X(%d)\n",
+ __func__, new_mode, new_mode);
+ return -EINVAL;
+ }
+
+ /* change mode */
+ dev_dbg(cd->dev, "%s: %s=%p new_dev_mode=%02X new_mode=%d\n",
+ __func__, "have exclusive", cd->exclusive_dev,
+ new_dev_mode, new_mode);
+
+ mutex_lock(&cd->system_lock);
+ rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), &mode);
+ if (rc < 0) {
+ mutex_unlock(&cd->system_lock);
+ dev_err(cd->dev, "%s: Fail read mode r=%d\n",
+ __func__, rc);
+ goto exit;
+ }
+
+ /* Clear device mode bits and set to new mode */
+ mode &= ~CY_HST_MODE;
+ mode |= new_dev_mode | CY_HST_MODE_CHANGE;
+
+ cd->int_status |= CY_INT_MODE_CHANGE;
+ rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(mode), &mode);
+ mutex_unlock(&cd->system_lock);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: Fail write mode change r=%d\n",
+ __func__, rc);
+ goto exit;
+ }
+
+ /* wait for mode change done interrupt */
+ t = wait_event_timeout(cd->wait_q,
+ (cd->int_status & CY_INT_MODE_CHANGE) == 0,
+ msecs_to_jiffies(CY_CORE_MODE_CHANGE_TIMEOUT));
+ dev_dbg(cd->dev, "%s: back from wait t=%ld cd->mode=%d\n",
+ __func__, t, cd->mode);
+
+ if (IS_TMO(t)) {
+ dev_err(cd->dev, "%s: %s\n", __func__,
+ "tmo waiting mode change");
+ mutex_lock(&cd->system_lock);
+ cd->int_status &= ~CY_INT_MODE_CHANGE;
+ mutex_unlock(&cd->system_lock);
+ rc = -EINVAL;
+ }
+
+exit:
+ return rc;
+}
+
+static void cyttsp4_watchdog_work(struct work_struct *work)
+{
+ struct cyttsp4 *cd =
+ container_of(work, struct cyttsp4, watchdog_work);
+ u8 *mode;
+ int retval;
+
+ mutex_lock(&cd->system_lock);
+ retval = cyttsp4_load_status_regs(cd);
+ if (retval < 0) {
+ dev_err(cd->dev,
+ "%s: failed to access device in watchdog timer r=%d\n",
+ __func__, retval);
+ cyttsp4_queue_startup_(cd);
+ goto cyttsp4_timer_watchdog_exit_error;
+ }
+ mode = &cd->sysinfo.xy_mode[CY_REG_BASE];
+ if (IS_BOOTLOADER(mode[0], mode[1])) {
+ dev_err(cd->dev,
+ "%s: device found in bootloader mode when operational mode\n",
+ __func__);
+ cyttsp4_queue_startup_(cd);
+ goto cyttsp4_timer_watchdog_exit_error;
+ }
+
+ cyttsp4_start_wd_timer(cd);
+cyttsp4_timer_watchdog_exit_error:
+ mutex_unlock(&cd->system_lock);
+ return;
+}
+
+static int cyttsp4_core_sleep_(struct cyttsp4 *cd)
+{
+ enum cyttsp4_sleep_state ss = SS_SLEEP_ON;
+ enum cyttsp4_int_state int_status = CY_INT_IGNORE;
+ int rc = 0;
+ u8 mode[2];
+
+ /* Already in sleep mode? */
+ mutex_lock(&cd->system_lock);
+ if (cd->sleep_state == SS_SLEEP_ON) {
+ mutex_unlock(&cd->system_lock);
+ return 0;
+ }
+ cd->sleep_state = SS_SLEEPING;
+ mutex_unlock(&cd->system_lock);
+
+ cyttsp4_stop_wd_timer(cd);
+
+ /* Wait until currently running IRQ handler exits and disable IRQ */
+ disable_irq(cd->irq);
+
+ dev_vdbg(cd->dev, "%s: write DEEP SLEEP...\n", __func__);
+ mutex_lock(&cd->system_lock);
+ rc = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), &mode);
+ if (rc) {
+ mutex_unlock(&cd->system_lock);
+ dev_err(cd->dev, "%s: Fail read adapter r=%d\n", __func__, rc);
+ goto error;
+ }
+
+ if (IS_BOOTLOADER(mode[0], mode[1])) {
+ mutex_unlock(&cd->system_lock);
+ dev_err(cd->dev, "%s: Device in BOOTLOADER mode.\n", __func__);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ mode[0] |= CY_HST_SLEEP;
+ rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(mode[0]), &mode[0]);
+ mutex_unlock(&cd->system_lock);
+ if (rc) {
+ dev_err(cd->dev, "%s: Fail write adapter r=%d\n", __func__, rc);
+ goto error;
+ }
+ dev_vdbg(cd->dev, "%s: write DEEP SLEEP succeeded\n", __func__);
+
+ if (cd->cpdata->power) {
+ dev_dbg(cd->dev, "%s: Power down HW\n", __func__);
+ rc = cd->cpdata->power(cd->cpdata, 0, cd->dev, &cd->ignore_irq);
+ } else {
+ dev_dbg(cd->dev, "%s: No power function\n", __func__);
+ rc = 0;
+ }
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: HW Power down fails r=%d\n",
+ __func__, rc);
+ goto error;
+ }
+
+ /* Give time to FW to sleep */
+ msleep(50);
+
+ goto exit;
+
+error:
+ ss = SS_SLEEP_OFF;
+ int_status = CY_INT_NONE;
+ cyttsp4_start_wd_timer(cd);
+
+exit:
+ mutex_lock(&cd->system_lock);
+ cd->sleep_state = ss;
+ cd->int_status |= int_status;
+ mutex_unlock(&cd->system_lock);
+ enable_irq(cd->irq);
+ return rc;
+}
+
+static int cyttsp4_startup_(struct cyttsp4 *cd)
+{
+ int retry = CY_CORE_STARTUP_RETRY_COUNT;
+ int rc;
+
+ cyttsp4_stop_wd_timer(cd);
+
+reset:
+ if (retry != CY_CORE_STARTUP_RETRY_COUNT)
+ dev_dbg(cd->dev, "%s: Retry %d\n", __func__,
+ CY_CORE_STARTUP_RETRY_COUNT - retry);
+
+ /* reset hardware and wait for heartbeat */
+ rc = cyttsp4_reset_and_wait(cd);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: Error on h/w reset r=%d\n", __func__, rc);
+ if (retry--)
+ goto reset;
+ goto exit;
+ }
+
+ /* exit bl into sysinfo mode */
+ dev_vdbg(cd->dev, "%s: write exit ldr...\n", __func__);
+ mutex_lock(&cd->system_lock);
+ cd->int_status &= ~CY_INT_IGNORE;
+ cd->int_status |= CY_INT_MODE_CHANGE;
+
+ rc = cyttsp4_adap_write(cd, CY_REG_BASE, sizeof(ldr_exit),
+ (u8 *)ldr_exit);
+ mutex_unlock(&cd->system_lock);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: Fail write r=%d\n", __func__, rc);
+ if (retry--)
+ goto reset;
+ goto exit;
+ }
+
+ rc = cyttsp4_wait_sysinfo_mode(cd);
+ if (rc < 0) {
+ u8 buf[sizeof(ldr_err_app)];
+ int rc1;
+
+ /* Check for invalid/corrupted touch application */
+ rc1 = cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(ldr_err_app),
+ buf);
+ if (rc1) {
+ dev_err(cd->dev, "%s: Fail read r=%d\n", __func__, rc1);
+ } else if (!memcmp(buf, ldr_err_app, sizeof(ldr_err_app))) {
+ dev_err(cd->dev, "%s: Error launching touch application\n",
+ __func__);
+ mutex_lock(&cd->system_lock);
+ cd->invalid_touch_app = true;
+ mutex_unlock(&cd->system_lock);
+ goto exit_no_wd;
+ }
+
+ if (retry--)
+ goto reset;
+ goto exit;
+ }
+
+ mutex_lock(&cd->system_lock);
+ cd->invalid_touch_app = false;
+ mutex_unlock(&cd->system_lock);
+
+ /* read sysinfo data */
+ dev_vdbg(cd->dev, "%s: get sysinfo regs..\n", __func__);
+ rc = cyttsp4_get_sysinfo_regs(cd);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: failed to get sysinfo regs rc=%d\n",
+ __func__, rc);
+ if (retry--)
+ goto reset;
+ goto exit;
+ }
+
+ rc = cyttsp4_set_mode(cd, CY_MODE_OPERATIONAL);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: failed to set mode to operational rc=%d\n",
+ __func__, rc);
+ if (retry--)
+ goto reset;
+ goto exit;
+ }
+
+ cyttsp4_lift_all(&cd->md);
+
+ /* restore to sleep if was suspended */
+ mutex_lock(&cd->system_lock);
+ if (cd->sleep_state == SS_SLEEP_ON) {
+ cd->sleep_state = SS_SLEEP_OFF;
+ mutex_unlock(&cd->system_lock);
+ cyttsp4_core_sleep_(cd);
+ goto exit_no_wd;
+ }
+ mutex_unlock(&cd->system_lock);
+
+exit:
+ cyttsp4_start_wd_timer(cd);
+exit_no_wd:
+ return rc;
+}
+
+static int cyttsp4_startup(struct cyttsp4 *cd)
+{
+ int rc;
+
+ mutex_lock(&cd->system_lock);
+ cd->startup_state = STARTUP_RUNNING;
+ mutex_unlock(&cd->system_lock);
+
+ rc = cyttsp4_request_exclusive(cd, cd->dev,
+ CY_CORE_REQUEST_EXCLUSIVE_TIMEOUT);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n",
+ __func__, cd->exclusive_dev, cd->dev);
+ goto exit;
+ }
+
+ rc = cyttsp4_startup_(cd);
+
+ if (cyttsp4_release_exclusive(cd, cd->dev) < 0)
+ /* Don't return fail code, mode is already changed. */
+ dev_err(cd->dev, "%s: fail to release exclusive\n", __func__);
+ else
+ dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__);
+
+exit:
+ mutex_lock(&cd->system_lock);
+ cd->startup_state = STARTUP_NONE;
+ mutex_unlock(&cd->system_lock);
+
+ /* Wake the waiters for end of startup */
+ wake_up(&cd->wait_q);
+
+ return rc;
+}
+
+static void cyttsp4_startup_work_function(struct work_struct *work)
+{
+ struct cyttsp4 *cd = container_of(work, struct cyttsp4, startup_work);
+ int rc;
+
+ rc = cyttsp4_startup(cd);
+ if (rc < 0)
+ dev_err(cd->dev, "%s: Fail queued startup r=%d\n",
+ __func__, rc);
+}
+
+static void cyttsp4_free_si_ptrs(struct cyttsp4 *cd)
+{
+ struct cyttsp4_sysinfo *si = &cd->sysinfo;
+
+ if (!si)
+ return;
+
+ kfree(si->si_ptrs.cydata);
+ kfree(si->si_ptrs.test);
+ kfree(si->si_ptrs.pcfg);
+ kfree(si->si_ptrs.opcfg);
+ kfree(si->si_ptrs.ddata);
+ kfree(si->si_ptrs.mdata);
+ kfree(si->btn);
+ kfree(si->xy_mode);
+ kfree(si->xy_data);
+ kfree(si->btn_rec_data);
+}
+
+#ifdef CONFIG_PM
+static int cyttsp4_core_sleep(struct cyttsp4 *cd)
+{
+ int rc;
+
+ rc = cyttsp4_request_exclusive(cd, cd->dev,
+ CY_CORE_SLEEP_REQUEST_EXCLUSIVE_TIMEOUT);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n",
+ __func__, cd->exclusive_dev, cd->dev);
+ return 0;
+ }
+
+ rc = cyttsp4_core_sleep_(cd);
+
+ if (cyttsp4_release_exclusive(cd, cd->dev) < 0)
+ dev_err(cd->dev, "%s: fail to release exclusive\n", __func__);
+ else
+ dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__);
+
+ return rc;
+}
+
+static int cyttsp4_core_wake_(struct cyttsp4 *cd)
+{
+ struct device *dev = cd->dev;
+ int rc;
+ u8 mode;
+ int t;
+
+ /* Already woken? */
+ mutex_lock(&cd->system_lock);
+ if (cd->sleep_state == SS_SLEEP_OFF) {
+ mutex_unlock(&cd->system_lock);
+ return 0;
+ }
+ cd->int_status &= ~CY_INT_IGNORE;
+ cd->int_status |= CY_INT_AWAKE;
+ cd->sleep_state = SS_WAKING;
+
+ if (cd->cpdata->power) {
+ dev_dbg(dev, "%s: Power up HW\n", __func__);
+ rc = cd->cpdata->power(cd->cpdata, 1, dev, &cd->ignore_irq);
+ } else {
+ dev_dbg(dev, "%s: No power function\n", __func__);
+ rc = -ENOSYS;
+ }
+ if (rc < 0) {
+ dev_err(dev, "%s: HW Power up fails r=%d\n",
+ __func__, rc);
+
+ /* Initiate a read transaction to wake up */
+ cyttsp4_adap_read(cd, CY_REG_BASE, sizeof(mode), &mode);
+ } else
+ dev_vdbg(cd->dev, "%s: HW power up succeeds\n",
+ __func__);
+ mutex_unlock(&cd->system_lock);
+
+ t = wait_event_timeout(cd->wait_q,
+ (cd->int_status & CY_INT_AWAKE) == 0,
+ msecs_to_jiffies(CY_CORE_WAKEUP_TIMEOUT));
+ if (IS_TMO(t)) {
+ dev_err(dev, "%s: TMO waiting for wakeup\n", __func__);
+ mutex_lock(&cd->system_lock);
+ cd->int_status &= ~CY_INT_AWAKE;
+ /* Try starting up */
+ cyttsp4_queue_startup_(cd);
+ mutex_unlock(&cd->system_lock);
+ }
+
+ mutex_lock(&cd->system_lock);
+ cd->sleep_state = SS_SLEEP_OFF;
+ mutex_unlock(&cd->system_lock);
+
+ cyttsp4_start_wd_timer(cd);
+
+ return 0;
+}
+
+static int cyttsp4_core_wake(struct cyttsp4 *cd)
+{
+ int rc;
+
+ rc = cyttsp4_request_exclusive(cd, cd->dev,
+ CY_CORE_REQUEST_EXCLUSIVE_TIMEOUT);
+ if (rc < 0) {
+ dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n",
+ __func__, cd->exclusive_dev, cd->dev);
+ return 0;
+ }
+
+ rc = cyttsp4_core_wake_(cd);
+
+ if (cyttsp4_release_exclusive(cd, cd->dev) < 0)
+ dev_err(cd->dev, "%s: fail to release exclusive\n", __func__);
+ else
+ dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__);
+
+ return rc;
+}
+
+static int cyttsp4_core_suspend(struct device *dev)
+{
+ struct cyttsp4 *cd = dev_get_drvdata(dev);
+ struct cyttsp4_mt_data *md = &cd->md;
+ int rc;
+
+ md->is_suspended = true;
+
+ rc = cyttsp4_core_sleep(cd);
+ if (rc < 0) {
+ dev_err(dev, "%s: Error on sleep\n", __func__);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int cyttsp4_core_resume(struct device *dev)
+{
+ struct cyttsp4 *cd = dev_get_drvdata(dev);
+ struct cyttsp4_mt_data *md = &cd->md;
+ int rc;
+
+ md->is_suspended = false;
+
+ rc = cyttsp4_core_wake(cd);
+ if (rc < 0) {
+ dev_err(dev, "%s: Error on wake\n", __func__);
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+#endif
+
+const struct dev_pm_ops cyttsp4_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(cyttsp4_core_suspend, cyttsp4_core_resume)
+ SET_RUNTIME_PM_OPS(cyttsp4_core_suspend, cyttsp4_core_resume, NULL)
+};
+EXPORT_SYMBOL_GPL(cyttsp4_pm_ops);
+
+static int cyttsp4_mt_open(struct input_dev *input)
+{
+ pm_runtime_get(input->dev.parent);
+ return 0;
+}
+
+static void cyttsp4_mt_close(struct input_dev *input)
+{
+ struct cyttsp4_mt_data *md = input_get_drvdata(input);
+ mutex_lock(&md->report_lock);
+ if (!md->is_suspended)
+ pm_runtime_put(input->dev.parent);
+ mutex_unlock(&md->report_lock);
+}
+
+
+static int cyttsp4_setup_input_device(struct cyttsp4 *cd)
+{
+ struct device *dev = cd->dev;
+ struct cyttsp4_mt_data *md = &cd->md;
+ int signal = CY_IGNORE_VALUE;
+ int max_x, max_y, max_p, min, max;
+ int max_x_tmp, max_y_tmp;
+ int i;
+ int rc;
+
+ dev_vdbg(dev, "%s: Initialize event signals\n", __func__);
+ __set_bit(EV_ABS, md->input->evbit);
+ __set_bit(EV_REL, md->input->evbit);
+ __set_bit(EV_KEY, md->input->evbit);
+
+ max_x_tmp = md->si->si_ofs.max_x;
+ max_y_tmp = md->si->si_ofs.max_y;
+
+ /* get maximum values from the sysinfo data */
+ if (md->pdata->flags & CY_FLAG_FLIP) {
+ max_x = max_y_tmp - 1;
+ max_y = max_x_tmp - 1;
+ } else {
+ max_x = max_x_tmp - 1;
+ max_y = max_y_tmp - 1;
+ }
+ max_p = md->si->si_ofs.max_p;
+
+ /* set event signal capabilities */
+ for (i = 0; i < (md->pdata->frmwrk->size / CY_NUM_ABS_SET); i++) {
+ signal = md->pdata->frmwrk->abs
+ [(i * CY_NUM_ABS_SET) + CY_SIGNAL_OST];
+ if (signal != CY_IGNORE_VALUE) {
+ __set_bit(signal, md->input->absbit);
+ min = md->pdata->frmwrk->abs
+ [(i * CY_NUM_ABS_SET) + CY_MIN_OST];
+ max = md->pdata->frmwrk->abs
+ [(i * CY_NUM_ABS_SET) + CY_MAX_OST];
+ if (i == CY_ABS_ID_OST) {
+ /* shift track ids down to start at 0 */
+ max = max - min;
+ min = min - min;
+ } else if (i == CY_ABS_X_OST)
+ max = max_x;
+ else if (i == CY_ABS_Y_OST)
+ max = max_y;
+ else if (i == CY_ABS_P_OST)
+ max = max_p;
+ input_set_abs_params(md->input, signal, min, max,
+ md->pdata->frmwrk->abs
+ [(i * CY_NUM_ABS_SET) + CY_FUZZ_OST],
+ md->pdata->frmwrk->abs
+ [(i * CY_NUM_ABS_SET) + CY_FLAT_OST]);
+ dev_dbg(dev, "%s: register signal=%02X min=%d max=%d\n",
+ __func__, signal, min, max);
+ if ((i == CY_ABS_ID_OST) &&
+ (md->si->si_ofs.tch_rec_size <
+ CY_TMA4XX_TCH_REC_SIZE))
+ break;
+ }
+ }
+
+ input_mt_init_slots(md->input, md->si->si_ofs.tch_abs[CY_TCH_T].max,
+ INPUT_MT_DIRECT);
+ rc = input_register_device(md->input);
+ if (rc < 0)
+ dev_err(dev, "%s: Error, failed register input device r=%d\n",
+ __func__, rc);
+ return rc;
+}
+
+static int cyttsp4_mt_probe(struct cyttsp4 *cd)
+{
+ struct device *dev = cd->dev;
+ struct cyttsp4_mt_data *md = &cd->md;
+ struct cyttsp4_mt_platform_data *pdata = cd->pdata->mt_pdata;
+ int rc = 0;
+
+ mutex_init(&md->report_lock);
+ md->pdata = pdata;
+ /* Create the input device and register it. */
+ dev_vdbg(dev, "%s: Create the input device and register it\n",
+ __func__);
+ md->input = input_allocate_device();
+ if (md->input == NULL) {
+ dev_err(dev, "%s: Error, failed to allocate input device\n",
+ __func__);
+ rc = -ENOSYS;
+ goto error_alloc_failed;
+ }
+
+ md->input->name = pdata->inp_dev_name;
+ scnprintf(md->phys, sizeof(md->phys)-1, "%s", dev_name(dev));
+ md->input->phys = md->phys;
+ md->input->id.bustype = cd->bus_ops->bustype;
+ md->input->dev.parent = dev;
+ md->input->open = cyttsp4_mt_open;
+ md->input->close = cyttsp4_mt_close;
+ input_set_drvdata(md->input, md);
+
+ /* get sysinfo */
+ md->si = &cd->sysinfo;
+
+ rc = cyttsp4_setup_input_device(cd);
+ if (rc)
+ goto error_init_input;
+
+ return 0;
+
+error_init_input:
+ input_free_device(md->input);
+error_alloc_failed:
+ dev_err(dev, "%s failed.\n", __func__);
+ return rc;
+}
+
+struct cyttsp4 *cyttsp4_probe(const struct cyttsp4_bus_ops *ops,
+ struct device *dev, u16 irq, size_t xfer_buf_size)
+{
+ struct cyttsp4 *cd;
+ struct cyttsp4_platform_data *pdata = dev_get_platdata(dev);
+ unsigned long irq_flags;
+ int rc = 0;
+
+ if (!pdata || !pdata->core_pdata || !pdata->mt_pdata) {
+ dev_err(dev, "%s: Missing platform data\n", __func__);
+ rc = -ENODEV;
+ goto error_no_pdata;
+ }
+
+ cd = kzalloc(sizeof(*cd), GFP_KERNEL);
+ if (!cd) {
+ dev_err(dev, "%s: Error, kzalloc\n", __func__);
+ rc = -ENOMEM;
+ goto error_alloc_data;
+ }
+
+ cd->xfer_buf = kzalloc(xfer_buf_size, GFP_KERNEL);
+ if (!cd->xfer_buf) {
+ dev_err(dev, "%s: Error, kzalloc\n", __func__);
+ rc = -ENOMEM;
+ goto error_free_cd;
+ }
+
+ /* Initialize device info */
+ cd->dev = dev;
+ cd->pdata = pdata;
+ cd->cpdata = pdata->core_pdata;
+ cd->bus_ops = ops;
+
+ /* Initialize mutexes and spinlocks */
+ mutex_init(&cd->system_lock);
+ mutex_init(&cd->adap_lock);
+
+ /* Initialize wait queue */
+ init_waitqueue_head(&cd->wait_q);
+
+ /* Initialize works */
+ INIT_WORK(&cd->startup_work, cyttsp4_startup_work_function);
+ INIT_WORK(&cd->watchdog_work, cyttsp4_watchdog_work);
+
+ /* Initialize IRQ */
+ cd->irq = gpio_to_irq(cd->cpdata->irq_gpio);
+ if (cd->irq < 0) {
+ rc = -EINVAL;
+ goto error_free_xfer;
+ }
+
+ dev_set_drvdata(dev, cd);
+
+ /* Call platform init function */
+ if (cd->cpdata->init) {
+ dev_dbg(cd->dev, "%s: Init HW\n", __func__);
+ rc = cd->cpdata->init(cd->cpdata, 1, cd->dev);
+ } else {
+ dev_dbg(cd->dev, "%s: No HW INIT function\n", __func__);
+ rc = 0;
+ }
+ if (rc < 0)
+ dev_err(cd->dev, "%s: HW Init fail r=%d\n", __func__, rc);
+
+ dev_dbg(dev, "%s: initialize threaded irq=%d\n", __func__, cd->irq);
+ if (cd->cpdata->level_irq_udelay > 0)
+ /* use level triggered interrupts */
+ irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
+ else
+ /* use edge triggered interrupts */
+ irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+
+ rc = request_threaded_irq(cd->irq, NULL, cyttsp4_irq, irq_flags,
+ dev_name(dev), cd);
+ if (rc < 0) {
+ dev_err(dev, "%s: Error, could not request irq\n", __func__);
+ goto error_request_irq;
+ }
+
+ /* Setup watchdog timer */
+ timer_setup(&cd->watchdog_timer, cyttsp4_watchdog_timer, 0);
+
+ /*
+ * call startup directly to ensure that the device
+ * is tested before leaving the probe
+ */
+ rc = cyttsp4_startup(cd);
+
+ /* Do not fail probe if startup fails but the device is detected */
+ if (rc < 0 && cd->mode == CY_MODE_UNKNOWN) {
+ dev_err(cd->dev, "%s: Fail initial startup r=%d\n",
+ __func__, rc);
+ goto error_startup;
+ }
+
+ rc = cyttsp4_mt_probe(cd);
+ if (rc < 0) {
+ dev_err(dev, "%s: Error, fail mt probe\n", __func__);
+ goto error_startup;
+ }
+
+ pm_runtime_enable(dev);
+
+ return cd;
+
+error_startup:
+ cancel_work_sync(&cd->startup_work);
+ cyttsp4_stop_wd_timer(cd);
+ pm_runtime_disable(dev);
+ cyttsp4_free_si_ptrs(cd);
+ free_irq(cd->irq, cd);
+error_request_irq:
+ if (cd->cpdata->init)
+ cd->cpdata->init(cd->cpdata, 0, dev);
+error_free_xfer:
+ kfree(cd->xfer_buf);
+error_free_cd:
+ kfree(cd);
+error_alloc_data:
+error_no_pdata:
+ dev_err(dev, "%s failed.\n", __func__);
+ return ERR_PTR(rc);
+}
+EXPORT_SYMBOL_GPL(cyttsp4_probe);
+
+static void cyttsp4_mt_release(struct cyttsp4_mt_data *md)
+{
+ input_unregister_device(md->input);
+ input_set_drvdata(md->input, NULL);
+}
+
+int cyttsp4_remove(struct cyttsp4 *cd)
+{
+ struct device *dev = cd->dev;
+
+ cyttsp4_mt_release(&cd->md);
+
+ /*
+ * Suspend the device before freeing the startup_work and stopping
+ * the watchdog since sleep function restarts watchdog on failure
+ */
+ pm_runtime_suspend(dev);
+ pm_runtime_disable(dev);
+
+ cancel_work_sync(&cd->startup_work);
+
+ cyttsp4_stop_wd_timer(cd);
+
+ free_irq(cd->irq, cd);
+ if (cd->cpdata->init)
+ cd->cpdata->init(cd->cpdata, 0, dev);
+ cyttsp4_free_si_ptrs(cd);
+ kfree(cd);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cyttsp4_remove);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard touchscreen core driver");
+MODULE_AUTHOR("Cypress");
diff --git a/drivers/input/touchscreen/cyttsp4_core.h b/drivers/input/touchscreen/cyttsp4_core.h
new file mode 100644
index 000000000..6262f6e45
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp4_core.h
@@ -0,0 +1,448 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * cyttsp4_core.h
+ * Cypress TrueTouch(TM) Standard Product V4 Core driver module.
+ * For use with Cypress Txx4xx parts.
+ * Supported parts include:
+ * TMA4XX
+ * TMA1036
+ *
+ * Copyright (C) 2012 Cypress Semiconductor
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#ifndef _LINUX_CYTTSP4_CORE_H
+#define _LINUX_CYTTSP4_CORE_H
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/platform_data/cyttsp4.h>
+
+#define CY_REG_BASE 0x00
+
+#define CY_POST_CODEL_WDG_RST 0x01
+#define CY_POST_CODEL_CFG_DATA_CRC_FAIL 0x02
+#define CY_POST_CODEL_PANEL_TEST_FAIL 0x04
+
+#define CY_NUM_BTN_PER_REG 4
+
+/* touch record system information offset masks and shifts */
+#define CY_BYTE_OFS_MASK 0x1F
+#define CY_BOFS_MASK 0xE0
+#define CY_BOFS_SHIFT 5
+
+#define CY_TMA1036_TCH_REC_SIZE 6
+#define CY_TMA4XX_TCH_REC_SIZE 9
+#define CY_TMA1036_MAX_TCH 0x0E
+#define CY_TMA4XX_MAX_TCH 0x1E
+
+#define CY_NORMAL_ORIGIN 0 /* upper, left corner */
+#define CY_INVERT_ORIGIN 1 /* lower, right corner */
+
+/* helpers */
+#define GET_NUM_TOUCHES(x) ((x) & 0x1F)
+#define IS_LARGE_AREA(x) ((x) & 0x20)
+#define IS_BAD_PKT(x) ((x) & 0x20)
+#define IS_BOOTLOADER(hst_mode, reset_detect) \
+ ((hst_mode) & 0x01 || (reset_detect) != 0)
+#define IS_TMO(t) ((t) == 0)
+
+
+enum cyttsp_cmd_bits {
+ CY_CMD_COMPLETE = (1 << 6),
+};
+
+/* Timeout in ms. */
+#define CY_WATCHDOG_TIMEOUT 1000
+
+#define CY_MAX_PRINT_SIZE 512
+#ifdef VERBOSE_DEBUG
+#define CY_MAX_PRBUF_SIZE PIPE_BUF
+#define CY_PR_TRUNCATED " truncated..."
+#endif
+
+enum cyttsp4_ic_grpnum {
+ CY_IC_GRPNUM_RESERVED,
+ CY_IC_GRPNUM_CMD_REGS,
+ CY_IC_GRPNUM_TCH_REP,
+ CY_IC_GRPNUM_DATA_REC,
+ CY_IC_GRPNUM_TEST_REC,
+ CY_IC_GRPNUM_PCFG_REC,
+ CY_IC_GRPNUM_TCH_PARM_VAL,
+ CY_IC_GRPNUM_TCH_PARM_SIZE,
+ CY_IC_GRPNUM_RESERVED1,
+ CY_IC_GRPNUM_RESERVED2,
+ CY_IC_GRPNUM_OPCFG_REC,
+ CY_IC_GRPNUM_DDATA_REC,
+ CY_IC_GRPNUM_MDATA_REC,
+ CY_IC_GRPNUM_TEST_REGS,
+ CY_IC_GRPNUM_BTN_KEYS,
+ CY_IC_GRPNUM_TTHE_REGS,
+ CY_IC_GRPNUM_NUM
+};
+
+enum cyttsp4_int_state {
+ CY_INT_NONE,
+ CY_INT_IGNORE = (1 << 0),
+ CY_INT_MODE_CHANGE = (1 << 1),
+ CY_INT_EXEC_CMD = (1 << 2),
+ CY_INT_AWAKE = (1 << 3),
+};
+
+enum cyttsp4_mode {
+ CY_MODE_UNKNOWN,
+ CY_MODE_BOOTLOADER = (1 << 1),
+ CY_MODE_OPERATIONAL = (1 << 2),
+ CY_MODE_SYSINFO = (1 << 3),
+ CY_MODE_CAT = (1 << 4),
+ CY_MODE_STARTUP = (1 << 5),
+ CY_MODE_LOADER = (1 << 6),
+ CY_MODE_CHANGE_MODE = (1 << 7),
+ CY_MODE_CHANGED = (1 << 8),
+ CY_MODE_CMD_COMPLETE = (1 << 9),
+};
+
+enum cyttsp4_sleep_state {
+ SS_SLEEP_OFF,
+ SS_SLEEP_ON,
+ SS_SLEEPING,
+ SS_WAKING,
+};
+
+enum cyttsp4_startup_state {
+ STARTUP_NONE,
+ STARTUP_QUEUED,
+ STARTUP_RUNNING,
+};
+
+#define CY_NUM_REVCTRL 8
+struct cyttsp4_cydata {
+ u8 ttpidh;
+ u8 ttpidl;
+ u8 fw_ver_major;
+ u8 fw_ver_minor;
+ u8 revctrl[CY_NUM_REVCTRL];
+ u8 blver_major;
+ u8 blver_minor;
+ u8 jtag_si_id3;
+ u8 jtag_si_id2;
+ u8 jtag_si_id1;
+ u8 jtag_si_id0;
+ u8 mfgid_sz;
+ u8 cyito_idh;
+ u8 cyito_idl;
+ u8 cyito_verh;
+ u8 cyito_verl;
+ u8 ttsp_ver_major;
+ u8 ttsp_ver_minor;
+ u8 device_info;
+ u8 mfg_id[];
+} __packed;
+
+struct cyttsp4_test {
+ u8 post_codeh;
+ u8 post_codel;
+} __packed;
+
+struct cyttsp4_pcfg {
+ u8 electrodes_x;
+ u8 electrodes_y;
+ u8 len_xh;
+ u8 len_xl;
+ u8 len_yh;
+ u8 len_yl;
+ u8 res_xh;
+ u8 res_xl;
+ u8 res_yh;
+ u8 res_yl;
+ u8 max_zh;
+ u8 max_zl;
+ u8 panel_info0;
+} __packed;
+
+struct cyttsp4_tch_rec_params {
+ u8 loc;
+ u8 size;
+} __packed;
+
+#define CY_NUM_TCH_FIELDS 7
+#define CY_NUM_EXT_TCH_FIELDS 3
+struct cyttsp4_opcfg {
+ u8 cmd_ofs;
+ u8 rep_ofs;
+ u8 rep_szh;
+ u8 rep_szl;
+ u8 num_btns;
+ u8 tt_stat_ofs;
+ u8 obj_cfg0;
+ u8 max_tchs;
+ u8 tch_rec_size;
+ struct cyttsp4_tch_rec_params tch_rec_old[CY_NUM_TCH_FIELDS];
+ u8 btn_rec_size; /* btn record size (in bytes) */
+ u8 btn_diff_ofs; /* btn data loc, diff counts */
+ u8 btn_diff_size; /* btn size of diff counts (in bits) */
+ struct cyttsp4_tch_rec_params tch_rec_new[CY_NUM_EXT_TCH_FIELDS];
+} __packed;
+
+struct cyttsp4_sysinfo_ptr {
+ struct cyttsp4_cydata *cydata;
+ struct cyttsp4_test *test;
+ struct cyttsp4_pcfg *pcfg;
+ struct cyttsp4_opcfg *opcfg;
+ struct cyttsp4_ddata *ddata;
+ struct cyttsp4_mdata *mdata;
+} __packed;
+
+struct cyttsp4_sysinfo_data {
+ u8 hst_mode;
+ u8 reserved;
+ u8 map_szh;
+ u8 map_szl;
+ u8 cydata_ofsh;
+ u8 cydata_ofsl;
+ u8 test_ofsh;
+ u8 test_ofsl;
+ u8 pcfg_ofsh;
+ u8 pcfg_ofsl;
+ u8 opcfg_ofsh;
+ u8 opcfg_ofsl;
+ u8 ddata_ofsh;
+ u8 ddata_ofsl;
+ u8 mdata_ofsh;
+ u8 mdata_ofsl;
+} __packed;
+
+enum cyttsp4_tch_abs { /* for ordering within the extracted touch data array */
+ CY_TCH_X, /* X */
+ CY_TCH_Y, /* Y */
+ CY_TCH_P, /* P (Z) */
+ CY_TCH_T, /* TOUCH ID */
+ CY_TCH_E, /* EVENT ID */
+ CY_TCH_O, /* OBJECT ID */
+ CY_TCH_W, /* SIZE */
+ CY_TCH_MAJ, /* TOUCH_MAJOR */
+ CY_TCH_MIN, /* TOUCH_MINOR */
+ CY_TCH_OR, /* ORIENTATION */
+ CY_TCH_NUM_ABS
+};
+
+struct cyttsp4_touch {
+ int abs[CY_TCH_NUM_ABS];
+};
+
+struct cyttsp4_tch_abs_params {
+ size_t ofs; /* abs byte offset */
+ size_t size; /* size in bits */
+ size_t max; /* max value */
+ size_t bofs; /* bit offset */
+};
+
+struct cyttsp4_sysinfo_ofs {
+ size_t chip_type;
+ size_t cmd_ofs;
+ size_t rep_ofs;
+ size_t rep_sz;
+ size_t num_btns;
+ size_t num_btn_regs; /* ceil(num_btns/4) */
+ size_t tt_stat_ofs;
+ size_t tch_rec_size;
+ size_t obj_cfg0;
+ size_t max_tchs;
+ size_t mode_size;
+ size_t data_size;
+ size_t map_sz;
+ size_t max_x;
+ size_t x_origin; /* left or right corner */
+ size_t max_y;
+ size_t y_origin; /* upper or lower corner */
+ size_t max_p;
+ size_t cydata_ofs;
+ size_t test_ofs;
+ size_t pcfg_ofs;
+ size_t opcfg_ofs;
+ size_t ddata_ofs;
+ size_t mdata_ofs;
+ size_t cydata_size;
+ size_t test_size;
+ size_t pcfg_size;
+ size_t opcfg_size;
+ size_t ddata_size;
+ size_t mdata_size;
+ size_t btn_keys_size;
+ struct cyttsp4_tch_abs_params tch_abs[CY_TCH_NUM_ABS];
+ size_t btn_rec_size; /* btn record size (in bytes) */
+ size_t btn_diff_ofs;/* btn data loc ,diff counts, (Op-Mode byte ofs) */
+ size_t btn_diff_size;/* btn size of diff counts (in bits) */
+};
+
+enum cyttsp4_btn_state {
+ CY_BTN_RELEASED,
+ CY_BTN_PRESSED,
+ CY_BTN_NUM_STATE
+};
+
+struct cyttsp4_btn {
+ bool enabled;
+ int state; /* CY_BTN_PRESSED, CY_BTN_RELEASED */
+ int key_code;
+};
+
+struct cyttsp4_sysinfo {
+ bool ready;
+ struct cyttsp4_sysinfo_data si_data;
+ struct cyttsp4_sysinfo_ptr si_ptrs;
+ struct cyttsp4_sysinfo_ofs si_ofs;
+ struct cyttsp4_btn *btn; /* button states */
+ u8 *btn_rec_data; /* button diff count data */
+ u8 *xy_mode; /* operational mode and status regs */
+ u8 *xy_data; /* operational touch regs */
+};
+
+struct cyttsp4_mt_data {
+ struct cyttsp4_mt_platform_data *pdata;
+ struct cyttsp4_sysinfo *si;
+ struct input_dev *input;
+ struct mutex report_lock;
+ bool is_suspended;
+ char phys[NAME_MAX];
+ int num_prv_tch;
+};
+
+struct cyttsp4 {
+ struct device *dev;
+ struct mutex system_lock;
+ struct mutex adap_lock;
+ enum cyttsp4_mode mode;
+ enum cyttsp4_sleep_state sleep_state;
+ enum cyttsp4_startup_state startup_state;
+ int int_status;
+ wait_queue_head_t wait_q;
+ int irq;
+ struct work_struct startup_work;
+ struct work_struct watchdog_work;
+ struct timer_list watchdog_timer;
+ struct cyttsp4_sysinfo sysinfo;
+ void *exclusive_dev;
+ int exclusive_waits;
+ atomic_t ignore_irq;
+ bool invalid_touch_app;
+ struct cyttsp4_mt_data md;
+ struct cyttsp4_platform_data *pdata;
+ struct cyttsp4_core_platform_data *cpdata;
+ const struct cyttsp4_bus_ops *bus_ops;
+ u8 *xfer_buf;
+#ifdef VERBOSE_DEBUG
+ u8 pr_buf[CY_MAX_PRBUF_SIZE];
+#endif
+};
+
+struct cyttsp4_bus_ops {
+ u16 bustype;
+ int (*write)(struct device *dev, u8 *xfer_buf, u16 addr, u8 length,
+ const void *values);
+ int (*read)(struct device *dev, u8 *xfer_buf, u16 addr, u8 length,
+ void *values);
+};
+
+enum cyttsp4_hst_mode_bits {
+ CY_HST_TOGGLE = (1 << 7),
+ CY_HST_MODE_CHANGE = (1 << 3),
+ CY_HST_MODE = (7 << 4),
+ CY_HST_OPERATE = (0 << 4),
+ CY_HST_SYSINFO = (1 << 4),
+ CY_HST_CAT = (2 << 4),
+ CY_HST_LOWPOW = (1 << 2),
+ CY_HST_SLEEP = (1 << 1),
+ CY_HST_RESET = (1 << 0),
+};
+
+/* abs settings */
+#define CY_IGNORE_VALUE 0xFFFF
+
+/* abs signal capabilities offsets in the frameworks array */
+enum cyttsp4_sig_caps {
+ CY_SIGNAL_OST,
+ CY_MIN_OST,
+ CY_MAX_OST,
+ CY_FUZZ_OST,
+ CY_FLAT_OST,
+ CY_NUM_ABS_SET /* number of signal capability fields */
+};
+
+/* abs axis signal offsets in the framworks array */
+enum cyttsp4_sig_ost {
+ CY_ABS_X_OST,
+ CY_ABS_Y_OST,
+ CY_ABS_P_OST,
+ CY_ABS_W_OST,
+ CY_ABS_ID_OST,
+ CY_ABS_MAJ_OST,
+ CY_ABS_MIN_OST,
+ CY_ABS_OR_OST,
+ CY_NUM_ABS_OST /* number of abs signals */
+};
+
+enum cyttsp4_flags {
+ CY_FLAG_NONE = 0x00,
+ CY_FLAG_HOVER = 0x04,
+ CY_FLAG_FLIP = 0x08,
+ CY_FLAG_INV_X = 0x10,
+ CY_FLAG_INV_Y = 0x20,
+ CY_FLAG_VKEYS = 0x40,
+};
+
+enum cyttsp4_object_id {
+ CY_OBJ_STANDARD_FINGER,
+ CY_OBJ_LARGE_OBJECT,
+ CY_OBJ_STYLUS,
+ CY_OBJ_HOVER,
+};
+
+enum cyttsp4_event_id {
+ CY_EV_NO_EVENT,
+ CY_EV_TOUCHDOWN,
+ CY_EV_MOVE, /* significant displacement (> act dist) */
+ CY_EV_LIFTOFF, /* record reports last position */
+};
+
+/* x-axis resolution of panel in pixels */
+#define CY_PCFG_RESOLUTION_X_MASK 0x7F
+
+/* y-axis resolution of panel in pixels */
+#define CY_PCFG_RESOLUTION_Y_MASK 0x7F
+
+/* x-axis, 0:origin is on left side of panel, 1: right */
+#define CY_PCFG_ORIGIN_X_MASK 0x80
+
+/* y-axis, 0:origin is on top side of panel, 1: bottom */
+#define CY_PCFG_ORIGIN_Y_MASK 0x80
+
+static inline int cyttsp4_adap_read(struct cyttsp4 *ts, u16 addr, int size,
+ void *buf)
+{
+ return ts->bus_ops->read(ts->dev, ts->xfer_buf, addr, size, buf);
+}
+
+static inline int cyttsp4_adap_write(struct cyttsp4 *ts, u16 addr, int size,
+ const void *buf)
+{
+ return ts->bus_ops->write(ts->dev, ts->xfer_buf, addr, size, buf);
+}
+
+extern struct cyttsp4 *cyttsp4_probe(const struct cyttsp4_bus_ops *ops,
+ struct device *dev, u16 irq, size_t xfer_buf_size);
+extern int cyttsp4_remove(struct cyttsp4 *ts);
+int cyttsp_i2c_write_block_data(struct device *dev, u8 *xfer_buf, u16 addr,
+ u8 length, const void *values);
+int cyttsp_i2c_read_block_data(struct device *dev, u8 *xfer_buf, u16 addr,
+ u8 length, void *values);
+extern const struct dev_pm_ops cyttsp4_pm_ops;
+
+#endif /* _LINUX_CYTTSP4_CORE_H */
diff --git a/drivers/input/touchscreen/cyttsp4_i2c.c b/drivers/input/touchscreen/cyttsp4_i2c.c
new file mode 100644
index 000000000..28ae7c153
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp4_i2c.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cyttsp_i2c.c
+ * Cypress TrueTouch(TM) Standard Product (TTSP) I2C touchscreen driver.
+ * For use with Cypress Txx4xx parts.
+ * Supported parts include:
+ * TMA4XX
+ * TMA1036
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ * Copyright (C) 2013 Cypress Semiconductor
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#include "cyttsp4_core.h"
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+
+#define CYTTSP4_I2C_DATA_SIZE (3 * 256)
+
+static const struct cyttsp4_bus_ops cyttsp4_i2c_bus_ops = {
+ .bustype = BUS_I2C,
+ .write = cyttsp_i2c_write_block_data,
+ .read = cyttsp_i2c_read_block_data,
+};
+
+static int cyttsp4_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cyttsp4 *ts;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C functionality not Supported\n");
+ return -EIO;
+ }
+
+ ts = cyttsp4_probe(&cyttsp4_i2c_bus_ops, &client->dev, client->irq,
+ CYTTSP4_I2C_DATA_SIZE);
+
+ return PTR_ERR_OR_ZERO(ts);
+}
+
+static void cyttsp4_i2c_remove(struct i2c_client *client)
+{
+ struct cyttsp4 *ts = i2c_get_clientdata(client);
+
+ cyttsp4_remove(ts);
+}
+
+static const struct i2c_device_id cyttsp4_i2c_id[] = {
+ { CYTTSP4_I2C_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, cyttsp4_i2c_id);
+
+static struct i2c_driver cyttsp4_i2c_driver = {
+ .driver = {
+ .name = CYTTSP4_I2C_NAME,
+ .pm = &cyttsp4_pm_ops,
+ },
+ .probe = cyttsp4_i2c_probe,
+ .remove = cyttsp4_i2c_remove,
+ .id_table = cyttsp4_i2c_id,
+};
+
+module_i2c_driver(cyttsp4_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard Product (TTSP) I2C driver");
+MODULE_AUTHOR("Cypress");
diff --git a/drivers/input/touchscreen/cyttsp4_spi.c b/drivers/input/touchscreen/cyttsp4_spi.c
new file mode 100644
index 000000000..5d7db84f2
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp4_spi.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Source for:
+ * Cypress TrueTouch(TM) Standard Product (TTSP) SPI touchscreen driver.
+ * For use with Cypress Txx4xx parts.
+ * Supported parts include:
+ * TMA4XX
+ * TMA1036
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ * Copyright (C) 2013 Cypress Semiconductor
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#include "cyttsp4_core.h"
+
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/spi/spi.h>
+
+#define CY_SPI_WR_OP 0x00 /* r/~w */
+#define CY_SPI_RD_OP 0x01
+#define CY_SPI_BITS_PER_WORD 8
+#define CY_SPI_A8_BIT 0x02
+#define CY_SPI_WR_HEADER_BYTES 2
+#define CY_SPI_RD_HEADER_BYTES 1
+#define CY_SPI_CMD_BYTES 2
+#define CY_SPI_SYNC_BYTE 0
+#define CY_SPI_SYNC_ACK 0x62 /* from TRM *A protocol */
+#define CY_SPI_DATA_SIZE (2 * 256)
+
+#define CY_SPI_DATA_BUF_SIZE (CY_SPI_CMD_BYTES + CY_SPI_DATA_SIZE)
+
+static int cyttsp_spi_xfer(struct device *dev, u8 *xfer_buf,
+ u8 op, u16 reg, u8 *buf, int length)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_message msg;
+ struct spi_transfer xfer[2];
+ u8 *wr_buf = &xfer_buf[0];
+ u8 rd_buf[CY_SPI_CMD_BYTES];
+ int retval;
+ int i;
+
+ if (length > CY_SPI_DATA_SIZE) {
+ dev_err(dev, "%s: length %d is too big.\n",
+ __func__, length);
+ return -EINVAL;
+ }
+
+ memset(wr_buf, 0, CY_SPI_DATA_BUF_SIZE);
+ memset(rd_buf, 0, CY_SPI_CMD_BYTES);
+
+ wr_buf[0] = op + (((reg >> 8) & 0x1) ? CY_SPI_A8_BIT : 0);
+ if (op == CY_SPI_WR_OP) {
+ wr_buf[1] = reg & 0xFF;
+ if (length > 0)
+ memcpy(wr_buf + CY_SPI_CMD_BYTES, buf, length);
+ }
+
+ memset(xfer, 0, sizeof(xfer));
+ spi_message_init(&msg);
+
+ /*
+ We set both TX and RX buffers because Cypress TTSP
+ requires full duplex operation.
+ */
+ xfer[0].tx_buf = wr_buf;
+ xfer[0].rx_buf = rd_buf;
+ switch (op) {
+ case CY_SPI_WR_OP:
+ xfer[0].len = length + CY_SPI_CMD_BYTES;
+ spi_message_add_tail(&xfer[0], &msg);
+ break;
+
+ case CY_SPI_RD_OP:
+ xfer[0].len = CY_SPI_RD_HEADER_BYTES;
+ spi_message_add_tail(&xfer[0], &msg);
+
+ xfer[1].rx_buf = buf;
+ xfer[1].len = length;
+ spi_message_add_tail(&xfer[1], &msg);
+ break;
+
+ default:
+ dev_err(dev, "%s: bad operation code=%d\n", __func__, op);
+ return -EINVAL;
+ }
+
+ retval = spi_sync(spi, &msg);
+ if (retval < 0) {
+ dev_dbg(dev, "%s: spi_sync() error %d, len=%d, op=%d\n",
+ __func__, retval, xfer[1].len, op);
+
+ /*
+ * do not return here since was a bad ACK sequence
+ * let the following ACK check handle any errors and
+ * allow silent retries
+ */
+ }
+
+ if (rd_buf[CY_SPI_SYNC_BYTE] != CY_SPI_SYNC_ACK) {
+ dev_dbg(dev, "%s: operation %d failed\n", __func__, op);
+
+ for (i = 0; i < CY_SPI_CMD_BYTES; i++)
+ dev_dbg(dev, "%s: test rd_buf[%d]:0x%02x\n",
+ __func__, i, rd_buf[i]);
+ for (i = 0; i < length; i++)
+ dev_dbg(dev, "%s: test buf[%d]:0x%02x\n",
+ __func__, i, buf[i]);
+
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cyttsp_spi_read_block_data(struct device *dev, u8 *xfer_buf,
+ u16 addr, u8 length, void *data)
+{
+ int rc;
+
+ rc = cyttsp_spi_xfer(dev, xfer_buf, CY_SPI_WR_OP, addr, NULL, 0);
+ if (rc)
+ return rc;
+ else
+ return cyttsp_spi_xfer(dev, xfer_buf, CY_SPI_RD_OP, addr, data,
+ length);
+}
+
+static int cyttsp_spi_write_block_data(struct device *dev, u8 *xfer_buf,
+ u16 addr, u8 length, const void *data)
+{
+ return cyttsp_spi_xfer(dev, xfer_buf, CY_SPI_WR_OP, addr, (void *)data,
+ length);
+}
+
+static const struct cyttsp4_bus_ops cyttsp_spi_bus_ops = {
+ .bustype = BUS_SPI,
+ .write = cyttsp_spi_write_block_data,
+ .read = cyttsp_spi_read_block_data,
+};
+
+static int cyttsp4_spi_probe(struct spi_device *spi)
+{
+ struct cyttsp4 *ts;
+ int error;
+
+ /* Set up SPI*/
+ spi->bits_per_word = CY_SPI_BITS_PER_WORD;
+ spi->mode = SPI_MODE_0;
+ error = spi_setup(spi);
+ if (error < 0) {
+ dev_err(&spi->dev, "%s: SPI setup error %d\n",
+ __func__, error);
+ return error;
+ }
+
+ ts = cyttsp4_probe(&cyttsp_spi_bus_ops, &spi->dev, spi->irq,
+ CY_SPI_DATA_BUF_SIZE);
+
+ return PTR_ERR_OR_ZERO(ts);
+}
+
+static void cyttsp4_spi_remove(struct spi_device *spi)
+{
+ struct cyttsp4 *ts = spi_get_drvdata(spi);
+ cyttsp4_remove(ts);
+}
+
+static struct spi_driver cyttsp4_spi_driver = {
+ .driver = {
+ .name = CYTTSP4_SPI_NAME,
+ .pm = &cyttsp4_pm_ops,
+ },
+ .probe = cyttsp4_spi_probe,
+ .remove = cyttsp4_spi_remove,
+};
+
+module_spi_driver(cyttsp4_spi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard Product (TTSP) SPI driver");
+MODULE_AUTHOR("Cypress");
+MODULE_ALIAS("spi:cyttsp4");
diff --git a/drivers/input/touchscreen/cyttsp_core.c b/drivers/input/touchscreen/cyttsp_core.c
new file mode 100644
index 000000000..1dbd849c9
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp_core.c
@@ -0,0 +1,736 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Core Source for:
+ * Cypress TrueTouch(TM) Standard Product (TTSP) touchscreen drivers.
+ * For use with Cypress Txx3xx parts.
+ * Supported parts include:
+ * CY8CTST341
+ * CY8CTMA340
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <kev@cypress.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/property.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include "cyttsp_core.h"
+
+/* Bootloader number of command keys */
+#define CY_NUM_BL_KEYS 8
+
+/* helpers */
+#define GET_NUM_TOUCHES(x) ((x) & 0x0F)
+#define IS_LARGE_AREA(x) (((x) & 0x10) >> 4)
+#define IS_BAD_PKT(x) ((x) & 0x20)
+#define IS_VALID_APP(x) ((x) & 0x01)
+#define IS_OPERATIONAL_ERR(x) ((x) & 0x3F)
+#define GET_HSTMODE(reg) (((reg) & 0x70) >> 4)
+#define GET_BOOTLOADERMODE(reg) (((reg) & 0x10) >> 4)
+
+#define CY_REG_BASE 0x00
+#define CY_REG_ACT_DIST 0x1E
+#define CY_REG_ACT_INTRVL 0x1D
+#define CY_REG_TCH_TMOUT (CY_REG_ACT_INTRVL + 1)
+#define CY_REG_LP_INTRVL (CY_REG_TCH_TMOUT + 1)
+#define CY_MAXZ 255
+#define CY_DELAY_DFLT 20 /* ms */
+#define CY_DELAY_MAX 500
+/* Active distance in pixels for a gesture to be reported */
+#define CY_ACT_DIST_DFLT 0xF8 /* pixels */
+#define CY_ACT_DIST_MASK 0x0F
+/* Active Power state scanning/processing refresh interval */
+#define CY_ACT_INTRVL_DFLT 0x00 /* ms */
+/* Low Power state scanning/processing refresh interval */
+#define CY_LP_INTRVL_DFLT 0x0A /* ms */
+/* touch timeout for the Active power */
+#define CY_TCH_TMOUT_DFLT 0xFF /* ms */
+#define CY_HNDSHK_BIT 0x80
+/* device mode bits */
+#define CY_OPERATE_MODE 0x00
+#define CY_SYSINFO_MODE 0x10
+/* power mode select bits */
+#define CY_SOFT_RESET_MODE 0x01 /* return to Bootloader mode */
+#define CY_DEEP_SLEEP_MODE 0x02
+#define CY_LOW_POWER_MODE 0x04
+
+/* Slots management */
+#define CY_MAX_FINGER 4
+#define CY_MAX_ID 16
+
+static const u8 bl_command[] = {
+ 0x00, /* file offset */
+ 0xFF, /* command */
+ 0xA5, /* exit bootloader command */
+ 0, 1, 2, 3, 4, 5, 6, 7 /* default keys */
+};
+
+static int ttsp_read_block_data(struct cyttsp *ts, u8 command,
+ u8 length, void *buf)
+{
+ int error;
+ int tries;
+
+ for (tries = 0; tries < CY_NUM_RETRY; tries++) {
+ error = ts->bus_ops->read(ts->dev, ts->xfer_buf, command,
+ length, buf);
+ if (!error)
+ return 0;
+
+ msleep(CY_DELAY_DFLT);
+ }
+
+ return -EIO;
+}
+
+static int ttsp_write_block_data(struct cyttsp *ts, u8 command,
+ u8 length, void *buf)
+{
+ int error;
+ int tries;
+
+ for (tries = 0; tries < CY_NUM_RETRY; tries++) {
+ error = ts->bus_ops->write(ts->dev, ts->xfer_buf, command,
+ length, buf);
+ if (!error)
+ return 0;
+
+ msleep(CY_DELAY_DFLT);
+ }
+
+ return -EIO;
+}
+
+static int ttsp_send_command(struct cyttsp *ts, u8 cmd)
+{
+ return ttsp_write_block_data(ts, CY_REG_BASE, sizeof(cmd), &cmd);
+}
+
+static int cyttsp_handshake(struct cyttsp *ts)
+{
+ if (ts->use_hndshk)
+ return ttsp_send_command(ts,
+ ts->xy_data.hst_mode ^ CY_HNDSHK_BIT);
+
+ return 0;
+}
+
+static int cyttsp_load_bl_regs(struct cyttsp *ts)
+{
+ memset(&ts->bl_data, 0, sizeof(ts->bl_data));
+ ts->bl_data.bl_status = 0x10;
+
+ return ttsp_read_block_data(ts, CY_REG_BASE,
+ sizeof(ts->bl_data), &ts->bl_data);
+}
+
+static int cyttsp_exit_bl_mode(struct cyttsp *ts)
+{
+ int error;
+ u8 bl_cmd[sizeof(bl_command)];
+
+ memcpy(bl_cmd, bl_command, sizeof(bl_command));
+ if (ts->bl_keys)
+ memcpy(&bl_cmd[sizeof(bl_command) - CY_NUM_BL_KEYS],
+ ts->bl_keys, CY_NUM_BL_KEYS);
+
+ error = ttsp_write_block_data(ts, CY_REG_BASE,
+ sizeof(bl_cmd), bl_cmd);
+ if (error)
+ return error;
+
+ /* wait for TTSP Device to complete the operation */
+ msleep(CY_DELAY_DFLT);
+
+ error = cyttsp_load_bl_regs(ts);
+ if (error)
+ return error;
+
+ if (GET_BOOTLOADERMODE(ts->bl_data.bl_status))
+ return -EIO;
+
+ return 0;
+}
+
+static int cyttsp_set_operational_mode(struct cyttsp *ts)
+{
+ int error;
+
+ error = ttsp_send_command(ts, CY_OPERATE_MODE);
+ if (error)
+ return error;
+
+ /* wait for TTSP Device to complete switch to Operational mode */
+ error = ttsp_read_block_data(ts, CY_REG_BASE,
+ sizeof(ts->xy_data), &ts->xy_data);
+ if (error)
+ return error;
+
+ error = cyttsp_handshake(ts);
+ if (error)
+ return error;
+
+ return ts->xy_data.act_dist == CY_ACT_DIST_DFLT ? -EIO : 0;
+}
+
+static int cyttsp_set_sysinfo_mode(struct cyttsp *ts)
+{
+ int error;
+
+ memset(&ts->sysinfo_data, 0, sizeof(ts->sysinfo_data));
+
+ /* switch to sysinfo mode */
+ error = ttsp_send_command(ts, CY_SYSINFO_MODE);
+ if (error)
+ return error;
+
+ /* read sysinfo registers */
+ msleep(CY_DELAY_DFLT);
+ error = ttsp_read_block_data(ts, CY_REG_BASE, sizeof(ts->sysinfo_data),
+ &ts->sysinfo_data);
+ if (error)
+ return error;
+
+ error = cyttsp_handshake(ts);
+ if (error)
+ return error;
+
+ if (!ts->sysinfo_data.tts_verh && !ts->sysinfo_data.tts_verl)
+ return -EIO;
+
+ return 0;
+}
+
+static int cyttsp_set_sysinfo_regs(struct cyttsp *ts)
+{
+ int retval = 0;
+
+ if (ts->act_intrvl != CY_ACT_INTRVL_DFLT ||
+ ts->tch_tmout != CY_TCH_TMOUT_DFLT ||
+ ts->lp_intrvl != CY_LP_INTRVL_DFLT) {
+
+ u8 intrvl_ray[] = {
+ ts->act_intrvl,
+ ts->tch_tmout,
+ ts->lp_intrvl
+ };
+
+ /* set intrvl registers */
+ retval = ttsp_write_block_data(ts, CY_REG_ACT_INTRVL,
+ sizeof(intrvl_ray), intrvl_ray);
+ msleep(CY_DELAY_DFLT);
+ }
+
+ return retval;
+}
+
+static void cyttsp_hard_reset(struct cyttsp *ts)
+{
+ if (ts->reset_gpio) {
+ /*
+ * According to the CY8CTMA340 datasheet page 21, the external
+ * reset pulse width should be >= 1 ms. The datasheet does not
+ * specify how long we have to wait after reset but a vendor
+ * tree specifies 5 ms here.
+ */
+ gpiod_set_value_cansleep(ts->reset_gpio, 1);
+ usleep_range(1000, 2000);
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+ usleep_range(5000, 6000);
+ }
+}
+
+static int cyttsp_soft_reset(struct cyttsp *ts)
+{
+ int retval;
+
+ /* wait for interrupt to set ready completion */
+ reinit_completion(&ts->bl_ready);
+ ts->state = CY_BL_STATE;
+
+ enable_irq(ts->irq);
+
+ retval = ttsp_send_command(ts, CY_SOFT_RESET_MODE);
+ if (retval) {
+ dev_err(ts->dev, "failed to send soft reset\n");
+ goto out;
+ }
+
+ if (!wait_for_completion_timeout(&ts->bl_ready,
+ msecs_to_jiffies(CY_DELAY_DFLT * CY_DELAY_MAX))) {
+ dev_err(ts->dev, "timeout waiting for soft reset\n");
+ retval = -EIO;
+ }
+
+out:
+ ts->state = CY_IDLE_STATE;
+ disable_irq(ts->irq);
+ return retval;
+}
+
+static int cyttsp_act_dist_setup(struct cyttsp *ts)
+{
+ u8 act_dist_setup = ts->act_dist;
+
+ /* Init gesture; active distance setup */
+ return ttsp_write_block_data(ts, CY_REG_ACT_DIST,
+ sizeof(act_dist_setup), &act_dist_setup);
+}
+
+static void cyttsp_extract_track_ids(struct cyttsp_xydata *xy_data, int *ids)
+{
+ ids[0] = xy_data->touch12_id >> 4;
+ ids[1] = xy_data->touch12_id & 0xF;
+ ids[2] = xy_data->touch34_id >> 4;
+ ids[3] = xy_data->touch34_id & 0xF;
+}
+
+static const struct cyttsp_tch *cyttsp_get_tch(struct cyttsp_xydata *xy_data,
+ int idx)
+{
+ switch (idx) {
+ case 0:
+ return &xy_data->tch1;
+ case 1:
+ return &xy_data->tch2;
+ case 2:
+ return &xy_data->tch3;
+ case 3:
+ return &xy_data->tch4;
+ default:
+ return NULL;
+ }
+}
+
+static void cyttsp_report_tchdata(struct cyttsp *ts)
+{
+ struct cyttsp_xydata *xy_data = &ts->xy_data;
+ struct input_dev *input = ts->input;
+ int num_tch = GET_NUM_TOUCHES(xy_data->tt_stat);
+ const struct cyttsp_tch *tch;
+ int ids[CY_MAX_ID];
+ int i;
+ DECLARE_BITMAP(used, CY_MAX_ID);
+
+ if (IS_LARGE_AREA(xy_data->tt_stat) == 1) {
+ /* terminate all active tracks */
+ num_tch = 0;
+ dev_dbg(ts->dev, "%s: Large area detected\n", __func__);
+ } else if (num_tch > CY_MAX_FINGER) {
+ /* terminate all active tracks */
+ num_tch = 0;
+ dev_dbg(ts->dev, "%s: Num touch error detected\n", __func__);
+ } else if (IS_BAD_PKT(xy_data->tt_mode)) {
+ /* terminate all active tracks */
+ num_tch = 0;
+ dev_dbg(ts->dev, "%s: Invalid buffer detected\n", __func__);
+ }
+
+ cyttsp_extract_track_ids(xy_data, ids);
+
+ bitmap_zero(used, CY_MAX_ID);
+
+ for (i = 0; i < num_tch; i++) {
+ tch = cyttsp_get_tch(xy_data, i);
+
+ input_mt_slot(input, ids[i]);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ input_report_abs(input, ABS_MT_POSITION_X, be16_to_cpu(tch->x));
+ input_report_abs(input, ABS_MT_POSITION_Y, be16_to_cpu(tch->y));
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, tch->z);
+
+ __set_bit(ids[i], used);
+ }
+
+ for (i = 0; i < CY_MAX_ID; i++) {
+ if (test_bit(i, used))
+ continue;
+
+ input_mt_slot(input, i);
+ input_mt_report_slot_inactive(input);
+ }
+
+ input_sync(input);
+}
+
+static irqreturn_t cyttsp_irq(int irq, void *handle)
+{
+ struct cyttsp *ts = handle;
+ int error;
+
+ if (unlikely(ts->state == CY_BL_STATE)) {
+ complete(&ts->bl_ready);
+ goto out;
+ }
+
+ /* Get touch data from CYTTSP device */
+ error = ttsp_read_block_data(ts, CY_REG_BASE,
+ sizeof(struct cyttsp_xydata), &ts->xy_data);
+ if (error)
+ goto out;
+
+ /* provide flow control handshake */
+ error = cyttsp_handshake(ts);
+ if (error)
+ goto out;
+
+ if (unlikely(ts->state == CY_IDLE_STATE))
+ goto out;
+
+ if (GET_BOOTLOADERMODE(ts->xy_data.tt_mode)) {
+ /*
+ * TTSP device has reset back to bootloader mode.
+ * Restore to operational mode.
+ */
+ error = cyttsp_exit_bl_mode(ts);
+ if (error) {
+ dev_err(ts->dev,
+ "Could not return to operational mode, err: %d\n",
+ error);
+ ts->state = CY_IDLE_STATE;
+ }
+ } else {
+ cyttsp_report_tchdata(ts);
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int cyttsp_power_on(struct cyttsp *ts)
+{
+ int error;
+
+ error = cyttsp_soft_reset(ts);
+ if (error)
+ return error;
+
+ error = cyttsp_load_bl_regs(ts);
+ if (error)
+ return error;
+
+ if (GET_BOOTLOADERMODE(ts->bl_data.bl_status) &&
+ IS_VALID_APP(ts->bl_data.bl_status)) {
+ error = cyttsp_exit_bl_mode(ts);
+ if (error) {
+ dev_err(ts->dev, "failed to exit bootloader mode\n");
+ return error;
+ }
+ }
+
+ if (GET_HSTMODE(ts->bl_data.bl_file) != CY_OPERATE_MODE ||
+ IS_OPERATIONAL_ERR(ts->bl_data.bl_status)) {
+ return -ENODEV;
+ }
+
+ error = cyttsp_set_sysinfo_mode(ts);
+ if (error)
+ return error;
+
+ error = cyttsp_set_sysinfo_regs(ts);
+ if (error)
+ return error;
+
+ error = cyttsp_set_operational_mode(ts);
+ if (error)
+ return error;
+
+ /* init active distance */
+ error = cyttsp_act_dist_setup(ts);
+ if (error)
+ return error;
+
+ ts->state = CY_ACTIVE_STATE;
+
+ return 0;
+}
+
+static int cyttsp_enable(struct cyttsp *ts)
+{
+ int error;
+
+ /*
+ * The device firmware can wake on an I2C or SPI memory slave
+ * address match. So just reading a register is sufficient to
+ * wake up the device. The first read attempt will fail but it
+ * will wake it up making the second read attempt successful.
+ */
+ error = ttsp_read_block_data(ts, CY_REG_BASE,
+ sizeof(ts->xy_data), &ts->xy_data);
+ if (error)
+ return error;
+
+ if (GET_HSTMODE(ts->xy_data.hst_mode))
+ return -EIO;
+
+ enable_irq(ts->irq);
+
+ return 0;
+}
+
+static int cyttsp_disable(struct cyttsp *ts)
+{
+ int error;
+
+ error = ttsp_send_command(ts, CY_LOW_POWER_MODE);
+ if (error)
+ return error;
+
+ disable_irq(ts->irq);
+
+ return 0;
+}
+
+static int __maybe_unused cyttsp_suspend(struct device *dev)
+{
+ struct cyttsp *ts = dev_get_drvdata(dev);
+ int retval = 0;
+
+ mutex_lock(&ts->input->mutex);
+
+ if (input_device_enabled(ts->input)) {
+ retval = cyttsp_disable(ts);
+ if (retval == 0)
+ ts->suspended = true;
+ }
+
+ mutex_unlock(&ts->input->mutex);
+
+ return retval;
+}
+
+static int __maybe_unused cyttsp_resume(struct device *dev)
+{
+ struct cyttsp *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->input->mutex);
+
+ if (input_device_enabled(ts->input))
+ cyttsp_enable(ts);
+
+ ts->suspended = false;
+
+ mutex_unlock(&ts->input->mutex);
+
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(cyttsp_pm_ops, cyttsp_suspend, cyttsp_resume);
+EXPORT_SYMBOL_GPL(cyttsp_pm_ops);
+
+static int cyttsp_open(struct input_dev *dev)
+{
+ struct cyttsp *ts = input_get_drvdata(dev);
+ int retval = 0;
+
+ if (!ts->suspended)
+ retval = cyttsp_enable(ts);
+
+ return retval;
+}
+
+static void cyttsp_close(struct input_dev *dev)
+{
+ struct cyttsp *ts = input_get_drvdata(dev);
+
+ if (!ts->suspended)
+ cyttsp_disable(ts);
+}
+
+static int cyttsp_parse_properties(struct cyttsp *ts)
+{
+ struct device *dev = ts->dev;
+ u32 dt_value;
+ int ret;
+
+ ts->bl_keys = devm_kzalloc(dev, CY_NUM_BL_KEYS, GFP_KERNEL);
+ if (!ts->bl_keys)
+ return -ENOMEM;
+
+ /* Set some default values */
+ ts->use_hndshk = false;
+ ts->act_dist = CY_ACT_DIST_DFLT;
+ ts->act_intrvl = CY_ACT_INTRVL_DFLT;
+ ts->tch_tmout = CY_TCH_TMOUT_DFLT;
+ ts->lp_intrvl = CY_LP_INTRVL_DFLT;
+
+ ret = device_property_read_u8_array(dev, "bootloader-key",
+ ts->bl_keys, CY_NUM_BL_KEYS);
+ if (ret) {
+ dev_err(dev,
+ "bootloader-key property could not be retrieved\n");
+ return ret;
+ }
+
+ ts->use_hndshk = device_property_present(dev, "use-handshake");
+
+ if (!device_property_read_u32(dev, "active-distance", &dt_value)) {
+ if (dt_value > 15) {
+ dev_err(dev, "active-distance (%u) must be [0-15]\n",
+ dt_value);
+ return -EINVAL;
+ }
+ ts->act_dist &= ~CY_ACT_DIST_MASK;
+ ts->act_dist |= dt_value;
+ }
+
+ if (!device_property_read_u32(dev, "active-interval-ms", &dt_value)) {
+ if (dt_value > 255) {
+ dev_err(dev, "active-interval-ms (%u) must be [0-255]\n",
+ dt_value);
+ return -EINVAL;
+ }
+ ts->act_intrvl = dt_value;
+ }
+
+ if (!device_property_read_u32(dev, "lowpower-interval-ms", &dt_value)) {
+ if (dt_value > 2550) {
+ dev_err(dev, "lowpower-interval-ms (%u) must be [0-2550]\n",
+ dt_value);
+ return -EINVAL;
+ }
+ /* Register value is expressed in 0.01s / bit */
+ ts->lp_intrvl = dt_value / 10;
+ }
+
+ if (!device_property_read_u32(dev, "touch-timeout-ms", &dt_value)) {
+ if (dt_value > 2550) {
+ dev_err(dev, "touch-timeout-ms (%u) must be [0-2550]\n",
+ dt_value);
+ return -EINVAL;
+ }
+ /* Register value is expressed in 0.01s / bit */
+ ts->tch_tmout = dt_value / 10;
+ }
+
+ return 0;
+}
+
+static void cyttsp_disable_regulators(void *_ts)
+{
+ struct cyttsp *ts = _ts;
+
+ regulator_bulk_disable(ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+}
+
+struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops,
+ struct device *dev, int irq, size_t xfer_buf_size)
+{
+ struct cyttsp *ts;
+ struct input_dev *input_dev;
+ int error;
+
+ ts = devm_kzalloc(dev, sizeof(*ts) + xfer_buf_size, GFP_KERNEL);
+ if (!ts)
+ return ERR_PTR(-ENOMEM);
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return ERR_PTR(-ENOMEM);
+
+ ts->dev = dev;
+ ts->input = input_dev;
+ ts->bus_ops = bus_ops;
+ ts->irq = irq;
+
+ /*
+ * VCPIN is the analog voltage supply
+ * VDD is the digital voltage supply
+ */
+ ts->regulators[0].supply = "vcpin";
+ ts->regulators[1].supply = "vdd";
+ error = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+ if (error) {
+ dev_err(dev, "Failed to get regulators: %d\n", error);
+ return ERR_PTR(error);
+ }
+
+ error = regulator_bulk_enable(ARRAY_SIZE(ts->regulators),
+ ts->regulators);
+ if (error) {
+ dev_err(dev, "Cannot enable regulators: %d\n", error);
+ return ERR_PTR(error);
+ }
+
+ error = devm_add_action_or_reset(dev, cyttsp_disable_regulators, ts);
+ if (error) {
+ dev_err(dev, "failed to install chip disable handler\n");
+ return ERR_PTR(error);
+ }
+
+ ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+ dev_err(dev, "Failed to request reset gpio, error %d\n", error);
+ return ERR_PTR(error);
+ }
+
+ error = cyttsp_parse_properties(ts);
+ if (error)
+ return ERR_PTR(error);
+
+ init_completion(&ts->bl_ready);
+
+ input_dev->name = "Cypress TTSP TouchScreen";
+ input_dev->id.bustype = bus_ops->bustype;
+ input_dev->dev.parent = ts->dev;
+
+ input_dev->open = cyttsp_open;
+ input_dev->close = cyttsp_close;
+
+ input_set_drvdata(input_dev, ts);
+
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y);
+ /* One byte for width 0..255 so this is the limit */
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(input_dev, true, NULL);
+
+ error = input_mt_init_slots(input_dev, CY_MAX_ID, INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(dev, "Unable to init MT slots.\n");
+ return ERR_PTR(error);
+ }
+
+ error = devm_request_threaded_irq(dev, ts->irq, NULL, cyttsp_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "cyttsp", ts);
+ if (error) {
+ dev_err(ts->dev, "failed to request IRQ %d, err: %d\n",
+ ts->irq, error);
+ return ERR_PTR(error);
+ }
+
+ cyttsp_hard_reset(ts);
+
+ error = cyttsp_power_on(ts);
+ if (error)
+ return ERR_PTR(error);
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(ts->dev, "failed to register input device: %d\n",
+ error);
+ return ERR_PTR(error);
+ }
+
+ return ts;
+}
+EXPORT_SYMBOL_GPL(cyttsp_probe);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard touchscreen driver core");
+MODULE_AUTHOR("Cypress");
diff --git a/drivers/input/touchscreen/cyttsp_core.h b/drivers/input/touchscreen/cyttsp_core.h
new file mode 100644
index 000000000..075509e69
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp_core.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Header file for:
+ * Cypress TrueTouch(TM) Standard Product (TTSP) touchscreen drivers.
+ * For use with Cypress Txx3xx parts.
+ * Supported parts include:
+ * CY8CTST341
+ * CY8CTMA340
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <kev@cypress.com>
+ */
+
+
+#ifndef __CYTTSP_CORE_H__
+#define __CYTTSP_CORE_H__
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/regulator/consumer.h>
+
+#define CY_NUM_RETRY 16 /* max number of retries for read ops */
+
+struct cyttsp_tch {
+ __be16 x, y;
+ u8 z;
+} __packed;
+
+/* TrueTouch Standard Product Gen3 interface definition */
+struct cyttsp_xydata {
+ u8 hst_mode;
+ u8 tt_mode;
+ u8 tt_stat;
+ struct cyttsp_tch tch1;
+ u8 touch12_id;
+ struct cyttsp_tch tch2;
+ u8 gest_cnt;
+ u8 gest_id;
+ struct cyttsp_tch tch3;
+ u8 touch34_id;
+ struct cyttsp_tch tch4;
+ u8 tt_undef[3];
+ u8 act_dist;
+ u8 tt_reserved;
+} __packed;
+
+
+/* TTSP System Information interface definition */
+struct cyttsp_sysinfo_data {
+ u8 hst_mode;
+ u8 mfg_stat;
+ u8 mfg_cmd;
+ u8 cid[3];
+ u8 tt_undef1;
+ u8 uid[8];
+ u8 bl_verh;
+ u8 bl_verl;
+ u8 tts_verh;
+ u8 tts_verl;
+ u8 app_idh;
+ u8 app_idl;
+ u8 app_verh;
+ u8 app_verl;
+ u8 tt_undef[5];
+ u8 scn_typ;
+ u8 act_intrvl;
+ u8 tch_tmout;
+ u8 lp_intrvl;
+};
+
+/* TTSP Bootloader Register Map interface definition */
+#define CY_BL_CHKSUM_OK 0x01
+struct cyttsp_bootloader_data {
+ u8 bl_file;
+ u8 bl_status;
+ u8 bl_error;
+ u8 blver_hi;
+ u8 blver_lo;
+ u8 bld_blver_hi;
+ u8 bld_blver_lo;
+ u8 ttspver_hi;
+ u8 ttspver_lo;
+ u8 appid_hi;
+ u8 appid_lo;
+ u8 appver_hi;
+ u8 appver_lo;
+ u8 cid_0;
+ u8 cid_1;
+ u8 cid_2;
+};
+
+struct cyttsp;
+
+struct cyttsp_bus_ops {
+ u16 bustype;
+ int (*write)(struct device *dev, u8 *xfer_buf, u16 addr, u8 length,
+ const void *values);
+ int (*read)(struct device *dev, u8 *xfer_buf, u16 addr, u8 length,
+ void *values);
+};
+
+enum cyttsp_state {
+ CY_IDLE_STATE,
+ CY_ACTIVE_STATE,
+ CY_BL_STATE,
+};
+
+struct cyttsp {
+ struct device *dev;
+ int irq;
+ struct input_dev *input;
+ const struct cyttsp_bus_ops *bus_ops;
+ struct cyttsp_bootloader_data bl_data;
+ struct cyttsp_sysinfo_data sysinfo_data;
+ struct cyttsp_xydata xy_data;
+ struct completion bl_ready;
+ enum cyttsp_state state;
+ bool suspended;
+
+ struct regulator_bulk_data regulators[2];
+ struct gpio_desc *reset_gpio;
+ bool use_hndshk;
+ u8 act_dist;
+ u8 act_intrvl;
+ u8 tch_tmout;
+ u8 lp_intrvl;
+ u8 *bl_keys;
+
+ u8 xfer_buf[] ____cacheline_aligned;
+};
+
+struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops,
+ struct device *dev, int irq, size_t xfer_buf_size);
+
+int cyttsp_i2c_write_block_data(struct device *dev, u8 *xfer_buf, u16 addr,
+ u8 length, const void *values);
+int cyttsp_i2c_read_block_data(struct device *dev, u8 *xfer_buf, u16 addr,
+ u8 length, void *values);
+extern const struct dev_pm_ops cyttsp_pm_ops;
+
+#endif /* __CYTTSP_CORE_H__ */
diff --git a/drivers/input/touchscreen/cyttsp_i2c.c b/drivers/input/touchscreen/cyttsp_i2c.c
new file mode 100644
index 000000000..4c8473d32
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp_i2c.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cyttsp_i2c.c
+ * Cypress TrueTouch(TM) Standard Product (TTSP) I2C touchscreen driver.
+ * For use with Cypress Txx3xx parts.
+ * Supported parts include:
+ * CY8CTST341
+ * CY8CTMA340
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#include "cyttsp_core.h"
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+
+#define CY_I2C_NAME "cyttsp-i2c"
+
+#define CY_I2C_DATA_SIZE 128
+
+static const struct cyttsp_bus_ops cyttsp_i2c_bus_ops = {
+ .bustype = BUS_I2C,
+ .write = cyttsp_i2c_write_block_data,
+ .read = cyttsp_i2c_read_block_data,
+};
+
+static int cyttsp_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cyttsp *ts;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C functionality not Supported\n");
+ return -EIO;
+ }
+
+ ts = cyttsp_probe(&cyttsp_i2c_bus_ops, &client->dev, client->irq,
+ CY_I2C_DATA_SIZE);
+
+ if (IS_ERR(ts))
+ return PTR_ERR(ts);
+
+ i2c_set_clientdata(client, ts);
+ return 0;
+}
+
+static const struct i2c_device_id cyttsp_i2c_id[] = {
+ { CY_I2C_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, cyttsp_i2c_id);
+
+static const struct of_device_id cyttsp_of_i2c_match[] = {
+ { .compatible = "cypress,cy8ctma340", },
+ { .compatible = "cypress,cy8ctst341", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cyttsp_of_i2c_match);
+
+static struct i2c_driver cyttsp_i2c_driver = {
+ .driver = {
+ .name = CY_I2C_NAME,
+ .pm = &cyttsp_pm_ops,
+ .of_match_table = cyttsp_of_i2c_match,
+ },
+ .probe = cyttsp_i2c_probe,
+ .id_table = cyttsp_i2c_id,
+};
+
+module_i2c_driver(cyttsp_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard Product (TTSP) I2C driver");
+MODULE_AUTHOR("Cypress");
diff --git a/drivers/input/touchscreen/cyttsp_i2c_common.c b/drivers/input/touchscreen/cyttsp_i2c_common.c
new file mode 100644
index 000000000..1f0b6d6f4
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp_i2c_common.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cyttsp_i2c_common.c
+ * Cypress TrueTouch(TM) Standard Product (TTSP) I2C touchscreen driver.
+ * For use with Cypress Txx3xx and Txx4xx parts.
+ * Supported parts include:
+ * CY8CTST341
+ * CY8CTMA340
+ * TMA4XX
+ * TMA1036
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include "cyttsp4_core.h"
+
+int cyttsp_i2c_read_block_data(struct device *dev, u8 *xfer_buf,
+ u16 addr, u8 length, void *values)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 client_addr = client->addr | ((addr >> 8) & 0x1);
+ u8 addr_lo = addr & 0xFF;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client_addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &addr_lo,
+ },
+ {
+ .addr = client_addr,
+ .flags = I2C_M_RD,
+ .len = length,
+ .buf = values,
+ },
+ };
+ int retval;
+
+ retval = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (retval < 0)
+ return retval;
+
+ return retval != ARRAY_SIZE(msgs) ? -EIO : 0;
+}
+EXPORT_SYMBOL_GPL(cyttsp_i2c_read_block_data);
+
+int cyttsp_i2c_write_block_data(struct device *dev, u8 *xfer_buf,
+ u16 addr, u8 length, const void *values)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 client_addr = client->addr | ((addr >> 8) & 0x1);
+ u8 addr_lo = addr & 0xFF;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client_addr,
+ .flags = 0,
+ .len = length + 1,
+ .buf = xfer_buf,
+ },
+ };
+ int retval;
+
+ xfer_buf[0] = addr_lo;
+ memcpy(&xfer_buf[1], values, length);
+
+ retval = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (retval < 0)
+ return retval;
+
+ return retval != ARRAY_SIZE(msgs) ? -EIO : 0;
+}
+EXPORT_SYMBOL_GPL(cyttsp_i2c_write_block_data);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cypress");
diff --git a/drivers/input/touchscreen/cyttsp_spi.c b/drivers/input/touchscreen/cyttsp_spi.c
new file mode 100644
index 000000000..30c6fbf86
--- /dev/null
+++ b/drivers/input/touchscreen/cyttsp_spi.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Source for:
+ * Cypress TrueTouch(TM) Standard Product (TTSP) SPI touchscreen driver.
+ * For use with Cypress Txx3xx parts.
+ * Supported parts include:
+ * CY8CTST341
+ * CY8CTMA340
+ *
+ * Copyright (C) 2009, 2010, 2011 Cypress Semiconductor, Inc.
+ * Copyright (C) 2012 Javier Martinez Canillas <javier@dowhile0.org>
+ * Copyright (C) 2013 Cypress Semiconductor
+ *
+ * Contact Cypress Semiconductor at www.cypress.com <ttdrivers@cypress.com>
+ */
+
+#include "cyttsp_core.h"
+
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/spi/spi.h>
+
+#define CY_SPI_NAME "cyttsp-spi"
+
+#define CY_SPI_WR_OP 0x00 /* r/~w */
+#define CY_SPI_RD_OP 0x01
+#define CY_SPI_CMD_BYTES 4
+#define CY_SPI_SYNC_BYTE 2
+#define CY_SPI_SYNC_ACK1 0x62 /* from protocol v.2 */
+#define CY_SPI_SYNC_ACK2 0x9D /* from protocol v.2 */
+#define CY_SPI_DATA_SIZE 128
+#define CY_SPI_DATA_BUF_SIZE (CY_SPI_CMD_BYTES + CY_SPI_DATA_SIZE)
+#define CY_SPI_BITS_PER_WORD 8
+
+static int cyttsp_spi_xfer(struct device *dev, u8 *xfer_buf,
+ u8 op, u16 reg, u8 *buf, int length)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_message msg;
+ struct spi_transfer xfer[2];
+ u8 *wr_buf = &xfer_buf[0];
+ u8 *rd_buf = &xfer_buf[CY_SPI_DATA_BUF_SIZE];
+ int retval;
+ int i;
+
+ if (length > CY_SPI_DATA_SIZE) {
+ dev_err(dev, "%s: length %d is too big.\n",
+ __func__, length);
+ return -EINVAL;
+ }
+
+ memset(wr_buf, 0, CY_SPI_DATA_BUF_SIZE);
+ memset(rd_buf, 0, CY_SPI_DATA_BUF_SIZE);
+
+ wr_buf[0] = 0x00; /* header byte 0 */
+ wr_buf[1] = 0xFF; /* header byte 1 */
+ wr_buf[2] = reg; /* reg index */
+ wr_buf[3] = op; /* r/~w */
+ if (op == CY_SPI_WR_OP)
+ memcpy(wr_buf + CY_SPI_CMD_BYTES, buf, length);
+
+ memset(xfer, 0, sizeof(xfer));
+ spi_message_init(&msg);
+
+ /*
+ We set both TX and RX buffers because Cypress TTSP
+ requires full duplex operation.
+ */
+ xfer[0].tx_buf = wr_buf;
+ xfer[0].rx_buf = rd_buf;
+ switch (op) {
+ case CY_SPI_WR_OP:
+ xfer[0].len = length + CY_SPI_CMD_BYTES;
+ spi_message_add_tail(&xfer[0], &msg);
+ break;
+
+ case CY_SPI_RD_OP:
+ xfer[0].len = CY_SPI_CMD_BYTES;
+ spi_message_add_tail(&xfer[0], &msg);
+
+ xfer[1].rx_buf = buf;
+ xfer[1].len = length;
+ spi_message_add_tail(&xfer[1], &msg);
+ break;
+
+ default:
+ dev_err(dev, "%s: bad operation code=%d\n", __func__, op);
+ return -EINVAL;
+ }
+
+ retval = spi_sync(spi, &msg);
+ if (retval < 0) {
+ dev_dbg(dev, "%s: spi_sync() error %d, len=%d, op=%d\n",
+ __func__, retval, xfer[1].len, op);
+
+ /*
+ * do not return here since was a bad ACK sequence
+ * let the following ACK check handle any errors and
+ * allow silent retries
+ */
+ }
+
+ if (rd_buf[CY_SPI_SYNC_BYTE] != CY_SPI_SYNC_ACK1 ||
+ rd_buf[CY_SPI_SYNC_BYTE + 1] != CY_SPI_SYNC_ACK2) {
+ dev_dbg(dev, "%s: operation %d failed\n", __func__, op);
+
+ for (i = 0; i < CY_SPI_CMD_BYTES; i++)
+ dev_dbg(dev, "%s: test rd_buf[%d]:0x%02x\n",
+ __func__, i, rd_buf[i]);
+ for (i = 0; i < length; i++)
+ dev_dbg(dev, "%s: test buf[%d]:0x%02x\n",
+ __func__, i, buf[i]);
+
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cyttsp_spi_read_block_data(struct device *dev, u8 *xfer_buf,
+ u16 addr, u8 length, void *data)
+{
+ return cyttsp_spi_xfer(dev, xfer_buf, CY_SPI_RD_OP, addr, data,
+ length);
+}
+
+static int cyttsp_spi_write_block_data(struct device *dev, u8 *xfer_buf,
+ u16 addr, u8 length, const void *data)
+{
+ return cyttsp_spi_xfer(dev, xfer_buf, CY_SPI_WR_OP, addr, (void *)data,
+ length);
+}
+
+static const struct cyttsp_bus_ops cyttsp_spi_bus_ops = {
+ .bustype = BUS_SPI,
+ .write = cyttsp_spi_write_block_data,
+ .read = cyttsp_spi_read_block_data,
+};
+
+static int cyttsp_spi_probe(struct spi_device *spi)
+{
+ struct cyttsp *ts;
+ int error;
+
+ /* Set up SPI*/
+ spi->bits_per_word = CY_SPI_BITS_PER_WORD;
+ spi->mode = SPI_MODE_0;
+ error = spi_setup(spi);
+ if (error < 0) {
+ dev_err(&spi->dev, "%s: SPI setup error %d\n",
+ __func__, error);
+ return error;
+ }
+
+ ts = cyttsp_probe(&cyttsp_spi_bus_ops, &spi->dev, spi->irq,
+ CY_SPI_DATA_BUF_SIZE * 2);
+ if (IS_ERR(ts))
+ return PTR_ERR(ts);
+
+ spi_set_drvdata(spi, ts);
+
+ return 0;
+}
+
+static const struct of_device_id cyttsp_of_spi_match[] = {
+ { .compatible = "cypress,cy8ctma340", },
+ { .compatible = "cypress,cy8ctst341", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cyttsp_of_spi_match);
+
+static struct spi_driver cyttsp_spi_driver = {
+ .driver = {
+ .name = CY_SPI_NAME,
+ .pm = &cyttsp_pm_ops,
+ .of_match_table = cyttsp_of_spi_match,
+ },
+ .probe = cyttsp_spi_probe,
+};
+
+module_spi_driver(cyttsp_spi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard Product (TTSP) SPI driver");
+MODULE_AUTHOR("Cypress");
+MODULE_ALIAS("spi:cyttsp");
diff --git a/drivers/input/touchscreen/da9034-ts.c b/drivers/input/touchscreen/da9034-ts.c
new file mode 100644
index 000000000..2943f6a58
--- /dev/null
+++ b/drivers/input/touchscreen/da9034-ts.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Touchscreen driver for Dialog Semiconductor DA9034
+ *
+ * Copyright (C) 2006-2008 Marvell International Ltd.
+ * Fengwei Yin <fengwei.yin@marvell.com>
+ * Bin Yang <bin.yang@marvell.com>
+ * Eric Miao <eric.miao@marvell.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/da903x.h>
+#include <linux/slab.h>
+
+#define DA9034_MANUAL_CTRL 0x50
+#define DA9034_LDO_ADC_EN (1 << 4)
+
+#define DA9034_AUTO_CTRL1 0x51
+
+#define DA9034_AUTO_CTRL2 0x52
+#define DA9034_AUTO_TSI_EN (1 << 3)
+#define DA9034_PEN_DETECT (1 << 4)
+
+#define DA9034_TSI_CTRL1 0x53
+#define DA9034_TSI_CTRL2 0x54
+#define DA9034_TSI_X_MSB 0x6c
+#define DA9034_TSI_Y_MSB 0x6d
+#define DA9034_TSI_XY_LSB 0x6e
+
+enum {
+ STATE_IDLE, /* wait for pendown */
+ STATE_BUSY, /* TSI busy sampling */
+ STATE_STOP, /* sample available */
+ STATE_WAIT, /* Wait to start next sample */
+};
+
+enum {
+ EVENT_PEN_DOWN,
+ EVENT_PEN_UP,
+ EVENT_TSI_READY,
+ EVENT_TIMEDOUT,
+};
+
+struct da9034_touch {
+ struct device *da9034_dev;
+ struct input_dev *input_dev;
+
+ struct delayed_work tsi_work;
+ struct notifier_block notifier;
+
+ int state;
+
+ int interval_ms;
+ int x_inverted;
+ int y_inverted;
+
+ int last_x;
+ int last_y;
+};
+
+static inline int is_pen_down(struct da9034_touch *touch)
+{
+ return da903x_query_status(touch->da9034_dev, DA9034_STATUS_PEN_DOWN);
+}
+
+static inline int detect_pen_down(struct da9034_touch *touch, int on)
+{
+ if (on)
+ return da903x_set_bits(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
+ else
+ return da903x_clr_bits(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
+}
+
+static int read_tsi(struct da9034_touch *touch)
+{
+ uint8_t _x, _y, _v;
+ int ret;
+
+ ret = da903x_read(touch->da9034_dev, DA9034_TSI_X_MSB, &_x);
+ if (ret)
+ return ret;
+
+ ret = da903x_read(touch->da9034_dev, DA9034_TSI_Y_MSB, &_y);
+ if (ret)
+ return ret;
+
+ ret = da903x_read(touch->da9034_dev, DA9034_TSI_XY_LSB, &_v);
+ if (ret)
+ return ret;
+
+ touch->last_x = ((_x << 2) & 0x3fc) | (_v & 0x3);
+ touch->last_y = ((_y << 2) & 0x3fc) | ((_v & 0xc) >> 2);
+
+ return 0;
+}
+
+static inline int start_tsi(struct da9034_touch *touch)
+{
+ return da903x_set_bits(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
+}
+
+static inline int stop_tsi(struct da9034_touch *touch)
+{
+ return da903x_clr_bits(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
+}
+
+static inline void report_pen_down(struct da9034_touch *touch)
+{
+ int x = touch->last_x;
+ int y = touch->last_y;
+
+ x &= 0xfff;
+ if (touch->x_inverted)
+ x = 1024 - x;
+ y &= 0xfff;
+ if (touch->y_inverted)
+ y = 1024 - y;
+
+ input_report_abs(touch->input_dev, ABS_X, x);
+ input_report_abs(touch->input_dev, ABS_Y, y);
+ input_report_key(touch->input_dev, BTN_TOUCH, 1);
+
+ input_sync(touch->input_dev);
+}
+
+static inline void report_pen_up(struct da9034_touch *touch)
+{
+ input_report_key(touch->input_dev, BTN_TOUCH, 0);
+ input_sync(touch->input_dev);
+}
+
+static void da9034_event_handler(struct da9034_touch *touch, int event)
+{
+ int err;
+
+ switch (touch->state) {
+ case STATE_IDLE:
+ if (event != EVENT_PEN_DOWN)
+ break;
+
+ /* Enable auto measurement of the TSI, this will
+ * automatically disable pen down detection
+ */
+ err = start_tsi(touch);
+ if (err)
+ goto err_reset;
+
+ touch->state = STATE_BUSY;
+ break;
+
+ case STATE_BUSY:
+ if (event != EVENT_TSI_READY)
+ break;
+
+ err = read_tsi(touch);
+ if (err)
+ goto err_reset;
+
+ /* Disable auto measurement of the TSI, so that
+ * pen down status will be available
+ */
+ err = stop_tsi(touch);
+ if (err)
+ goto err_reset;
+
+ touch->state = STATE_STOP;
+
+ /* FIXME: PEN_{UP/DOWN} events are expected to be
+ * available by stopping TSI, but this is found not
+ * always true, delay and simulate such an event
+ * here is more reliable
+ */
+ mdelay(1);
+ da9034_event_handler(touch,
+ is_pen_down(touch) ? EVENT_PEN_DOWN :
+ EVENT_PEN_UP);
+ break;
+
+ case STATE_STOP:
+ if (event == EVENT_PEN_DOWN) {
+ report_pen_down(touch);
+ schedule_delayed_work(&touch->tsi_work,
+ msecs_to_jiffies(touch->interval_ms));
+ touch->state = STATE_WAIT;
+ }
+
+ if (event == EVENT_PEN_UP) {
+ report_pen_up(touch);
+ touch->state = STATE_IDLE;
+ }
+ break;
+
+ case STATE_WAIT:
+ if (event != EVENT_TIMEDOUT)
+ break;
+
+ if (is_pen_down(touch)) {
+ start_tsi(touch);
+ touch->state = STATE_BUSY;
+ } else {
+ report_pen_up(touch);
+ touch->state = STATE_IDLE;
+ }
+ break;
+ }
+ return;
+
+err_reset:
+ touch->state = STATE_IDLE;
+ stop_tsi(touch);
+ detect_pen_down(touch, 1);
+}
+
+static void da9034_tsi_work(struct work_struct *work)
+{
+ struct da9034_touch *touch =
+ container_of(work, struct da9034_touch, tsi_work.work);
+
+ da9034_event_handler(touch, EVENT_TIMEDOUT);
+}
+
+static int da9034_touch_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct da9034_touch *touch =
+ container_of(nb, struct da9034_touch, notifier);
+
+ if (event & DA9034_EVENT_TSI_READY)
+ da9034_event_handler(touch, EVENT_TSI_READY);
+
+ if ((event & DA9034_EVENT_PEN_DOWN) && touch->state == STATE_IDLE)
+ da9034_event_handler(touch, EVENT_PEN_DOWN);
+
+ return 0;
+}
+
+static int da9034_touch_open(struct input_dev *dev)
+{
+ struct da9034_touch *touch = input_get_drvdata(dev);
+ int ret;
+
+ ret = da903x_register_notifier(touch->da9034_dev, &touch->notifier,
+ DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
+ if (ret)
+ return -EBUSY;
+
+ /* Enable ADC LDO */
+ ret = da903x_set_bits(touch->da9034_dev,
+ DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
+ if (ret)
+ return ret;
+
+ /* TSI_DELAY: 3 slots, TSI_SKIP: 3 slots */
+ ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL1, 0x1b);
+ if (ret)
+ return ret;
+
+ ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL2, 0x00);
+ if (ret)
+ return ret;
+
+ touch->state = STATE_IDLE;
+ detect_pen_down(touch, 1);
+
+ return 0;
+}
+
+static void da9034_touch_close(struct input_dev *dev)
+{
+ struct da9034_touch *touch = input_get_drvdata(dev);
+
+ da903x_unregister_notifier(touch->da9034_dev, &touch->notifier,
+ DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
+
+ cancel_delayed_work_sync(&touch->tsi_work);
+
+ touch->state = STATE_IDLE;
+ stop_tsi(touch);
+ detect_pen_down(touch, 0);
+
+ /* Disable ADC LDO */
+ da903x_clr_bits(touch->da9034_dev,
+ DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
+}
+
+
+static int da9034_touch_probe(struct platform_device *pdev)
+{
+ struct da9034_touch_pdata *pdata = dev_get_platdata(&pdev->dev);
+ struct da9034_touch *touch;
+ struct input_dev *input_dev;
+ int error;
+
+ touch = devm_kzalloc(&pdev->dev, sizeof(struct da9034_touch),
+ GFP_KERNEL);
+ if (!touch) {
+ dev_err(&pdev->dev, "failed to allocate driver data\n");
+ return -ENOMEM;
+ }
+
+ touch->da9034_dev = pdev->dev.parent;
+
+ if (pdata) {
+ touch->interval_ms = pdata->interval_ms;
+ touch->x_inverted = pdata->x_inverted;
+ touch->y_inverted = pdata->y_inverted;
+ } else {
+ /* fallback into default */
+ touch->interval_ms = 10;
+ }
+
+ INIT_DELAYED_WORK(&touch->tsi_work, da9034_tsi_work);
+ touch->notifier.notifier_call = da9034_touch_notifier;
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev) {
+ dev_err(&pdev->dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = pdev->name;
+ input_dev->open = da9034_touch_open;
+ input_dev->close = da9034_touch_close;
+ input_dev->dev.parent = &pdev->dev;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(ABS_X, input_dev->absbit);
+ __set_bit(ABS_Y, input_dev->absbit);
+ input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
+
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+
+ touch->input_dev = input_dev;
+ input_set_drvdata(input_dev, touch);
+
+ error = input_register_device(input_dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static struct platform_driver da9034_touch_driver = {
+ .driver = {
+ .name = "da9034-touch",
+ },
+ .probe = da9034_touch_probe,
+};
+module_platform_driver(da9034_touch_driver);
+
+MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9034");
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>, Bin Yang <bin.yang@marvell.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9034-touch");
diff --git a/drivers/input/touchscreen/da9052_tsi.c b/drivers/input/touchscreen/da9052_tsi.c
new file mode 100644
index 000000000..f91d0e02d
--- /dev/null
+++ b/drivers/input/touchscreen/da9052_tsi.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * TSI driver for Dialog DA9052
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.com>
+ */
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+#include <linux/mfd/da9052/reg.h>
+#include <linux/mfd/da9052/da9052.h>
+
+#define TSI_PEN_DOWN_STATUS 0x40
+
+struct da9052_tsi {
+ struct da9052 *da9052;
+ struct input_dev *dev;
+ struct delayed_work ts_pen_work;
+ bool stopped;
+ bool adc_on;
+};
+
+static void da9052_ts_adc_toggle(struct da9052_tsi *tsi, bool on)
+{
+ da9052_reg_update(tsi->da9052, DA9052_TSI_CONT_A_REG, 1 << 0, on);
+ tsi->adc_on = on;
+}
+
+static irqreturn_t da9052_ts_pendwn_irq(int irq, void *data)
+{
+ struct da9052_tsi *tsi = data;
+
+ if (!tsi->stopped) {
+ /* Mask PEN_DOWN event and unmask TSI_READY event */
+ da9052_disable_irq_nosync(tsi->da9052, DA9052_IRQ_PENDOWN);
+ da9052_enable_irq(tsi->da9052, DA9052_IRQ_TSIREADY);
+
+ da9052_ts_adc_toggle(tsi, true);
+
+ schedule_delayed_work(&tsi->ts_pen_work, HZ / 50);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void da9052_ts_read(struct da9052_tsi *tsi)
+{
+ struct input_dev *input = tsi->dev;
+ int ret;
+ u16 x, y, z;
+ u8 v;
+
+ ret = da9052_reg_read(tsi->da9052, DA9052_TSI_X_MSB_REG);
+ if (ret < 0)
+ return;
+
+ x = (u16) ret;
+
+ ret = da9052_reg_read(tsi->da9052, DA9052_TSI_Y_MSB_REG);
+ if (ret < 0)
+ return;
+
+ y = (u16) ret;
+
+ ret = da9052_reg_read(tsi->da9052, DA9052_TSI_Z_MSB_REG);
+ if (ret < 0)
+ return;
+
+ z = (u16) ret;
+
+ ret = da9052_reg_read(tsi->da9052, DA9052_TSI_LSB_REG);
+ if (ret < 0)
+ return;
+
+ v = (u8) ret;
+
+ x = ((x << 2) & 0x3fc) | (v & 0x3);
+ y = ((y << 2) & 0x3fc) | ((v & 0xc) >> 2);
+ z = ((z << 2) & 0x3fc) | ((v & 0x30) >> 4);
+
+ input_report_key(input, BTN_TOUCH, 1);
+ input_report_abs(input, ABS_X, x);
+ input_report_abs(input, ABS_Y, y);
+ input_report_abs(input, ABS_PRESSURE, z);
+ input_sync(input);
+}
+
+static irqreturn_t da9052_ts_datardy_irq(int irq, void *data)
+{
+ struct da9052_tsi *tsi = data;
+
+ da9052_ts_read(tsi);
+
+ return IRQ_HANDLED;
+}
+
+static void da9052_ts_pen_work(struct work_struct *work)
+{
+ struct da9052_tsi *tsi = container_of(work, struct da9052_tsi,
+ ts_pen_work.work);
+ if (!tsi->stopped) {
+ int ret = da9052_reg_read(tsi->da9052, DA9052_TSI_LSB_REG);
+ if (ret < 0 || (ret & TSI_PEN_DOWN_STATUS)) {
+ /* Pen is still DOWN (or read error) */
+ schedule_delayed_work(&tsi->ts_pen_work, HZ / 50);
+ } else {
+ struct input_dev *input = tsi->dev;
+
+ /* Pen UP */
+ da9052_ts_adc_toggle(tsi, false);
+
+ /* Report Pen UP */
+ input_report_key(input, BTN_TOUCH, 0);
+ input_report_abs(input, ABS_PRESSURE, 0);
+ input_sync(input);
+
+ /*
+ * FIXME: Fixes the unhandled irq issue when quick
+ * pen down and pen up events occurs
+ */
+ ret = da9052_reg_update(tsi->da9052,
+ DA9052_EVENT_B_REG, 0xC0, 0xC0);
+ if (ret < 0)
+ return;
+
+ /* Mask TSI_READY event and unmask PEN_DOWN event */
+ da9052_disable_irq(tsi->da9052, DA9052_IRQ_TSIREADY);
+ da9052_enable_irq(tsi->da9052, DA9052_IRQ_PENDOWN);
+ }
+ }
+}
+
+static int da9052_ts_configure_gpio(struct da9052 *da9052)
+{
+ int error;
+
+ error = da9052_reg_update(da9052, DA9052_GPIO_2_3_REG, 0x30, 0);
+ if (error < 0)
+ return error;
+
+ error = da9052_reg_update(da9052, DA9052_GPIO_4_5_REG, 0x33, 0);
+ if (error < 0)
+ return error;
+
+ error = da9052_reg_update(da9052, DA9052_GPIO_6_7_REG, 0x33, 0);
+ if (error < 0)
+ return error;
+
+ return 0;
+}
+
+static int da9052_configure_tsi(struct da9052_tsi *tsi)
+{
+ int error;
+
+ error = da9052_ts_configure_gpio(tsi->da9052);
+ if (error)
+ return error;
+
+ /* Measure TSI sample every 1ms */
+ error = da9052_reg_update(tsi->da9052, DA9052_ADC_CONT_REG,
+ 1 << 6, 1 << 6);
+ if (error < 0)
+ return error;
+
+ /* TSI_DELAY: 3 slots, TSI_SKIP: 0 slots, TSI_MODE: XYZP */
+ error = da9052_reg_update(tsi->da9052, DA9052_TSI_CONT_A_REG, 0xFC, 0xC0);
+ if (error < 0)
+ return error;
+
+ /* Supply TSIRef through LD09 */
+ error = da9052_reg_write(tsi->da9052, DA9052_LDO9_REG, 0x59);
+ if (error < 0)
+ return error;
+
+ return 0;
+}
+
+static int da9052_ts_input_open(struct input_dev *input_dev)
+{
+ struct da9052_tsi *tsi = input_get_drvdata(input_dev);
+
+ tsi->stopped = false;
+ mb();
+
+ /* Unmask PEN_DOWN event */
+ da9052_enable_irq(tsi->da9052, DA9052_IRQ_PENDOWN);
+
+ /* Enable Pen Detect Circuit */
+ return da9052_reg_update(tsi->da9052, DA9052_TSI_CONT_A_REG,
+ 1 << 1, 1 << 1);
+}
+
+static void da9052_ts_input_close(struct input_dev *input_dev)
+{
+ struct da9052_tsi *tsi = input_get_drvdata(input_dev);
+
+ tsi->stopped = true;
+ mb();
+ da9052_disable_irq(tsi->da9052, DA9052_IRQ_PENDOWN);
+ cancel_delayed_work_sync(&tsi->ts_pen_work);
+
+ if (tsi->adc_on) {
+ da9052_disable_irq(tsi->da9052, DA9052_IRQ_TSIREADY);
+ da9052_ts_adc_toggle(tsi, false);
+
+ /*
+ * If ADC was on that means that pendwn IRQ was disabled
+ * twice and we need to enable it to keep enable/disable
+ * counter balanced. IRQ is still off though.
+ */
+ da9052_enable_irq(tsi->da9052, DA9052_IRQ_PENDOWN);
+ }
+
+ /* Disable Pen Detect Circuit */
+ da9052_reg_update(tsi->da9052, DA9052_TSI_CONT_A_REG, 1 << 1, 0);
+}
+
+static int da9052_ts_probe(struct platform_device *pdev)
+{
+ struct da9052 *da9052;
+ struct da9052_tsi *tsi;
+ struct input_dev *input_dev;
+ int error;
+
+ da9052 = dev_get_drvdata(pdev->dev.parent);
+ if (!da9052)
+ return -EINVAL;
+
+ tsi = kzalloc(sizeof(struct da9052_tsi), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!tsi || !input_dev) {
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ tsi->da9052 = da9052;
+ tsi->dev = input_dev;
+ tsi->stopped = true;
+ INIT_DELAYED_WORK(&tsi->ts_pen_work, da9052_ts_pen_work);
+
+ input_dev->id.version = 0x0101;
+ input_dev->id.vendor = 0x15B6;
+ input_dev->id.product = 0x9052;
+ input_dev->name = "Dialog DA9052 TouchScreen Driver";
+ input_dev->dev.parent = &pdev->dev;
+ input_dev->open = da9052_ts_input_open;
+ input_dev->close = da9052_ts_input_close;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1023, 0, 0);
+
+ input_set_drvdata(input_dev, tsi);
+
+ /* Disable Pen Detect Circuit */
+ da9052_reg_update(tsi->da9052, DA9052_TSI_CONT_A_REG, 1 << 1, 0);
+
+ /* Disable ADC */
+ da9052_ts_adc_toggle(tsi, false);
+
+ error = da9052_request_irq(tsi->da9052, DA9052_IRQ_PENDOWN,
+ "pendown-irq", da9052_ts_pendwn_irq, tsi);
+ if (error) {
+ dev_err(tsi->da9052->dev,
+ "Failed to register PENDWN IRQ: %d\n", error);
+ goto err_free_mem;
+ }
+
+ error = da9052_request_irq(tsi->da9052, DA9052_IRQ_TSIREADY,
+ "tsiready-irq", da9052_ts_datardy_irq, tsi);
+ if (error) {
+ dev_err(tsi->da9052->dev,
+ "Failed to register TSIRDY IRQ :%d\n", error);
+ goto err_free_pendwn_irq;
+ }
+
+ /* Mask PEN_DOWN and TSI_READY events */
+ da9052_disable_irq(tsi->da9052, DA9052_IRQ_PENDOWN);
+ da9052_disable_irq(tsi->da9052, DA9052_IRQ_TSIREADY);
+
+ error = da9052_configure_tsi(tsi);
+ if (error)
+ goto err_free_datardy_irq;
+
+ error = input_register_device(tsi->dev);
+ if (error)
+ goto err_free_datardy_irq;
+
+ platform_set_drvdata(pdev, tsi);
+
+ return 0;
+
+err_free_datardy_irq:
+ da9052_free_irq(tsi->da9052, DA9052_IRQ_TSIREADY, tsi);
+err_free_pendwn_irq:
+ da9052_free_irq(tsi->da9052, DA9052_IRQ_PENDOWN, tsi);
+err_free_mem:
+ kfree(tsi);
+ input_free_device(input_dev);
+
+ return error;
+}
+
+static int da9052_ts_remove(struct platform_device *pdev)
+{
+ struct da9052_tsi *tsi = platform_get_drvdata(pdev);
+
+ da9052_reg_write(tsi->da9052, DA9052_LDO9_REG, 0x19);
+
+ da9052_free_irq(tsi->da9052, DA9052_IRQ_TSIREADY, tsi);
+ da9052_free_irq(tsi->da9052, DA9052_IRQ_PENDOWN, tsi);
+
+ input_unregister_device(tsi->dev);
+ kfree(tsi);
+
+ return 0;
+}
+
+static struct platform_driver da9052_tsi_driver = {
+ .probe = da9052_ts_probe,
+ .remove = da9052_ts_remove,
+ .driver = {
+ .name = "da9052-tsi",
+ },
+};
+
+module_platform_driver(da9052_tsi_driver);
+
+MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9052");
+MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9052-tsi");
diff --git a/drivers/input/touchscreen/dynapro.c b/drivers/input/touchscreen/dynapro.c
new file mode 100644
index 000000000..dc07fca7c
--- /dev/null
+++ b/drivers/input/touchscreen/dynapro.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Dynapro serial touchscreen driver
+ *
+ * Copyright (c) 2009 Tias Guns
+ * Based on the inexio driver (c) Vojtech Pavlik and Dan Streetman and
+ * Richard Lemon
+ */
+
+
+/*
+ * 2009/09/19 Tias Guns <tias@ulyssis.org>
+ * Copied inexio.c and edited for Dynapro protocol (from retired Xorg module)
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Dynapro serial touchscreen driver"
+
+MODULE_AUTHOR("Tias Guns <tias@ulyssis.org>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define DYNAPRO_FORMAT_TOUCH_BIT 0x40
+#define DYNAPRO_FORMAT_LENGTH 3
+#define DYNAPRO_RESPONSE_BEGIN_BYTE 0x80
+
+#define DYNAPRO_MIN_XC 0
+#define DYNAPRO_MAX_XC 0x3ff
+#define DYNAPRO_MIN_YC 0
+#define DYNAPRO_MAX_YC 0x3ff
+
+#define DYNAPRO_GET_XC(data) (data[1] | ((data[0] & 0x38) << 4))
+#define DYNAPRO_GET_YC(data) (data[2] | ((data[0] & 0x07) << 7))
+#define DYNAPRO_GET_TOUCHED(data) (DYNAPRO_FORMAT_TOUCH_BIT & data[0])
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct dynapro {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[DYNAPRO_FORMAT_LENGTH];
+ char phys[32];
+};
+
+static void dynapro_process_data(struct dynapro *pdynapro)
+{
+ struct input_dev *dev = pdynapro->dev;
+
+ if (DYNAPRO_FORMAT_LENGTH == ++pdynapro->idx) {
+ input_report_abs(dev, ABS_X, DYNAPRO_GET_XC(pdynapro->data));
+ input_report_abs(dev, ABS_Y, DYNAPRO_GET_YC(pdynapro->data));
+ input_report_key(dev, BTN_TOUCH,
+ DYNAPRO_GET_TOUCHED(pdynapro->data));
+ input_sync(dev);
+
+ pdynapro->idx = 0;
+ }
+}
+
+static irqreturn_t dynapro_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct dynapro *pdynapro = serio_get_drvdata(serio);
+
+ pdynapro->data[pdynapro->idx] = data;
+
+ if (DYNAPRO_RESPONSE_BEGIN_BYTE & pdynapro->data[0])
+ dynapro_process_data(pdynapro);
+ else
+ dev_dbg(&serio->dev, "unknown/unsynchronized data: %x\n",
+ pdynapro->data[0]);
+
+ return IRQ_HANDLED;
+}
+
+static void dynapro_disconnect(struct serio *serio)
+{
+ struct dynapro *pdynapro = serio_get_drvdata(serio);
+
+ input_get_device(pdynapro->dev);
+ input_unregister_device(pdynapro->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(pdynapro->dev);
+ kfree(pdynapro);
+}
+
+/*
+ * dynapro_connect() is the routine that is called when someone adds a
+ * new serio device that supports dynapro protocol and registers it as
+ * an input device. This is usually accomplished using inputattach.
+ */
+
+static int dynapro_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct dynapro *pdynapro;
+ struct input_dev *input_dev;
+ int err;
+
+ pdynapro = kzalloc(sizeof(struct dynapro), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!pdynapro || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ pdynapro->serio = serio;
+ pdynapro->dev = input_dev;
+ snprintf(pdynapro->phys, sizeof(pdynapro->phys),
+ "%s/input0", serio->phys);
+
+ input_dev->name = "Dynapro Serial TouchScreen";
+ input_dev->phys = pdynapro->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_DYNAPRO;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0001;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(pdynapro->dev, ABS_X,
+ DYNAPRO_MIN_XC, DYNAPRO_MAX_XC, 0, 0);
+ input_set_abs_params(pdynapro->dev, ABS_Y,
+ DYNAPRO_MIN_YC, DYNAPRO_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, pdynapro);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(pdynapro->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(pdynapro);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id dynapro_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_DYNAPRO,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, dynapro_serio_ids);
+
+static struct serio_driver dynapro_drv = {
+ .driver = {
+ .name = "dynapro",
+ },
+ .description = DRIVER_DESC,
+ .id_table = dynapro_serio_ids,
+ .interrupt = dynapro_interrupt,
+ .connect = dynapro_connect,
+ .disconnect = dynapro_disconnect,
+};
+
+module_serio_driver(dynapro_drv);
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
new file mode 100644
index 000000000..9ac137861
--- /dev/null
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -0,0 +1,1515 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
+ * Daniel Wagener <daniel.wagener@kernelconcepts.de> (M09 firmware support)
+ * Lothar Waßmann <LW@KARO-electronics.de> (DT support)
+ */
+
+/*
+ * This is a driver for the EDT "Polytouch" family of touch controllers
+ * based on the FocalTech FT5x06 line of chips.
+ *
+ * Development of this driver has been sponsored by Glyn:
+ * http://www.glyn.com/Products/Displays
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/ratelimit.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <asm/unaligned.h>
+
+#define WORK_REGISTER_THRESHOLD 0x00
+#define WORK_REGISTER_REPORT_RATE 0x08
+#define WORK_REGISTER_GAIN 0x30
+#define WORK_REGISTER_OFFSET 0x31
+#define WORK_REGISTER_NUM_X 0x33
+#define WORK_REGISTER_NUM_Y 0x34
+
+#define PMOD_REGISTER_ACTIVE 0x00
+#define PMOD_REGISTER_HIBERNATE 0x03
+
+#define M09_REGISTER_THRESHOLD 0x80
+#define M09_REGISTER_GAIN 0x92
+#define M09_REGISTER_OFFSET 0x93
+#define M09_REGISTER_NUM_X 0x94
+#define M09_REGISTER_NUM_Y 0x95
+
+#define M12_REGISTER_REPORT_RATE 0x88
+
+#define EV_REGISTER_THRESHOLD 0x40
+#define EV_REGISTER_GAIN 0x41
+#define EV_REGISTER_OFFSET_Y 0x45
+#define EV_REGISTER_OFFSET_X 0x46
+
+#define NO_REGISTER 0xff
+
+#define WORK_REGISTER_OPMODE 0x3c
+#define FACTORY_REGISTER_OPMODE 0x01
+#define PMOD_REGISTER_OPMODE 0xa5
+
+#define TOUCH_EVENT_DOWN 0x00
+#define TOUCH_EVENT_UP 0x01
+#define TOUCH_EVENT_ON 0x02
+#define TOUCH_EVENT_RESERVED 0x03
+
+#define EDT_NAME_LEN 23
+#define EDT_SWITCH_MODE_RETRIES 10
+#define EDT_SWITCH_MODE_DELAY 5 /* msec */
+#define EDT_RAW_DATA_RETRIES 100
+#define EDT_RAW_DATA_DELAY 1000 /* usec */
+
+#define EDT_DEFAULT_NUM_X 1024
+#define EDT_DEFAULT_NUM_Y 1024
+
+enum edt_pmode {
+ EDT_PMODE_NOT_SUPPORTED,
+ EDT_PMODE_HIBERNATE,
+ EDT_PMODE_POWEROFF,
+};
+
+enum edt_ver {
+ EDT_M06,
+ EDT_M09,
+ EDT_M12,
+ EV_FT,
+ GENERIC_FT,
+};
+
+struct edt_reg_addr {
+ int reg_threshold;
+ int reg_report_rate;
+ int reg_gain;
+ int reg_offset;
+ int reg_offset_x;
+ int reg_offset_y;
+ int reg_num_x;
+ int reg_num_y;
+};
+
+struct edt_ft5x06_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct touchscreen_properties prop;
+ u16 num_x;
+ u16 num_y;
+ struct regulator *vcc;
+ struct regulator *iovcc;
+
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *wake_gpio;
+
+#if defined(CONFIG_DEBUG_FS)
+ struct dentry *debug_dir;
+ u8 *raw_buffer;
+ size_t raw_bufsize;
+#endif
+
+ struct mutex mutex;
+ bool factory_mode;
+ enum edt_pmode suspend_mode;
+ int threshold;
+ int gain;
+ int offset;
+ int offset_x;
+ int offset_y;
+ int report_rate;
+ int max_support_points;
+
+ char name[EDT_NAME_LEN];
+ char fw_version[EDT_NAME_LEN];
+
+ struct edt_reg_addr reg_addr;
+ enum edt_ver version;
+ unsigned int crc_errors;
+ unsigned int header_errors;
+};
+
+struct edt_i2c_chip_data {
+ int max_support_points;
+};
+
+static int edt_ft5x06_ts_readwrite(struct i2c_client *client,
+ u16 wr_len, u8 *wr_buf,
+ u16 rd_len, u8 *rd_buf)
+{
+ struct i2c_msg wrmsg[2];
+ int i = 0;
+ int ret;
+
+ if (wr_len) {
+ wrmsg[i].addr = client->addr;
+ wrmsg[i].flags = 0;
+ wrmsg[i].len = wr_len;
+ wrmsg[i].buf = wr_buf;
+ i++;
+ }
+ if (rd_len) {
+ wrmsg[i].addr = client->addr;
+ wrmsg[i].flags = I2C_M_RD;
+ wrmsg[i].len = rd_len;
+ wrmsg[i].buf = rd_buf;
+ i++;
+ }
+
+ ret = i2c_transfer(client->adapter, wrmsg, i);
+ if (ret < 0)
+ return ret;
+ if (ret != i)
+ return -EIO;
+
+ return 0;
+}
+
+static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata,
+ u8 *buf, int buflen)
+{
+ int i;
+ u8 crc = 0;
+
+ for (i = 0; i < buflen - 1; i++)
+ crc ^= buf[i];
+
+ if (crc != buf[buflen-1]) {
+ tsdata->crc_errors++;
+ dev_err_ratelimited(&tsdata->client->dev,
+ "crc error: 0x%02x expected, got 0x%02x\n",
+ crc, buf[buflen-1]);
+ return false;
+ }
+
+ return true;
+}
+
+static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
+{
+ struct edt_ft5x06_ts_data *tsdata = dev_id;
+ struct device *dev = &tsdata->client->dev;
+ u8 cmd;
+ u8 rdbuf[63];
+ int i, type, x, y, id;
+ int offset, tplen, datalen, crclen;
+ int error;
+
+ switch (tsdata->version) {
+ case EDT_M06:
+ cmd = 0xf9; /* tell the controller to send touch data */
+ offset = 5; /* where the actual touch data starts */
+ tplen = 4; /* data comes in so called frames */
+ crclen = 1; /* length of the crc data */
+ break;
+
+ case EDT_M09:
+ case EDT_M12:
+ case EV_FT:
+ case GENERIC_FT:
+ cmd = 0x0;
+ offset = 3;
+ tplen = 6;
+ crclen = 0;
+ break;
+
+ default:
+ goto out;
+ }
+
+ memset(rdbuf, 0, sizeof(rdbuf));
+ datalen = tplen * tsdata->max_support_points + offset + crclen;
+
+ error = edt_ft5x06_ts_readwrite(tsdata->client,
+ sizeof(cmd), &cmd,
+ datalen, rdbuf);
+ if (error) {
+ dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
+ error);
+ goto out;
+ }
+
+ /* M09/M12 does not send header or CRC */
+ if (tsdata->version == EDT_M06) {
+ if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa ||
+ rdbuf[2] != datalen) {
+ tsdata->header_errors++;
+ dev_err_ratelimited(dev,
+ "Unexpected header: %02x%02x%02x!\n",
+ rdbuf[0], rdbuf[1], rdbuf[2]);
+ goto out;
+ }
+
+ if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen))
+ goto out;
+ }
+
+ for (i = 0; i < tsdata->max_support_points; i++) {
+ u8 *buf = &rdbuf[i * tplen + offset];
+
+ type = buf[0] >> 6;
+ /* ignore Reserved events */
+ if (type == TOUCH_EVENT_RESERVED)
+ continue;
+
+ /* M06 sometimes sends bogus coordinates in TOUCH_DOWN */
+ if (tsdata->version == EDT_M06 && type == TOUCH_EVENT_DOWN)
+ continue;
+
+ x = get_unaligned_be16(buf) & 0x0fff;
+ y = get_unaligned_be16(buf + 2) & 0x0fff;
+ /* The FT5x26 send the y coordinate first */
+ if (tsdata->version == EV_FT)
+ swap(x, y);
+
+ id = (buf[2] >> 4) & 0x0f;
+
+ input_mt_slot(tsdata->input, id);
+ if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER,
+ type != TOUCH_EVENT_UP))
+ touchscreen_report_pos(tsdata->input, &tsdata->prop,
+ x, y, true);
+ }
+
+ input_mt_report_pointer_emulation(tsdata->input, true);
+ input_sync(tsdata->input);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata,
+ u8 addr, u8 value)
+{
+ u8 wrbuf[4];
+
+ switch (tsdata->version) {
+ case EDT_M06:
+ wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
+ wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
+ wrbuf[2] = value;
+ wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2];
+ return edt_ft5x06_ts_readwrite(tsdata->client, 4,
+ wrbuf, 0, NULL);
+
+ case EDT_M09:
+ case EDT_M12:
+ case EV_FT:
+ case GENERIC_FT:
+ wrbuf[0] = addr;
+ wrbuf[1] = value;
+
+ return edt_ft5x06_ts_readwrite(tsdata->client, 2,
+ wrbuf, 0, NULL);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
+ u8 addr)
+{
+ u8 wrbuf[2], rdbuf[2];
+ int error;
+
+ switch (tsdata->version) {
+ case EDT_M06:
+ wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
+ wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
+ wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40;
+
+ error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2,
+ rdbuf);
+ if (error)
+ return error;
+
+ if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) {
+ dev_err(&tsdata->client->dev,
+ "crc error: 0x%02x expected, got 0x%02x\n",
+ wrbuf[0] ^ wrbuf[1] ^ rdbuf[0],
+ rdbuf[1]);
+ return -EIO;
+ }
+ break;
+
+ case EDT_M09:
+ case EDT_M12:
+ case EV_FT:
+ case GENERIC_FT:
+ wrbuf[0] = addr;
+ error = edt_ft5x06_ts_readwrite(tsdata->client, 1,
+ wrbuf, 1, rdbuf);
+ if (error)
+ return error;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return rdbuf[0];
+}
+
+struct edt_ft5x06_attribute {
+ struct device_attribute dattr;
+ size_t field_offset;
+ u8 limit_low;
+ u8 limit_high;
+ u8 addr_m06;
+ u8 addr_m09;
+ u8 addr_ev;
+};
+
+#define EDT_ATTR(_field, _mode, _addr_m06, _addr_m09, _addr_ev, \
+ _limit_low, _limit_high) \
+ struct edt_ft5x06_attribute edt_ft5x06_attr_##_field = { \
+ .dattr = __ATTR(_field, _mode, \
+ edt_ft5x06_setting_show, \
+ edt_ft5x06_setting_store), \
+ .field_offset = offsetof(struct edt_ft5x06_ts_data, _field), \
+ .addr_m06 = _addr_m06, \
+ .addr_m09 = _addr_m09, \
+ .addr_ev = _addr_ev, \
+ .limit_low = _limit_low, \
+ .limit_high = _limit_high, \
+ }
+
+static ssize_t edt_ft5x06_setting_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_attribute *attr =
+ container_of(dattr, struct edt_ft5x06_attribute, dattr);
+ u8 *field = (u8 *)tsdata + attr->field_offset;
+ int val;
+ size_t count = 0;
+ int error = 0;
+ u8 addr;
+
+ mutex_lock(&tsdata->mutex);
+
+ if (tsdata->factory_mode) {
+ error = -EIO;
+ goto out;
+ }
+
+ switch (tsdata->version) {
+ case EDT_M06:
+ addr = attr->addr_m06;
+ break;
+
+ case EDT_M09:
+ case EDT_M12:
+ case GENERIC_FT:
+ addr = attr->addr_m09;
+ break;
+
+ case EV_FT:
+ addr = attr->addr_ev;
+ break;
+
+ default:
+ error = -ENODEV;
+ goto out;
+ }
+
+ if (addr != NO_REGISTER) {
+ val = edt_ft5x06_register_read(tsdata, addr);
+ if (val < 0) {
+ error = val;
+ dev_err(&tsdata->client->dev,
+ "Failed to fetch attribute %s, error %d\n",
+ dattr->attr.name, error);
+ goto out;
+ }
+ } else {
+ val = *field;
+ }
+
+ if (val != *field) {
+ dev_warn(&tsdata->client->dev,
+ "%s: read (%d) and stored value (%d) differ\n",
+ dattr->attr.name, val, *field);
+ *field = val;
+ }
+
+ count = scnprintf(buf, PAGE_SIZE, "%d\n", val);
+out:
+ mutex_unlock(&tsdata->mutex);
+ return error ?: count;
+}
+
+static ssize_t edt_ft5x06_setting_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_attribute *attr =
+ container_of(dattr, struct edt_ft5x06_attribute, dattr);
+ u8 *field = (u8 *)tsdata + attr->field_offset;
+ unsigned int val;
+ int error;
+ u8 addr;
+
+ mutex_lock(&tsdata->mutex);
+
+ if (tsdata->factory_mode) {
+ error = -EIO;
+ goto out;
+ }
+
+ error = kstrtouint(buf, 0, &val);
+ if (error)
+ goto out;
+
+ if (val < attr->limit_low || val > attr->limit_high) {
+ error = -ERANGE;
+ goto out;
+ }
+
+ switch (tsdata->version) {
+ case EDT_M06:
+ addr = attr->addr_m06;
+ break;
+
+ case EDT_M09:
+ case EDT_M12:
+ case GENERIC_FT:
+ addr = attr->addr_m09;
+ break;
+
+ case EV_FT:
+ addr = attr->addr_ev;
+ break;
+
+ default:
+ error = -ENODEV;
+ goto out;
+ }
+
+ if (addr != NO_REGISTER) {
+ error = edt_ft5x06_register_write(tsdata, addr, val);
+ if (error) {
+ dev_err(&tsdata->client->dev,
+ "Failed to update attribute %s, error: %d\n",
+ dattr->attr.name, error);
+ goto out;
+ }
+ }
+ *field = val;
+
+out:
+ mutex_unlock(&tsdata->mutex);
+ return error ?: count;
+}
+
+/* m06, m09: range 0-31, m12: range 0-5 */
+static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN,
+ M09_REGISTER_GAIN, EV_REGISTER_GAIN, 0, 31);
+/* m06, m09: range 0-31, m12: range 0-16 */
+static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET,
+ M09_REGISTER_OFFSET, NO_REGISTER, 0, 31);
+/* m06, m09, m12: no supported, ev_ft: range 0-80 */
+static EDT_ATTR(offset_x, S_IWUSR | S_IRUGO, NO_REGISTER, NO_REGISTER,
+ EV_REGISTER_OFFSET_X, 0, 80);
+/* m06, m09, m12: no supported, ev_ft: range 0-80 */
+static EDT_ATTR(offset_y, S_IWUSR | S_IRUGO, NO_REGISTER, NO_REGISTER,
+ EV_REGISTER_OFFSET_Y, 0, 80);
+/* m06: range 20 to 80, m09: range 0 to 30, m12: range 1 to 255... */
+static EDT_ATTR(threshold, S_IWUSR | S_IRUGO, WORK_REGISTER_THRESHOLD,
+ M09_REGISTER_THRESHOLD, EV_REGISTER_THRESHOLD, 0, 255);
+/* m06: range 3 to 14, m12: range 1 to 255 */
+static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, WORK_REGISTER_REPORT_RATE,
+ M12_REGISTER_REPORT_RATE, NO_REGISTER, 0, 255);
+
+static ssize_t model_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+
+ return sysfs_emit(buf, "%s\n", tsdata->name);
+}
+
+static DEVICE_ATTR_RO(model);
+
+static ssize_t fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+
+ return sysfs_emit(buf, "%s\n", tsdata->fw_version);
+}
+
+static DEVICE_ATTR_RO(fw_version);
+
+/* m06 only */
+static ssize_t header_errors_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+
+ return sysfs_emit(buf, "%d\n", tsdata->header_errors);
+}
+
+static DEVICE_ATTR_RO(header_errors);
+
+/* m06 only */
+static ssize_t crc_errors_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+
+ return sysfs_emit(buf, "%d\n", tsdata->crc_errors);
+}
+
+static DEVICE_ATTR_RO(crc_errors);
+
+static struct attribute *edt_ft5x06_attrs[] = {
+ &edt_ft5x06_attr_gain.dattr.attr,
+ &edt_ft5x06_attr_offset.dattr.attr,
+ &edt_ft5x06_attr_offset_x.dattr.attr,
+ &edt_ft5x06_attr_offset_y.dattr.attr,
+ &edt_ft5x06_attr_threshold.dattr.attr,
+ &edt_ft5x06_attr_report_rate.dattr.attr,
+ &dev_attr_model.attr,
+ &dev_attr_fw_version.attr,
+ &dev_attr_header_errors.attr,
+ &dev_attr_crc_errors.attr,
+ NULL
+};
+
+static const struct attribute_group edt_ft5x06_attr_group = {
+ .attrs = edt_ft5x06_attrs,
+};
+
+static void edt_ft5x06_restore_reg_parameters(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
+
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold,
+ tsdata->threshold);
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_gain,
+ tsdata->gain);
+ if (reg_addr->reg_offset != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_offset,
+ tsdata->offset);
+ if (reg_addr->reg_offset_x != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_x,
+ tsdata->offset_x);
+ if (reg_addr->reg_offset_y != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_y,
+ tsdata->offset_y);
+ if (reg_addr->reg_report_rate != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_report_rate,
+ tsdata->report_rate);
+
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct i2c_client *client = tsdata->client;
+ int retries = EDT_SWITCH_MODE_RETRIES;
+ int ret;
+ int error;
+
+ if (tsdata->version != EDT_M06) {
+ dev_err(&client->dev,
+ "No factory mode support for non-M06 devices\n");
+ return -EINVAL;
+ }
+
+ disable_irq(client->irq);
+
+ if (!tsdata->raw_buffer) {
+ tsdata->raw_bufsize = tsdata->num_x * tsdata->num_y *
+ sizeof(u16);
+ tsdata->raw_buffer = kzalloc(tsdata->raw_bufsize, GFP_KERNEL);
+ if (!tsdata->raw_buffer) {
+ error = -ENOMEM;
+ goto err_out;
+ }
+ }
+
+ /* mode register is 0x3c when in the work mode */
+ error = edt_ft5x06_register_write(tsdata, WORK_REGISTER_OPMODE, 0x03);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to switch to factory mode, error %d\n", error);
+ goto err_out;
+ }
+
+ tsdata->factory_mode = true;
+ do {
+ mdelay(EDT_SWITCH_MODE_DELAY);
+ /* mode register is 0x01 when in factory mode */
+ ret = edt_ft5x06_register_read(tsdata, FACTORY_REGISTER_OPMODE);
+ if (ret == 0x03)
+ break;
+ } while (--retries > 0);
+
+ if (retries == 0) {
+ dev_err(&client->dev, "not in factory mode after %dms.\n",
+ EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY);
+ error = -EIO;
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ kfree(tsdata->raw_buffer);
+ tsdata->raw_buffer = NULL;
+ tsdata->factory_mode = false;
+ enable_irq(client->irq);
+
+ return error;
+}
+
+static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct i2c_client *client = tsdata->client;
+ int retries = EDT_SWITCH_MODE_RETRIES;
+ int ret;
+ int error;
+
+ /* mode register is 0x01 when in the factory mode */
+ error = edt_ft5x06_register_write(tsdata, FACTORY_REGISTER_OPMODE, 0x1);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to switch to work mode, error: %d\n", error);
+ return error;
+ }
+
+ tsdata->factory_mode = false;
+
+ do {
+ mdelay(EDT_SWITCH_MODE_DELAY);
+ /* mode register is 0x01 when in factory mode */
+ ret = edt_ft5x06_register_read(tsdata, WORK_REGISTER_OPMODE);
+ if (ret == 0x01)
+ break;
+ } while (--retries > 0);
+
+ if (retries == 0) {
+ dev_err(&client->dev, "not in work mode after %dms.\n",
+ EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY);
+ tsdata->factory_mode = true;
+ return -EIO;
+ }
+
+ kfree(tsdata->raw_buffer);
+ tsdata->raw_buffer = NULL;
+
+ edt_ft5x06_restore_reg_parameters(tsdata);
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static int edt_ft5x06_debugfs_mode_get(void *data, u64 *mode)
+{
+ struct edt_ft5x06_ts_data *tsdata = data;
+
+ *mode = tsdata->factory_mode;
+
+ return 0;
+};
+
+static int edt_ft5x06_debugfs_mode_set(void *data, u64 mode)
+{
+ struct edt_ft5x06_ts_data *tsdata = data;
+ int retval = 0;
+
+ if (mode > 1)
+ return -ERANGE;
+
+ mutex_lock(&tsdata->mutex);
+
+ if (mode != tsdata->factory_mode) {
+ retval = mode ? edt_ft5x06_factory_mode(tsdata) :
+ edt_ft5x06_work_mode(tsdata);
+ }
+
+ mutex_unlock(&tsdata->mutex);
+
+ return retval;
+};
+
+DEFINE_SIMPLE_ATTRIBUTE(debugfs_mode_fops, edt_ft5x06_debugfs_mode_get,
+ edt_ft5x06_debugfs_mode_set, "%llu\n");
+
+static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file,
+ char __user *buf, size_t count, loff_t *off)
+{
+ struct edt_ft5x06_ts_data *tsdata = file->private_data;
+ struct i2c_client *client = tsdata->client;
+ int retries = EDT_RAW_DATA_RETRIES;
+ int val, i, error;
+ size_t read = 0;
+ int colbytes;
+ char wrbuf[3];
+ u8 *rdbuf;
+
+ if (*off < 0 || *off >= tsdata->raw_bufsize)
+ return 0;
+
+ mutex_lock(&tsdata->mutex);
+
+ if (!tsdata->factory_mode || !tsdata->raw_buffer) {
+ error = -EIO;
+ goto out;
+ }
+
+ error = edt_ft5x06_register_write(tsdata, 0x08, 0x01);
+ if (error) {
+ dev_dbg(&client->dev,
+ "failed to write 0x08 register, error %d\n", error);
+ goto out;
+ }
+
+ do {
+ usleep_range(EDT_RAW_DATA_DELAY, EDT_RAW_DATA_DELAY + 100);
+ val = edt_ft5x06_register_read(tsdata, 0x08);
+ if (val < 1)
+ break;
+ } while (--retries > 0);
+
+ if (val < 0) {
+ error = val;
+ dev_dbg(&client->dev,
+ "failed to read 0x08 register, error %d\n", error);
+ goto out;
+ }
+
+ if (retries == 0) {
+ dev_dbg(&client->dev,
+ "timed out waiting for register to settle\n");
+ error = -ETIMEDOUT;
+ goto out;
+ }
+
+ rdbuf = tsdata->raw_buffer;
+ colbytes = tsdata->num_y * sizeof(u16);
+
+ wrbuf[0] = 0xf5;
+ wrbuf[1] = 0x0e;
+ for (i = 0; i < tsdata->num_x; i++) {
+ wrbuf[2] = i; /* column index */
+ error = edt_ft5x06_ts_readwrite(tsdata->client,
+ sizeof(wrbuf), wrbuf,
+ colbytes, rdbuf);
+ if (error)
+ goto out;
+
+ rdbuf += colbytes;
+ }
+
+ read = min_t(size_t, count, tsdata->raw_bufsize - *off);
+ if (copy_to_user(buf, tsdata->raw_buffer + *off, read)) {
+ error = -EFAULT;
+ goto out;
+ }
+
+ *off += read;
+out:
+ mutex_unlock(&tsdata->mutex);
+ return error ?: read;
+};
+
+static const struct file_operations debugfs_raw_data_fops = {
+ .open = simple_open,
+ .read = edt_ft5x06_debugfs_raw_data_read,
+};
+
+static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
+ const char *debugfs_name)
+{
+ tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL);
+
+ debugfs_create_u16("num_x", S_IRUSR, tsdata->debug_dir, &tsdata->num_x);
+ debugfs_create_u16("num_y", S_IRUSR, tsdata->debug_dir, &tsdata->num_y);
+
+ debugfs_create_file("mode", S_IRUSR | S_IWUSR,
+ tsdata->debug_dir, tsdata, &debugfs_mode_fops);
+ debugfs_create_file("raw_data", S_IRUSR,
+ tsdata->debug_dir, tsdata, &debugfs_raw_data_fops);
+}
+
+static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
+{
+ debugfs_remove_recursive(tsdata->debug_dir);
+ kfree(tsdata->raw_buffer);
+}
+
+#else
+
+static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
+{
+ return -ENOSYS;
+}
+
+static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
+ const char *debugfs_name)
+{
+}
+
+static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
+{
+}
+
+#endif /* CONFIG_DEBUGFS */
+
+static int edt_ft5x06_ts_identify(struct i2c_client *client,
+ struct edt_ft5x06_ts_data *tsdata)
+{
+ u8 rdbuf[EDT_NAME_LEN];
+ char *p;
+ int error;
+ char *model_name = tsdata->name;
+ char *fw_version = tsdata->fw_version;
+
+ /* see what we find if we assume it is a M06 *
+ * if we get less than EDT_NAME_LEN, we don't want
+ * to have garbage in there
+ */
+ memset(rdbuf, 0, sizeof(rdbuf));
+ error = edt_ft5x06_ts_readwrite(client, 1, "\xBB",
+ EDT_NAME_LEN - 1, rdbuf);
+ if (error)
+ return error;
+
+ /* Probe content for something consistent.
+ * M06 starts with a response byte, M12 gives the data directly.
+ * M09/Generic does not provide model number information.
+ */
+ if (!strncasecmp(rdbuf + 1, "EP0", 3)) {
+ tsdata->version = EDT_M06;
+
+ /* remove last '$' end marker */
+ rdbuf[EDT_NAME_LEN - 1] = '\0';
+ if (rdbuf[EDT_NAME_LEN - 2] == '$')
+ rdbuf[EDT_NAME_LEN - 2] = '\0';
+
+ /* look for Model/Version separator */
+ p = strchr(rdbuf, '*');
+ if (p)
+ *p++ = '\0';
+ strscpy(model_name, rdbuf + 1, EDT_NAME_LEN);
+ strscpy(fw_version, p ? p : "", EDT_NAME_LEN);
+ } else if (!strncasecmp(rdbuf, "EP0", 3)) {
+ tsdata->version = EDT_M12;
+
+ /* remove last '$' end marker */
+ rdbuf[EDT_NAME_LEN - 2] = '\0';
+ if (rdbuf[EDT_NAME_LEN - 3] == '$')
+ rdbuf[EDT_NAME_LEN - 3] = '\0';
+
+ /* look for Model/Version separator */
+ p = strchr(rdbuf, '*');
+ if (p)
+ *p++ = '\0';
+ strscpy(model_name, rdbuf, EDT_NAME_LEN);
+ strscpy(fw_version, p ? p : "", EDT_NAME_LEN);
+ } else {
+ /* If it is not an EDT M06/M12 touchscreen, then the model
+ * detection is a bit hairy. The different ft5x06
+ * firmares around don't reliably implement the
+ * identification registers. Well, we'll take a shot.
+ *
+ * The main difference between generic focaltec based
+ * touches and EDT M09 is that we know how to retrieve
+ * the max coordinates for the latter.
+ */
+ tsdata->version = GENERIC_FT;
+
+ error = edt_ft5x06_ts_readwrite(client, 1, "\xA6",
+ 2, rdbuf);
+ if (error)
+ return error;
+
+ strscpy(fw_version, rdbuf, 2);
+
+ error = edt_ft5x06_ts_readwrite(client, 1, "\xA8",
+ 1, rdbuf);
+ if (error)
+ return error;
+
+ /* This "model identification" is not exact. Unfortunately
+ * not all firmwares for the ft5x06 put useful values in
+ * the identification registers.
+ */
+ switch (rdbuf[0]) {
+ case 0x11: /* EDT EP0110M09 */
+ case 0x35: /* EDT EP0350M09 */
+ case 0x43: /* EDT EP0430M09 */
+ case 0x50: /* EDT EP0500M09 */
+ case 0x57: /* EDT EP0570M09 */
+ case 0x70: /* EDT EP0700M09 */
+ tsdata->version = EDT_M09;
+ snprintf(model_name, EDT_NAME_LEN, "EP0%i%i0M09",
+ rdbuf[0] >> 4, rdbuf[0] & 0x0F);
+ break;
+ case 0xa1: /* EDT EP1010ML00 */
+ tsdata->version = EDT_M09;
+ snprintf(model_name, EDT_NAME_LEN, "EP%i%i0ML00",
+ rdbuf[0] >> 4, rdbuf[0] & 0x0F);
+ break;
+ case 0x5a: /* Solomon Goldentek Display */
+ snprintf(model_name, EDT_NAME_LEN, "GKTW50SCED1R0");
+ break;
+ case 0x59: /* Evervision Display with FT5xx6 TS */
+ tsdata->version = EV_FT;
+ error = edt_ft5x06_ts_readwrite(client, 1, "\x53",
+ 1, rdbuf);
+ if (error)
+ return error;
+ strscpy(fw_version, rdbuf, 1);
+ snprintf(model_name, EDT_NAME_LEN,
+ "EVERVISION-FT5726NEi");
+ break;
+ default:
+ snprintf(model_name, EDT_NAME_LEN,
+ "generic ft5x06 (%02x)",
+ rdbuf[0]);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void edt_ft5x06_ts_get_defaults(struct device *dev,
+ struct edt_ft5x06_ts_data *tsdata)
+{
+ struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
+ u32 val;
+ int error;
+
+ error = device_property_read_u32(dev, "threshold", &val);
+ if (!error) {
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold, val);
+ tsdata->threshold = val;
+ }
+
+ error = device_property_read_u32(dev, "gain", &val);
+ if (!error) {
+ edt_ft5x06_register_write(tsdata, reg_addr->reg_gain, val);
+ tsdata->gain = val;
+ }
+
+ error = device_property_read_u32(dev, "offset", &val);
+ if (!error) {
+ if (reg_addr->reg_offset != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata,
+ reg_addr->reg_offset, val);
+ tsdata->offset = val;
+ }
+
+ error = device_property_read_u32(dev, "offset-x", &val);
+ if (!error) {
+ if (reg_addr->reg_offset_x != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata,
+ reg_addr->reg_offset_x, val);
+ tsdata->offset_x = val;
+ }
+
+ error = device_property_read_u32(dev, "offset-y", &val);
+ if (!error) {
+ if (reg_addr->reg_offset_y != NO_REGISTER)
+ edt_ft5x06_register_write(tsdata,
+ reg_addr->reg_offset_y, val);
+ tsdata->offset_y = val;
+ }
+}
+
+static void edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
+
+ tsdata->threshold = edt_ft5x06_register_read(tsdata,
+ reg_addr->reg_threshold);
+ tsdata->gain = edt_ft5x06_register_read(tsdata, reg_addr->reg_gain);
+ if (reg_addr->reg_offset != NO_REGISTER)
+ tsdata->offset =
+ edt_ft5x06_register_read(tsdata, reg_addr->reg_offset);
+ if (reg_addr->reg_offset_x != NO_REGISTER)
+ tsdata->offset_x = edt_ft5x06_register_read(tsdata,
+ reg_addr->reg_offset_x);
+ if (reg_addr->reg_offset_y != NO_REGISTER)
+ tsdata->offset_y = edt_ft5x06_register_read(tsdata,
+ reg_addr->reg_offset_y);
+ if (reg_addr->reg_report_rate != NO_REGISTER)
+ tsdata->report_rate = edt_ft5x06_register_read(tsdata,
+ reg_addr->reg_report_rate);
+ tsdata->num_x = EDT_DEFAULT_NUM_X;
+ if (reg_addr->reg_num_x != NO_REGISTER)
+ tsdata->num_x = edt_ft5x06_register_read(tsdata,
+ reg_addr->reg_num_x);
+ tsdata->num_y = EDT_DEFAULT_NUM_Y;
+ if (reg_addr->reg_num_y != NO_REGISTER)
+ tsdata->num_y = edt_ft5x06_register_read(tsdata,
+ reg_addr->reg_num_y);
+}
+
+static void edt_ft5x06_ts_set_regs(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
+
+ switch (tsdata->version) {
+ case EDT_M06:
+ reg_addr->reg_threshold = WORK_REGISTER_THRESHOLD;
+ reg_addr->reg_report_rate = WORK_REGISTER_REPORT_RATE;
+ reg_addr->reg_gain = WORK_REGISTER_GAIN;
+ reg_addr->reg_offset = WORK_REGISTER_OFFSET;
+ reg_addr->reg_offset_x = NO_REGISTER;
+ reg_addr->reg_offset_y = NO_REGISTER;
+ reg_addr->reg_num_x = WORK_REGISTER_NUM_X;
+ reg_addr->reg_num_y = WORK_REGISTER_NUM_Y;
+ break;
+
+ case EDT_M09:
+ case EDT_M12:
+ reg_addr->reg_threshold = M09_REGISTER_THRESHOLD;
+ reg_addr->reg_report_rate = tsdata->version == EDT_M12 ?
+ M12_REGISTER_REPORT_RATE : NO_REGISTER;
+ reg_addr->reg_gain = M09_REGISTER_GAIN;
+ reg_addr->reg_offset = M09_REGISTER_OFFSET;
+ reg_addr->reg_offset_x = NO_REGISTER;
+ reg_addr->reg_offset_y = NO_REGISTER;
+ reg_addr->reg_num_x = M09_REGISTER_NUM_X;
+ reg_addr->reg_num_y = M09_REGISTER_NUM_Y;
+ break;
+
+ case EV_FT:
+ reg_addr->reg_threshold = EV_REGISTER_THRESHOLD;
+ reg_addr->reg_report_rate = NO_REGISTER;
+ reg_addr->reg_gain = EV_REGISTER_GAIN;
+ reg_addr->reg_offset = NO_REGISTER;
+ reg_addr->reg_offset_x = EV_REGISTER_OFFSET_X;
+ reg_addr->reg_offset_y = EV_REGISTER_OFFSET_Y;
+ reg_addr->reg_num_x = NO_REGISTER;
+ reg_addr->reg_num_y = NO_REGISTER;
+ break;
+
+ case GENERIC_FT:
+ /* this is a guesswork */
+ reg_addr->reg_threshold = M09_REGISTER_THRESHOLD;
+ reg_addr->reg_report_rate = NO_REGISTER;
+ reg_addr->reg_gain = M09_REGISTER_GAIN;
+ reg_addr->reg_offset = M09_REGISTER_OFFSET;
+ reg_addr->reg_offset_x = NO_REGISTER;
+ reg_addr->reg_offset_y = NO_REGISTER;
+ reg_addr->reg_num_x = NO_REGISTER;
+ reg_addr->reg_num_y = NO_REGISTER;
+ break;
+ }
+}
+
+static void edt_ft5x06_disable_regulators(void *arg)
+{
+ struct edt_ft5x06_ts_data *data = arg;
+
+ regulator_disable(data->vcc);
+ regulator_disable(data->iovcc);
+}
+
+static int edt_ft5x06_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct edt_i2c_chip_data *chip_data;
+ struct edt_ft5x06_ts_data *tsdata;
+ u8 buf[2] = { 0xfc, 0x00 };
+ struct input_dev *input;
+ unsigned long irq_flags;
+ int error;
+ u32 report_rate;
+
+ dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
+
+ tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
+ if (!tsdata) {
+ dev_err(&client->dev, "failed to allocate driver data.\n");
+ return -ENOMEM;
+ }
+
+ chip_data = device_get_match_data(&client->dev);
+ if (!chip_data)
+ chip_data = (const struct edt_i2c_chip_data *)id->driver_data;
+ if (!chip_data || !chip_data->max_support_points) {
+ dev_err(&client->dev, "invalid or missing chip data\n");
+ return -EINVAL;
+ }
+
+ tsdata->max_support_points = chip_data->max_support_points;
+
+ tsdata->vcc = devm_regulator_get(&client->dev, "vcc");
+ if (IS_ERR(tsdata->vcc)) {
+ error = PTR_ERR(tsdata->vcc);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "failed to request regulator: %d\n", error);
+ return error;
+ }
+
+ tsdata->iovcc = devm_regulator_get(&client->dev, "iovcc");
+ if (IS_ERR(tsdata->iovcc)) {
+ error = PTR_ERR(tsdata->iovcc);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "failed to request iovcc regulator: %d\n", error);
+ return error;
+ }
+
+ error = regulator_enable(tsdata->iovcc);
+ if (error < 0) {
+ dev_err(&client->dev, "failed to enable iovcc: %d\n", error);
+ return error;
+ }
+
+ /* Delay enabling VCC for > 10us (T_ivd) after IOVCC */
+ usleep_range(10, 100);
+
+ error = regulator_enable(tsdata->vcc);
+ if (error < 0) {
+ dev_err(&client->dev, "failed to enable vcc: %d\n", error);
+ regulator_disable(tsdata->iovcc);
+ return error;
+ }
+
+ error = devm_add_action_or_reset(&client->dev,
+ edt_ft5x06_disable_regulators,
+ tsdata);
+ if (error)
+ return error;
+
+ tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(tsdata->reset_gpio)) {
+ error = PTR_ERR(tsdata->reset_gpio);
+ dev_err(&client->dev,
+ "Failed to request GPIO reset pin, error %d\n", error);
+ return error;
+ }
+
+ tsdata->wake_gpio = devm_gpiod_get_optional(&client->dev,
+ "wake", GPIOD_OUT_LOW);
+ if (IS_ERR(tsdata->wake_gpio)) {
+ error = PTR_ERR(tsdata->wake_gpio);
+ dev_err(&client->dev,
+ "Failed to request GPIO wake pin, error %d\n", error);
+ return error;
+ }
+
+ /*
+ * Check which sleep modes we can support. Power-off requieres the
+ * reset-pin to ensure correct power-down/power-up behaviour. Start with
+ * the EDT_PMODE_POWEROFF test since this is the deepest possible sleep
+ * mode.
+ */
+ if (tsdata->reset_gpio)
+ tsdata->suspend_mode = EDT_PMODE_POWEROFF;
+ else if (tsdata->wake_gpio)
+ tsdata->suspend_mode = EDT_PMODE_HIBERNATE;
+ else
+ tsdata->suspend_mode = EDT_PMODE_NOT_SUPPORTED;
+
+ if (tsdata->wake_gpio) {
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(tsdata->wake_gpio, 1);
+ }
+
+ if (tsdata->reset_gpio) {
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+ msleep(300);
+ }
+
+ input = devm_input_allocate_device(&client->dev);
+ if (!input) {
+ dev_err(&client->dev, "failed to allocate input device.\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&tsdata->mutex);
+ tsdata->client = client;
+ tsdata->input = input;
+ tsdata->factory_mode = false;
+
+ error = edt_ft5x06_ts_identify(client, tsdata);
+ if (error) {
+ dev_err(&client->dev, "touchscreen probe failed\n");
+ return error;
+ }
+
+ /*
+ * Dummy read access. EP0700MLP1 returns bogus data on the first
+ * register read access and ignores writes.
+ */
+ edt_ft5x06_ts_readwrite(tsdata->client, 2, buf, 2, buf);
+
+ edt_ft5x06_ts_set_regs(tsdata);
+ edt_ft5x06_ts_get_defaults(&client->dev, tsdata);
+ edt_ft5x06_ts_get_parameters(tsdata);
+
+ if (tsdata->reg_addr.reg_report_rate != NO_REGISTER &&
+ !device_property_read_u32(&client->dev,
+ "report-rate-hz", &report_rate)) {
+ if (tsdata->version == EDT_M06)
+ tsdata->report_rate = clamp_val(report_rate, 30, 140);
+ else
+ tsdata->report_rate = clamp_val(report_rate, 1, 255);
+
+ if (report_rate != tsdata->report_rate)
+ dev_warn(&client->dev,
+ "report-rate %dHz is unsupported, use %dHz\n",
+ report_rate, tsdata->report_rate);
+
+ if (tsdata->version == EDT_M06)
+ tsdata->report_rate /= 10;
+
+ edt_ft5x06_register_write(tsdata,
+ tsdata->reg_addr.reg_report_rate,
+ tsdata->report_rate);
+ }
+
+ dev_dbg(&client->dev,
+ "Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
+ tsdata->name, tsdata->fw_version, tsdata->num_x, tsdata->num_y);
+
+ input->name = tsdata->name;
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &client->dev;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X,
+ 0, tsdata->num_x * 64 - 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y,
+ 0, tsdata->num_y * 64 - 1, 0, 0);
+
+ touchscreen_parse_properties(input, true, &tsdata->prop);
+
+ error = input_mt_init_slots(input, tsdata->max_support_points,
+ INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(&client->dev, "Unable to init MT slots.\n");
+ return error;
+ }
+
+ i2c_set_clientdata(client, tsdata);
+
+ irq_flags = irq_get_trigger_type(client->irq);
+ if (irq_flags == IRQF_TRIGGER_NONE)
+ irq_flags = IRQF_TRIGGER_FALLING;
+ irq_flags |= IRQF_ONESHOT;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, edt_ft5x06_ts_isr, irq_flags,
+ client->name, tsdata);
+ if (error) {
+ dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &edt_ft5x06_attr_group);
+ if (error)
+ return error;
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev));
+
+ dev_dbg(&client->dev,
+ "EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n",
+ client->irq,
+ tsdata->wake_gpio ? desc_to_gpio(tsdata->wake_gpio) : -1,
+ tsdata->reset_gpio ? desc_to_gpio(tsdata->reset_gpio) : -1);
+
+ return 0;
+}
+
+static void edt_ft5x06_ts_remove(struct i2c_client *client)
+{
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+
+ edt_ft5x06_ts_teardown_debugfs(tsdata);
+}
+
+static int __maybe_unused edt_ft5x06_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct gpio_desc *reset_gpio = tsdata->reset_gpio;
+ int ret;
+
+ if (device_may_wakeup(dev))
+ return 0;
+
+ if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
+ return 0;
+
+ /* Enter hibernate mode. */
+ ret = edt_ft5x06_register_write(tsdata, PMOD_REGISTER_OPMODE,
+ PMOD_REGISTER_HIBERNATE);
+ if (ret)
+ dev_warn(dev, "Failed to set hibernate mode\n");
+
+ if (tsdata->suspend_mode == EDT_PMODE_HIBERNATE)
+ return 0;
+
+ /*
+ * Power-off according the datasheet. Cut the power may leaf the irq
+ * line in an undefined state depending on the host pull resistor
+ * settings. Disable the irq to avoid adjusting each host till the
+ * device is back in a full functional state.
+ */
+ disable_irq(tsdata->client->irq);
+
+ gpiod_set_value_cansleep(reset_gpio, 1);
+ usleep_range(1000, 2000);
+
+ ret = regulator_disable(tsdata->vcc);
+ if (ret)
+ dev_warn(dev, "Failed to disable vcc\n");
+ ret = regulator_disable(tsdata->iovcc);
+ if (ret)
+ dev_warn(dev, "Failed to disable iovcc\n");
+
+ return 0;
+}
+
+static int __maybe_unused edt_ft5x06_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ return 0;
+
+ if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
+ return 0;
+
+ if (tsdata->suspend_mode == EDT_PMODE_POWEROFF) {
+ struct gpio_desc *reset_gpio = tsdata->reset_gpio;
+
+ /*
+ * We can't check if the regulator is a dummy or a real
+ * regulator. So we need to specify the 5ms reset time (T_rst)
+ * here instead of the 100us T_rtp time. We also need to wait
+ * 300ms in case it was a real supply and the power was cutted
+ * of. Toggle the reset pin is also a way to exit the hibernate
+ * mode.
+ */
+ gpiod_set_value_cansleep(reset_gpio, 1);
+ usleep_range(5000, 6000);
+
+ ret = regulator_enable(tsdata->iovcc);
+ if (ret) {
+ dev_err(dev, "Failed to enable iovcc\n");
+ return ret;
+ }
+
+ /* Delay enabling VCC for > 10us (T_ivd) after IOVCC */
+ usleep_range(10, 100);
+
+ ret = regulator_enable(tsdata->vcc);
+ if (ret) {
+ dev_err(dev, "Failed to enable vcc\n");
+ regulator_disable(tsdata->iovcc);
+ return ret;
+ }
+
+ usleep_range(1000, 2000);
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ msleep(300);
+
+ edt_ft5x06_restore_reg_parameters(tsdata);
+ enable_irq(tsdata->client->irq);
+
+ if (tsdata->factory_mode)
+ ret = edt_ft5x06_factory_mode(tsdata);
+ } else {
+ struct gpio_desc *wake_gpio = tsdata->wake_gpio;
+
+ gpiod_set_value_cansleep(wake_gpio, 0);
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(wake_gpio, 1);
+ }
+
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
+ edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
+
+static const struct edt_i2c_chip_data edt_ft5x06_data = {
+ .max_support_points = 5,
+};
+
+static const struct edt_i2c_chip_data edt_ft5506_data = {
+ .max_support_points = 10,
+};
+
+static const struct edt_i2c_chip_data edt_ft6236_data = {
+ .max_support_points = 2,
+};
+
+static const struct i2c_device_id edt_ft5x06_ts_id[] = {
+ { .name = "edt-ft5x06", .driver_data = (long)&edt_ft5x06_data },
+ { .name = "edt-ft5506", .driver_data = (long)&edt_ft5506_data },
+ { .name = "ev-ft5726", .driver_data = (long)&edt_ft5506_data },
+ /* Note no edt- prefix for compatibility with the ft6236.c driver */
+ { .name = "ft6236", .driver_data = (long)&edt_ft6236_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, edt_ft5x06_ts_id);
+
+static const struct of_device_id edt_ft5x06_of_match[] = {
+ { .compatible = "edt,edt-ft5206", .data = &edt_ft5x06_data },
+ { .compatible = "edt,edt-ft5306", .data = &edt_ft5x06_data },
+ { .compatible = "edt,edt-ft5406", .data = &edt_ft5x06_data },
+ { .compatible = "edt,edt-ft5506", .data = &edt_ft5506_data },
+ { .compatible = "evervision,ev-ft5726", .data = &edt_ft5506_data },
+ /* Note focaltech vendor prefix for compatibility with ft6236.c */
+ { .compatible = "focaltech,ft6236", .data = &edt_ft6236_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, edt_ft5x06_of_match);
+
+static struct i2c_driver edt_ft5x06_ts_driver = {
+ .driver = {
+ .name = "edt_ft5x06",
+ .of_match_table = edt_ft5x06_of_match,
+ .pm = &edt_ft5x06_ts_pm_ops,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = edt_ft5x06_ts_id,
+ .probe = edt_ft5x06_ts_probe,
+ .remove = edt_ft5x06_ts_remove,
+};
+
+module_i2c_driver(edt_ft5x06_ts_driver);
+
+MODULE_AUTHOR("Simon Budig <simon.budig@kernelconcepts.de>");
+MODULE_DESCRIPTION("EDT FT5x06 I2C Touchscreen Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c
new file mode 100644
index 000000000..a639ba7e5
--- /dev/null
+++ b/drivers/input/touchscreen/eeti_ts.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Touch Screen driver for EETI's I2C connected touch screen panels
+ * Copyright (c) 2009,2018 Daniel Mack <daniel@zonque.org>
+ *
+ * See EETI's software guide for the protocol specification:
+ * http://home.eeti.com.tw/documentation.html
+ *
+ * Based on migor_ts.c
+ * Copyright (c) 2008 Magnus Damm
+ * Copyright (c) 2007 Ujjwal Pande <ujjwal@kenati.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/timer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+struct eeti_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *attn_gpio;
+ struct touchscreen_properties props;
+ struct mutex mutex;
+ bool running;
+};
+
+#define EETI_TS_BITDEPTH (11)
+#define EETI_MAXVAL ((1 << (EETI_TS_BITDEPTH + 1)) - 1)
+
+#define REPORT_BIT_PRESSED BIT(0)
+#define REPORT_BIT_AD0 BIT(1)
+#define REPORT_BIT_AD1 BIT(2)
+#define REPORT_BIT_HAS_PRESSURE BIT(6)
+#define REPORT_RES_BITS(v) (((v) >> 1) + EETI_TS_BITDEPTH)
+
+static void eeti_ts_report_event(struct eeti_ts *eeti, u8 *buf)
+{
+ unsigned int res;
+ u16 x, y;
+
+ res = REPORT_RES_BITS(buf[0] & (REPORT_BIT_AD0 | REPORT_BIT_AD1));
+
+ x = get_unaligned_be16(&buf[1]);
+ y = get_unaligned_be16(&buf[3]);
+
+ /* fix the range to 11 bits */
+ x >>= res - EETI_TS_BITDEPTH;
+ y >>= res - EETI_TS_BITDEPTH;
+
+ if (buf[0] & REPORT_BIT_HAS_PRESSURE)
+ input_report_abs(eeti->input, ABS_PRESSURE, buf[5]);
+
+ touchscreen_report_pos(eeti->input, &eeti->props, x, y, false);
+ input_report_key(eeti->input, BTN_TOUCH, buf[0] & REPORT_BIT_PRESSED);
+ input_sync(eeti->input);
+}
+
+static int eeti_ts_read(struct eeti_ts *eeti)
+{
+ int len, error;
+ char buf[6];
+
+ len = i2c_master_recv(eeti->client, buf, sizeof(buf));
+ if (len != sizeof(buf)) {
+ error = len < 0 ? len : -EIO;
+ dev_err(&eeti->client->dev,
+ "failed to read touchscreen data: %d\n",
+ error);
+ return error;
+ }
+
+ /* Motion packet */
+ if (buf[0] & 0x80)
+ eeti_ts_report_event(eeti, buf);
+
+ return 0;
+}
+
+static irqreturn_t eeti_ts_isr(int irq, void *dev_id)
+{
+ struct eeti_ts *eeti = dev_id;
+ int error;
+
+ mutex_lock(&eeti->mutex);
+
+ do {
+ /*
+ * If we have attention GPIO, trust it. Otherwise we'll read
+ * once and exit. We assume that in this case we are using
+ * level triggered interrupt and it will get raised again
+ * if/when there is more data.
+ */
+ if (eeti->attn_gpio &&
+ !gpiod_get_value_cansleep(eeti->attn_gpio)) {
+ break;
+ }
+
+ error = eeti_ts_read(eeti);
+ if (error)
+ break;
+
+ } while (eeti->running && eeti->attn_gpio);
+
+ mutex_unlock(&eeti->mutex);
+ return IRQ_HANDLED;
+}
+
+static void eeti_ts_start(struct eeti_ts *eeti)
+{
+ mutex_lock(&eeti->mutex);
+
+ eeti->running = true;
+ enable_irq(eeti->client->irq);
+
+ /*
+ * Kick the controller in case we are using edge interrupt and
+ * we missed our edge while interrupt was disabled. We expect
+ * the attention GPIO to be wired in this case.
+ */
+ if (eeti->attn_gpio && gpiod_get_value_cansleep(eeti->attn_gpio))
+ eeti_ts_read(eeti);
+
+ mutex_unlock(&eeti->mutex);
+}
+
+static void eeti_ts_stop(struct eeti_ts *eeti)
+{
+ /*
+ * Not locking here, just setting a flag and expect that the
+ * interrupt thread will notice the flag eventually.
+ */
+ eeti->running = false;
+ wmb();
+ disable_irq(eeti->client->irq);
+}
+
+static int eeti_ts_open(struct input_dev *dev)
+{
+ struct eeti_ts *eeti = input_get_drvdata(dev);
+
+ eeti_ts_start(eeti);
+
+ return 0;
+}
+
+static void eeti_ts_close(struct input_dev *dev)
+{
+ struct eeti_ts *eeti = input_get_drvdata(dev);
+
+ eeti_ts_stop(eeti);
+}
+
+static int eeti_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *idp)
+{
+ struct device *dev = &client->dev;
+ struct eeti_ts *eeti;
+ struct input_dev *input;
+ int error;
+
+ /*
+ * In contrast to what's described in the datasheet, there seems
+ * to be no way of probing the presence of that device using I2C
+ * commands. So we need to blindly believe it is there, and wait
+ * for interrupts to occur.
+ */
+
+ eeti = devm_kzalloc(dev, sizeof(*eeti), GFP_KERNEL);
+ if (!eeti) {
+ dev_err(dev, "failed to allocate driver data\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&eeti->mutex);
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "Failed to allocate input device.\n");
+ return -ENOMEM;
+ }
+
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+
+ input_set_abs_params(input, ABS_X, 0, EETI_MAXVAL, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, EETI_MAXVAL, 0, 0);
+ input_set_abs_params(input, ABS_PRESSURE, 0, 0xff, 0, 0);
+
+ touchscreen_parse_properties(input, false, &eeti->props);
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->open = eeti_ts_open;
+ input->close = eeti_ts_close;
+
+ eeti->client = client;
+ eeti->input = input;
+
+ eeti->attn_gpio = devm_gpiod_get_optional(dev, "attn", GPIOD_IN);
+ if (IS_ERR(eeti->attn_gpio))
+ return PTR_ERR(eeti->attn_gpio);
+
+ i2c_set_clientdata(client, eeti);
+ input_set_drvdata(input, eeti);
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, eeti_ts_isr,
+ IRQF_ONESHOT,
+ client->name, eeti);
+ if (error) {
+ dev_err(dev, "Unable to request touchscreen IRQ: %d\n",
+ error);
+ return error;
+ }
+
+ /*
+ * Disable the device for now. It will be enabled once the
+ * input device is opened.
+ */
+ eeti_ts_stop(eeti);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int __maybe_unused eeti_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct eeti_ts *eeti = i2c_get_clientdata(client);
+ struct input_dev *input_dev = eeti->input;
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ eeti_ts_stop(eeti);
+
+ mutex_unlock(&input_dev->mutex);
+
+ if (device_may_wakeup(&client->dev))
+ enable_irq_wake(client->irq);
+
+ return 0;
+}
+
+static int __maybe_unused eeti_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct eeti_ts *eeti = i2c_get_clientdata(client);
+ struct input_dev *input_dev = eeti->input;
+
+ if (device_may_wakeup(&client->dev))
+ disable_irq_wake(client->irq);
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ eeti_ts_start(eeti);
+
+ mutex_unlock(&input_dev->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(eeti_ts_pm, eeti_ts_suspend, eeti_ts_resume);
+
+static const struct i2c_device_id eeti_ts_id[] = {
+ { "eeti_ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, eeti_ts_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_eeti_ts_match[] = {
+ { .compatible = "eeti,exc3000-i2c", },
+ { }
+};
+#endif
+
+static struct i2c_driver eeti_ts_driver = {
+ .driver = {
+ .name = "eeti_ts",
+ .pm = &eeti_ts_pm,
+ .of_match_table = of_match_ptr(of_eeti_ts_match),
+ },
+ .probe = eeti_ts_probe,
+ .id_table = eeti_ts_id,
+};
+
+module_i2c_driver(eeti_ts_driver);
+
+MODULE_DESCRIPTION("EETI Touchscreen driver");
+MODULE_AUTHOR("Daniel Mack <daniel@zonque.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/egalax_ts.c b/drivers/input/touchscreen/egalax_ts.c
new file mode 100644
index 000000000..83ac8c128
--- /dev/null
+++ b/drivers/input/touchscreen/egalax_ts.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for EETI eGalax Multiple Touch Controller
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * based on max11801_ts.c
+ */
+
+/* EETI eGalax serial touch screen controller is a I2C based multiple
+ * touch screen controller, it supports 5 point multiple touch. */
+
+/* TODO:
+ - auto idle mode support
+*/
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <linux/input/mt.h>
+#include <linux/of_gpio.h>
+
+/*
+ * Mouse Mode: some panel may configure the controller to mouse mode,
+ * which can only report one point at a given time.
+ * This driver will ignore events in this mode.
+ */
+#define REPORT_MODE_MOUSE 0x1
+/*
+ * Vendor Mode: this mode is used to transfer some vendor specific
+ * messages.
+ * This driver will ignore events in this mode.
+ */
+#define REPORT_MODE_VENDOR 0x3
+/* Multiple Touch Mode */
+#define REPORT_MODE_MTTOUCH 0x4
+
+#define MAX_SUPPORT_POINTS 5
+
+#define EVENT_VALID_OFFSET 7
+#define EVENT_VALID_MASK (0x1 << EVENT_VALID_OFFSET)
+#define EVENT_ID_OFFSET 2
+#define EVENT_ID_MASK (0xf << EVENT_ID_OFFSET)
+#define EVENT_IN_RANGE (0x1 << 1)
+#define EVENT_DOWN_UP (0X1 << 0)
+
+#define MAX_I2C_DATA_LEN 10
+
+#define EGALAX_MAX_X 32760
+#define EGALAX_MAX_Y 32760
+#define EGALAX_MAX_TRIES 100
+
+struct egalax_ts {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+};
+
+static irqreturn_t egalax_ts_interrupt(int irq, void *dev_id)
+{
+ struct egalax_ts *ts = dev_id;
+ struct input_dev *input_dev = ts->input_dev;
+ struct i2c_client *client = ts->client;
+ u8 buf[MAX_I2C_DATA_LEN];
+ int id, ret, x, y, z;
+ int tries = 0;
+ bool down, valid;
+ u8 state;
+
+ do {
+ ret = i2c_master_recv(client, buf, MAX_I2C_DATA_LEN);
+ } while (ret == -EAGAIN && tries++ < EGALAX_MAX_TRIES);
+
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ if (buf[0] != REPORT_MODE_MTTOUCH) {
+ /* ignore mouse events and vendor events */
+ return IRQ_HANDLED;
+ }
+
+ state = buf[1];
+ x = (buf[3] << 8) | buf[2];
+ y = (buf[5] << 8) | buf[4];
+ z = (buf[7] << 8) | buf[6];
+
+ valid = state & EVENT_VALID_MASK;
+ id = (state & EVENT_ID_MASK) >> EVENT_ID_OFFSET;
+ down = state & EVENT_DOWN_UP;
+
+ if (!valid || id > MAX_SUPPORT_POINTS) {
+ dev_dbg(&client->dev, "point invalid\n");
+ return IRQ_HANDLED;
+ }
+
+ input_mt_slot(input_dev, id);
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, down);
+
+ dev_dbg(&client->dev, "%s id:%d x:%d y:%d z:%d",
+ down ? "down" : "up", id, x, y, z);
+
+ if (down) {
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+ input_report_abs(input_dev, ABS_MT_PRESSURE, z);
+ }
+
+ input_mt_report_pointer_emulation(input_dev, true);
+ input_sync(input_dev);
+
+ return IRQ_HANDLED;
+}
+
+/* wake up controller by an falling edge of interrupt gpio. */
+static int egalax_wake_up_device(struct i2c_client *client)
+{
+ struct device_node *np = client->dev.of_node;
+ int gpio;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ gpio = of_get_named_gpio(np, "wakeup-gpios", 0);
+ if (!gpio_is_valid(gpio))
+ return -ENODEV;
+
+ ret = gpio_request(gpio, "egalax_irq");
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "request gpio failed, cannot wake up controller: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* wake up controller via an falling edge on IRQ gpio. */
+ gpio_direction_output(gpio, 0);
+ gpio_set_value(gpio, 1);
+
+ /* controller should be waken up, return irq. */
+ gpio_direction_input(gpio);
+ gpio_free(gpio);
+
+ return 0;
+}
+
+static int egalax_firmware_version(struct i2c_client *client)
+{
+ static const u8 cmd[MAX_I2C_DATA_LEN] = { 0x03, 0x03, 0xa, 0x01, 0x41 };
+ int ret;
+
+ ret = i2c_master_send(client, cmd, MAX_I2C_DATA_LEN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int egalax_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct egalax_ts *ts;
+ struct input_dev *input_dev;
+ int error;
+
+ ts = devm_kzalloc(&client->dev, sizeof(struct egalax_ts), GFP_KERNEL);
+ if (!ts) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ ts->client = client;
+ ts->input_dev = input_dev;
+
+ /* controller may be in sleep, wake it up. */
+ error = egalax_wake_up_device(client);
+ if (error) {
+ dev_err(&client->dev, "Failed to wake up the controller\n");
+ return error;
+ }
+
+ error = egalax_firmware_version(client);
+ if (error < 0) {
+ dev_err(&client->dev, "Failed to read firmware version\n");
+ return error;
+ }
+
+ input_dev->name = "EETI eGalax Touch Screen";
+ input_dev->id.bustype = BUS_I2C;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_X, 0, EGALAX_MAX_X, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, EGALAX_MAX_Y, 0, 0);
+ input_set_abs_params(input_dev,
+ ABS_MT_POSITION_X, 0, EGALAX_MAX_X, 0, 0);
+ input_set_abs_params(input_dev,
+ ABS_MT_POSITION_Y, 0, EGALAX_MAX_Y, 0, 0);
+ input_mt_init_slots(input_dev, MAX_SUPPORT_POINTS, 0);
+
+ error = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ egalax_ts_interrupt,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "egalax_ts", ts);
+ if (error < 0) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = input_register_device(ts->input_dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct i2c_device_id egalax_ts_id[] = {
+ { "egalax_ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, egalax_ts_id);
+
+static int __maybe_unused egalax_ts_suspend(struct device *dev)
+{
+ static const u8 suspend_cmd[MAX_I2C_DATA_LEN] = {
+ 0x3, 0x6, 0xa, 0x3, 0x36, 0x3f, 0x2, 0, 0, 0
+ };
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ if (device_may_wakeup(dev))
+ return enable_irq_wake(client->irq);
+
+ ret = i2c_master_send(client, suspend_cmd, MAX_I2C_DATA_LEN);
+ return ret > 0 ? 0 : ret;
+}
+
+static int __maybe_unused egalax_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (device_may_wakeup(dev))
+ return disable_irq_wake(client->irq);
+
+ return egalax_wake_up_device(client);
+}
+
+static SIMPLE_DEV_PM_OPS(egalax_ts_pm_ops, egalax_ts_suspend, egalax_ts_resume);
+
+static const struct of_device_id egalax_ts_dt_ids[] = {
+ { .compatible = "eeti,egalax_ts" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, egalax_ts_dt_ids);
+
+static struct i2c_driver egalax_ts_driver = {
+ .driver = {
+ .name = "egalax_ts",
+ .pm = &egalax_ts_pm_ops,
+ .of_match_table = egalax_ts_dt_ids,
+ },
+ .id_table = egalax_ts_id,
+ .probe = egalax_ts_probe,
+};
+
+module_i2c_driver(egalax_ts_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Touchscreen driver for EETI eGalax touch controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/egalax_ts_serial.c b/drivers/input/touchscreen/egalax_ts_serial.c
new file mode 100644
index 000000000..375922d3a
--- /dev/null
+++ b/drivers/input/touchscreen/egalax_ts_serial.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EETI Egalax serial touchscreen driver
+ *
+ * Copyright (c) 2015 Zoltán Böszörményi <zboszor@pr.hu>
+ *
+ * based on the
+ *
+ * Hampshire serial touchscreen driver (Copyright (c) 2010 Adam Bennett)
+ */
+
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "EETI Egalax serial touchscreen driver"
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define EGALAX_FORMAT_MAX_LENGTH 6
+#define EGALAX_FORMAT_START_BIT BIT(7)
+#define EGALAX_FORMAT_PRESSURE_BIT BIT(6)
+#define EGALAX_FORMAT_TOUCH_BIT BIT(0)
+#define EGALAX_FORMAT_RESOLUTION_MASK 0x06
+
+#define EGALAX_MIN_XC 0
+#define EGALAX_MAX_XC 0x4000
+#define EGALAX_MIN_YC 0
+#define EGALAX_MAX_YC 0x4000
+
+/*
+ * Per-touchscreen data.
+ */
+struct egalax {
+ struct input_dev *input;
+ struct serio *serio;
+ int idx;
+ u8 data[EGALAX_FORMAT_MAX_LENGTH];
+ char phys[32];
+};
+
+static void egalax_process_data(struct egalax *egalax)
+{
+ struct input_dev *dev = egalax->input;
+ u8 *data = egalax->data;
+ u16 x, y;
+ u8 shift;
+ u8 mask;
+
+ shift = 3 - ((data[0] & EGALAX_FORMAT_RESOLUTION_MASK) >> 1);
+ mask = 0xff >> (shift + 1);
+
+ x = (((u16)(data[1] & mask) << 7) | (data[2] & 0x7f)) << shift;
+ y = (((u16)(data[3] & mask) << 7) | (data[4] & 0x7f)) << shift;
+
+ input_report_key(dev, BTN_TOUCH, data[0] & EGALAX_FORMAT_TOUCH_BIT);
+ input_report_abs(dev, ABS_X, x);
+ input_report_abs(dev, ABS_Y, y);
+ input_sync(dev);
+}
+
+static irqreturn_t egalax_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct egalax *egalax = serio_get_drvdata(serio);
+ int pkt_len;
+
+ egalax->data[egalax->idx++] = data;
+
+ if (likely(egalax->data[0] & EGALAX_FORMAT_START_BIT)) {
+ pkt_len = egalax->data[0] & EGALAX_FORMAT_PRESSURE_BIT ? 6 : 5;
+ if (pkt_len == egalax->idx) {
+ egalax_process_data(egalax);
+ egalax->idx = 0;
+ }
+ } else {
+ dev_dbg(&serio->dev, "unknown/unsynchronized data: %x\n",
+ egalax->data[0]);
+ egalax->idx = 0;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * egalax_connect() is the routine that is called when someone adds a
+ * new serio device that supports egalax protocol and registers it as
+ * an input device. This is usually accomplished using inputattach.
+ */
+static int egalax_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct egalax *egalax;
+ struct input_dev *input_dev;
+ int error;
+
+ egalax = kzalloc(sizeof(struct egalax), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!egalax || !input_dev) {
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ egalax->serio = serio;
+ egalax->input = input_dev;
+ snprintf(egalax->phys, sizeof(egalax->phys),
+ "%s/input0", serio->phys);
+
+ input_dev->name = "EETI eGalaxTouch Serial TouchScreen";
+ input_dev->phys = egalax->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_EGALAX;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0001;
+ input_dev->dev.parent = &serio->dev;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X,
+ EGALAX_MIN_XC, EGALAX_MAX_XC, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ EGALAX_MIN_YC, EGALAX_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, egalax);
+
+ error = serio_open(serio, drv);
+ if (error)
+ goto err_reset_drvdata;
+
+ error = input_register_device(input_dev);
+ if (error)
+ goto err_close_serio;
+
+ return 0;
+
+err_close_serio:
+ serio_close(serio);
+err_reset_drvdata:
+ serio_set_drvdata(serio, NULL);
+err_free_mem:
+ input_free_device(input_dev);
+ kfree(egalax);
+ return error;
+}
+
+static void egalax_disconnect(struct serio *serio)
+{
+ struct egalax *egalax = serio_get_drvdata(serio);
+
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_unregister_device(egalax->input);
+ kfree(egalax);
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id egalax_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_EGALAX,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, egalax_serio_ids);
+
+static struct serio_driver egalax_drv = {
+ .driver = {
+ .name = "egalax",
+ },
+ .description = DRIVER_DESC,
+ .id_table = egalax_serio_ids,
+ .interrupt = egalax_interrupt,
+ .connect = egalax_connect,
+ .disconnect = egalax_disconnect,
+};
+module_serio_driver(egalax_drv);
+
+MODULE_AUTHOR("Zoltán Böszörményi <zboszor@pr.hu>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/ektf2127.c b/drivers/input/touchscreen/ektf2127.c
new file mode 100644
index 000000000..2d01a8cbf
--- /dev/null
+++ b/drivers/input/touchscreen/ektf2127.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for ELAN eKTF2127 i2c touchscreen controller
+ *
+ * For this driver the layout of the Chipone icn8318 i2c
+ * touchscreencontroller is used.
+ *
+ * Author:
+ * Michel Verlaan <michel.verl@gmail.com>
+ * Siebren Vroegindeweij <siebren.vroegindeweij@hotmail.com>
+ *
+ * Original chipone_icn8318 driver:
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+
+/* Packet header defines (first byte of data send / received) */
+#define EKTF2127_NOISE 0x40
+#define EKTF2127_RESPONSE 0x52
+#define EKTF2127_REQUEST 0x53
+#define EKTF2127_HELLO 0x55
+#define EKTF2127_REPORT2 0x5a
+#define EKTF2127_REPORT 0x5d
+#define EKTF2127_CALIB_DONE 0x66
+
+/* Register defines (second byte of data send / received) */
+#define EKTF2127_ENV_NOISY 0x41
+#define EKTF2127_HEIGHT 0x60
+#define EKTF2127_WIDTH 0x63
+
+/* 2 bytes header + 5 * 3 bytes coordinates + 3 bytes pressure info + footer */
+#define EKTF2127_TOUCH_REPORT_SIZE 21
+#define EKTF2127_MAX_TOUCHES 5
+
+struct ektf2127_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *power_gpios;
+ struct touchscreen_properties prop;
+};
+
+static void ektf2127_parse_coordinates(const u8 *buf, unsigned int touch_count,
+ struct input_mt_pos *touches)
+{
+ int index = 0;
+ int i;
+
+ for (i = 0; i < touch_count; i++) {
+ index = 2 + i * 3;
+
+ touches[i].x = (buf[index] & 0x0f);
+ touches[i].x <<= 8;
+ touches[i].x |= buf[index + 2];
+
+ touches[i].y = (buf[index] & 0xf0);
+ touches[i].y <<= 4;
+ touches[i].y |= buf[index + 1];
+ }
+}
+
+static void ektf2127_report_event(struct ektf2127_ts *ts, const u8 *buf)
+{
+ struct input_mt_pos touches[EKTF2127_MAX_TOUCHES];
+ int slots[EKTF2127_MAX_TOUCHES];
+ unsigned int touch_count, i;
+
+ touch_count = buf[1] & 0x07;
+ if (touch_count > EKTF2127_MAX_TOUCHES) {
+ dev_err(&ts->client->dev,
+ "Too many touches %d > %d\n",
+ touch_count, EKTF2127_MAX_TOUCHES);
+ touch_count = EKTF2127_MAX_TOUCHES;
+ }
+
+ ektf2127_parse_coordinates(buf, touch_count, touches);
+ input_mt_assign_slots(ts->input, slots, touches,
+ touch_count, 0);
+
+ for (i = 0; i < touch_count; i++) {
+ input_mt_slot(ts->input, slots[i]);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+ touchscreen_report_pos(ts->input, &ts->prop,
+ touches[i].x, touches[i].y, true);
+ }
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+}
+
+static void ektf2127_report2_contact(struct ektf2127_ts *ts, int slot,
+ const u8 *buf, bool active)
+{
+ input_mt_slot(ts->input, slot);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, active);
+
+ if (active) {
+ int x = (buf[0] & 0xf0) << 4 | buf[1];
+ int y = (buf[0] & 0x0f) << 8 | buf[2];
+
+ touchscreen_report_pos(ts->input, &ts->prop, x, y, true);
+ }
+}
+
+static void ektf2127_report2_event(struct ektf2127_ts *ts, const u8 *buf)
+{
+ ektf2127_report2_contact(ts, 0, &buf[1], !!(buf[7] & 2));
+ ektf2127_report2_contact(ts, 1, &buf[4], !!(buf[7] & 4));
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+}
+
+static irqreturn_t ektf2127_irq(int irq, void *dev_id)
+{
+ struct ektf2127_ts *ts = dev_id;
+ struct device *dev = &ts->client->dev;
+ char buf[EKTF2127_TOUCH_REPORT_SIZE];
+ int ret;
+
+ ret = i2c_master_recv(ts->client, buf, EKTF2127_TOUCH_REPORT_SIZE);
+ if (ret != EKTF2127_TOUCH_REPORT_SIZE) {
+ dev_err(dev, "Error reading touch data: %d\n", ret);
+ goto out;
+ }
+
+ switch (buf[0]) {
+ case EKTF2127_REPORT:
+ ektf2127_report_event(ts, buf);
+ break;
+
+ case EKTF2127_REPORT2:
+ ektf2127_report2_event(ts, buf);
+ break;
+
+ case EKTF2127_NOISE:
+ if (buf[1] == EKTF2127_ENV_NOISY)
+ dev_dbg(dev, "Environment is electrically noisy\n");
+ break;
+
+ case EKTF2127_HELLO:
+ case EKTF2127_CALIB_DONE:
+ break;
+
+ default:
+ dev_err(dev, "Unexpected packet header byte %#02x\n", buf[0]);
+ break;
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int ektf2127_start(struct input_dev *dev)
+{
+ struct ektf2127_ts *ts = input_get_drvdata(dev);
+
+ enable_irq(ts->client->irq);
+ gpiod_set_value_cansleep(ts->power_gpios, 1);
+
+ return 0;
+}
+
+static void ektf2127_stop(struct input_dev *dev)
+{
+ struct ektf2127_ts *ts = input_get_drvdata(dev);
+
+ disable_irq(ts->client->irq);
+ gpiod_set_value_cansleep(ts->power_gpios, 0);
+}
+
+static int __maybe_unused ektf2127_suspend(struct device *dev)
+{
+ struct ektf2127_ts *ts = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&ts->input->mutex);
+ if (input_device_enabled(ts->input))
+ ektf2127_stop(ts->input);
+ mutex_unlock(&ts->input->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused ektf2127_resume(struct device *dev)
+{
+ struct ektf2127_ts *ts = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&ts->input->mutex);
+ if (input_device_enabled(ts->input))
+ ektf2127_start(ts->input);
+ mutex_unlock(&ts->input->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ektf2127_pm_ops, ektf2127_suspend,
+ ektf2127_resume);
+
+static int ektf2127_query_dimension(struct i2c_client *client, bool width)
+{
+ struct device *dev = &client->dev;
+ const char *what = width ? "width" : "height";
+ u8 what_code = width ? EKTF2127_WIDTH : EKTF2127_HEIGHT;
+ u8 buf[4];
+ int ret;
+ int error;
+
+ /* Request dimension */
+ buf[0] = EKTF2127_REQUEST;
+ buf[1] = width ? EKTF2127_WIDTH : EKTF2127_HEIGHT;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret != sizeof(buf)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(dev, "Failed to request %s: %d\n", what, error);
+ return error;
+ }
+
+ msleep(20);
+
+ /* Read response */
+ ret = i2c_master_recv(client, buf, sizeof(buf));
+ if (ret != sizeof(buf)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(dev, "Failed to receive %s data: %d\n", what, error);
+ return error;
+ }
+
+ if (buf[0] != EKTF2127_RESPONSE || buf[1] != what_code) {
+ dev_err(dev, "Unexpected %s data: %#02x %#02x\n",
+ what, buf[0], buf[1]);
+ return -EIO;
+ }
+
+ return (((buf[3] & 0xf0) << 4) | buf[2]) - 1;
+}
+
+static int ektf2127_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct ektf2127_ts *ts;
+ struct input_dev *input;
+ u8 buf[4];
+ int max_x, max_y;
+ int error;
+
+ if (!client->irq) {
+ dev_err(dev, "Error no irq specified\n");
+ return -EINVAL;
+ }
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ /* This requests the gpio *and* turns on the touchscreen controller */
+ ts->power_gpios = devm_gpiod_get(dev, "power", GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->power_gpios)) {
+ error = PTR_ERR(ts->power_gpios);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Error getting power gpio: %d\n", error);
+ return error;
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->open = ektf2127_start;
+ input->close = ektf2127_stop;
+
+ ts->client = client;
+
+ /* Read hello (ignore result, depends on initial power state) */
+ msleep(20);
+ i2c_master_recv(ts->client, buf, sizeof(buf));
+
+ /* Read resolution from chip */
+ max_x = ektf2127_query_dimension(client, true);
+ if (max_x < 0)
+ return max_x;
+
+ max_y = ektf2127_query_dimension(client, false);
+ if (max_y < 0)
+ return max_y;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
+ touchscreen_parse_properties(input, true, &ts->prop);
+
+ error = input_mt_init_slots(input, EKTF2127_MAX_TOUCHES,
+ INPUT_MT_DIRECT |
+ INPUT_MT_DROP_UNUSED |
+ INPUT_MT_TRACK);
+ if (error)
+ return error;
+
+ ts->input = input;
+ input_set_drvdata(input, ts);
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, ektf2127_irq,
+ IRQF_ONESHOT, client->name, ts);
+ if (error) {
+ dev_err(dev, "Error requesting irq: %d\n", error);
+ return error;
+ }
+
+ /* Stop device till opened */
+ ektf2127_stop(ts->input);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ektf2127_of_match[] = {
+ { .compatible = "elan,ektf2127" },
+ { .compatible = "elan,ektf2132" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ektf2127_of_match);
+#endif
+
+static const struct i2c_device_id ektf2127_i2c_id[] = {
+ { "ektf2127", 0 },
+ { "ektf2132", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ektf2127_i2c_id);
+
+static struct i2c_driver ektf2127_driver = {
+ .driver = {
+ .name = "elan_ektf2127",
+ .pm = &ektf2127_pm_ops,
+ .of_match_table = of_match_ptr(ektf2127_of_match),
+ },
+ .probe = ektf2127_probe,
+ .id_table = ektf2127_i2c_id,
+};
+module_i2c_driver(ektf2127_driver);
+
+MODULE_DESCRIPTION("ELAN eKTF2127/eKTF2132 I2C Touchscreen Driver");
+MODULE_AUTHOR("Michel Verlaan, Siebren Vroegindeweij");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 000000000..e1308e179
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1701 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Elan Microelectronics touch panels with I2C interface
+ *
+ * Copyright (C) 2014 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ * Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ * Copyright (c) 2010-2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France
+ *
+ * This code is partly based on i2c-hid.c:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ */
+
+
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/uuid.h>
+#include <asm/unaligned.h>
+
+/* Device, Driver information */
+#define DEVICE_NAME "elants_i2c"
+
+/* Convert from rows or columns into resolution */
+#define ELAN_TS_RESOLUTION(n, m) (((n) - 1) * (m))
+
+/* FW header data */
+#define HEADER_SIZE 4
+#define FW_HDR_TYPE 0
+#define FW_HDR_COUNT 1
+#define FW_HDR_LENGTH 2
+
+/* Buffer mode Queue Header information */
+#define QUEUE_HEADER_SINGLE 0x62
+#define QUEUE_HEADER_NORMAL 0X63
+#define QUEUE_HEADER_WAIT 0x64
+#define QUEUE_HEADER_NORMAL2 0x66
+
+/* Command header definition */
+#define CMD_HEADER_WRITE 0x54
+#define CMD_HEADER_READ 0x53
+#define CMD_HEADER_6B_READ 0x5B
+#define CMD_HEADER_ROM_READ 0x96
+#define CMD_HEADER_RESP 0x52
+#define CMD_HEADER_6B_RESP 0x9B
+#define CMD_HEADER_ROM_RESP 0x95
+#define CMD_HEADER_HELLO 0x55
+#define CMD_HEADER_REK 0x66
+
+/* FW position data */
+#define PACKET_SIZE_OLD 40
+#define PACKET_SIZE 55
+#define MAX_CONTACT_NUM 10
+#define FW_POS_HEADER 0
+#define FW_POS_STATE 1
+#define FW_POS_TOTAL 2
+#define FW_POS_XY 3
+#define FW_POS_TOOL_TYPE 33
+#define FW_POS_CHECKSUM 34
+#define FW_POS_WIDTH 35
+#define FW_POS_PRESSURE 45
+
+#define HEADER_REPORT_10_FINGER 0x62
+
+/* Header (4 bytes) plus 3 full 10-finger packets */
+#define MAX_PACKET_SIZE 169
+
+#define BOOT_TIME_DELAY_MS 50
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER 0x00
+#define E_ELAN_INFO_BC_VER 0x10
+#define E_ELAN_INFO_X_RES 0x60
+#define E_ELAN_INFO_Y_RES 0x63
+#define E_ELAN_INFO_REK 0xD0
+#define E_ELAN_INFO_TEST_VER 0xE0
+#define E_ELAN_INFO_FW_ID 0xF0
+#define E_INFO_OSR 0xD6
+#define E_INFO_PHY_SCAN 0xD7
+#define E_INFO_PHY_DRIVER 0xD8
+
+/* FW write command, 0x54 0x?? 0x0, 0x01 */
+#define E_POWER_STATE_SLEEP 0x50
+#define E_POWER_STATE_RESUME 0x58
+
+#define MAX_RETRIES 3
+#define MAX_FW_UPDATE_RETRIES 30
+
+#define ELAN_FW_PAGESIZE 132
+
+/* calibration timeout definition */
+#define ELAN_CALI_TIMEOUT_MSEC 12000
+
+#define ELAN_POWERON_DELAY_USEC 500
+#define ELAN_RESET_DELAY_MSEC 20
+
+/* FW boot code version */
+#define BC_VER_H_BYTE_FOR_EKTH3900x1_I2C 0x72
+#define BC_VER_H_BYTE_FOR_EKTH3900x2_I2C 0x82
+#define BC_VER_H_BYTE_FOR_EKTH3900x3_I2C 0x92
+#define BC_VER_H_BYTE_FOR_EKTH5312x1_I2C 0x6D
+#define BC_VER_H_BYTE_FOR_EKTH5312x2_I2C 0x6E
+#define BC_VER_H_BYTE_FOR_EKTH5312cx1_I2C 0x77
+#define BC_VER_H_BYTE_FOR_EKTH5312cx2_I2C 0x78
+#define BC_VER_H_BYTE_FOR_EKTH5312x1_I2C_USB 0x67
+#define BC_VER_H_BYTE_FOR_EKTH5312x2_I2C_USB 0x68
+#define BC_VER_H_BYTE_FOR_EKTH5312cx1_I2C_USB 0x74
+#define BC_VER_H_BYTE_FOR_EKTH5312cx2_I2C_USB 0x75
+
+enum elants_chip_id {
+ EKTH3500,
+ EKTF3624,
+};
+
+enum elants_state {
+ ELAN_STATE_NORMAL,
+ ELAN_WAIT_QUEUE_HEADER,
+ ELAN_WAIT_RECALIBRATION,
+};
+
+enum elants_iap_mode {
+ ELAN_IAP_OPERATIONAL,
+ ELAN_IAP_RECOVERY,
+};
+
+/* struct elants_data - represents state of Elan touchscreen device */
+struct elants_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+
+ struct regulator *vcc33;
+ struct regulator *vccio;
+ struct gpio_desc *reset_gpio;
+
+ u16 fw_version;
+ u8 test_version;
+ u8 solution_version;
+ u8 bc_version;
+ u8 iap_version;
+ u16 hw_version;
+ u8 major_res;
+ unsigned int x_res; /* resolution in units/mm */
+ unsigned int y_res;
+ unsigned int x_max;
+ unsigned int y_max;
+ unsigned int phy_x;
+ unsigned int phy_y;
+ struct touchscreen_properties prop;
+
+ enum elants_state state;
+ enum elants_chip_id chip_id;
+ enum elants_iap_mode iap_mode;
+
+ /* Guards against concurrent access to the device via sysfs */
+ struct mutex sysfs_mutex;
+
+ u8 cmd_resp[HEADER_SIZE];
+ struct completion cmd_done;
+
+ bool wake_irq_enabled;
+ bool keep_power_in_suspend;
+
+ /* Must be last to be used for DMA operations */
+ u8 buf[MAX_PACKET_SIZE] ____cacheline_aligned;
+};
+
+static int elants_i2c_send(struct i2c_client *client,
+ const void *data, size_t size)
+{
+ int ret;
+
+ ret = i2c_master_send(client, data, size);
+ if (ret == size)
+ return 0;
+
+ if (ret >= 0)
+ ret = -EIO;
+
+ dev_err(&client->dev, "%s failed (%*ph): %d\n",
+ __func__, (int)size, data, ret);
+
+ return ret;
+}
+
+static int elants_i2c_read(struct i2c_client *client, void *data, size_t size)
+{
+ int ret;
+
+ ret = i2c_master_recv(client, data, size);
+ if (ret == size)
+ return 0;
+
+ if (ret >= 0)
+ ret = -EIO;
+
+ dev_err(&client->dev, "%s failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int elants_i2c_execute_command(struct i2c_client *client,
+ const u8 *cmd, size_t cmd_size,
+ u8 *resp, size_t resp_size,
+ int retries, const char *cmd_name)
+{
+ struct i2c_msg msgs[2];
+ int ret;
+ u8 expected_response;
+
+ switch (cmd[0]) {
+ case CMD_HEADER_READ:
+ expected_response = CMD_HEADER_RESP;
+ break;
+
+ case CMD_HEADER_6B_READ:
+ expected_response = CMD_HEADER_6B_RESP;
+ break;
+
+ case CMD_HEADER_ROM_READ:
+ expected_response = CMD_HEADER_ROM_RESP;
+ break;
+
+ default:
+ dev_err(&client->dev, "(%s): invalid command: %*ph\n",
+ cmd_name, (int)cmd_size, cmd);
+ return -EINVAL;
+ }
+
+ for (;;) {
+ msgs[0].addr = client->addr;
+ msgs[0].flags = client->flags & I2C_M_TEN;
+ msgs[0].len = cmd_size;
+ msgs[0].buf = (u8 *)cmd;
+
+ msgs[1].addr = client->addr;
+ msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD;
+ msgs[1].flags |= I2C_M_RD;
+ msgs[1].len = resp_size;
+ msgs[1].buf = resp;
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0) {
+ if (--retries > 0) {
+ dev_dbg(&client->dev,
+ "(%s) I2C transfer failed: %pe (retrying)\n",
+ cmd_name, ERR_PTR(ret));
+ continue;
+ }
+
+ dev_err(&client->dev,
+ "(%s) I2C transfer failed: %pe\n",
+ cmd_name, ERR_PTR(ret));
+ return ret;
+ }
+
+ if (ret != ARRAY_SIZE(msgs) ||
+ resp[FW_HDR_TYPE] != expected_response) {
+ if (--retries > 0) {
+ dev_dbg(&client->dev,
+ "(%s) unexpected response: %*ph (retrying)\n",
+ cmd_name, ret, resp);
+ continue;
+ }
+
+ dev_err(&client->dev,
+ "(%s) unexpected response: %*ph\n",
+ cmd_name, ret, resp);
+ return -EIO;
+ }
+
+ return 0;
+ }
+}
+
+static int elants_i2c_calibrate(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int ret, error;
+ static const u8 w_flashkey[] = { CMD_HEADER_WRITE, 0xC0, 0xE1, 0x5A };
+ static const u8 rek[] = { CMD_HEADER_WRITE, 0x29, 0x00, 0x01 };
+ static const u8 rek_resp[] = { CMD_HEADER_REK, 0x66, 0x66, 0x66 };
+
+ disable_irq(client->irq);
+
+ ts->state = ELAN_WAIT_RECALIBRATION;
+ reinit_completion(&ts->cmd_done);
+
+ elants_i2c_send(client, w_flashkey, sizeof(w_flashkey));
+ elants_i2c_send(client, rek, sizeof(rek));
+
+ enable_irq(client->irq);
+
+ ret = wait_for_completion_interruptible_timeout(&ts->cmd_done,
+ msecs_to_jiffies(ELAN_CALI_TIMEOUT_MSEC));
+
+ ts->state = ELAN_STATE_NORMAL;
+
+ if (ret <= 0) {
+ error = ret < 0 ? ret : -ETIMEDOUT;
+ dev_err(&client->dev,
+ "error while waiting for calibration to complete: %d\n",
+ error);
+ return error;
+ }
+
+ if (memcmp(rek_resp, ts->cmd_resp, sizeof(rek_resp))) {
+ dev_err(&client->dev,
+ "unexpected calibration response: %*ph\n",
+ (int)sizeof(ts->cmd_resp), ts->cmd_resp);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int elants_i2c_sw_reset(struct i2c_client *client)
+{
+ const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
+ int error;
+
+ error = elants_i2c_send(client, soft_rst_cmd,
+ sizeof(soft_rst_cmd));
+ if (error) {
+ dev_err(&client->dev, "software reset failed: %d\n", error);
+ return error;
+ }
+
+ /*
+ * We should wait at least 10 msec (but no more than 40) before
+ * sending fastboot or IAP command to the device.
+ */
+ msleep(30);
+
+ return 0;
+}
+
+static u16 elants_i2c_parse_version(u8 *buf)
+{
+ return get_unaligned_be32(buf) >> 4;
+}
+
+static int elants_i2c_query_hw_version(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int retry_cnt = MAX_RETRIES;
+ const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 };
+ u8 resp[HEADER_SIZE];
+ int error;
+
+ while (retry_cnt--) {
+ error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+ resp, sizeof(resp), 1,
+ "read fw id");
+ if (error)
+ return error;
+
+ ts->hw_version = elants_i2c_parse_version(resp);
+ if (ts->hw_version != 0xffff)
+ return 0;
+ }
+
+ dev_err(&client->dev, "Invalid fw id: %#04x\n", ts->hw_version);
+
+ return -EINVAL;
+}
+
+static int elants_i2c_query_fw_version(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int retry_cnt = MAX_RETRIES;
+ const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01 };
+ u8 resp[HEADER_SIZE];
+ int error;
+
+ while (retry_cnt--) {
+ error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+ resp, sizeof(resp), 1,
+ "read fw version");
+ if (error)
+ return error;
+
+ ts->fw_version = elants_i2c_parse_version(resp);
+ if (ts->fw_version != 0x0000 && ts->fw_version != 0xffff)
+ return 0;
+
+ dev_dbg(&client->dev, "(read fw version) resp %*phC\n",
+ (int)sizeof(resp), resp);
+ }
+
+ dev_err(&client->dev, "Invalid fw ver: %#04x\n", ts->fw_version);
+
+ return -EINVAL;
+}
+
+static int elants_i2c_query_test_version(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int error;
+ u16 version;
+ const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01 };
+ u8 resp[HEADER_SIZE];
+
+ error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+ resp, sizeof(resp), MAX_RETRIES,
+ "read test version");
+ if (error) {
+ dev_err(&client->dev, "Failed to read test version\n");
+ return error;
+ }
+
+ version = elants_i2c_parse_version(resp);
+ ts->test_version = version >> 8;
+ ts->solution_version = version & 0xff;
+
+ return 0;
+}
+
+static int elants_i2c_query_bc_version(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_BC_VER, 0x00, 0x01 };
+ u8 resp[HEADER_SIZE];
+ u16 version;
+ int error;
+
+ error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+ resp, sizeof(resp), 1,
+ "read BC version");
+ if (error)
+ return error;
+
+ version = elants_i2c_parse_version(resp);
+ ts->bc_version = version >> 8;
+ ts->iap_version = version & 0xff;
+
+ return 0;
+}
+
+static int elants_i2c_query_ts_info_ektf(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int error;
+ u8 resp[4];
+ u16 phy_x, phy_y;
+ const u8 get_xres_cmd[] = {
+ CMD_HEADER_READ, E_ELAN_INFO_X_RES, 0x00, 0x00
+ };
+ const u8 get_yres_cmd[] = {
+ CMD_HEADER_READ, E_ELAN_INFO_Y_RES, 0x00, 0x00
+ };
+
+ /* Get X/Y size in mm */
+ error = elants_i2c_execute_command(client, get_xres_cmd,
+ sizeof(get_xres_cmd),
+ resp, sizeof(resp), 1,
+ "get X size");
+ if (error)
+ return error;
+
+ phy_x = resp[2] | ((resp[3] & 0xF0) << 4);
+
+ error = elants_i2c_execute_command(client, get_yres_cmd,
+ sizeof(get_yres_cmd),
+ resp, sizeof(resp), 1,
+ "get Y size");
+ if (error)
+ return error;
+
+ phy_y = resp[2] | ((resp[3] & 0xF0) << 4);
+
+ dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+ ts->phy_x = phy_x;
+ ts->phy_y = phy_y;
+
+ /* eKTF doesn't report max size, set it to default values */
+ ts->x_max = 2240 - 1;
+ ts->y_max = 1408 - 1;
+
+ return 0;
+}
+
+static int elants_i2c_query_ts_info_ekth(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int error;
+ u8 resp[17];
+ u16 phy_x, phy_y, rows, cols, osr;
+ const u8 get_resolution_cmd[] = {
+ CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ const u8 get_osr_cmd[] = {
+ CMD_HEADER_READ, E_INFO_OSR, 0x00, 0x01
+ };
+ const u8 get_physical_scan_cmd[] = {
+ CMD_HEADER_READ, E_INFO_PHY_SCAN, 0x00, 0x01
+ };
+ const u8 get_physical_drive_cmd[] = {
+ CMD_HEADER_READ, E_INFO_PHY_DRIVER, 0x00, 0x01
+ };
+
+ /* Get trace number */
+ error = elants_i2c_execute_command(client,
+ get_resolution_cmd,
+ sizeof(get_resolution_cmd),
+ resp, sizeof(resp), 1,
+ "get resolution");
+ if (error)
+ return error;
+
+ rows = resp[2] + resp[6] + resp[10];
+ cols = resp[3] + resp[7] + resp[11];
+
+ /* Get report resolution value of ABS_MT_TOUCH_MAJOR */
+ ts->major_res = resp[16];
+
+ /* Process mm_to_pixel information */
+ error = elants_i2c_execute_command(client,
+ get_osr_cmd, sizeof(get_osr_cmd),
+ resp, sizeof(resp), 1, "get osr");
+ if (error)
+ return error;
+
+ osr = resp[3];
+
+ error = elants_i2c_execute_command(client,
+ get_physical_scan_cmd,
+ sizeof(get_physical_scan_cmd),
+ resp, sizeof(resp), 1,
+ "get physical scan");
+ if (error)
+ return error;
+
+ phy_x = get_unaligned_be16(&resp[2]);
+
+ error = elants_i2c_execute_command(client,
+ get_physical_drive_cmd,
+ sizeof(get_physical_drive_cmd),
+ resp, sizeof(resp), 1,
+ "get physical drive");
+ if (error)
+ return error;
+
+ phy_y = get_unaligned_be16(&resp[2]);
+
+ dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+ if (rows == 0 || cols == 0 || osr == 0) {
+ dev_warn(&client->dev,
+ "invalid trace number data: %d, %d, %d\n",
+ rows, cols, osr);
+ } else {
+ /* translate trace number to TS resolution */
+ ts->x_max = ELAN_TS_RESOLUTION(rows, osr);
+ ts->x_res = DIV_ROUND_CLOSEST(ts->x_max, phy_x);
+ ts->y_max = ELAN_TS_RESOLUTION(cols, osr);
+ ts->y_res = DIV_ROUND_CLOSEST(ts->y_max, phy_y);
+ ts->phy_x = phy_x;
+ ts->phy_y = phy_y;
+ }
+
+ return 0;
+}
+
+static int elants_i2c_fastboot(struct i2c_client *client)
+{
+ const u8 boot_cmd[] = { 0x4D, 0x61, 0x69, 0x6E };
+ int error;
+
+ error = elants_i2c_send(client, boot_cmd, sizeof(boot_cmd));
+ if (error) {
+ dev_err(&client->dev, "boot failed: %d\n", error);
+ return error;
+ }
+
+ dev_dbg(&client->dev, "boot success -- 0x%x\n", client->addr);
+ return 0;
+}
+
+static int elants_i2c_initialize(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int error, error2, retry_cnt;
+ const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
+ const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
+ u8 buf[HEADER_SIZE];
+
+ for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+ error = elants_i2c_sw_reset(client);
+ if (error) {
+ /* Continue initializing if it's the last try */
+ if (retry_cnt < MAX_RETRIES - 1)
+ continue;
+ }
+
+ error = elants_i2c_fastboot(client);
+ if (error) {
+ /* Continue initializing if it's the last try */
+ if (retry_cnt < MAX_RETRIES - 1)
+ continue;
+ }
+
+ /* Wait for Hello packet */
+ msleep(BOOT_TIME_DELAY_MS);
+
+ error = elants_i2c_read(client, buf, sizeof(buf));
+ if (error) {
+ dev_err(&client->dev,
+ "failed to read 'hello' packet: %d\n", error);
+ } else if (!memcmp(buf, hello_packet, sizeof(hello_packet))) {
+ ts->iap_mode = ELAN_IAP_OPERATIONAL;
+ break;
+ } else if (!memcmp(buf, recov_packet, sizeof(recov_packet))) {
+ /*
+ * Setting error code will mark device
+ * in recovery mode below.
+ */
+ error = -EIO;
+ break;
+ } else {
+ error = -EINVAL;
+ dev_err(&client->dev,
+ "invalid 'hello' packet: %*ph\n",
+ (int)sizeof(buf), buf);
+ }
+ }
+
+ /* hw version is available even if device in recovery state */
+ error2 = elants_i2c_query_hw_version(ts);
+ if (!error2)
+ error2 = elants_i2c_query_bc_version(ts);
+ if (!error)
+ error = error2;
+
+ if (!error)
+ error = elants_i2c_query_fw_version(ts);
+ if (!error)
+ error = elants_i2c_query_test_version(ts);
+
+ switch (ts->chip_id) {
+ case EKTH3500:
+ if (!error)
+ error = elants_i2c_query_ts_info_ekth(ts);
+ break;
+ case EKTF3624:
+ if (!error)
+ error = elants_i2c_query_ts_info_ektf(ts);
+ break;
+ default:
+ BUG();
+ }
+
+ if (error)
+ ts->iap_mode = ELAN_IAP_RECOVERY;
+
+ return 0;
+}
+
+/*
+ * Firmware update interface.
+ */
+
+static int elants_i2c_fw_write_page(struct i2c_client *client,
+ const void *page)
+{
+ const u8 ack_ok[] = { 0xaa, 0xaa };
+ u8 buf[2];
+ int retry;
+ int error;
+
+ for (retry = 0; retry < MAX_FW_UPDATE_RETRIES; retry++) {
+ error = elants_i2c_send(client, page, ELAN_FW_PAGESIZE);
+ if (error) {
+ dev_err(&client->dev,
+ "IAP Write Page failed: %d\n", error);
+ continue;
+ }
+
+ error = elants_i2c_read(client, buf, 2);
+ if (error) {
+ dev_err(&client->dev,
+ "IAP Ack read failed: %d\n", error);
+ return error;
+ }
+
+ if (!memcmp(buf, ack_ok, sizeof(ack_ok)))
+ return 0;
+
+ error = -EIO;
+ dev_err(&client->dev,
+ "IAP Get Ack Error [%02x:%02x]\n",
+ buf[0], buf[1]);
+ }
+
+ return error;
+}
+
+static int elants_i2c_validate_remark_id(struct elants_data *ts,
+ const struct firmware *fw)
+{
+ struct i2c_client *client = ts->client;
+ int error;
+ const u8 cmd[] = { CMD_HEADER_ROM_READ, 0x80, 0x1F, 0x00, 0x00, 0x21 };
+ u8 resp[6] = { 0 };
+ u16 ts_remark_id = 0;
+ u16 fw_remark_id = 0;
+
+ /* Compare TS Remark ID and FW Remark ID */
+ error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+ resp, sizeof(resp),
+ 1, "read Remark ID");
+ if (error)
+ return error;
+
+ ts_remark_id = get_unaligned_be16(&resp[3]);
+
+ fw_remark_id = get_unaligned_le16(&fw->data[fw->size - 4]);
+
+ if (fw_remark_id != ts_remark_id) {
+ dev_err(&client->dev,
+ "Remark ID Mismatched: ts_remark_id=0x%04x, fw_remark_id=0x%04x.\n",
+ ts_remark_id, fw_remark_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static bool elants_i2c_should_check_remark_id(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ const u8 bootcode_version = ts->iap_version;
+ bool check;
+
+ /* I2C eKTH3900 and eKTH5312 are NOT support Remark ID */
+ if ((bootcode_version == BC_VER_H_BYTE_FOR_EKTH3900x1_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH3900x2_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH3900x3_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312x1_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312x2_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312cx1_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312cx2_I2C) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312x1_I2C_USB) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312x2_I2C_USB) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312cx1_I2C_USB) ||
+ (bootcode_version == BC_VER_H_BYTE_FOR_EKTH5312cx2_I2C_USB)) {
+ dev_dbg(&client->dev,
+ "eKTH3900/eKTH5312(0x%02x) are not support remark id\n",
+ bootcode_version);
+ check = false;
+ } else if (bootcode_version >= 0x60) {
+ check = true;
+ } else {
+ check = false;
+ }
+
+ return check;
+}
+
+static int elants_i2c_do_update_firmware(struct i2c_client *client,
+ const struct firmware *fw,
+ bool force)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 };
+ const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 };
+ const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc };
+ const u8 close_idle[] = { 0x54, 0x2c, 0x01, 0x01 };
+ u8 buf[HEADER_SIZE];
+ u16 send_id;
+ int page, n_fw_pages;
+ int error;
+ bool check_remark_id = elants_i2c_should_check_remark_id(ts);
+
+ /* Recovery mode detection! */
+ if (force) {
+ dev_dbg(&client->dev, "Recovery mode procedure\n");
+
+ if (check_remark_id) {
+ error = elants_i2c_validate_remark_id(ts, fw);
+ if (error)
+ return error;
+ }
+
+ error = elants_i2c_send(client, enter_iap2, sizeof(enter_iap2));
+ if (error) {
+ dev_err(&client->dev, "failed to enter IAP mode: %d\n",
+ error);
+ return error;
+ }
+ } else {
+ /* Start IAP Procedure */
+ dev_dbg(&client->dev, "Normal IAP procedure\n");
+
+ /* Close idle mode */
+ error = elants_i2c_send(client, close_idle, sizeof(close_idle));
+ if (error)
+ dev_err(&client->dev, "Failed close idle: %d\n", error);
+ msleep(60);
+
+ elants_i2c_sw_reset(client);
+ msleep(20);
+
+ if (check_remark_id) {
+ error = elants_i2c_validate_remark_id(ts, fw);
+ if (error)
+ return error;
+ }
+
+ error = elants_i2c_send(client, enter_iap, sizeof(enter_iap));
+ if (error) {
+ dev_err(&client->dev, "failed to enter IAP mode: %d\n",
+ error);
+ return error;
+ }
+ }
+
+ msleep(20);
+
+ /* check IAP state */
+ error = elants_i2c_read(client, buf, 4);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to read IAP acknowledgement: %d\n",
+ error);
+ return error;
+ }
+
+ if (memcmp(buf, iap_ack, sizeof(iap_ack))) {
+ dev_err(&client->dev,
+ "failed to enter IAP: %*ph (expected %*ph)\n",
+ (int)sizeof(buf), buf, (int)sizeof(iap_ack), iap_ack);
+ return -EIO;
+ }
+
+ dev_info(&client->dev, "successfully entered IAP mode");
+
+ send_id = client->addr;
+ error = elants_i2c_send(client, &send_id, 1);
+ if (error) {
+ dev_err(&client->dev, "sending dummy byte failed: %d\n",
+ error);
+ return error;
+ }
+
+ /* Clear the last page of Master */
+ error = elants_i2c_send(client, fw->data, ELAN_FW_PAGESIZE);
+ if (error) {
+ dev_err(&client->dev, "clearing of the last page failed: %d\n",
+ error);
+ return error;
+ }
+
+ error = elants_i2c_read(client, buf, 2);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to read ACK for clearing the last page: %d\n",
+ error);
+ return error;
+ }
+
+ n_fw_pages = fw->size / ELAN_FW_PAGESIZE;
+ dev_dbg(&client->dev, "IAP Pages = %d\n", n_fw_pages);
+
+ for (page = 0; page < n_fw_pages; page++) {
+ error = elants_i2c_fw_write_page(client,
+ fw->data + page * ELAN_FW_PAGESIZE);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to write FW page %d: %d\n",
+ page, error);
+ return error;
+ }
+ }
+
+ /* Old iap needs to wait 200ms for WDT and rest is for hello packets */
+ msleep(300);
+
+ dev_info(&client->dev, "firmware update completed\n");
+ return 0;
+}
+
+static int elants_i2c_fw_update(struct elants_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ const struct firmware *fw;
+ char *fw_name;
+ int error;
+
+ fw_name = kasprintf(GFP_KERNEL, "elants_i2c_%04x.bin", ts->hw_version);
+ if (!fw_name)
+ return -ENOMEM;
+
+ dev_info(&client->dev, "requesting fw name = %s\n", fw_name);
+ error = request_firmware(&fw, fw_name, &client->dev);
+ kfree(fw_name);
+ if (error) {
+ dev_err(&client->dev, "failed to request firmware: %d\n",
+ error);
+ return error;
+ }
+
+ if (fw->size % ELAN_FW_PAGESIZE) {
+ dev_err(&client->dev, "invalid firmware length: %zu\n",
+ fw->size);
+ error = -EINVAL;
+ goto out;
+ }
+
+ disable_irq(client->irq);
+
+ error = elants_i2c_do_update_firmware(client, fw,
+ ts->iap_mode == ELAN_IAP_RECOVERY);
+ if (error) {
+ dev_err(&client->dev, "firmware update failed: %d\n", error);
+ ts->iap_mode = ELAN_IAP_RECOVERY;
+ goto out_enable_irq;
+ }
+
+ error = elants_i2c_initialize(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to initialize device after firmware update: %d\n",
+ error);
+ ts->iap_mode = ELAN_IAP_RECOVERY;
+ goto out_enable_irq;
+ }
+
+ ts->iap_mode = ELAN_IAP_OPERATIONAL;
+
+out_enable_irq:
+ ts->state = ELAN_STATE_NORMAL;
+ enable_irq(client->irq);
+ msleep(100);
+
+ if (!error)
+ elants_i2c_calibrate(ts);
+out:
+ release_firmware(fw);
+ return error;
+}
+
+/*
+ * Event reporting.
+ */
+
+static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf,
+ size_t packet_size)
+{
+ struct input_dev *input = ts->input;
+ unsigned int n_fingers;
+ unsigned int tool_type;
+ u16 finger_state;
+ int i;
+
+ n_fingers = buf[FW_POS_STATE + 1] & 0x0f;
+ finger_state = ((buf[FW_POS_STATE + 1] & 0x30) << 4) |
+ buf[FW_POS_STATE];
+
+ dev_dbg(&ts->client->dev,
+ "n_fingers: %u, state: %04x\n", n_fingers, finger_state);
+
+ /* Note: all fingers have the same tool type */
+ tool_type = buf[FW_POS_TOOL_TYPE] & BIT(0) ?
+ MT_TOOL_FINGER : MT_TOOL_PALM;
+
+ for (i = 0; i < MAX_CONTACT_NUM && n_fingers; i++) {
+ if (finger_state & 1) {
+ unsigned int x, y, p, w;
+ u8 *pos;
+
+ pos = &buf[FW_POS_XY + i * 3];
+ x = (((u16)pos[0] & 0xf0) << 4) | pos[1];
+ y = (((u16)pos[0] & 0x0f) << 8) | pos[2];
+
+ /*
+ * eKTF3624 may have use "old" touch-report format,
+ * depending on a device and TS firmware version.
+ * For example, ASUS Transformer devices use the "old"
+ * format, while ASUS Nexus 7 uses the "new" formant.
+ */
+ if (packet_size == PACKET_SIZE_OLD &&
+ ts->chip_id == EKTF3624) {
+ w = buf[FW_POS_WIDTH + i / 2];
+ w >>= 4 * (~i & 1);
+ w |= w << 4;
+ w |= !w;
+ p = w;
+ } else {
+ p = buf[FW_POS_PRESSURE + i];
+ w = buf[FW_POS_WIDTH + i];
+ }
+
+ dev_dbg(&ts->client->dev, "i=%d x=%d y=%d p=%d w=%d\n",
+ i, x, y, p, w);
+
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, tool_type, true);
+ touchscreen_report_pos(input, &ts->prop, x, y, true);
+ input_event(input, EV_ABS, ABS_MT_PRESSURE, p);
+ input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w);
+
+ n_fingers--;
+ }
+
+ finger_state >>= 1;
+ }
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+}
+
+static u8 elants_i2c_calculate_checksum(u8 *buf)
+{
+ u8 checksum = 0;
+ u8 i;
+
+ for (i = 0; i < FW_POS_CHECKSUM; i++)
+ checksum += buf[i];
+
+ return checksum;
+}
+
+static void elants_i2c_event(struct elants_data *ts, u8 *buf,
+ size_t packet_size)
+{
+ u8 checksum = elants_i2c_calculate_checksum(buf);
+
+ if (unlikely(buf[FW_POS_CHECKSUM] != checksum))
+ dev_warn(&ts->client->dev,
+ "%s: invalid checksum for packet %02x: %02x vs. %02x\n",
+ __func__, buf[FW_POS_HEADER],
+ checksum, buf[FW_POS_CHECKSUM]);
+ else if (unlikely(buf[FW_POS_HEADER] != HEADER_REPORT_10_FINGER))
+ dev_warn(&ts->client->dev,
+ "%s: unknown packet type: %02x\n",
+ __func__, buf[FW_POS_HEADER]);
+ else
+ elants_i2c_mt_event(ts, buf, packet_size);
+}
+
+static irqreturn_t elants_i2c_irq(int irq, void *_dev)
+{
+ const u8 wait_packet[] = { 0x64, 0x64, 0x64, 0x64 };
+ struct elants_data *ts = _dev;
+ struct i2c_client *client = ts->client;
+ int report_count, report_len;
+ int i;
+ int len;
+
+ len = i2c_master_recv_dmasafe(client, ts->buf, sizeof(ts->buf));
+ if (len < 0) {
+ dev_err(&client->dev, "%s: failed to read data: %d\n",
+ __func__, len);
+ goto out;
+ }
+
+ dev_dbg(&client->dev, "%s: packet %*ph\n",
+ __func__, HEADER_SIZE, ts->buf);
+
+ switch (ts->state) {
+ case ELAN_WAIT_RECALIBRATION:
+ if (ts->buf[FW_HDR_TYPE] == CMD_HEADER_REK) {
+ memcpy(ts->cmd_resp, ts->buf, sizeof(ts->cmd_resp));
+ complete(&ts->cmd_done);
+ ts->state = ELAN_STATE_NORMAL;
+ }
+ break;
+
+ case ELAN_WAIT_QUEUE_HEADER:
+ if (ts->buf[FW_HDR_TYPE] != QUEUE_HEADER_NORMAL)
+ break;
+
+ ts->state = ELAN_STATE_NORMAL;
+ fallthrough;
+
+ case ELAN_STATE_NORMAL:
+
+ switch (ts->buf[FW_HDR_TYPE]) {
+ case CMD_HEADER_HELLO:
+ case CMD_HEADER_RESP:
+ break;
+
+ case QUEUE_HEADER_WAIT:
+ if (memcmp(ts->buf, wait_packet, sizeof(wait_packet))) {
+ dev_err(&client->dev,
+ "invalid wait packet %*ph\n",
+ HEADER_SIZE, ts->buf);
+ } else {
+ ts->state = ELAN_WAIT_QUEUE_HEADER;
+ udelay(30);
+ }
+ break;
+
+ case QUEUE_HEADER_SINGLE:
+ elants_i2c_event(ts, &ts->buf[HEADER_SIZE],
+ ts->buf[FW_HDR_LENGTH]);
+ break;
+
+ case QUEUE_HEADER_NORMAL2: /* CMD_HEADER_REK */
+ /*
+ * Depending on firmware version, eKTF3624 touchscreens
+ * may utilize one of these opcodes for the touch events:
+ * 0x63 (NORMAL) and 0x66 (NORMAL2). The 0x63 is used by
+ * older firmware version and differs from 0x66 such that
+ * touch pressure value needs to be adjusted. The 0x66
+ * opcode of newer firmware is equal to 0x63 of eKTH3500.
+ */
+ if (ts->chip_id != EKTF3624)
+ break;
+
+ fallthrough;
+
+ case QUEUE_HEADER_NORMAL:
+ report_count = ts->buf[FW_HDR_COUNT];
+ if (report_count == 0 || report_count > 3) {
+ dev_err(&client->dev,
+ "bad report count: %*ph\n",
+ HEADER_SIZE, ts->buf);
+ break;
+ }
+
+ report_len = ts->buf[FW_HDR_LENGTH] / report_count;
+
+ if (report_len == PACKET_SIZE_OLD &&
+ ts->chip_id == EKTF3624) {
+ dev_dbg_once(&client->dev,
+ "using old report format\n");
+ } else if (report_len != PACKET_SIZE) {
+ dev_err(&client->dev,
+ "mismatching report length: %*ph\n",
+ HEADER_SIZE, ts->buf);
+ break;
+ }
+
+ for (i = 0; i < report_count; i++) {
+ u8 *buf = ts->buf + HEADER_SIZE +
+ i * report_len;
+ elants_i2c_event(ts, buf, report_len);
+ }
+ break;
+
+ default:
+ dev_err(&client->dev, "unknown packet %*ph\n",
+ HEADER_SIZE, ts->buf);
+ break;
+ }
+ break;
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t calibrate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ int error;
+
+ error = mutex_lock_interruptible(&ts->sysfs_mutex);
+ if (error)
+ return error;
+
+ error = elants_i2c_calibrate(ts);
+
+ mutex_unlock(&ts->sysfs_mutex);
+ return error ?: count;
+}
+
+static ssize_t write_update_fw(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ int error;
+
+ error = mutex_lock_interruptible(&ts->sysfs_mutex);
+ if (error)
+ return error;
+
+ error = elants_i2c_fw_update(ts);
+ dev_dbg(dev, "firmware update result: %d\n", error);
+
+ mutex_unlock(&ts->sysfs_mutex);
+ return error ?: count;
+}
+
+static ssize_t show_iap_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%s\n",
+ ts->iap_mode == ELAN_IAP_OPERATIONAL ?
+ "Normal" : "Recovery");
+}
+
+static ssize_t show_calibration_count(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_REK, 0x00, 0x01 };
+ u8 resp[HEADER_SIZE];
+ u16 rek_count;
+ int error;
+
+ error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+ resp, sizeof(resp), 1,
+ "read ReK status");
+ if (error)
+ return sprintf(buf, "%d\n", error);
+
+ rek_count = get_unaligned_be16(&resp[2]);
+ return sprintf(buf, "0x%04x\n", rek_count);
+}
+
+static DEVICE_ATTR_WO(calibrate);
+static DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL);
+static DEVICE_ATTR(calibration_count, S_IRUGO, show_calibration_count, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw);
+
+struct elants_version_attribute {
+ struct device_attribute dattr;
+ size_t field_offset;
+ size_t field_size;
+};
+
+#define __ELANTS_FIELD_SIZE(_field) \
+ sizeof(((struct elants_data *)NULL)->_field)
+#define __ELANTS_VERIFY_SIZE(_field) \
+ (BUILD_BUG_ON_ZERO(__ELANTS_FIELD_SIZE(_field) > 2) + \
+ __ELANTS_FIELD_SIZE(_field))
+#define ELANTS_VERSION_ATTR(_field) \
+ struct elants_version_attribute elants_ver_attr_##_field = { \
+ .dattr = __ATTR(_field, S_IRUGO, \
+ elants_version_attribute_show, NULL), \
+ .field_offset = offsetof(struct elants_data, _field), \
+ .field_size = __ELANTS_VERIFY_SIZE(_field), \
+ }
+
+static ssize_t elants_version_attribute_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ struct elants_version_attribute *attr =
+ container_of(dattr, struct elants_version_attribute, dattr);
+ u8 *field = (u8 *)((char *)ts + attr->field_offset);
+ unsigned int fmt_size;
+ unsigned int val;
+
+ if (attr->field_size == 1) {
+ val = *field;
+ fmt_size = 2; /* 2 HEX digits */
+ } else {
+ val = *(u16 *)field;
+ fmt_size = 4; /* 4 HEX digits */
+ }
+
+ return sprintf(buf, "%0*x\n", fmt_size, val);
+}
+
+static ELANTS_VERSION_ATTR(fw_version);
+static ELANTS_VERSION_ATTR(hw_version);
+static ELANTS_VERSION_ATTR(test_version);
+static ELANTS_VERSION_ATTR(solution_version);
+static ELANTS_VERSION_ATTR(bc_version);
+static ELANTS_VERSION_ATTR(iap_version);
+
+static struct attribute *elants_attributes[] = {
+ &dev_attr_calibrate.attr,
+ &dev_attr_update_fw.attr,
+ &dev_attr_iap_mode.attr,
+ &dev_attr_calibration_count.attr,
+
+ &elants_ver_attr_fw_version.dattr.attr,
+ &elants_ver_attr_hw_version.dattr.attr,
+ &elants_ver_attr_test_version.dattr.attr,
+ &elants_ver_attr_solution_version.dattr.attr,
+ &elants_ver_attr_bc_version.dattr.attr,
+ &elants_ver_attr_iap_version.dattr.attr,
+ NULL
+};
+
+static const struct attribute_group elants_attribute_group = {
+ .attrs = elants_attributes,
+};
+
+static int elants_i2c_power_on(struct elants_data *ts)
+{
+ int error;
+
+ /*
+ * If we do not have reset gpio assume platform firmware
+ * controls regulators and does power them on for us.
+ */
+ if (IS_ERR_OR_NULL(ts->reset_gpio))
+ return 0;
+
+ error = regulator_enable(ts->vcc33);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "failed to enable vcc33 regulator: %d\n",
+ error);
+ return error;
+ }
+
+ error = regulator_enable(ts->vccio);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "failed to enable vccio regulator: %d\n",
+ error);
+ regulator_disable(ts->vcc33);
+ return error;
+ }
+
+ /*
+ * We need to wait a bit after powering on controller before
+ * we are allowed to release reset GPIO.
+ */
+ udelay(ELAN_POWERON_DELAY_USEC);
+
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+ if (error)
+ return error;
+
+ msleep(ELAN_RESET_DELAY_MSEC);
+
+ return 0;
+}
+
+static void elants_i2c_power_off(void *_data)
+{
+ struct elants_data *ts = _data;
+
+ if (!IS_ERR_OR_NULL(ts->reset_gpio)) {
+ /*
+ * Activate reset gpio to prevent leakage through the
+ * pin once we shut off power to the controller.
+ */
+ gpiod_set_value_cansleep(ts->reset_gpio, 1);
+ regulator_disable(ts->vccio);
+ regulator_disable(ts->vcc33);
+ }
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id i2c_hid_ids[] = {
+ {"ACPI0C50", 0 },
+ {"PNP0C50", 0 },
+ { },
+};
+
+static const guid_t i2c_hid_guid =
+ GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
+ 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
+
+static bool elants_acpi_is_hid_device(struct device *dev)
+{
+ acpi_handle handle = ACPI_HANDLE(dev);
+ union acpi_object *obj;
+
+ if (acpi_match_device_ids(ACPI_COMPANION(dev), i2c_hid_ids))
+ return false;
+
+ obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL, ACPI_TYPE_INTEGER);
+ if (obj) {
+ ACPI_FREE(obj);
+ return true;
+ }
+
+ return false;
+}
+#else
+static bool elants_acpi_is_hid_device(struct device *dev)
+{
+ return false;
+}
+#endif
+
+static int elants_i2c_probe(struct i2c_client *client)
+{
+ union i2c_smbus_data dummy;
+ struct elants_data *ts;
+ unsigned long irqflags;
+ int error;
+
+ /* Don't bind to i2c-hid compatible devices, these are handled by the i2c-hid drv. */
+ if (elants_acpi_is_hid_device(&client->dev)) {
+ dev_warn(&client->dev, "This device appears to be an I2C-HID device, not binding\n");
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check functionality error\n");
+ return -ENXIO;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(struct elants_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ mutex_init(&ts->sysfs_mutex);
+ init_completion(&ts->cmd_done);
+
+ ts->client = client;
+ ts->chip_id = (enum elants_chip_id)(uintptr_t)device_get_match_data(&client->dev);
+ i2c_set_clientdata(client, ts);
+
+ ts->vcc33 = devm_regulator_get(&client->dev, "vcc33");
+ if (IS_ERR(ts->vcc33)) {
+ error = PTR_ERR(ts->vcc33);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get 'vcc33' regulator: %d\n",
+ error);
+ return error;
+ }
+
+ ts->vccio = devm_regulator_get(&client->dev, "vccio");
+ if (IS_ERR(ts->vccio)) {
+ error = PTR_ERR(ts->vccio);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get 'vccio' regulator: %d\n",
+ error);
+ return error;
+ }
+
+ ts->reset_gpio = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+
+ if (error == -EPROBE_DEFER)
+ return error;
+
+ if (error != -ENOENT && error != -ENOSYS) {
+ dev_err(&client->dev,
+ "failed to get reset gpio: %d\n",
+ error);
+ return error;
+ }
+
+ ts->keep_power_in_suspend = true;
+ }
+
+ error = elants_i2c_power_on(ts);
+ if (error)
+ return error;
+
+ error = devm_add_action_or_reset(&client->dev,
+ elants_i2c_power_off, ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to install power off action: %d\n", error);
+ return error;
+ }
+
+ /* Make sure there is something at this address */
+ if (i2c_smbus_xfer(client->adapter, client->addr, 0,
+ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) {
+ dev_err(&client->dev, "nothing at this address\n");
+ return -ENXIO;
+ }
+
+ error = elants_i2c_initialize(ts);
+ if (error) {
+ dev_err(&client->dev, "failed to initialize: %d\n", error);
+ return error;
+ }
+
+ ts->input = devm_input_allocate_device(&client->dev);
+ if (!ts->input) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->input->name = "Elan Touchscreen";
+ ts->input->id.bustype = BUS_I2C;
+
+ /* Multitouch input params setup */
+
+ input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_TOOL_TYPE,
+ 0, MT_TOOL_PALM, 0, 0);
+
+ touchscreen_parse_properties(ts->input, true, &ts->prop);
+
+ if (ts->chip_id == EKTF3624 && ts->phy_x && ts->phy_y) {
+ /* calculate resolution from size */
+ ts->x_res = DIV_ROUND_CLOSEST(ts->prop.max_x, ts->phy_x);
+ ts->y_res = DIV_ROUND_CLOSEST(ts->prop.max_y, ts->phy_y);
+ }
+
+ input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->x_res);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res);
+ input_abs_set_res(ts->input, ABS_MT_TOUCH_MAJOR, ts->major_res);
+
+ error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to initialize MT slots: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(ts->input);
+ if (error) {
+ dev_err(&client->dev,
+ "unable to register input device: %d\n", error);
+ return error;
+ }
+
+ /*
+ * Platform code (ACPI, DTS) should normally set up interrupt
+ * for us, but in case it did not let's fall back to using falling
+ * edge to be compatible with older Chromebooks.
+ */
+ irqflags = irq_get_trigger_type(client->irq);
+ if (!irqflags)
+ irqflags = IRQF_TRIGGER_FALLING;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, elants_i2c_irq,
+ irqflags | IRQF_ONESHOT,
+ client->name, ts);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ /*
+ * Systems using device tree should set up wakeup via DTS,
+ * the rest will configure device as wakeup source by default.
+ */
+ if (!client->dev.of_node)
+ device_init_wakeup(&client->dev, true);
+
+ error = devm_device_add_group(&client->dev, &elants_attribute_group);
+ if (error) {
+ dev_err(&client->dev, "failed to create sysfs attributes: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused elants_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ const u8 set_sleep_cmd[] = {
+ CMD_HEADER_WRITE, E_POWER_STATE_SLEEP, 0x00, 0x01
+ };
+ int retry_cnt;
+ int error;
+
+ /* Command not support in IAP recovery mode */
+ if (ts->iap_mode != ELAN_IAP_OPERATIONAL)
+ return -EBUSY;
+
+ disable_irq(client->irq);
+
+ if (device_may_wakeup(dev)) {
+ /*
+ * The device will automatically enter idle mode
+ * that has reduced power consumption.
+ */
+ ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+ } else if (ts->keep_power_in_suspend) {
+ for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+ error = elants_i2c_send(client, set_sleep_cmd,
+ sizeof(set_sleep_cmd));
+ if (!error)
+ break;
+
+ dev_err(&client->dev,
+ "suspend command failed: %d\n", error);
+ }
+ } else {
+ elants_i2c_power_off(ts);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused elants_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ const u8 set_active_cmd[] = {
+ CMD_HEADER_WRITE, E_POWER_STATE_RESUME, 0x00, 0x01
+ };
+ int retry_cnt;
+ int error;
+
+ if (device_may_wakeup(dev)) {
+ if (ts->wake_irq_enabled)
+ disable_irq_wake(client->irq);
+ elants_i2c_sw_reset(client);
+ } else if (ts->keep_power_in_suspend) {
+ for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+ error = elants_i2c_send(client, set_active_cmd,
+ sizeof(set_active_cmd));
+ if (!error)
+ break;
+
+ dev_err(&client->dev,
+ "resume command failed: %d\n", error);
+ }
+ } else {
+ elants_i2c_power_on(ts);
+ elants_i2c_initialize(ts);
+ }
+
+ ts->state = ELAN_STATE_NORMAL;
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
+ elants_i2c_suspend, elants_i2c_resume);
+
+static const struct i2c_device_id elants_i2c_id[] = {
+ { DEVICE_NAME, EKTH3500 },
+ { "ekth3500", EKTH3500 },
+ { "ektf3624", EKTF3624 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id elants_acpi_id[] = {
+ { "ELAN0001", EKTH3500 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, elants_acpi_id);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id elants_of_match[] = {
+ { .compatible = "elan,ekth3500", .data = (void *)EKTH3500 },
+ { .compatible = "elan,ektf3624", .data = (void *)EKTF3624 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, elants_of_match);
+#endif
+
+static struct i2c_driver elants_i2c_driver = {
+ .probe_new = elants_i2c_probe,
+ .id_table = elants_i2c_id,
+ .driver = {
+ .name = DEVICE_NAME,
+ .pm = &elants_i2c_pm_ops,
+ .acpi_match_table = ACPI_PTR(elants_acpi_id),
+ .of_match_table = of_match_ptr(elants_of_match),
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_i2c_driver(elants_i2c_driver);
+
+MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>");
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c
new file mode 100644
index 000000000..96173232e
--- /dev/null
+++ b/drivers/input/touchscreen/elo.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Elo serial touchscreen driver
+ *
+ * Copyright (c) 2004 Vojtech Pavlik
+ */
+
+
+/*
+ * This driver can handle serial Elo touchscreens using either the Elo standard
+ * 'E271-2210' 10-byte protocol, Elo legacy 'E281A-4002' 6-byte protocol, Elo
+ * legacy 'E271-140' 4-byte protocol and Elo legacy 'E261-280' 3-byte protocol.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+#include <linux/ctype.h>
+
+#define DRIVER_DESC "Elo serial touchscreen driver"
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define ELO_MAX_LENGTH 10
+
+#define ELO10_PACKET_LEN 8
+#define ELO10_TOUCH 0x03
+#define ELO10_PRESSURE 0x80
+
+#define ELO10_LEAD_BYTE 'U'
+
+#define ELO10_ID_CMD 'i'
+
+#define ELO10_TOUCH_PACKET 'T'
+#define ELO10_ACK_PACKET 'A'
+#define ELI10_ID_PACKET 'I'
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct elo {
+ struct input_dev *dev;
+ struct serio *serio;
+ struct mutex cmd_mutex;
+ struct completion cmd_done;
+ int id;
+ int idx;
+ unsigned char expected_packet;
+ unsigned char csum;
+ unsigned char data[ELO_MAX_LENGTH];
+ unsigned char response[ELO10_PACKET_LEN];
+ char phys[32];
+};
+
+static void elo_process_data_10(struct elo *elo, unsigned char data)
+{
+ struct input_dev *dev = elo->dev;
+
+ elo->data[elo->idx] = data;
+
+ switch (elo->idx++) {
+ case 0:
+ elo->csum = 0xaa;
+ if (data != ELO10_LEAD_BYTE) {
+ dev_dbg(&elo->serio->dev,
+ "unsynchronized data: 0x%02x\n", data);
+ elo->idx = 0;
+ }
+ break;
+
+ case 9:
+ elo->idx = 0;
+ if (data != elo->csum) {
+ dev_dbg(&elo->serio->dev,
+ "bad checksum: 0x%02x, expected 0x%02x\n",
+ data, elo->csum);
+ break;
+ }
+ if (elo->data[1] != elo->expected_packet) {
+ if (elo->data[1] != ELO10_TOUCH_PACKET)
+ dev_dbg(&elo->serio->dev,
+ "unexpected packet: 0x%02x\n",
+ elo->data[1]);
+ break;
+ }
+ if (likely(elo->data[1] == ELO10_TOUCH_PACKET)) {
+ input_report_abs(dev, ABS_X, (elo->data[4] << 8) | elo->data[3]);
+ input_report_abs(dev, ABS_Y, (elo->data[6] << 8) | elo->data[5]);
+ if (elo->data[2] & ELO10_PRESSURE)
+ input_report_abs(dev, ABS_PRESSURE,
+ (elo->data[8] << 8) | elo->data[7]);
+ input_report_key(dev, BTN_TOUCH, elo->data[2] & ELO10_TOUCH);
+ input_sync(dev);
+ } else if (elo->data[1] == ELO10_ACK_PACKET) {
+ if (elo->data[2] == '0')
+ elo->expected_packet = ELO10_TOUCH_PACKET;
+ complete(&elo->cmd_done);
+ } else {
+ memcpy(elo->response, &elo->data[1], ELO10_PACKET_LEN);
+ elo->expected_packet = ELO10_ACK_PACKET;
+ }
+ break;
+ }
+ elo->csum += data;
+}
+
+static void elo_process_data_6(struct elo *elo, unsigned char data)
+{
+ struct input_dev *dev = elo->dev;
+
+ elo->data[elo->idx] = data;
+
+ switch (elo->idx++) {
+
+ case 0:
+ if ((data & 0xc0) != 0xc0)
+ elo->idx = 0;
+ break;
+
+ case 1:
+ if ((data & 0xc0) != 0x80)
+ elo->idx = 0;
+ break;
+
+ case 2:
+ if ((data & 0xc0) != 0x40)
+ elo->idx = 0;
+ break;
+
+ case 3:
+ if (data & 0xc0) {
+ elo->idx = 0;
+ break;
+ }
+
+ input_report_abs(dev, ABS_X, ((elo->data[0] & 0x3f) << 6) | (elo->data[1] & 0x3f));
+ input_report_abs(dev, ABS_Y, ((elo->data[2] & 0x3f) << 6) | (elo->data[3] & 0x3f));
+
+ if (elo->id == 2) {
+ input_report_key(dev, BTN_TOUCH, 1);
+ input_sync(dev);
+ elo->idx = 0;
+ }
+
+ break;
+
+ case 4:
+ if (data) {
+ input_sync(dev);
+ elo->idx = 0;
+ }
+ break;
+
+ case 5:
+ if ((data & 0xf0) == 0) {
+ input_report_abs(dev, ABS_PRESSURE, elo->data[5]);
+ input_report_key(dev, BTN_TOUCH, !!elo->data[5]);
+ }
+ input_sync(dev);
+ elo->idx = 0;
+ break;
+ }
+}
+
+static void elo_process_data_3(struct elo *elo, unsigned char data)
+{
+ struct input_dev *dev = elo->dev;
+
+ elo->data[elo->idx] = data;
+
+ switch (elo->idx++) {
+
+ case 0:
+ if ((data & 0x7f) != 0x01)
+ elo->idx = 0;
+ break;
+ case 2:
+ input_report_key(dev, BTN_TOUCH, !(elo->data[1] & 0x80));
+ input_report_abs(dev, ABS_X, elo->data[1]);
+ input_report_abs(dev, ABS_Y, elo->data[2]);
+ input_sync(dev);
+ elo->idx = 0;
+ break;
+ }
+}
+
+static irqreturn_t elo_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct elo *elo = serio_get_drvdata(serio);
+
+ switch (elo->id) {
+ case 0:
+ elo_process_data_10(elo, data);
+ break;
+
+ case 1:
+ case 2:
+ elo_process_data_6(elo, data);
+ break;
+
+ case 3:
+ elo_process_data_3(elo, data);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int elo_command_10(struct elo *elo, unsigned char *packet)
+{
+ int rc = -1;
+ int i;
+ unsigned char csum = 0xaa + ELO10_LEAD_BYTE;
+
+ mutex_lock(&elo->cmd_mutex);
+
+ serio_pause_rx(elo->serio);
+ elo->expected_packet = toupper(packet[0]);
+ init_completion(&elo->cmd_done);
+ serio_continue_rx(elo->serio);
+
+ if (serio_write(elo->serio, ELO10_LEAD_BYTE))
+ goto out;
+
+ for (i = 0; i < ELO10_PACKET_LEN; i++) {
+ csum += packet[i];
+ if (serio_write(elo->serio, packet[i]))
+ goto out;
+ }
+
+ if (serio_write(elo->serio, csum))
+ goto out;
+
+ wait_for_completion_timeout(&elo->cmd_done, HZ);
+
+ if (elo->expected_packet == ELO10_TOUCH_PACKET) {
+ /* We are back in reporting mode, the command was ACKed */
+ memcpy(packet, elo->response, ELO10_PACKET_LEN);
+ rc = 0;
+ }
+
+ out:
+ mutex_unlock(&elo->cmd_mutex);
+ return rc;
+}
+
+static int elo_setup_10(struct elo *elo)
+{
+ static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" };
+ struct input_dev *dev = elo->dev;
+ unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD };
+
+ if (elo_command_10(elo, packet))
+ return -1;
+
+ dev->id.version = (packet[5] << 8) | packet[4];
+
+ input_set_abs_params(dev, ABS_X, 96, 4000, 0, 0);
+ input_set_abs_params(dev, ABS_Y, 96, 4000, 0, 0);
+ if (packet[3] & ELO10_PRESSURE)
+ input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0);
+
+ dev_info(&elo->serio->dev,
+ "%sTouch touchscreen, fw: %02x.%02x, features: 0x%02x, controller: 0x%02x\n",
+ elo_types[(packet[1] -'0') & 0x03],
+ packet[5], packet[4], packet[3], packet[7]);
+
+ return 0;
+}
+
+/*
+ * elo_disconnect() is the opposite of elo_connect()
+ */
+
+static void elo_disconnect(struct serio *serio)
+{
+ struct elo *elo = serio_get_drvdata(serio);
+
+ input_get_device(elo->dev);
+ input_unregister_device(elo->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(elo->dev);
+ kfree(elo);
+}
+
+/*
+ * elo_connect() is the routine that is called when someone adds a
+ * new serio device that supports Gunze protocol and registers it as
+ * an input device.
+ */
+
+static int elo_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct elo *elo;
+ struct input_dev *input_dev;
+ int err;
+
+ elo = kzalloc(sizeof(struct elo), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!elo || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ elo->serio = serio;
+ elo->id = serio->id.id;
+ elo->dev = input_dev;
+ elo->expected_packet = ELO10_TOUCH_PACKET;
+ mutex_init(&elo->cmd_mutex);
+ init_completion(&elo->cmd_done);
+ snprintf(elo->phys, sizeof(elo->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "Elo Serial TouchScreen";
+ input_dev->phys = elo->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_ELO;
+ input_dev->id.product = elo->id;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ serio_set_drvdata(serio, elo);
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ switch (elo->id) {
+
+ case 0: /* 10-byte protocol */
+ if (elo_setup_10(elo)) {
+ err = -EIO;
+ goto fail3;
+ }
+
+ break;
+
+ case 1: /* 6-byte protocol */
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 15, 0, 0);
+ fallthrough;
+
+ case 2: /* 4-byte protocol */
+ input_set_abs_params(input_dev, ABS_X, 96, 4000, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 96, 4000, 0, 0);
+ break;
+
+ case 3: /* 3-byte protocol */
+ input_set_abs_params(input_dev, ABS_X, 0, 255, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 255, 0, 0);
+ break;
+ }
+
+ err = input_register_device(elo->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(elo);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id elo_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_ELO,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, elo_serio_ids);
+
+static struct serio_driver elo_drv = {
+ .driver = {
+ .name = "elo",
+ },
+ .description = DRIVER_DESC,
+ .id_table = elo_serio_ids,
+ .interrupt = elo_interrupt,
+ .connect = elo_connect,
+ .disconnect = elo_disconnect,
+};
+
+module_serio_driver(elo_drv);
diff --git a/drivers/input/touchscreen/exc3000.c b/drivers/input/touchscreen/exc3000.c
new file mode 100644
index 000000000..615646a03
--- /dev/null
+++ b/drivers/input/touchscreen/exc3000.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for I2C connected EETI EXC3000 multiple touch controller
+ *
+ * Copyright (C) 2017 Ahmet Inan <inan@distec.de>
+ *
+ * minimal implementation based on egalax_ts.c and egalax_i2c.c
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/sizes.h>
+#include <linux/timer.h>
+#include <asm/unaligned.h>
+
+#define EXC3000_NUM_SLOTS 10
+#define EXC3000_SLOTS_PER_FRAME 5
+#define EXC3000_LEN_FRAME 66
+#define EXC3000_LEN_VENDOR_REQUEST 68
+#define EXC3000_LEN_POINT 10
+
+#define EXC3000_LEN_MODEL_NAME 16
+#define EXC3000_LEN_FW_VERSION 16
+
+#define EXC3000_VENDOR_EVENT 0x03
+#define EXC3000_MT1_EVENT 0x06
+#define EXC3000_MT2_EVENT 0x18
+
+#define EXC3000_TIMEOUT_MS 100
+
+#define EXC3000_RESET_MS 10
+#define EXC3000_READY_MS 100
+
+static const struct i2c_device_id exc3000_id[];
+
+struct eeti_dev_info {
+ const char *name;
+ int max_xy;
+};
+
+enum eeti_dev_id {
+ EETI_EXC3000,
+ EETI_EXC80H60,
+ EETI_EXC80H84,
+};
+
+static struct eeti_dev_info exc3000_info[] = {
+ [EETI_EXC3000] = {
+ .name = "EETI EXC3000 Touch Screen",
+ .max_xy = SZ_4K - 1,
+ },
+ [EETI_EXC80H60] = {
+ .name = "EETI EXC80H60 Touch Screen",
+ .max_xy = SZ_16K - 1,
+ },
+ [EETI_EXC80H84] = {
+ .name = "EETI EXC80H84 Touch Screen",
+ .max_xy = SZ_16K - 1,
+ },
+};
+
+struct exc3000_data {
+ struct i2c_client *client;
+ const struct eeti_dev_info *info;
+ struct input_dev *input;
+ struct touchscreen_properties prop;
+ struct gpio_desc *reset;
+ struct timer_list timer;
+ u8 buf[2 * EXC3000_LEN_FRAME];
+ struct completion wait_event;
+ struct mutex query_lock;
+};
+
+static void exc3000_report_slots(struct input_dev *input,
+ struct touchscreen_properties *prop,
+ const u8 *buf, int num)
+{
+ for (; num--; buf += EXC3000_LEN_POINT) {
+ if (buf[0] & BIT(0)) {
+ input_mt_slot(input, buf[1]);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ touchscreen_report_pos(input, prop,
+ get_unaligned_le16(buf + 2),
+ get_unaligned_le16(buf + 4),
+ true);
+ }
+ }
+}
+
+static void exc3000_timer(struct timer_list *t)
+{
+ struct exc3000_data *data = from_timer(data, t, timer);
+
+ input_mt_sync_frame(data->input);
+ input_sync(data->input);
+}
+
+static inline void exc3000_schedule_timer(struct exc3000_data *data)
+{
+ mod_timer(&data->timer, jiffies + msecs_to_jiffies(EXC3000_TIMEOUT_MS));
+}
+
+static void exc3000_shutdown_timer(void *timer)
+{
+ del_timer_sync(timer);
+}
+
+static int exc3000_read_frame(struct exc3000_data *data, u8 *buf)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+
+ ret = i2c_master_send(client, "'", 2);
+ if (ret < 0)
+ return ret;
+
+ if (ret != 2)
+ return -EIO;
+
+ ret = i2c_master_recv(client, buf, EXC3000_LEN_FRAME);
+ if (ret < 0)
+ return ret;
+
+ if (ret != EXC3000_LEN_FRAME)
+ return -EIO;
+
+ if (get_unaligned_le16(buf) != EXC3000_LEN_FRAME)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int exc3000_handle_mt_event(struct exc3000_data *data)
+{
+ struct input_dev *input = data->input;
+ int ret, total_slots;
+ u8 *buf = data->buf;
+
+ total_slots = buf[3];
+ if (!total_slots || total_slots > EXC3000_NUM_SLOTS) {
+ ret = -EINVAL;
+ goto out_fail;
+ }
+
+ if (total_slots > EXC3000_SLOTS_PER_FRAME) {
+ /* Read 2nd frame to get the rest of the contacts. */
+ ret = exc3000_read_frame(data, buf + EXC3000_LEN_FRAME);
+ if (ret)
+ goto out_fail;
+
+ /* 2nd chunk must have number of contacts set to 0. */
+ if (buf[EXC3000_LEN_FRAME + 3] != 0) {
+ ret = -EINVAL;
+ goto out_fail;
+ }
+ }
+
+ /*
+ * We read full state successfully, no contacts will be "stuck".
+ */
+ del_timer_sync(&data->timer);
+
+ while (total_slots > 0) {
+ int slots = min(total_slots, EXC3000_SLOTS_PER_FRAME);
+
+ exc3000_report_slots(input, &data->prop, buf + 4, slots);
+ total_slots -= slots;
+ buf += EXC3000_LEN_FRAME;
+ }
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+
+ return 0;
+
+out_fail:
+ /* Schedule a timer to release "stuck" contacts */
+ exc3000_schedule_timer(data);
+
+ return ret;
+}
+
+static irqreturn_t exc3000_interrupt(int irq, void *dev_id)
+{
+ struct exc3000_data *data = dev_id;
+ u8 *buf = data->buf;
+ int ret;
+
+ ret = exc3000_read_frame(data, buf);
+ if (ret) {
+ /* Schedule a timer to release "stuck" contacts */
+ exc3000_schedule_timer(data);
+ goto out;
+ }
+
+ switch (buf[2]) {
+ case EXC3000_VENDOR_EVENT:
+ complete(&data->wait_event);
+ break;
+
+ case EXC3000_MT1_EVENT:
+ case EXC3000_MT2_EVENT:
+ exc3000_handle_mt_event(data);
+ break;
+
+ default:
+ break;
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int exc3000_vendor_data_request(struct exc3000_data *data, u8 *request,
+ u8 request_len, u8 *response, int timeout)
+{
+ u8 buf[EXC3000_LEN_VENDOR_REQUEST] = { 0x67, 0x00, 0x42, 0x00, 0x03 };
+ int ret;
+ unsigned long time_left;
+
+ mutex_lock(&data->query_lock);
+
+ reinit_completion(&data->wait_event);
+
+ buf[5] = request_len;
+ memcpy(&buf[6], request, request_len);
+
+ ret = i2c_master_send(data->client, buf, EXC3000_LEN_VENDOR_REQUEST);
+ if (ret < 0)
+ goto out_unlock;
+
+ if (response) {
+ time_left = wait_for_completion_timeout(&data->wait_event,
+ timeout * HZ);
+ if (time_left == 0) {
+ ret = -ETIMEDOUT;
+ goto out_unlock;
+ }
+
+ if (data->buf[3] >= EXC3000_LEN_FRAME) {
+ ret = -ENOSPC;
+ goto out_unlock;
+ }
+
+ memcpy(response, &data->buf[4], data->buf[3]);
+ ret = data->buf[3];
+ }
+
+out_unlock:
+ mutex_unlock(&data->query_lock);
+
+ return ret;
+}
+
+static ssize_t fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct exc3000_data *data = i2c_get_clientdata(client);
+ u8 response[EXC3000_LEN_FRAME];
+ int ret;
+
+ /* query bootloader info */
+ ret = exc3000_vendor_data_request(data,
+ (u8[]){0x39, 0x02}, 2, response, 1);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * If the bootloader version is non-zero then the device is in
+ * bootloader mode and won't answer a query for the application FW
+ * version, so we just use the bootloader version info.
+ */
+ if (response[2] || response[3])
+ return sprintf(buf, "%d.%d\n", response[2], response[3]);
+
+ ret = exc3000_vendor_data_request(data, (u8[]){'D'}, 1, response, 1);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%s\n", &response[1]);
+}
+static DEVICE_ATTR_RO(fw_version);
+
+static ssize_t model_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct exc3000_data *data = i2c_get_clientdata(client);
+ u8 response[EXC3000_LEN_FRAME];
+ int ret;
+
+ ret = exc3000_vendor_data_request(data, (u8[]){'E'}, 1, response, 1);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%s\n", &response[1]);
+}
+static DEVICE_ATTR_RO(model);
+
+static ssize_t type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct exc3000_data *data = i2c_get_clientdata(client);
+ u8 response[EXC3000_LEN_FRAME];
+ int ret;
+
+ ret = exc3000_vendor_data_request(data, (u8[]){'F'}, 1, response, 1);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%s\n", &response[1]);
+}
+static DEVICE_ATTR_RO(type);
+
+static struct attribute *sysfs_attrs[] = {
+ &dev_attr_fw_version.attr,
+ &dev_attr_model.attr,
+ &dev_attr_type.attr,
+ NULL
+};
+
+static struct attribute_group exc3000_attribute_group = {
+ .attrs = sysfs_attrs
+};
+
+static int exc3000_probe(struct i2c_client *client)
+{
+ struct exc3000_data *data;
+ struct input_dev *input;
+ int error, max_xy, retry;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ data->info = device_get_match_data(&client->dev);
+ if (!data->info) {
+ enum eeti_dev_id eeti_dev_id =
+ i2c_match_id(exc3000_id, client)->driver_data;
+ data->info = &exc3000_info[eeti_dev_id];
+ }
+ timer_setup(&data->timer, exc3000_timer, 0);
+ init_completion(&data->wait_event);
+ mutex_init(&data->query_lock);
+
+ data->reset = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(data->reset))
+ return PTR_ERR(data->reset);
+
+ if (data->reset) {
+ msleep(EXC3000_RESET_MS);
+ gpiod_set_value_cansleep(data->reset, 0);
+ msleep(EXC3000_READY_MS);
+ }
+
+ input = devm_input_allocate_device(&client->dev);
+ if (!input)
+ return -ENOMEM;
+
+ data->input = input;
+ input_set_drvdata(input, data);
+
+ input->name = data->info->name;
+ input->id.bustype = BUS_I2C;
+
+ max_xy = data->info->max_xy;
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_xy, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_xy, 0, 0);
+
+ touchscreen_parse_properties(input, true, &data->prop);
+
+ error = input_mt_init_slots(input, EXC3000_NUM_SLOTS,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error)
+ return error;
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ error = devm_add_action_or_reset(&client->dev, exc3000_shutdown_timer,
+ &data->timer);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, exc3000_interrupt, IRQF_ONESHOT,
+ client->name, data);
+ if (error)
+ return error;
+
+ /*
+ * I²C does not have built-in recovery, so retry on failure. This
+ * ensures, that the device probe will not fail for temporary issues
+ * on the bus. This is not needed for the sysfs calls (userspace
+ * will receive the error code and can start another query) and
+ * cannot be done for touch events (but that only means loosing one
+ * or two touch events anyways).
+ */
+ for (retry = 0; retry < 3; retry++) {
+ u8 response[EXC3000_LEN_FRAME];
+
+ error = exc3000_vendor_data_request(data, (u8[]){'E'}, 1,
+ response, 1);
+ if (error > 0) {
+ dev_dbg(&client->dev, "TS Model: %s", &response[1]);
+ error = 0;
+ break;
+ }
+ dev_warn(&client->dev, "Retry %d get EETI EXC3000 model: %d\n",
+ retry + 1, error);
+ }
+
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, data);
+
+ error = devm_device_add_group(&client->dev, &exc3000_attribute_group);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct i2c_device_id exc3000_id[] = {
+ { "exc3000", EETI_EXC3000 },
+ { "exc80h60", EETI_EXC80H60 },
+ { "exc80h84", EETI_EXC80H84 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, exc3000_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id exc3000_of_match[] = {
+ { .compatible = "eeti,exc3000", .data = &exc3000_info[EETI_EXC3000] },
+ { .compatible = "eeti,exc80h60", .data = &exc3000_info[EETI_EXC80H60] },
+ { .compatible = "eeti,exc80h84", .data = &exc3000_info[EETI_EXC80H84] },
+ { }
+};
+MODULE_DEVICE_TABLE(of, exc3000_of_match);
+#endif
+
+static struct i2c_driver exc3000_driver = {
+ .driver = {
+ .name = "exc3000",
+ .of_match_table = of_match_ptr(exc3000_of_match),
+ },
+ .id_table = exc3000_id,
+ .probe_new = exc3000_probe,
+};
+
+module_i2c_driver(exc3000_driver);
+
+MODULE_AUTHOR("Ahmet Inan <inan@distec.de>");
+MODULE_DESCRIPTION("I2C connected EETI EXC3000 multiple touch controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/fsl-imx25-tcq.c b/drivers/input/touchscreen/fsl-imx25-tcq.c
new file mode 100644
index 000000000..60a7246c5
--- /dev/null
+++ b/drivers/input/touchscreen/fsl-imx25-tcq.c
@@ -0,0 +1,588 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2014-2015 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+// Based on driver from 2011:
+// Juergen Beisert, Pengutronix <kernel@pengutronix.de>
+//
+// This is the driver for the imx25 TCQ (Touchscreen Conversion Queue)
+// connected to the imx25 ADC.
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/imx25-tsadc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+static const char mx25_tcq_name[] = "mx25-tcq";
+
+enum mx25_tcq_mode {
+ MX25_TS_4WIRE,
+};
+
+struct mx25_tcq_priv {
+ struct regmap *regs;
+ struct regmap *core_regs;
+ struct input_dev *idev;
+ enum mx25_tcq_mode mode;
+ unsigned int pen_threshold;
+ unsigned int sample_count;
+ unsigned int expected_samples;
+ unsigned int pen_debounce;
+ unsigned int settling_time;
+ struct clk *clk;
+ int irq;
+ struct device *dev;
+};
+
+static struct regmap_config mx25_tcq_regconfig = {
+ .fast_io = true,
+ .max_register = 0x5c,
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static const struct of_device_id mx25_tcq_ids[] = {
+ { .compatible = "fsl,imx25-tcq", },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mx25_tcq_ids);
+
+#define TSC_4WIRE_PRE_INDEX 0
+#define TSC_4WIRE_X_INDEX 1
+#define TSC_4WIRE_Y_INDEX 2
+#define TSC_4WIRE_POST_INDEX 3
+#define TSC_4WIRE_LEAVE 4
+
+#define MX25_TSC_DEF_THRESHOLD 80
+#define TSC_MAX_SAMPLES 16
+
+#define MX25_TSC_REPEAT_WAIT 14
+
+enum mx25_adc_configurations {
+ MX25_CFG_PRECHARGE = 0,
+ MX25_CFG_TOUCH_DETECT,
+ MX25_CFG_X_MEASUREMENT,
+ MX25_CFG_Y_MEASUREMENT,
+};
+
+#define MX25_PRECHARGE_VALUE (\
+ MX25_ADCQ_CFG_YPLL_OFF | \
+ MX25_ADCQ_CFG_XNUR_OFF | \
+ MX25_ADCQ_CFG_XPUL_HIGH | \
+ MX25_ADCQ_CFG_REFP_INT | \
+ MX25_ADCQ_CFG_IN_XP | \
+ MX25_ADCQ_CFG_REFN_NGND2 | \
+ MX25_ADCQ_CFG_IGS)
+
+#define MX25_TOUCH_DETECT_VALUE (\
+ MX25_ADCQ_CFG_YNLR | \
+ MX25_ADCQ_CFG_YPLL_OFF | \
+ MX25_ADCQ_CFG_XNUR_OFF | \
+ MX25_ADCQ_CFG_XPUL_OFF | \
+ MX25_ADCQ_CFG_REFP_INT | \
+ MX25_ADCQ_CFG_IN_XP | \
+ MX25_ADCQ_CFG_REFN_NGND2 | \
+ MX25_ADCQ_CFG_PENIACK)
+
+static void imx25_setup_queue_cfgs(struct mx25_tcq_priv *priv,
+ unsigned int settling_cnt)
+{
+ u32 precharge_cfg =
+ MX25_PRECHARGE_VALUE |
+ MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt);
+ u32 touch_detect_cfg =
+ MX25_TOUCH_DETECT_VALUE |
+ MX25_ADCQ_CFG_NOS(1) |
+ MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt);
+
+ regmap_write(priv->core_regs, MX25_TSC_TICR, precharge_cfg);
+
+ /* PRECHARGE */
+ regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_PRECHARGE),
+ precharge_cfg);
+
+ /* TOUCH_DETECT */
+ regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_TOUCH_DETECT),
+ touch_detect_cfg);
+
+ /* X Measurement */
+ regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_X_MEASUREMENT),
+ MX25_ADCQ_CFG_YPLL_OFF |
+ MX25_ADCQ_CFG_XNUR_LOW |
+ MX25_ADCQ_CFG_XPUL_HIGH |
+ MX25_ADCQ_CFG_REFP_XP |
+ MX25_ADCQ_CFG_IN_YP |
+ MX25_ADCQ_CFG_REFN_XN |
+ MX25_ADCQ_CFG_NOS(priv->sample_count) |
+ MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt));
+
+ /* Y Measurement */
+ regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_Y_MEASUREMENT),
+ MX25_ADCQ_CFG_YNLR |
+ MX25_ADCQ_CFG_YPLL_HIGH |
+ MX25_ADCQ_CFG_XNUR_OFF |
+ MX25_ADCQ_CFG_XPUL_OFF |
+ MX25_ADCQ_CFG_REFP_YP |
+ MX25_ADCQ_CFG_IN_XP |
+ MX25_ADCQ_CFG_REFN_YN |
+ MX25_ADCQ_CFG_NOS(priv->sample_count) |
+ MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt));
+
+ /* Enable the touch detection right now */
+ regmap_write(priv->core_regs, MX25_TSC_TICR, touch_detect_cfg |
+ MX25_ADCQ_CFG_IGS);
+}
+
+static int imx25_setup_queue_4wire(struct mx25_tcq_priv *priv,
+ unsigned settling_cnt, int *items)
+{
+ imx25_setup_queue_cfgs(priv, settling_cnt);
+
+ /* Setup the conversion queue */
+ regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0,
+ MX25_ADCQ_ITEM(0, MX25_CFG_PRECHARGE) |
+ MX25_ADCQ_ITEM(1, MX25_CFG_TOUCH_DETECT) |
+ MX25_ADCQ_ITEM(2, MX25_CFG_X_MEASUREMENT) |
+ MX25_ADCQ_ITEM(3, MX25_CFG_Y_MEASUREMENT) |
+ MX25_ADCQ_ITEM(4, MX25_CFG_PRECHARGE) |
+ MX25_ADCQ_ITEM(5, MX25_CFG_TOUCH_DETECT));
+
+ /*
+ * We measure X/Y with 'sample_count' number of samples and execute a
+ * touch detection twice, with 1 sample each
+ */
+ priv->expected_samples = priv->sample_count * 2 + 2;
+ *items = 6;
+
+ return 0;
+}
+
+static void mx25_tcq_disable_touch_irq(struct mx25_tcq_priv *priv)
+{
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_PDMSK,
+ MX25_ADCQ_CR_PDMSK);
+}
+
+static void mx25_tcq_enable_touch_irq(struct mx25_tcq_priv *priv)
+{
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_PDMSK, 0);
+}
+
+static void mx25_tcq_disable_fifo_irq(struct mx25_tcq_priv *priv)
+{
+ regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_FDRY_IRQ,
+ MX25_ADCQ_MR_FDRY_IRQ);
+}
+
+static void mx25_tcq_enable_fifo_irq(struct mx25_tcq_priv *priv)
+{
+ regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_FDRY_IRQ, 0);
+}
+
+static void mx25_tcq_force_queue_start(struct mx25_tcq_priv *priv)
+{
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR,
+ MX25_ADCQ_CR_FQS,
+ MX25_ADCQ_CR_FQS);
+}
+
+static void mx25_tcq_force_queue_stop(struct mx25_tcq_priv *priv)
+{
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR,
+ MX25_ADCQ_CR_FQS, 0);
+}
+
+static void mx25_tcq_fifo_reset(struct mx25_tcq_priv *priv)
+{
+ u32 tcqcr;
+
+ regmap_read(priv->regs, MX25_ADCQ_CR, &tcqcr);
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FRST,
+ MX25_ADCQ_CR_FRST);
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FRST, 0);
+ regmap_write(priv->regs, MX25_ADCQ_CR, tcqcr);
+}
+
+static void mx25_tcq_re_enable_touch_detection(struct mx25_tcq_priv *priv)
+{
+ /* stop the queue from looping */
+ mx25_tcq_force_queue_stop(priv);
+
+ /* for a clean touch detection, preload the X plane */
+ regmap_write(priv->core_regs, MX25_TSC_TICR, MX25_PRECHARGE_VALUE);
+
+ /* waste some time now to pre-load the X plate to high voltage */
+ mx25_tcq_fifo_reset(priv);
+
+ /* re-enable the detection right now */
+ regmap_write(priv->core_regs, MX25_TSC_TICR,
+ MX25_TOUCH_DETECT_VALUE | MX25_ADCQ_CFG_IGS);
+
+ regmap_update_bits(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_PD,
+ MX25_ADCQ_SR_PD);
+
+ /* enable the pen down event to be a source for the interrupt */
+ regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_PD_IRQ, 0);
+
+ /* lets fire the next IRQ if someone touches the touchscreen */
+ mx25_tcq_enable_touch_irq(priv);
+}
+
+static void mx25_tcq_create_event_for_4wire(struct mx25_tcq_priv *priv,
+ u32 *sample_buf,
+ unsigned int samples)
+{
+ unsigned int x_pos = 0;
+ unsigned int y_pos = 0;
+ unsigned int touch_pre = 0;
+ unsigned int touch_post = 0;
+ unsigned int i;
+
+ for (i = 0; i < samples; i++) {
+ unsigned int index = MX25_ADCQ_FIFO_ID(sample_buf[i]);
+ unsigned int val = MX25_ADCQ_FIFO_DATA(sample_buf[i]);
+
+ switch (index) {
+ case 1:
+ touch_pre = val;
+ break;
+ case 2:
+ x_pos = val;
+ break;
+ case 3:
+ y_pos = val;
+ break;
+ case 5:
+ touch_post = val;
+ break;
+ default:
+ dev_dbg(priv->dev, "Dropped samples because of invalid index %d\n",
+ index);
+ return;
+ }
+ }
+
+ if (samples != 0) {
+ /*
+ * only if both touch measures are below a threshold,
+ * the position is valid
+ */
+ if (touch_pre < priv->pen_threshold &&
+ touch_post < priv->pen_threshold) {
+ /* valid samples, generate a report */
+ x_pos /= priv->sample_count;
+ y_pos /= priv->sample_count;
+ input_report_abs(priv->idev, ABS_X, x_pos);
+ input_report_abs(priv->idev, ABS_Y, y_pos);
+ input_report_key(priv->idev, BTN_TOUCH, 1);
+ input_sync(priv->idev);
+
+ /* get next sample */
+ mx25_tcq_enable_fifo_irq(priv);
+ } else if (touch_pre >= priv->pen_threshold &&
+ touch_post >= priv->pen_threshold) {
+ /*
+ * if both samples are invalid,
+ * generate a release report
+ */
+ input_report_key(priv->idev, BTN_TOUCH, 0);
+ input_sync(priv->idev);
+ mx25_tcq_re_enable_touch_detection(priv);
+ } else {
+ /*
+ * if only one of both touch measurements are
+ * below the threshold, still some bouncing
+ * happens. Take additional samples in this
+ * case to be sure
+ */
+ mx25_tcq_enable_fifo_irq(priv);
+ }
+ }
+}
+
+static irqreturn_t mx25_tcq_irq_thread(int irq, void *dev_id)
+{
+ struct mx25_tcq_priv *priv = dev_id;
+ u32 sample_buf[TSC_MAX_SAMPLES];
+ unsigned int samples;
+ u32 stats;
+ unsigned int i;
+
+ /*
+ * Check how many samples are available. We always have to read exactly
+ * sample_count samples from the fifo, or a multiple of sample_count.
+ * Otherwise we mixup samples into different touch events.
+ */
+ regmap_read(priv->regs, MX25_ADCQ_SR, &stats);
+ samples = MX25_ADCQ_SR_FDN(stats);
+ samples -= samples % priv->sample_count;
+
+ if (!samples)
+ return IRQ_HANDLED;
+
+ for (i = 0; i != samples; ++i)
+ regmap_read(priv->regs, MX25_ADCQ_FIFO, &sample_buf[i]);
+
+ mx25_tcq_create_event_for_4wire(priv, sample_buf, samples);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mx25_tcq_irq(int irq, void *dev_id)
+{
+ struct mx25_tcq_priv *priv = dev_id;
+ u32 stat;
+ int ret = IRQ_HANDLED;
+
+ regmap_read(priv->regs, MX25_ADCQ_SR, &stat);
+
+ if (stat & (MX25_ADCQ_SR_FRR | MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR))
+ mx25_tcq_re_enable_touch_detection(priv);
+
+ if (stat & MX25_ADCQ_SR_PD) {
+ mx25_tcq_disable_touch_irq(priv);
+ mx25_tcq_force_queue_start(priv);
+ mx25_tcq_enable_fifo_irq(priv);
+ }
+
+ if (stat & MX25_ADCQ_SR_FDRY) {
+ mx25_tcq_disable_fifo_irq(priv);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ regmap_update_bits(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR |
+ MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR |
+ MX25_ADCQ_SR_PD,
+ MX25_ADCQ_SR_FRR | MX25_ADCQ_SR_FUR |
+ MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_PD);
+
+ return ret;
+}
+
+/* configure the state machine for a 4-wire touchscreen */
+static int mx25_tcq_init(struct mx25_tcq_priv *priv)
+{
+ u32 tgcr;
+ unsigned int ipg_div;
+ unsigned int adc_period;
+ unsigned int debounce_cnt;
+ unsigned int settling_cnt;
+ int itemct;
+ int error;
+
+ regmap_read(priv->core_regs, MX25_TSC_TGCR, &tgcr);
+ ipg_div = max_t(unsigned int, 4, MX25_TGCR_GET_ADCCLK(tgcr));
+ adc_period = USEC_PER_SEC * ipg_div * 2 + 2;
+ adc_period /= clk_get_rate(priv->clk) / 1000 + 1;
+ debounce_cnt = DIV_ROUND_UP(priv->pen_debounce, adc_period * 8) - 1;
+ settling_cnt = DIV_ROUND_UP(priv->settling_time, adc_period * 8) - 1;
+
+ /* Reset */
+ regmap_write(priv->regs, MX25_ADCQ_CR,
+ MX25_ADCQ_CR_QRST | MX25_ADCQ_CR_FRST);
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR,
+ MX25_ADCQ_CR_QRST | MX25_ADCQ_CR_FRST, 0);
+
+ /* up to 128 * 8 ADC clocks are possible */
+ if (debounce_cnt > 127)
+ debounce_cnt = 127;
+
+ /* up to 255 * 8 ADC clocks are possible */
+ if (settling_cnt > 255)
+ settling_cnt = 255;
+
+ error = imx25_setup_queue_4wire(priv, settling_cnt, &itemct);
+ if (error)
+ return error;
+
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR,
+ MX25_ADCQ_CR_LITEMID_MASK | MX25_ADCQ_CR_WMRK_MASK,
+ MX25_ADCQ_CR_LITEMID(itemct - 1) |
+ MX25_ADCQ_CR_WMRK(priv->expected_samples - 1));
+
+ /* setup debounce count */
+ regmap_update_bits(priv->core_regs, MX25_TSC_TGCR,
+ MX25_TGCR_PDBTIME_MASK,
+ MX25_TGCR_PDBTIME(debounce_cnt));
+
+ /* enable debounce */
+ regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, MX25_TGCR_PDBEN,
+ MX25_TGCR_PDBEN);
+ regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, MX25_TGCR_PDEN,
+ MX25_TGCR_PDEN);
+
+ /* enable the engine on demand */
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_QSM_MASK,
+ MX25_ADCQ_CR_QSM_FQS);
+
+ /* Enable repeat and repeat wait */
+ regmap_update_bits(priv->regs, MX25_ADCQ_CR,
+ MX25_ADCQ_CR_RPT | MX25_ADCQ_CR_RWAIT_MASK,
+ MX25_ADCQ_CR_RPT |
+ MX25_ADCQ_CR_RWAIT(MX25_TSC_REPEAT_WAIT));
+
+ return 0;
+}
+
+static int mx25_tcq_parse_dt(struct platform_device *pdev,
+ struct mx25_tcq_priv *priv)
+{
+ struct device_node *np = pdev->dev.of_node;
+ u32 wires;
+ int error;
+
+ /* Setup defaults */
+ priv->pen_threshold = 500;
+ priv->sample_count = 3;
+ priv->pen_debounce = 1000000;
+ priv->settling_time = 250000;
+
+ error = of_property_read_u32(np, "fsl,wires", &wires);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to find fsl,wires properties\n");
+ return error;
+ }
+
+ if (wires == 4) {
+ priv->mode = MX25_TS_4WIRE;
+ } else {
+ dev_err(&pdev->dev, "%u-wire mode not supported\n", wires);
+ return -EINVAL;
+ }
+
+ /* These are optional, we don't care about the return values */
+ of_property_read_u32(np, "fsl,pen-threshold", &priv->pen_threshold);
+ of_property_read_u32(np, "fsl,settling-time-ns", &priv->settling_time);
+ of_property_read_u32(np, "fsl,pen-debounce-ns", &priv->pen_debounce);
+
+ return 0;
+}
+
+static int mx25_tcq_open(struct input_dev *idev)
+{
+ struct device *dev = &idev->dev;
+ struct mx25_tcq_priv *priv = dev_get_drvdata(dev);
+ int error;
+
+ error = clk_prepare_enable(priv->clk);
+ if (error) {
+ dev_err(dev, "Failed to enable ipg clock\n");
+ return error;
+ }
+
+ error = mx25_tcq_init(priv);
+ if (error) {
+ dev_err(dev, "Failed to init tcq\n");
+ clk_disable_unprepare(priv->clk);
+ return error;
+ }
+
+ mx25_tcq_re_enable_touch_detection(priv);
+
+ return 0;
+}
+
+static void mx25_tcq_close(struct input_dev *idev)
+{
+ struct mx25_tcq_priv *priv = input_get_drvdata(idev);
+
+ mx25_tcq_force_queue_stop(priv);
+ mx25_tcq_disable_touch_irq(priv);
+ mx25_tcq_disable_fifo_irq(priv);
+ clk_disable_unprepare(priv->clk);
+}
+
+static int mx25_tcq_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct input_dev *idev;
+ struct mx25_tcq_priv *priv;
+ struct mx25_tsadc *tsadc = dev_get_drvdata(dev->parent);
+ void __iomem *mem;
+ int error;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dev = dev;
+
+ mem = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ error = mx25_tcq_parse_dt(pdev, priv);
+ if (error)
+ return error;
+
+ priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_tcq_regconfig);
+ if (IS_ERR(priv->regs)) {
+ dev_err(dev, "Failed to initialize regmap\n");
+ return PTR_ERR(priv->regs);
+ }
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq <= 0)
+ return priv->irq;
+
+ idev = devm_input_allocate_device(dev);
+ if (!idev) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ idev->name = mx25_tcq_name;
+ input_set_capability(idev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0);
+
+ idev->id.bustype = BUS_HOST;
+ idev->open = mx25_tcq_open;
+ idev->close = mx25_tcq_close;
+
+ priv->idev = idev;
+ input_set_drvdata(idev, priv);
+
+ priv->core_regs = tsadc->regs;
+ if (!priv->core_regs)
+ return -EINVAL;
+
+ priv->clk = tsadc->clk;
+ if (!priv->clk)
+ return -EINVAL;
+
+ platform_set_drvdata(pdev, priv);
+
+ error = devm_request_threaded_irq(dev, priv->irq, mx25_tcq_irq,
+ mx25_tcq_irq_thread, 0, pdev->name,
+ priv);
+ if (error) {
+ dev_err(dev, "Failed requesting IRQ\n");
+ return error;
+ }
+
+ error = input_register_device(idev);
+ if (error) {
+ dev_err(dev, "Failed to register input device\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static struct platform_driver mx25_tcq_driver = {
+ .driver = {
+ .name = "mx25-tcq",
+ .of_match_table = mx25_tcq_ids,
+ },
+ .probe = mx25_tcq_probe,
+};
+module_platform_driver(mx25_tcq_driver);
+
+MODULE_DESCRIPTION("TS input driver for Freescale mx25");
+MODULE_AUTHOR("Markus Pargmann <mpa@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/fujitsu_ts.c b/drivers/input/touchscreen/fujitsu_ts.c
new file mode 100644
index 000000000..3b0b8fccc
--- /dev/null
+++ b/drivers/input/touchscreen/fujitsu_ts.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Fujitsu serial touchscreen driver
+ *
+ * Copyright (c) Dmitry Torokhov <dtor@mail.ru>
+ */
+
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Fujitsu serial touchscreen driver"
+
+MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+#define FUJITSU_LENGTH 5
+
+/*
+ * Per-touchscreen data.
+ */
+struct fujitsu {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[FUJITSU_LENGTH];
+ char phys[32];
+};
+
+/*
+ * Decode serial data (5 bytes per packet)
+ * First byte
+ * 1 C 0 0 R S S S
+ * Where C is 1 while in calibration mode (which we don't use)
+ * R is 1 when no coordinate corection was done.
+ * S are button state
+ */
+static irqreturn_t fujitsu_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct fujitsu *fujitsu = serio_get_drvdata(serio);
+ struct input_dev *dev = fujitsu->dev;
+
+ if (fujitsu->idx == 0) {
+ /* resync skip until start of frame */
+ if ((data & 0xf0) != 0x80)
+ return IRQ_HANDLED;
+ } else {
+ /* resync skip garbage */
+ if (data & 0x80) {
+ fujitsu->idx = 0;
+ return IRQ_HANDLED;
+ }
+ }
+
+ fujitsu->data[fujitsu->idx++] = data;
+ if (fujitsu->idx == FUJITSU_LENGTH) {
+ input_report_abs(dev, ABS_X,
+ (fujitsu->data[2] << 7) | fujitsu->data[1]);
+ input_report_abs(dev, ABS_Y,
+ (fujitsu->data[4] << 7) | fujitsu->data[3]);
+ input_report_key(dev, BTN_TOUCH,
+ (fujitsu->data[0] & 0x03) != 2);
+ input_sync(dev);
+ fujitsu->idx = 0;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * fujitsu_disconnect() is the opposite of fujitsu_connect()
+ */
+static void fujitsu_disconnect(struct serio *serio)
+{
+ struct fujitsu *fujitsu = serio_get_drvdata(serio);
+
+ input_get_device(fujitsu->dev);
+ input_unregister_device(fujitsu->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(fujitsu->dev);
+ kfree(fujitsu);
+}
+
+/*
+ * fujitsu_connect() is the routine that is called when someone adds a
+ * new serio device that supports the Fujitsu protocol and registers it
+ * as input device.
+ */
+static int fujitsu_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct fujitsu *fujitsu;
+ struct input_dev *input_dev;
+ int err;
+
+ fujitsu = kzalloc(sizeof(struct fujitsu), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!fujitsu || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ fujitsu->serio = serio;
+ fujitsu->dev = input_dev;
+ snprintf(fujitsu->phys, sizeof(fujitsu->phys),
+ "%s/input0", serio->phys);
+
+ input_dev->name = "Fujitsu Serial Touchscreen";
+ input_dev->phys = fujitsu->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_FUJITSU;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0100;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ input_set_abs_params(input_dev, ABS_X, 0, 4096, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 4096, 0, 0);
+ serio_set_drvdata(serio, fujitsu);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(fujitsu->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3:
+ serio_close(serio);
+ fail2:
+ serio_set_drvdata(serio, NULL);
+ fail1:
+ input_free_device(input_dev);
+ kfree(fujitsu);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+static const struct serio_device_id fujitsu_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_FUJITSU,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, fujitsu_serio_ids);
+
+static struct serio_driver fujitsu_drv = {
+ .driver = {
+ .name = "fujitsu_ts",
+ },
+ .description = DRIVER_DESC,
+ .id_table = fujitsu_serio_ids,
+ .interrupt = fujitsu_interrupt,
+ .connect = fujitsu_connect,
+ .disconnect = fujitsu_disconnect,
+};
+
+module_serio_driver(fujitsu_drv);
diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
new file mode 100644
index 000000000..3f0732db7
--- /dev/null
+++ b/drivers/input/touchscreen/goodix.c
@@ -0,0 +1,1582 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Goodix Touchscreens
+ *
+ * Copyright (c) 2014 Red Hat Inc.
+ * Copyright (c) 2015 K. Merker <merker@debian.org>
+ *
+ * This code is based on gt9xx.c authored by andrew@goodix.com:
+ *
+ * 2010 - 2012 Goodix Technology.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/dmi.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_data/x86/soc.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <asm/unaligned.h>
+#include "goodix.h"
+
+#define GOODIX_GPIO_INT_NAME "irq"
+#define GOODIX_GPIO_RST_NAME "reset"
+
+#define GOODIX_MAX_HEIGHT 4096
+#define GOODIX_MAX_WIDTH 4096
+#define GOODIX_INT_TRIGGER 1
+#define GOODIX_CONTACT_SIZE 8
+#define GOODIX_MAX_CONTACT_SIZE 9
+#define GOODIX_MAX_CONTACTS 10
+
+#define GOODIX_CONFIG_MIN_LENGTH 186
+#define GOODIX_CONFIG_911_LENGTH 186
+#define GOODIX_CONFIG_967_LENGTH 228
+#define GOODIX_CONFIG_GT9X_LENGTH 240
+
+#define GOODIX_BUFFER_STATUS_READY BIT(7)
+#define GOODIX_HAVE_KEY BIT(4)
+#define GOODIX_BUFFER_STATUS_TIMEOUT 20
+
+#define RESOLUTION_LOC 1
+#define MAX_CONTACTS_LOC 5
+#define TRIGGER_LOC 6
+
+/* Our special handling for GPIO accesses through ACPI is x86 specific */
+#if defined CONFIG_X86 && defined CONFIG_ACPI
+#define ACPI_GPIO_SUPPORT
+#endif
+
+struct goodix_chip_id {
+ const char *id;
+ const struct goodix_chip_data *data;
+};
+
+static int goodix_check_cfg_8(struct goodix_ts_data *ts,
+ const u8 *cfg, int len);
+static int goodix_check_cfg_16(struct goodix_ts_data *ts,
+ const u8 *cfg, int len);
+static void goodix_calc_cfg_checksum_8(struct goodix_ts_data *ts);
+static void goodix_calc_cfg_checksum_16(struct goodix_ts_data *ts);
+
+static const struct goodix_chip_data gt1x_chip_data = {
+ .config_addr = GOODIX_GT1X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_GT9X_LENGTH,
+ .check_config = goodix_check_cfg_16,
+ .calc_config_checksum = goodix_calc_cfg_checksum_16,
+};
+
+static const struct goodix_chip_data gt911_chip_data = {
+ .config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_911_LENGTH,
+ .check_config = goodix_check_cfg_8,
+ .calc_config_checksum = goodix_calc_cfg_checksum_8,
+};
+
+static const struct goodix_chip_data gt967_chip_data = {
+ .config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_967_LENGTH,
+ .check_config = goodix_check_cfg_8,
+ .calc_config_checksum = goodix_calc_cfg_checksum_8,
+};
+
+static const struct goodix_chip_data gt9x_chip_data = {
+ .config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_GT9X_LENGTH,
+ .check_config = goodix_check_cfg_8,
+ .calc_config_checksum = goodix_calc_cfg_checksum_8,
+};
+
+static const struct goodix_chip_id goodix_chip_ids[] = {
+ { .id = "1151", .data = &gt1x_chip_data },
+ { .id = "1158", .data = &gt1x_chip_data },
+ { .id = "5663", .data = &gt1x_chip_data },
+ { .id = "5688", .data = &gt1x_chip_data },
+ { .id = "917S", .data = &gt1x_chip_data },
+ { .id = "9286", .data = &gt1x_chip_data },
+
+ { .id = "911", .data = &gt911_chip_data },
+ { .id = "9271", .data = &gt911_chip_data },
+ { .id = "9110", .data = &gt911_chip_data },
+ { .id = "9111", .data = &gt911_chip_data },
+ { .id = "927", .data = &gt911_chip_data },
+ { .id = "928", .data = &gt911_chip_data },
+
+ { .id = "912", .data = &gt967_chip_data },
+ { .id = "9147", .data = &gt967_chip_data },
+ { .id = "967", .data = &gt967_chip_data },
+ { }
+};
+
+static const unsigned long goodix_irq_flags[] = {
+ IRQ_TYPE_EDGE_RISING,
+ IRQ_TYPE_EDGE_FALLING,
+ IRQ_TYPE_LEVEL_LOW,
+ IRQ_TYPE_LEVEL_HIGH,
+};
+
+static const struct dmi_system_id nine_bytes_report[] = {
+#if defined(CONFIG_DMI) && defined(CONFIG_X86)
+ {
+ /* Lenovo Yoga Book X90F / X90L */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"),
+ }
+ },
+ {
+ /* Lenovo Yoga Book X91F / X91L */
+ .matches = {
+ /* Non exact match to match F + L versions */
+ DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91"),
+ }
+ },
+#endif
+ {}
+};
+
+/*
+ * Those tablets have their x coordinate inverted
+ */
+static const struct dmi_system_id inverted_x_screen[] = {
+#if defined(CONFIG_DMI) && defined(CONFIG_X86)
+ {
+ .ident = "Cube I15-TC",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Cube"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "I15-TC")
+ },
+ },
+#endif
+ {}
+};
+
+/**
+ * goodix_i2c_read - read data from a register of the i2c slave device.
+ *
+ * @client: i2c device.
+ * @reg: the register to read from.
+ * @buf: raw write data buffer.
+ * @len: length of the buffer to write
+ */
+int goodix_i2c_read(struct i2c_client *client, u16 reg, u8 *buf, int len)
+{
+ struct i2c_msg msgs[2];
+ __be16 wbuf = cpu_to_be16(reg);
+ int ret;
+
+ msgs[0].flags = 0;
+ msgs[0].addr = client->addr;
+ msgs[0].len = 2;
+ msgs[0].buf = (u8 *)&wbuf;
+
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].addr = client->addr;
+ msgs[1].len = len;
+ msgs[1].buf = buf;
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ if (ret >= 0)
+ ret = (ret == ARRAY_SIZE(msgs) ? 0 : -EIO);
+
+ if (ret)
+ dev_err(&client->dev, "Error reading %d bytes from 0x%04x: %d\n",
+ len, reg, ret);
+ return ret;
+}
+
+/**
+ * goodix_i2c_write - write data to a register of the i2c slave device.
+ *
+ * @client: i2c device.
+ * @reg: the register to write to.
+ * @buf: raw data buffer to write.
+ * @len: length of the buffer to write
+ */
+int goodix_i2c_write(struct i2c_client *client, u16 reg, const u8 *buf, int len)
+{
+ u8 *addr_buf;
+ struct i2c_msg msg;
+ int ret;
+
+ addr_buf = kmalloc(len + 2, GFP_KERNEL);
+ if (!addr_buf)
+ return -ENOMEM;
+
+ addr_buf[0] = reg >> 8;
+ addr_buf[1] = reg & 0xFF;
+ memcpy(&addr_buf[2], buf, len);
+
+ msg.flags = 0;
+ msg.addr = client->addr;
+ msg.buf = addr_buf;
+ msg.len = len + 2;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret >= 0)
+ ret = (ret == 1 ? 0 : -EIO);
+
+ kfree(addr_buf);
+
+ if (ret)
+ dev_err(&client->dev, "Error writing %d bytes to 0x%04x: %d\n",
+ len, reg, ret);
+ return ret;
+}
+
+int goodix_i2c_write_u8(struct i2c_client *client, u16 reg, u8 value)
+{
+ return goodix_i2c_write(client, reg, &value, sizeof(value));
+}
+
+static const struct goodix_chip_data *goodix_get_chip_data(const char *id)
+{
+ unsigned int i;
+
+ for (i = 0; goodix_chip_ids[i].id; i++) {
+ if (!strcmp(goodix_chip_ids[i].id, id))
+ return goodix_chip_ids[i].data;
+ }
+
+ return &gt9x_chip_data;
+}
+
+static int goodix_ts_read_input_report(struct goodix_ts_data *ts, u8 *data)
+{
+ unsigned long max_timeout;
+ int touch_num;
+ int error;
+ u16 addr = GOODIX_READ_COOR_ADDR;
+ /*
+ * We are going to read 1-byte header,
+ * ts->contact_size * max(1, touch_num) bytes of coordinates
+ * and 1-byte footer which contains the touch-key code.
+ */
+ const int header_contact_keycode_size = 1 + ts->contact_size + 1;
+
+ /*
+ * The 'buffer status' bit, which indicates that the data is valid, is
+ * not set as soon as the interrupt is raised, but slightly after.
+ * This takes around 10 ms to happen, so we poll for 20 ms.
+ */
+ max_timeout = jiffies + msecs_to_jiffies(GOODIX_BUFFER_STATUS_TIMEOUT);
+ do {
+ error = goodix_i2c_read(ts->client, addr, data,
+ header_contact_keycode_size);
+ if (error)
+ return error;
+
+ if (data[0] & GOODIX_BUFFER_STATUS_READY) {
+ touch_num = data[0] & 0x0f;
+ if (touch_num > ts->max_touch_num)
+ return -EPROTO;
+
+ if (touch_num > 1) {
+ addr += header_contact_keycode_size;
+ data += header_contact_keycode_size;
+ error = goodix_i2c_read(ts->client,
+ addr, data,
+ ts->contact_size *
+ (touch_num - 1));
+ if (error)
+ return error;
+ }
+
+ return touch_num;
+ }
+
+ if (data[0] == 0 && ts->firmware_name) {
+ if (goodix_handle_fw_request(ts))
+ return 0;
+ }
+
+ usleep_range(1000, 2000); /* Poll every 1 - 2 ms */
+ } while (time_before(jiffies, max_timeout));
+
+ /*
+ * The Goodix panel will send spurious interrupts after a
+ * 'finger up' event, which will always cause a timeout.
+ */
+ return -ENOMSG;
+}
+
+static int goodix_create_pen_input(struct goodix_ts_data *ts)
+{
+ struct device *dev = &ts->client->dev;
+ struct input_dev *input;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input_copy_abs(input, ABS_X, ts->input_dev, ABS_MT_POSITION_X);
+ input_copy_abs(input, ABS_Y, ts->input_dev, ABS_MT_POSITION_Y);
+ /*
+ * The resolution of these touchscreens is about 10 units/mm, the actual
+ * resolution does not matter much since we set INPUT_PROP_DIRECT.
+ * Userspace wants something here though, so just set it to 10 units/mm.
+ */
+ input_abs_set_res(input, ABS_X, 10);
+ input_abs_set_res(input, ABS_Y, 10);
+ input_set_abs_params(input, ABS_PRESSURE, 0, 255, 0, 0);
+
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+ input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
+ input_set_capability(input, EV_KEY, BTN_STYLUS);
+ input_set_capability(input, EV_KEY, BTN_STYLUS2);
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+
+ input->name = "Goodix Active Pen";
+ input->phys = "input/pen";
+ input->id.bustype = BUS_I2C;
+ input->id.vendor = 0x0416;
+ if (kstrtou16(ts->id, 10, &input->id.product))
+ input->id.product = 0x1001;
+ input->id.version = ts->version;
+
+ ts->input_pen = input;
+ return 0;
+}
+
+static void goodix_ts_report_pen_down(struct goodix_ts_data *ts, u8 *data)
+{
+ int input_x, input_y, input_w, error;
+ u8 key_value;
+
+ if (!ts->pen_input_registered) {
+ error = input_register_device(ts->input_pen);
+ ts->pen_input_registered = (error == 0) ? 1 : error;
+ }
+
+ if (ts->pen_input_registered < 0)
+ return;
+
+ if (ts->contact_size == 9) {
+ input_x = get_unaligned_le16(&data[4]);
+ input_y = get_unaligned_le16(&data[6]);
+ input_w = get_unaligned_le16(&data[8]);
+ } else {
+ input_x = get_unaligned_le16(&data[2]);
+ input_y = get_unaligned_le16(&data[4]);
+ input_w = get_unaligned_le16(&data[6]);
+ }
+
+ touchscreen_report_pos(ts->input_pen, &ts->prop, input_x, input_y, false);
+ input_report_abs(ts->input_pen, ABS_PRESSURE, input_w);
+
+ input_report_key(ts->input_pen, BTN_TOUCH, 1);
+ input_report_key(ts->input_pen, BTN_TOOL_PEN, 1);
+
+ if (data[0] & GOODIX_HAVE_KEY) {
+ key_value = data[1 + ts->contact_size];
+ input_report_key(ts->input_pen, BTN_STYLUS, key_value & 0x10);
+ input_report_key(ts->input_pen, BTN_STYLUS2, key_value & 0x20);
+ } else {
+ input_report_key(ts->input_pen, BTN_STYLUS, 0);
+ input_report_key(ts->input_pen, BTN_STYLUS2, 0);
+ }
+
+ input_sync(ts->input_pen);
+}
+
+static void goodix_ts_report_pen_up(struct goodix_ts_data *ts)
+{
+ if (!ts->input_pen)
+ return;
+
+ input_report_key(ts->input_pen, BTN_TOUCH, 0);
+ input_report_key(ts->input_pen, BTN_TOOL_PEN, 0);
+ input_report_key(ts->input_pen, BTN_STYLUS, 0);
+ input_report_key(ts->input_pen, BTN_STYLUS2, 0);
+
+ input_sync(ts->input_pen);
+}
+
+static void goodix_ts_report_touch_8b(struct goodix_ts_data *ts, u8 *coor_data)
+{
+ int id = coor_data[0] & 0x0F;
+ int input_x = get_unaligned_le16(&coor_data[1]);
+ int input_y = get_unaligned_le16(&coor_data[3]);
+ int input_w = get_unaligned_le16(&coor_data[5]);
+
+ input_mt_slot(ts->input_dev, id);
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
+ touchscreen_report_pos(ts->input_dev, &ts->prop,
+ input_x, input_y, true);
+ input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);
+ input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w);
+}
+
+static void goodix_ts_report_touch_9b(struct goodix_ts_data *ts, u8 *coor_data)
+{
+ int id = coor_data[1] & 0x0F;
+ int input_x = get_unaligned_le16(&coor_data[3]);
+ int input_y = get_unaligned_le16(&coor_data[5]);
+ int input_w = get_unaligned_le16(&coor_data[7]);
+
+ input_mt_slot(ts->input_dev, id);
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
+ touchscreen_report_pos(ts->input_dev, &ts->prop,
+ input_x, input_y, true);
+ input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);
+ input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w);
+}
+
+static void goodix_ts_release_keys(struct goodix_ts_data *ts)
+{
+ int i;
+
+ for (i = 0; i < GOODIX_MAX_KEYS; i++)
+ input_report_key(ts->input_dev, ts->keymap[i], 0);
+}
+
+static void goodix_ts_report_key(struct goodix_ts_data *ts, u8 *data)
+{
+ int touch_num;
+ u8 key_value;
+ int i;
+
+ if (data[0] & GOODIX_HAVE_KEY) {
+ touch_num = data[0] & 0x0f;
+ key_value = data[1 + ts->contact_size * touch_num];
+ for (i = 0; i < GOODIX_MAX_KEYS; i++)
+ if (key_value & BIT(i))
+ input_report_key(ts->input_dev,
+ ts->keymap[i], 1);
+ } else {
+ goodix_ts_release_keys(ts);
+ }
+}
+
+/**
+ * goodix_process_events - Process incoming events
+ *
+ * @ts: our goodix_ts_data pointer
+ *
+ * Called when the IRQ is triggered. Read the current device state, and push
+ * the input events to the user space.
+ */
+static void goodix_process_events(struct goodix_ts_data *ts)
+{
+ u8 point_data[2 + GOODIX_MAX_CONTACT_SIZE * GOODIX_MAX_CONTACTS];
+ int touch_num;
+ int i;
+
+ touch_num = goodix_ts_read_input_report(ts, point_data);
+ if (touch_num < 0)
+ return;
+
+ /* The pen being down is always reported as a single touch */
+ if (touch_num == 1 && (point_data[1] & 0x80)) {
+ goodix_ts_report_pen_down(ts, point_data);
+ goodix_ts_release_keys(ts);
+ goto sync; /* Release any previously registered touches */
+ } else {
+ goodix_ts_report_pen_up(ts);
+ }
+
+ goodix_ts_report_key(ts, point_data);
+
+ for (i = 0; i < touch_num; i++)
+ if (ts->contact_size == 9)
+ goodix_ts_report_touch_9b(ts,
+ &point_data[1 + ts->contact_size * i]);
+ else
+ goodix_ts_report_touch_8b(ts,
+ &point_data[1 + ts->contact_size * i]);
+
+sync:
+ input_mt_sync_frame(ts->input_dev);
+ input_sync(ts->input_dev);
+}
+
+/**
+ * goodix_ts_irq_handler - The IRQ handler
+ *
+ * @irq: interrupt number.
+ * @dev_id: private data pointer.
+ */
+static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
+{
+ struct goodix_ts_data *ts = dev_id;
+
+ goodix_process_events(ts);
+ goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void goodix_free_irq(struct goodix_ts_data *ts)
+{
+ devm_free_irq(&ts->client->dev, ts->client->irq, ts);
+}
+
+static int goodix_request_irq(struct goodix_ts_data *ts)
+{
+ return devm_request_threaded_irq(&ts->client->dev, ts->client->irq,
+ NULL, goodix_ts_irq_handler,
+ ts->irq_flags, ts->client->name, ts);
+}
+
+static int goodix_check_cfg_8(struct goodix_ts_data *ts, const u8 *cfg, int len)
+{
+ int i, raw_cfg_len = len - 2;
+ u8 check_sum = 0;
+
+ for (i = 0; i < raw_cfg_len; i++)
+ check_sum += cfg[i];
+ check_sum = (~check_sum) + 1;
+ if (check_sum != cfg[raw_cfg_len]) {
+ dev_err(&ts->client->dev,
+ "The checksum of the config fw is not correct");
+ return -EINVAL;
+ }
+
+ if (cfg[raw_cfg_len + 1] != 1) {
+ dev_err(&ts->client->dev,
+ "Config fw must have Config_Fresh register set");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void goodix_calc_cfg_checksum_8(struct goodix_ts_data *ts)
+{
+ int i, raw_cfg_len = ts->chip->config_len - 2;
+ u8 check_sum = 0;
+
+ for (i = 0; i < raw_cfg_len; i++)
+ check_sum += ts->config[i];
+ check_sum = (~check_sum) + 1;
+
+ ts->config[raw_cfg_len] = check_sum;
+ ts->config[raw_cfg_len + 1] = 1; /* Set "config_fresh" bit */
+}
+
+static int goodix_check_cfg_16(struct goodix_ts_data *ts, const u8 *cfg,
+ int len)
+{
+ int i, raw_cfg_len = len - 3;
+ u16 check_sum = 0;
+
+ for (i = 0; i < raw_cfg_len; i += 2)
+ check_sum += get_unaligned_be16(&cfg[i]);
+ check_sum = (~check_sum) + 1;
+ if (check_sum != get_unaligned_be16(&cfg[raw_cfg_len])) {
+ dev_err(&ts->client->dev,
+ "The checksum of the config fw is not correct");
+ return -EINVAL;
+ }
+
+ if (cfg[raw_cfg_len + 2] != 1) {
+ dev_err(&ts->client->dev,
+ "Config fw must have Config_Fresh register set");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void goodix_calc_cfg_checksum_16(struct goodix_ts_data *ts)
+{
+ int i, raw_cfg_len = ts->chip->config_len - 3;
+ u16 check_sum = 0;
+
+ for (i = 0; i < raw_cfg_len; i += 2)
+ check_sum += get_unaligned_be16(&ts->config[i]);
+ check_sum = (~check_sum) + 1;
+
+ put_unaligned_be16(check_sum, &ts->config[raw_cfg_len]);
+ ts->config[raw_cfg_len + 2] = 1; /* Set "config_fresh" bit */
+}
+
+/**
+ * goodix_check_cfg - Checks if config fw is valid
+ *
+ * @ts: goodix_ts_data pointer
+ * @cfg: firmware config data
+ * @len: config data length
+ */
+static int goodix_check_cfg(struct goodix_ts_data *ts, const u8 *cfg, int len)
+{
+ if (len < GOODIX_CONFIG_MIN_LENGTH ||
+ len > GOODIX_CONFIG_MAX_LENGTH) {
+ dev_err(&ts->client->dev,
+ "The length of the config fw is not correct");
+ return -EINVAL;
+ }
+
+ return ts->chip->check_config(ts, cfg, len);
+}
+
+/**
+ * goodix_send_cfg - Write fw config to device
+ *
+ * @ts: goodix_ts_data pointer
+ * @cfg: config firmware to write to device
+ * @len: config data length
+ */
+int goodix_send_cfg(struct goodix_ts_data *ts, const u8 *cfg, int len)
+{
+ int error;
+
+ error = goodix_check_cfg(ts, cfg, len);
+ if (error)
+ return error;
+
+ error = goodix_i2c_write(ts->client, ts->chip->config_addr, cfg, len);
+ if (error)
+ return error;
+
+ dev_dbg(&ts->client->dev, "Config sent successfully.");
+
+ /* Let the firmware reconfigure itself, so sleep for 10ms */
+ usleep_range(10000, 11000);
+
+ return 0;
+}
+
+#ifdef ACPI_GPIO_SUPPORT
+static int goodix_pin_acpi_direction_input(struct goodix_ts_data *ts)
+{
+ acpi_handle handle = ACPI_HANDLE(&ts->client->dev);
+ acpi_status status;
+
+ status = acpi_evaluate_object(handle, "INTI", NULL, NULL);
+ return ACPI_SUCCESS(status) ? 0 : -EIO;
+}
+
+static int goodix_pin_acpi_output_method(struct goodix_ts_data *ts, int value)
+{
+ acpi_handle handle = ACPI_HANDLE(&ts->client->dev);
+ acpi_status status;
+
+ status = acpi_execute_simple_method(handle, "INTO", value);
+ return ACPI_SUCCESS(status) ? 0 : -EIO;
+}
+#else
+static int goodix_pin_acpi_direction_input(struct goodix_ts_data *ts)
+{
+ dev_err(&ts->client->dev,
+ "%s called on device without ACPI support\n", __func__);
+ return -EINVAL;
+}
+
+static int goodix_pin_acpi_output_method(struct goodix_ts_data *ts, int value)
+{
+ dev_err(&ts->client->dev,
+ "%s called on device without ACPI support\n", __func__);
+ return -EINVAL;
+}
+#endif
+
+static int goodix_irq_direction_output(struct goodix_ts_data *ts, int value)
+{
+ switch (ts->irq_pin_access_method) {
+ case IRQ_PIN_ACCESS_NONE:
+ dev_err(&ts->client->dev,
+ "%s called without an irq_pin_access_method set\n",
+ __func__);
+ return -EINVAL;
+ case IRQ_PIN_ACCESS_GPIO:
+ return gpiod_direction_output(ts->gpiod_int, value);
+ case IRQ_PIN_ACCESS_ACPI_GPIO:
+ /*
+ * The IRQ pin triggers on a falling edge, so its gets marked
+ * as active-low, use output_raw to avoid the value inversion.
+ */
+ return gpiod_direction_output_raw(ts->gpiod_int, value);
+ case IRQ_PIN_ACCESS_ACPI_METHOD:
+ return goodix_pin_acpi_output_method(ts, value);
+ }
+
+ return -EINVAL; /* Never reached */
+}
+
+static int goodix_irq_direction_input(struct goodix_ts_data *ts)
+{
+ switch (ts->irq_pin_access_method) {
+ case IRQ_PIN_ACCESS_NONE:
+ dev_err(&ts->client->dev,
+ "%s called without an irq_pin_access_method set\n",
+ __func__);
+ return -EINVAL;
+ case IRQ_PIN_ACCESS_GPIO:
+ return gpiod_direction_input(ts->gpiod_int);
+ case IRQ_PIN_ACCESS_ACPI_GPIO:
+ return gpiod_direction_input(ts->gpiod_int);
+ case IRQ_PIN_ACCESS_ACPI_METHOD:
+ return goodix_pin_acpi_direction_input(ts);
+ }
+
+ return -EINVAL; /* Never reached */
+}
+
+int goodix_int_sync(struct goodix_ts_data *ts)
+{
+ int error;
+
+ error = goodix_irq_direction_output(ts, 0);
+ if (error)
+ goto error;
+
+ msleep(50); /* T5: 50ms */
+
+ error = goodix_irq_direction_input(ts);
+ if (error)
+ goto error;
+
+ return 0;
+
+error:
+ dev_err(&ts->client->dev, "Controller irq sync failed.\n");
+ return error;
+}
+
+/**
+ * goodix_reset_no_int_sync - Reset device, leaving interrupt line in output mode
+ *
+ * @ts: goodix_ts_data pointer
+ */
+int goodix_reset_no_int_sync(struct goodix_ts_data *ts)
+{
+ int error;
+
+ /* begin select I2C slave addr */
+ error = gpiod_direction_output(ts->gpiod_rst, 0);
+ if (error)
+ goto error;
+
+ msleep(20); /* T2: > 10ms */
+
+ /* HIGH: 0x28/0x29, LOW: 0xBA/0xBB */
+ error = goodix_irq_direction_output(ts, ts->client->addr == 0x14);
+ if (error)
+ goto error;
+
+ usleep_range(100, 2000); /* T3: > 100us */
+
+ error = gpiod_direction_output(ts->gpiod_rst, 1);
+ if (error)
+ goto error;
+
+ usleep_range(6000, 10000); /* T4: > 5ms */
+
+ /*
+ * Put the reset pin back in to input / high-impedance mode to save
+ * power. Only do this in the non ACPI case since some ACPI boards
+ * don't have a pull-up, so there the reset pin must stay active-high.
+ */
+ if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_GPIO) {
+ error = gpiod_direction_input(ts->gpiod_rst);
+ if (error)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ dev_err(&ts->client->dev, "Controller reset failed.\n");
+ return error;
+}
+
+/**
+ * goodix_reset - Reset device during power on
+ *
+ * @ts: goodix_ts_data pointer
+ */
+static int goodix_reset(struct goodix_ts_data *ts)
+{
+ int error;
+
+ error = goodix_reset_no_int_sync(ts);
+ if (error)
+ return error;
+
+ return goodix_int_sync(ts);
+}
+
+#ifdef ACPI_GPIO_SUPPORT
+static const struct acpi_gpio_params first_gpio = { 0, 0, false };
+static const struct acpi_gpio_params second_gpio = { 1, 0, false };
+
+static const struct acpi_gpio_mapping acpi_goodix_int_first_gpios[] = {
+ { GOODIX_GPIO_INT_NAME "-gpios", &first_gpio, 1 },
+ { GOODIX_GPIO_RST_NAME "-gpios", &second_gpio, 1 },
+ { },
+};
+
+static const struct acpi_gpio_mapping acpi_goodix_int_last_gpios[] = {
+ { GOODIX_GPIO_RST_NAME "-gpios", &first_gpio, 1 },
+ { GOODIX_GPIO_INT_NAME "-gpios", &second_gpio, 1 },
+ { },
+};
+
+static const struct acpi_gpio_mapping acpi_goodix_reset_only_gpios[] = {
+ { GOODIX_GPIO_RST_NAME "-gpios", &first_gpio, 1 },
+ { },
+};
+
+static int goodix_resource(struct acpi_resource *ares, void *data)
+{
+ struct goodix_ts_data *ts = data;
+ struct device *dev = &ts->client->dev;
+ struct acpi_resource_gpio *gpio;
+
+ if (acpi_gpio_get_irq_resource(ares, &gpio)) {
+ if (ts->gpio_int_idx == -1) {
+ ts->gpio_int_idx = ts->gpio_count;
+ } else {
+ dev_err(dev, "More then one GpioInt resource, ignoring ACPI GPIO resources\n");
+ ts->gpio_int_idx = -2;
+ }
+ ts->gpio_count++;
+ } else if (acpi_gpio_get_io_resource(ares, &gpio))
+ ts->gpio_count++;
+
+ return 0;
+}
+
+/*
+ * This function gets called in case we fail to get the irq GPIO directly
+ * because the ACPI tables lack GPIO-name to APCI _CRS index mappings
+ * (no _DSD UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301 data).
+ * In that case we add our own mapping and then goodix_get_gpio_config()
+ * retries to get the GPIOs based on the added mapping.
+ */
+static int goodix_add_acpi_gpio_mappings(struct goodix_ts_data *ts)
+{
+ const struct acpi_gpio_mapping *gpio_mapping = NULL;
+ struct device *dev = &ts->client->dev;
+ LIST_HEAD(resources);
+ int irq, ret;
+
+ ts->gpio_count = 0;
+ ts->gpio_int_idx = -1;
+ ret = acpi_dev_get_resources(ACPI_COMPANION(dev), &resources,
+ goodix_resource, ts);
+ if (ret < 0) {
+ dev_err(dev, "Error getting ACPI resources: %d\n", ret);
+ return ret;
+ }
+
+ acpi_dev_free_resource_list(&resources);
+
+ /*
+ * CHT devices should have a GpioInt + a regular GPIO ACPI resource.
+ * Some CHT devices have a bug (where the also is bogus Interrupt
+ * resource copied from a previous BYT based generation). i2c-core-acpi
+ * will use the non-working Interrupt resource, fix this up.
+ */
+ if (soc_intel_is_cht() && ts->gpio_count == 2 && ts->gpio_int_idx != -1) {
+ irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);
+ if (irq > 0 && irq != ts->client->irq) {
+ dev_warn(dev, "Overriding IRQ %d -> %d\n", ts->client->irq, irq);
+ ts->client->irq = irq;
+ }
+ }
+
+ if (ts->gpio_count == 2 && ts->gpio_int_idx == 0) {
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_ACPI_GPIO;
+ gpio_mapping = acpi_goodix_int_first_gpios;
+ } else if (ts->gpio_count == 2 && ts->gpio_int_idx == 1) {
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_ACPI_GPIO;
+ gpio_mapping = acpi_goodix_int_last_gpios;
+ } else if (ts->gpio_count == 1 && ts->gpio_int_idx == -1 &&
+ acpi_has_method(ACPI_HANDLE(dev), "INTI") &&
+ acpi_has_method(ACPI_HANDLE(dev), "INTO")) {
+ dev_info(dev, "Using ACPI INTI and INTO methods for IRQ pin access\n");
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_ACPI_METHOD;
+ gpio_mapping = acpi_goodix_reset_only_gpios;
+ } else if (soc_intel_is_byt() && ts->gpio_count == 2 && ts->gpio_int_idx == -1) {
+ dev_info(dev, "No ACPI GpioInt resource, assuming that the GPIO order is reset, int\n");
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_ACPI_GPIO;
+ gpio_mapping = acpi_goodix_int_last_gpios;
+ } else if (ts->gpio_count == 1 && ts->gpio_int_idx == 0) {
+ /*
+ * On newer devices there is only 1 GpioInt resource and _PS0
+ * does the whole reset sequence for us.
+ */
+ acpi_device_fix_up_power(ACPI_COMPANION(dev));
+
+ /*
+ * Before the _PS0 call the int GPIO may have been in output
+ * mode and the call should have put the int GPIO in input mode,
+ * but the GPIO subsys cached state may still think it is
+ * in output mode, causing gpiochip_lock_as_irq() failure.
+ *
+ * Add a mapping for the int GPIO to make the
+ * gpiod_int = gpiod_get(..., GPIOD_IN) call succeed,
+ * which will explicitly set the direction to input.
+ */
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_NONE;
+ gpio_mapping = acpi_goodix_int_first_gpios;
+ } else {
+ dev_warn(dev, "Unexpected ACPI resources: gpio_count %d, gpio_int_idx %d\n",
+ ts->gpio_count, ts->gpio_int_idx);
+ /*
+ * On some devices _PS0 does a reset for us and
+ * sometimes this is necessary for things to work.
+ */
+ acpi_device_fix_up_power(ACPI_COMPANION(dev));
+ return -EINVAL;
+ }
+
+ /*
+ * Normally we put the reset pin in input / high-impedance mode to save
+ * power. But some x86/ACPI boards don't have a pull-up, so for the ACPI
+ * case, leave the pin as is. This results in the pin not being touched
+ * at all on x86/ACPI boards, except when needed for error-recover.
+ */
+ ts->gpiod_rst_flags = GPIOD_ASIS;
+
+ return devm_acpi_dev_add_driver_gpios(dev, gpio_mapping);
+}
+#else
+static int goodix_add_acpi_gpio_mappings(struct goodix_ts_data *ts)
+{
+ return -EINVAL;
+}
+#endif /* CONFIG_X86 && CONFIG_ACPI */
+
+/**
+ * goodix_get_gpio_config - Get GPIO config from ACPI/DT
+ *
+ * @ts: goodix_ts_data pointer
+ */
+static int goodix_get_gpio_config(struct goodix_ts_data *ts)
+{
+ int error;
+ struct device *dev;
+ struct gpio_desc *gpiod;
+ bool added_acpi_mappings = false;
+
+ if (!ts->client)
+ return -EINVAL;
+ dev = &ts->client->dev;
+
+ /*
+ * By default we request the reset pin as input, leaving it in
+ * high-impedance when not resetting the controller to save power.
+ */
+ ts->gpiod_rst_flags = GPIOD_IN;
+
+ ts->avdd28 = devm_regulator_get(dev, "AVDD28");
+ if (IS_ERR(ts->avdd28)) {
+ error = PTR_ERR(ts->avdd28);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev,
+ "Failed to get AVDD28 regulator: %d\n", error);
+ return error;
+ }
+
+ ts->vddio = devm_regulator_get(dev, "VDDIO");
+ if (IS_ERR(ts->vddio)) {
+ error = PTR_ERR(ts->vddio);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev,
+ "Failed to get VDDIO regulator: %d\n", error);
+ return error;
+ }
+
+retry_get_irq_gpio:
+ /* Get the interrupt GPIO pin number */
+ gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_INT_NAME, GPIOD_IN);
+ if (IS_ERR(gpiod)) {
+ error = PTR_ERR(gpiod);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get %s GPIO: %d\n",
+ GOODIX_GPIO_INT_NAME, error);
+ return error;
+ }
+ if (!gpiod && has_acpi_companion(dev) && !added_acpi_mappings) {
+ added_acpi_mappings = true;
+ if (goodix_add_acpi_gpio_mappings(ts) == 0)
+ goto retry_get_irq_gpio;
+ }
+
+ ts->gpiod_int = gpiod;
+
+ /* Get the reset line GPIO pin number */
+ gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_RST_NAME, ts->gpiod_rst_flags);
+ if (IS_ERR(gpiod)) {
+ error = PTR_ERR(gpiod);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get %s GPIO: %d\n",
+ GOODIX_GPIO_RST_NAME, error);
+ return error;
+ }
+
+ ts->gpiod_rst = gpiod;
+
+ switch (ts->irq_pin_access_method) {
+ case IRQ_PIN_ACCESS_ACPI_GPIO:
+ /*
+ * We end up here if goodix_add_acpi_gpio_mappings() has
+ * called devm_acpi_dev_add_driver_gpios() because the ACPI
+ * tables did not contain name to index mappings.
+ * Check that we successfully got both GPIOs after we've
+ * added our own acpi_gpio_mapping and if we did not get both
+ * GPIOs reset irq_pin_access_method to IRQ_PIN_ACCESS_NONE.
+ */
+ if (!ts->gpiod_int || !ts->gpiod_rst)
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_NONE;
+ break;
+ case IRQ_PIN_ACCESS_ACPI_METHOD:
+ if (!ts->gpiod_rst)
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_NONE;
+ break;
+ default:
+ if (ts->gpiod_int && ts->gpiod_rst) {
+ ts->reset_controller_at_probe = true;
+ ts->load_cfg_from_disk = true;
+ ts->irq_pin_access_method = IRQ_PIN_ACCESS_GPIO;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * goodix_read_config - Read the embedded configuration of the panel
+ *
+ * @ts: our goodix_ts_data pointer
+ *
+ * Must be called during probe
+ */
+static void goodix_read_config(struct goodix_ts_data *ts)
+{
+ int x_max, y_max;
+ int error;
+
+ /*
+ * On controllers where we need to upload the firmware
+ * (controllers without flash) ts->config already has the config
+ * at this point and the controller itself does not have it yet!
+ */
+ if (!ts->firmware_name) {
+ error = goodix_i2c_read(ts->client, ts->chip->config_addr,
+ ts->config, ts->chip->config_len);
+ if (error) {
+ ts->int_trigger_type = GOODIX_INT_TRIGGER;
+ ts->max_touch_num = GOODIX_MAX_CONTACTS;
+ return;
+ }
+ }
+
+ ts->int_trigger_type = ts->config[TRIGGER_LOC] & 0x03;
+ ts->max_touch_num = ts->config[MAX_CONTACTS_LOC] & 0x0f;
+
+ x_max = get_unaligned_le16(&ts->config[RESOLUTION_LOC]);
+ y_max = get_unaligned_le16(&ts->config[RESOLUTION_LOC + 2]);
+ if (x_max && y_max) {
+ input_abs_set_max(ts->input_dev, ABS_MT_POSITION_X, x_max - 1);
+ input_abs_set_max(ts->input_dev, ABS_MT_POSITION_Y, y_max - 1);
+ }
+
+ ts->chip->calc_config_checksum(ts);
+}
+
+/**
+ * goodix_read_version - Read goodix touchscreen version
+ *
+ * @ts: our goodix_ts_data pointer
+ */
+static int goodix_read_version(struct goodix_ts_data *ts)
+{
+ int error;
+ u8 buf[6];
+ char id_str[GOODIX_ID_MAX_LEN + 1];
+
+ error = goodix_i2c_read(ts->client, GOODIX_REG_ID, buf, sizeof(buf));
+ if (error)
+ return error;
+
+ memcpy(id_str, buf, GOODIX_ID_MAX_LEN);
+ id_str[GOODIX_ID_MAX_LEN] = 0;
+ strscpy(ts->id, id_str, GOODIX_ID_MAX_LEN + 1);
+
+ ts->version = get_unaligned_le16(&buf[4]);
+
+ dev_info(&ts->client->dev, "ID %s, version: %04x\n", ts->id,
+ ts->version);
+
+ return 0;
+}
+
+/**
+ * goodix_i2c_test - I2C test function to check if the device answers.
+ *
+ * @client: the i2c client
+ */
+static int goodix_i2c_test(struct i2c_client *client)
+{
+ int retry = 0;
+ int error;
+ u8 test;
+
+ while (retry++ < 2) {
+ error = goodix_i2c_read(client, GOODIX_REG_ID, &test, 1);
+ if (!error)
+ return 0;
+
+ msleep(20);
+ }
+
+ return error;
+}
+
+/**
+ * goodix_configure_dev - Finish device initialization
+ *
+ * @ts: our goodix_ts_data pointer
+ *
+ * Must be called from probe to finish initialization of the device.
+ * Contains the common initialization code for both devices that
+ * declare gpio pins and devices that do not. It is either called
+ * directly from probe or from request_firmware_wait callback.
+ */
+static int goodix_configure_dev(struct goodix_ts_data *ts)
+{
+ int error;
+ int i;
+
+ ts->int_trigger_type = GOODIX_INT_TRIGGER;
+ ts->max_touch_num = GOODIX_MAX_CONTACTS;
+
+ ts->input_dev = devm_input_allocate_device(&ts->client->dev);
+ if (!ts->input_dev) {
+ dev_err(&ts->client->dev, "Failed to allocate input device.");
+ return -ENOMEM;
+ }
+
+ ts->input_dev->name = "Goodix Capacitive TouchScreen";
+ ts->input_dev->phys = "input/ts";
+ ts->input_dev->id.bustype = BUS_I2C;
+ ts->input_dev->id.vendor = 0x0416;
+ if (kstrtou16(ts->id, 10, &ts->input_dev->id.product))
+ ts->input_dev->id.product = 0x1001;
+ ts->input_dev->id.version = ts->version;
+
+ ts->input_dev->keycode = ts->keymap;
+ ts->input_dev->keycodesize = sizeof(ts->keymap[0]);
+ ts->input_dev->keycodemax = GOODIX_MAX_KEYS;
+
+ /* Capacitive Windows/Home button on some devices */
+ for (i = 0; i < GOODIX_MAX_KEYS; ++i) {
+ if (i == 0)
+ ts->keymap[i] = KEY_LEFTMETA;
+ else
+ ts->keymap[i] = KEY_F1 + (i - 1);
+
+ input_set_capability(ts->input_dev, EV_KEY, ts->keymap[i]);
+ }
+
+ input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y);
+ input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+
+retry_read_config:
+ /* Read configuration and apply touchscreen parameters */
+ goodix_read_config(ts);
+
+ /* Try overriding touchscreen parameters via device properties */
+ touchscreen_parse_properties(ts->input_dev, true, &ts->prop);
+
+ if (!ts->prop.max_x || !ts->prop.max_y || !ts->max_touch_num) {
+ if (!ts->reset_controller_at_probe &&
+ ts->irq_pin_access_method != IRQ_PIN_ACCESS_NONE) {
+ dev_info(&ts->client->dev, "Config not set, resetting controller\n");
+ /* Retry after a controller reset */
+ ts->reset_controller_at_probe = true;
+ error = goodix_reset(ts);
+ if (error)
+ return error;
+ goto retry_read_config;
+ }
+ dev_err(&ts->client->dev,
+ "Invalid config (%d, %d, %d), using defaults\n",
+ ts->prop.max_x, ts->prop.max_y, ts->max_touch_num);
+ ts->prop.max_x = GOODIX_MAX_WIDTH - 1;
+ ts->prop.max_y = GOODIX_MAX_HEIGHT - 1;
+ ts->max_touch_num = GOODIX_MAX_CONTACTS;
+ input_abs_set_max(ts->input_dev,
+ ABS_MT_POSITION_X, ts->prop.max_x);
+ input_abs_set_max(ts->input_dev,
+ ABS_MT_POSITION_Y, ts->prop.max_y);
+ }
+
+ if (dmi_check_system(nine_bytes_report)) {
+ ts->contact_size = 9;
+
+ dev_dbg(&ts->client->dev,
+ "Non-standard 9-bytes report format quirk\n");
+ }
+
+ if (dmi_check_system(inverted_x_screen)) {
+ ts->prop.invert_x = true;
+ dev_dbg(&ts->client->dev,
+ "Applying 'inverted x screen' quirk\n");
+ }
+
+ error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Failed to initialize MT slots: %d", error);
+ return error;
+ }
+
+ error = input_register_device(ts->input_dev);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Failed to register input device: %d", error);
+ return error;
+ }
+
+ /*
+ * Create the input_pen device before goodix_request_irq() calls
+ * devm_request_threaded_irq() so that the devm framework frees
+ * it after disabling the irq.
+ * Unfortunately there is no way to detect if the touchscreen has pen
+ * support, so registering the dev is delayed till the first pen event.
+ */
+ error = goodix_create_pen_input(ts);
+ if (error)
+ return error;
+
+ ts->irq_flags = goodix_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT;
+ error = goodix_request_irq(ts);
+ if (error) {
+ dev_err(&ts->client->dev, "request IRQ failed: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+/**
+ * goodix_config_cb - Callback to finish device init
+ *
+ * @cfg: firmware config
+ * @ctx: our goodix_ts_data pointer
+ *
+ * request_firmware_wait callback that finishes
+ * initialization of the device.
+ */
+static void goodix_config_cb(const struct firmware *cfg, void *ctx)
+{
+ struct goodix_ts_data *ts = ctx;
+ int error;
+
+ if (ts->firmware_name) {
+ if (!cfg)
+ goto err_release_cfg;
+
+ error = goodix_check_cfg(ts, cfg->data, cfg->size);
+ if (error)
+ goto err_release_cfg;
+
+ memcpy(ts->config, cfg->data, cfg->size);
+ } else if (cfg) {
+ /* send device configuration to the firmware */
+ error = goodix_send_cfg(ts, cfg->data, cfg->size);
+ if (error)
+ goto err_release_cfg;
+ }
+
+ goodix_configure_dev(ts);
+
+err_release_cfg:
+ release_firmware(cfg);
+ complete_all(&ts->firmware_loading_complete);
+}
+
+static void goodix_disable_regulators(void *arg)
+{
+ struct goodix_ts_data *ts = arg;
+
+ regulator_disable(ts->vddio);
+ regulator_disable(ts->avdd28);
+}
+
+static int goodix_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct goodix_ts_data *ts;
+ const char *cfg_name;
+ int error;
+
+ dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check functionality failed.\n");
+ return -ENXIO;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+ i2c_set_clientdata(client, ts);
+ init_completion(&ts->firmware_loading_complete);
+ ts->contact_size = GOODIX_CONTACT_SIZE;
+
+ error = goodix_get_gpio_config(ts);
+ if (error)
+ return error;
+
+ /* power up the controller */
+ error = regulator_enable(ts->avdd28);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to enable AVDD28 regulator: %d\n",
+ error);
+ return error;
+ }
+
+ error = regulator_enable(ts->vddio);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to enable VDDIO regulator: %d\n",
+ error);
+ regulator_disable(ts->avdd28);
+ return error;
+ }
+
+ error = devm_add_action_or_reset(&client->dev,
+ goodix_disable_regulators, ts);
+ if (error)
+ return error;
+
+reset:
+ if (ts->reset_controller_at_probe) {
+ /* reset the controller */
+ error = goodix_reset(ts);
+ if (error)
+ return error;
+ }
+
+ error = goodix_i2c_test(client);
+ if (error) {
+ if (!ts->reset_controller_at_probe &&
+ ts->irq_pin_access_method != IRQ_PIN_ACCESS_NONE) {
+ /* Retry after a controller reset */
+ ts->reset_controller_at_probe = true;
+ goto reset;
+ }
+ dev_err(&client->dev, "I2C communication failure: %d\n", error);
+ return error;
+ }
+
+ error = goodix_firmware_check(ts);
+ if (error)
+ return error;
+
+ error = goodix_read_version(ts);
+ if (error)
+ return error;
+
+ ts->chip = goodix_get_chip_data(ts->id);
+
+ if (ts->load_cfg_from_disk) {
+ /* update device config */
+ error = device_property_read_string(&client->dev,
+ "goodix,config-name",
+ &cfg_name);
+ if (!error)
+ snprintf(ts->cfg_name, sizeof(ts->cfg_name),
+ "goodix/%s", cfg_name);
+ else
+ snprintf(ts->cfg_name, sizeof(ts->cfg_name),
+ "goodix_%s_cfg.bin", ts->id);
+
+ error = request_firmware_nowait(THIS_MODULE, true, ts->cfg_name,
+ &client->dev, GFP_KERNEL, ts,
+ goodix_config_cb);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to invoke firmware loader: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+ } else {
+ error = goodix_configure_dev(ts);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static void goodix_ts_remove(struct i2c_client *client)
+{
+ struct goodix_ts_data *ts = i2c_get_clientdata(client);
+
+ if (ts->load_cfg_from_disk)
+ wait_for_completion(&ts->firmware_loading_complete);
+}
+
+static int __maybe_unused goodix_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct goodix_ts_data *ts = i2c_get_clientdata(client);
+ int error;
+
+ if (ts->load_cfg_from_disk)
+ wait_for_completion(&ts->firmware_loading_complete);
+
+ /* We need gpio pins to suspend/resume */
+ if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) {
+ disable_irq(client->irq);
+ return 0;
+ }
+
+ /* Free IRQ as IRQ pin is used as output in the suspend sequence */
+ goodix_free_irq(ts);
+
+ /* Save reference (calibration) info if necessary */
+ goodix_save_bak_ref(ts);
+
+ /* Output LOW on the INT pin for 5 ms */
+ error = goodix_irq_direction_output(ts, 0);
+ if (error) {
+ goodix_request_irq(ts);
+ return error;
+ }
+
+ usleep_range(5000, 6000);
+
+ error = goodix_i2c_write_u8(ts->client, GOODIX_REG_COMMAND,
+ GOODIX_CMD_SCREEN_OFF);
+ if (error) {
+ goodix_irq_direction_input(ts);
+ goodix_request_irq(ts);
+ return -EAGAIN;
+ }
+
+ /*
+ * The datasheet specifies that the interval between sending screen-off
+ * command and wake-up should be longer than 58 ms. To avoid waking up
+ * sooner, delay 58ms here.
+ */
+ msleep(58);
+ return 0;
+}
+
+static int __maybe_unused goodix_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct goodix_ts_data *ts = i2c_get_clientdata(client);
+ u8 config_ver;
+ int error;
+
+ if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) {
+ enable_irq(client->irq);
+ return 0;
+ }
+
+ /*
+ * Exit sleep mode by outputting HIGH level to INT pin
+ * for 2ms~5ms.
+ */
+ error = goodix_irq_direction_output(ts, 1);
+ if (error)
+ return error;
+
+ usleep_range(2000, 5000);
+
+ error = goodix_int_sync(ts);
+ if (error)
+ return error;
+
+ error = goodix_i2c_read(ts->client, ts->chip->config_addr,
+ &config_ver, 1);
+ if (!error && config_ver != ts->config[0])
+ dev_info(dev, "Config version mismatch %d != %d, resetting controller\n",
+ config_ver, ts->config[0]);
+
+ if (error != 0 || config_ver != ts->config[0]) {
+ error = goodix_reset(ts);
+ if (error)
+ return error;
+
+ error = goodix_send_cfg(ts, ts->config, ts->chip->config_len);
+ if (error)
+ return error;
+ }
+
+ error = goodix_request_irq(ts);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(goodix_pm_ops, goodix_suspend, goodix_resume);
+
+static const struct i2c_device_id goodix_ts_id[] = {
+ { "GDIX1001:00", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, goodix_ts_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id goodix_acpi_match[] = {
+ { "GDIX1001", 0 },
+ { "GDIX1002", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, goodix_acpi_match);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id goodix_of_match[] = {
+ { .compatible = "goodix,gt1151" },
+ { .compatible = "goodix,gt1158" },
+ { .compatible = "goodix,gt5663" },
+ { .compatible = "goodix,gt5688" },
+ { .compatible = "goodix,gt911" },
+ { .compatible = "goodix,gt9110" },
+ { .compatible = "goodix,gt912" },
+ { .compatible = "goodix,gt9147" },
+ { .compatible = "goodix,gt917s" },
+ { .compatible = "goodix,gt927" },
+ { .compatible = "goodix,gt9271" },
+ { .compatible = "goodix,gt928" },
+ { .compatible = "goodix,gt9286" },
+ { .compatible = "goodix,gt967" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, goodix_of_match);
+#endif
+
+static struct i2c_driver goodix_ts_driver = {
+ .probe = goodix_ts_probe,
+ .remove = goodix_ts_remove,
+ .id_table = goodix_ts_id,
+ .driver = {
+ .name = "Goodix-TS",
+ .acpi_match_table = ACPI_PTR(goodix_acpi_match),
+ .of_match_table = of_match_ptr(goodix_of_match),
+ .pm = &goodix_pm_ops,
+ },
+};
+module_i2c_driver(goodix_ts_driver);
+
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
+MODULE_DESCRIPTION("Goodix touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/goodix.h b/drivers/input/touchscreen/goodix.h
new file mode 100644
index 000000000..87797cc88
--- /dev/null
+++ b/drivers/input/touchscreen/goodix.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __GOODIX_H__
+#define __GOODIX_H__
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/regulator/consumer.h>
+
+/* Register defines */
+#define GOODIX_REG_MISCTL_DSP_CTL 0x4010
+#define GOODIX_REG_MISCTL_SRAM_BANK 0x4048
+#define GOODIX_REG_MISCTL_MEM_CD_EN 0x4049
+#define GOODIX_REG_MISCTL_CACHE_EN 0x404B
+#define GOODIX_REG_MISCTL_TMR0_EN 0x40B0
+#define GOODIX_REG_MISCTL_SWRST 0x4180
+#define GOODIX_REG_MISCTL_CPU_SWRST_PULSE 0x4184
+#define GOODIX_REG_MISCTL_BOOTCTL 0x4190
+#define GOODIX_REG_MISCTL_BOOT_OPT 0x4218
+#define GOODIX_REG_MISCTL_BOOT_CTL 0x5094
+
+#define GOODIX_REG_FW_SIG 0x8000
+#define GOODIX_FW_SIG_LEN 10
+
+#define GOODIX_REG_MAIN_CLK 0x8020
+#define GOODIX_MAIN_CLK_LEN 6
+
+#define GOODIX_REG_COMMAND 0x8040
+#define GOODIX_CMD_SCREEN_OFF 0x05
+
+#define GOODIX_REG_SW_WDT 0x8041
+
+#define GOODIX_REG_REQUEST 0x8043
+#define GOODIX_RQST_RESPONDED 0x00
+#define GOODIX_RQST_CONFIG 0x01
+#define GOODIX_RQST_BAK_REF 0x02
+#define GOODIX_RQST_RESET 0x03
+#define GOODIX_RQST_MAIN_CLOCK 0x04
+/*
+ * Unknown request which gets send by the controller aprox.
+ * every 34 seconds once it is up and running.
+ */
+#define GOODIX_RQST_UNKNOWN 0x06
+#define GOODIX_RQST_IDLE 0xFF
+
+#define GOODIX_REG_STATUS 0x8044
+
+#define GOODIX_GT1X_REG_CONFIG_DATA 0x8050
+#define GOODIX_GT9X_REG_CONFIG_DATA 0x8047
+#define GOODIX_REG_ID 0x8140
+#define GOODIX_READ_COOR_ADDR 0x814E
+#define GOODIX_REG_BAK_REF 0x99D0
+
+#define GOODIX_ID_MAX_LEN 4
+#define GOODIX_CONFIG_MAX_LENGTH 240
+#define GOODIX_MAX_KEYS 7
+
+enum goodix_irq_pin_access_method {
+ IRQ_PIN_ACCESS_NONE,
+ IRQ_PIN_ACCESS_GPIO,
+ IRQ_PIN_ACCESS_ACPI_GPIO,
+ IRQ_PIN_ACCESS_ACPI_METHOD,
+};
+
+struct goodix_ts_data;
+
+struct goodix_chip_data {
+ u16 config_addr;
+ int config_len;
+ int (*check_config)(struct goodix_ts_data *ts, const u8 *cfg, int len);
+ void (*calc_config_checksum)(struct goodix_ts_data *ts);
+};
+
+struct goodix_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct input_dev *input_pen;
+ const struct goodix_chip_data *chip;
+ const char *firmware_name;
+ struct touchscreen_properties prop;
+ unsigned int max_touch_num;
+ unsigned int int_trigger_type;
+ struct regulator *avdd28;
+ struct regulator *vddio;
+ struct gpio_desc *gpiod_int;
+ struct gpio_desc *gpiod_rst;
+ int gpio_count;
+ int gpio_int_idx;
+ enum gpiod_flags gpiod_rst_flags;
+ char id[GOODIX_ID_MAX_LEN + 1];
+ char cfg_name[64];
+ u16 version;
+ bool reset_controller_at_probe;
+ bool load_cfg_from_disk;
+ int pen_input_registered;
+ struct completion firmware_loading_complete;
+ unsigned long irq_flags;
+ enum goodix_irq_pin_access_method irq_pin_access_method;
+ unsigned int contact_size;
+ u8 config[GOODIX_CONFIG_MAX_LENGTH];
+ unsigned short keymap[GOODIX_MAX_KEYS];
+ u8 main_clk[GOODIX_MAIN_CLK_LEN];
+ int bak_ref_len;
+ u8 *bak_ref;
+};
+
+int goodix_i2c_read(struct i2c_client *client, u16 reg, u8 *buf, int len);
+int goodix_i2c_write(struct i2c_client *client, u16 reg, const u8 *buf, int len);
+int goodix_i2c_write_u8(struct i2c_client *client, u16 reg, u8 value);
+int goodix_send_cfg(struct goodix_ts_data *ts, const u8 *cfg, int len);
+int goodix_int_sync(struct goodix_ts_data *ts);
+int goodix_reset_no_int_sync(struct goodix_ts_data *ts);
+
+int goodix_firmware_check(struct goodix_ts_data *ts);
+bool goodix_handle_fw_request(struct goodix_ts_data *ts);
+void goodix_save_bak_ref(struct goodix_ts_data *ts);
+
+#endif
diff --git a/drivers/input/touchscreen/goodix_fwupload.c b/drivers/input/touchscreen/goodix_fwupload.c
new file mode 100644
index 000000000..191d4f38d
--- /dev/null
+++ b/drivers/input/touchscreen/goodix_fwupload.c
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Goodix Touchscreen firmware upload support
+ *
+ * Copyright (c) 2021 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This is a rewrite of gt9xx_update.c from the Allwinner H3 BSP which is:
+ * Copyright (c) 2010 - 2012 Goodix Technology.
+ * Author: andrew@goodix.com
+ */
+
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include "goodix.h"
+
+#define GOODIX_FW_HEADER_LENGTH sizeof(struct goodix_fw_header)
+#define GOODIX_FW_SECTION_LENGTH 0x2000
+#define GOODIX_FW_DSP_LENGTH 0x1000
+#define GOODIX_FW_UPLOAD_ADDRESS 0xc000
+
+#define GOODIX_CFG_LOC_HAVE_KEY 7
+#define GOODIX_CFG_LOC_DRVA_NUM 27
+#define GOODIX_CFG_LOC_DRVB_NUM 28
+#define GOODIX_CFG_LOC_SENS_NUM 29
+
+struct goodix_fw_header {
+ u8 hw_info[4];
+ u8 pid[8];
+ u8 vid[2];
+} __packed;
+
+static u16 goodix_firmware_checksum(const u8 *data, int size)
+{
+ u16 checksum = 0;
+ int i;
+
+ for (i = 0; i < size; i += 2)
+ checksum += (data[i] << 8) + data[i + 1];
+
+ return checksum;
+}
+
+static int goodix_firmware_verify(struct device *dev, const struct firmware *fw)
+{
+ const struct goodix_fw_header *fw_header;
+ size_t expected_size;
+ const u8 *data;
+ u16 checksum;
+ char buf[9];
+
+ expected_size = GOODIX_FW_HEADER_LENGTH + 4 * GOODIX_FW_SECTION_LENGTH +
+ GOODIX_FW_DSP_LENGTH;
+ if (fw->size != expected_size) {
+ dev_err(dev, "Firmware has wrong size, expected %zu got %zu\n",
+ expected_size, fw->size);
+ return -EINVAL;
+ }
+
+ data = fw->data + GOODIX_FW_HEADER_LENGTH;
+ checksum = goodix_firmware_checksum(data, 4 * GOODIX_FW_SECTION_LENGTH);
+ if (checksum) {
+ dev_err(dev, "Main firmware checksum error\n");
+ return -EINVAL;
+ }
+
+ data += 4 * GOODIX_FW_SECTION_LENGTH;
+ checksum = goodix_firmware_checksum(data, GOODIX_FW_DSP_LENGTH);
+ if (checksum) {
+ dev_err(dev, "DSP firmware checksum error\n");
+ return -EINVAL;
+ }
+
+ fw_header = (const struct goodix_fw_header *)fw->data;
+ dev_info(dev, "Firmware hardware info %02x%02x%02x%02x\n",
+ fw_header->hw_info[0], fw_header->hw_info[1],
+ fw_header->hw_info[2], fw_header->hw_info[3]);
+ /* pid is a 8 byte buffer containing a string, weird I know */
+ memcpy(buf, fw_header->pid, 8);
+ buf[8] = 0;
+ dev_info(dev, "Firmware PID: %s VID: %02x%02x\n", buf,
+ fw_header->vid[0], fw_header->vid[1]);
+ return 0;
+}
+
+static int goodix_enter_upload_mode(struct i2c_client *client)
+{
+ int tries, error;
+ u8 val;
+
+ tries = 200;
+ do {
+ error = goodix_i2c_write_u8(client,
+ GOODIX_REG_MISCTL_SWRST, 0x0c);
+ if (error)
+ return error;
+
+ error = goodix_i2c_read(client,
+ GOODIX_REG_MISCTL_SWRST, &val, 1);
+ if (error)
+ return error;
+
+ if (val == 0x0c)
+ break;
+ } while (--tries);
+
+ if (!tries) {
+ dev_err(&client->dev, "Error could not hold ss51 & dsp\n");
+ return -EIO;
+ }
+
+ /* DSP_CK and DSP_ALU_CK PowerOn */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_DSP_CTL, 0x00);
+ if (error)
+ return error;
+
+ /* Disable watchdog */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_TMR0_EN, 0x00);
+ if (error)
+ return error;
+
+ /* Clear cache enable */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_CACHE_EN, 0x00);
+ if (error)
+ return error;
+
+ /* Set boot from SRAM */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_BOOTCTL, 0x02);
+ if (error)
+ return error;
+
+ /* Software reboot */
+ error = goodix_i2c_write_u8(client,
+ GOODIX_REG_MISCTL_CPU_SWRST_PULSE, 0x01);
+ if (error)
+ return error;
+
+ /* Clear control flag */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_BOOTCTL, 0x00);
+ if (error)
+ return error;
+
+ /* Set scramble */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_BOOT_OPT, 0x00);
+ if (error)
+ return error;
+
+ /* Enable accessing code */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_MEM_CD_EN, 0x01);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int goodix_start_firmware(struct i2c_client *client)
+{
+ int error;
+ u8 val;
+
+ /* Init software watchdog */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_SW_WDT, 0xaa);
+ if (error)
+ return error;
+
+ /* Release SS51 & DSP */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_MISCTL_SWRST, 0x00);
+ if (error)
+ return error;
+
+ error = goodix_i2c_read(client, GOODIX_REG_SW_WDT, &val, 1);
+ if (error)
+ return error;
+
+ /* The value we've written to SW_WDT should have been cleared now */
+ if (val == 0xaa) {
+ dev_err(&client->dev, "Error SW_WDT reg not cleared on fw startup\n");
+ return -EIO;
+ }
+
+ /* Re-init software watchdog */
+ error = goodix_i2c_write_u8(client, GOODIX_REG_SW_WDT, 0xaa);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int goodix_firmware_upload(struct goodix_ts_data *ts)
+{
+ const struct firmware *fw;
+ char fw_name[64];
+ const u8 *data;
+ int error;
+
+ snprintf(fw_name, sizeof(fw_name), "goodix/%s", ts->firmware_name);
+
+ error = request_firmware(&fw, fw_name, &ts->client->dev);
+ if (error) {
+ dev_err(&ts->client->dev, "Firmware request error %d\n", error);
+ return error;
+ }
+
+ error = goodix_firmware_verify(&ts->client->dev, fw);
+ if (error)
+ goto release;
+
+ error = goodix_reset_no_int_sync(ts);
+ if (error)
+ goto release;
+
+ error = goodix_enter_upload_mode(ts->client);
+ if (error)
+ goto release;
+
+ /* Select SRAM bank 0 and upload section 1 & 2 */
+ error = goodix_i2c_write_u8(ts->client,
+ GOODIX_REG_MISCTL_SRAM_BANK, 0x00);
+ if (error)
+ goto release;
+
+ data = fw->data + GOODIX_FW_HEADER_LENGTH;
+ error = goodix_i2c_write(ts->client, GOODIX_FW_UPLOAD_ADDRESS,
+ data, 2 * GOODIX_FW_SECTION_LENGTH);
+ if (error)
+ goto release;
+
+ /* Select SRAM bank 1 and upload section 3 & 4 */
+ error = goodix_i2c_write_u8(ts->client,
+ GOODIX_REG_MISCTL_SRAM_BANK, 0x01);
+ if (error)
+ goto release;
+
+ data += 2 * GOODIX_FW_SECTION_LENGTH;
+ error = goodix_i2c_write(ts->client, GOODIX_FW_UPLOAD_ADDRESS,
+ data, 2 * GOODIX_FW_SECTION_LENGTH);
+ if (error)
+ goto release;
+
+ /* Select SRAM bank 2 and upload the DSP firmware */
+ error = goodix_i2c_write_u8(ts->client,
+ GOODIX_REG_MISCTL_SRAM_BANK, 0x02);
+ if (error)
+ goto release;
+
+ data += 2 * GOODIX_FW_SECTION_LENGTH;
+ error = goodix_i2c_write(ts->client, GOODIX_FW_UPLOAD_ADDRESS,
+ data, GOODIX_FW_DSP_LENGTH);
+ if (error)
+ goto release;
+
+ error = goodix_start_firmware(ts->client);
+ if (error)
+ goto release;
+
+ error = goodix_int_sync(ts);
+release:
+ release_firmware(fw);
+ return error;
+}
+
+static int goodix_prepare_bak_ref(struct goodix_ts_data *ts)
+{
+ u8 have_key, driver_num, sensor_num;
+
+ if (ts->bak_ref)
+ return 0; /* Already done */
+
+ have_key = (ts->config[GOODIX_CFG_LOC_HAVE_KEY] & 0x01);
+
+ driver_num = (ts->config[GOODIX_CFG_LOC_DRVA_NUM] & 0x1f) +
+ (ts->config[GOODIX_CFG_LOC_DRVB_NUM] & 0x1f);
+ if (have_key)
+ driver_num--;
+
+ sensor_num = (ts->config[GOODIX_CFG_LOC_SENS_NUM] & 0x0f) +
+ ((ts->config[GOODIX_CFG_LOC_SENS_NUM] >> 4) & 0x0f);
+
+ dev_dbg(&ts->client->dev, "Drv %d Sen %d Key %d\n",
+ driver_num, sensor_num, have_key);
+
+ ts->bak_ref_len = (driver_num * (sensor_num - 2) + 2) * 2;
+
+ ts->bak_ref = devm_kzalloc(&ts->client->dev,
+ ts->bak_ref_len, GFP_KERNEL);
+ if (!ts->bak_ref)
+ return -ENOMEM;
+
+ /*
+ * The bak_ref array contains the backup of an array of (self/auto)
+ * calibration related values which the Android version of the driver
+ * stores on the filesystem so that it can be restored after reboot.
+ * The mainline kernel never writes directly to the filesystem like
+ * this, we always start will all the values which give a correction
+ * factor in approx. the -20 - +20 range (in 2s complement) set to 0.
+ *
+ * Note the touchscreen works fine without restoring the reference
+ * values after a reboot / power-cycle.
+ *
+ * The last 2 bytes are a 16 bits unsigned checksum which is expected
+ * to make the addition al all 16 bit unsigned values in the array add
+ * up to 1 (rather then the usual 0), so we must set the last byte to 1.
+ */
+ ts->bak_ref[ts->bak_ref_len - 1] = 1;
+
+ return 0;
+}
+
+static int goodix_send_main_clock(struct goodix_ts_data *ts)
+{
+ u32 main_clk = 54; /* Default main clock */
+ u8 checksum = 0;
+ int i;
+
+ device_property_read_u32(&ts->client->dev,
+ "goodix,main-clk", &main_clk);
+
+ for (i = 0; i < (GOODIX_MAIN_CLK_LEN - 1); i++) {
+ ts->main_clk[i] = main_clk;
+ checksum += main_clk;
+ }
+
+ /* The value of all bytes combines must be 0 */
+ ts->main_clk[GOODIX_MAIN_CLK_LEN - 1] = 256 - checksum;
+
+ return goodix_i2c_write(ts->client, GOODIX_REG_MAIN_CLK,
+ ts->main_clk, GOODIX_MAIN_CLK_LEN);
+}
+
+int goodix_firmware_check(struct goodix_ts_data *ts)
+{
+ device_property_read_string(&ts->client->dev,
+ "firmware-name", &ts->firmware_name);
+ if (!ts->firmware_name)
+ return 0;
+
+ if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) {
+ dev_err(&ts->client->dev, "Error no IRQ-pin access method, cannot upload fw.\n");
+ return -EINVAL;
+ }
+
+ dev_info(&ts->client->dev, "Touchscreen controller needs fw-upload\n");
+ ts->load_cfg_from_disk = true;
+
+ return goodix_firmware_upload(ts);
+}
+
+bool goodix_handle_fw_request(struct goodix_ts_data *ts)
+{
+ int error;
+ u8 val;
+
+ error = goodix_i2c_read(ts->client, GOODIX_REG_REQUEST, &val, 1);
+ if (error)
+ return false;
+
+ switch (val) {
+ case GOODIX_RQST_RESPONDED:
+ /*
+ * If we read back our own last ack the IRQ was not for
+ * a request.
+ */
+ return false;
+ case GOODIX_RQST_CONFIG:
+ error = goodix_send_cfg(ts, ts->config, ts->chip->config_len);
+ if (error)
+ return false;
+
+ break;
+ case GOODIX_RQST_BAK_REF:
+ error = goodix_prepare_bak_ref(ts);
+ if (error)
+ return false;
+
+ error = goodix_i2c_write(ts->client, GOODIX_REG_BAK_REF,
+ ts->bak_ref, ts->bak_ref_len);
+ if (error)
+ return false;
+
+ break;
+ case GOODIX_RQST_RESET:
+ error = goodix_firmware_upload(ts);
+ if (error)
+ return false;
+
+ break;
+ case GOODIX_RQST_MAIN_CLOCK:
+ error = goodix_send_main_clock(ts);
+ if (error)
+ return false;
+
+ break;
+ case GOODIX_RQST_UNKNOWN:
+ case GOODIX_RQST_IDLE:
+ break;
+ default:
+ dev_err_ratelimited(&ts->client->dev, "Unknown Request: 0x%02x\n", val);
+ }
+
+ /* Ack the request */
+ goodix_i2c_write_u8(ts->client,
+ GOODIX_REG_REQUEST, GOODIX_RQST_RESPONDED);
+ return true;
+}
+
+void goodix_save_bak_ref(struct goodix_ts_data *ts)
+{
+ int error;
+ u8 val;
+
+ if (!ts->firmware_name)
+ return;
+
+ error = goodix_i2c_read(ts->client, GOODIX_REG_STATUS, &val, 1);
+ if (error)
+ return;
+
+ if (!(val & 0x80))
+ return;
+
+ error = goodix_i2c_read(ts->client, GOODIX_REG_BAK_REF,
+ ts->bak_ref, ts->bak_ref_len);
+ if (error) {
+ memset(ts->bak_ref, 0, ts->bak_ref_len);
+ ts->bak_ref[ts->bak_ref_len - 1] = 1;
+ }
+}
diff --git a/drivers/input/touchscreen/gunze.c b/drivers/input/touchscreen/gunze.c
new file mode 100644
index 000000000..5a5f9da73
--- /dev/null
+++ b/drivers/input/touchscreen/gunze.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2000-2001 Vojtech Pavlik
+ */
+
+/*
+ * Gunze AHL-51S touchscreen driver for Linux
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Gunze AHL-51S touchscreen driver"
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define GUNZE_MAX_LENGTH 10
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct gunze {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[GUNZE_MAX_LENGTH];
+ char phys[32];
+};
+
+static void gunze_process_packet(struct gunze *gunze)
+{
+ struct input_dev *dev = gunze->dev;
+
+ if (gunze->idx != GUNZE_MAX_LENGTH || gunze->data[5] != ',' ||
+ (gunze->data[0] != 'T' && gunze->data[0] != 'R')) {
+ printk(KERN_WARNING "gunze.c: bad packet: >%.*s<\n", GUNZE_MAX_LENGTH, gunze->data);
+ return;
+ }
+
+ input_report_abs(dev, ABS_X, simple_strtoul(gunze->data + 1, NULL, 10));
+ input_report_abs(dev, ABS_Y, 1024 - simple_strtoul(gunze->data + 6, NULL, 10));
+ input_report_key(dev, BTN_TOUCH, gunze->data[0] == 'T');
+ input_sync(dev);
+}
+
+static irqreturn_t gunze_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct gunze *gunze = serio_get_drvdata(serio);
+
+ if (data == '\r') {
+ gunze_process_packet(gunze);
+ gunze->idx = 0;
+ } else {
+ if (gunze->idx < GUNZE_MAX_LENGTH)
+ gunze->data[gunze->idx++] = data;
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * gunze_disconnect() is the opposite of gunze_connect()
+ */
+
+static void gunze_disconnect(struct serio *serio)
+{
+ struct gunze *gunze = serio_get_drvdata(serio);
+
+ input_get_device(gunze->dev);
+ input_unregister_device(gunze->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(gunze->dev);
+ kfree(gunze);
+}
+
+/*
+ * gunze_connect() is the routine that is called when someone adds a
+ * new serio device that supports Gunze protocol and registers it as
+ * an input device.
+ */
+
+static int gunze_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct gunze *gunze;
+ struct input_dev *input_dev;
+ int err;
+
+ gunze = kzalloc(sizeof(struct gunze), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!gunze || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ gunze->serio = serio;
+ gunze->dev = input_dev;
+ snprintf(gunze->phys, sizeof(serio->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "Gunze AHL-51S TouchScreen";
+ input_dev->phys = gunze->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_GUNZE;
+ input_dev->id.product = 0x0051;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, 24, 1000, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 24, 1000, 0, 0);
+
+ serio_set_drvdata(serio, gunze);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(gunze->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(gunze);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id gunze_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_GUNZE,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, gunze_serio_ids);
+
+static struct serio_driver gunze_drv = {
+ .driver = {
+ .name = "gunze",
+ },
+ .description = DRIVER_DESC,
+ .id_table = gunze_serio_ids,
+ .interrupt = gunze_interrupt,
+ .connect = gunze_connect,
+ .disconnect = gunze_disconnect,
+};
+
+module_serio_driver(gunze_drv);
diff --git a/drivers/input/touchscreen/hampshire.c b/drivers/input/touchscreen/hampshire.c
new file mode 100644
index 000000000..5c4d87756
--- /dev/null
+++ b/drivers/input/touchscreen/hampshire.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hampshire serial touchscreen driver
+ *
+ * Copyright (c) 2010 Adam Bennett
+ * Based on the dynapro driver (c) Tias Guns
+ */
+
+
+/*
+ * 2010/04/08 Adam Bennett <abennett72@gmail.com>
+ * Copied dynapro.c and edited for Hampshire 4-byte protocol
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Hampshire serial touchscreen driver"
+
+MODULE_AUTHOR("Adam Bennett <abennett72@gmail.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define HAMPSHIRE_FORMAT_TOUCH_BIT 0x40
+#define HAMPSHIRE_FORMAT_LENGTH 4
+#define HAMPSHIRE_RESPONSE_BEGIN_BYTE 0x80
+
+#define HAMPSHIRE_MIN_XC 0
+#define HAMPSHIRE_MAX_XC 0x1000
+#define HAMPSHIRE_MIN_YC 0
+#define HAMPSHIRE_MAX_YC 0x1000
+
+#define HAMPSHIRE_GET_XC(data) (((data[3] & 0x0c) >> 2) | (data[1] << 2) | ((data[0] & 0x38) << 6))
+#define HAMPSHIRE_GET_YC(data) ((data[3] & 0x03) | (data[2] << 2) | ((data[0] & 0x07) << 9))
+#define HAMPSHIRE_GET_TOUCHED(data) (HAMPSHIRE_FORMAT_TOUCH_BIT & data[0])
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct hampshire {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[HAMPSHIRE_FORMAT_LENGTH];
+ char phys[32];
+};
+
+static void hampshire_process_data(struct hampshire *phampshire)
+{
+ struct input_dev *dev = phampshire->dev;
+
+ if (HAMPSHIRE_FORMAT_LENGTH == ++phampshire->idx) {
+ input_report_abs(dev, ABS_X, HAMPSHIRE_GET_XC(phampshire->data));
+ input_report_abs(dev, ABS_Y, HAMPSHIRE_GET_YC(phampshire->data));
+ input_report_key(dev, BTN_TOUCH,
+ HAMPSHIRE_GET_TOUCHED(phampshire->data));
+ input_sync(dev);
+
+ phampshire->idx = 0;
+ }
+}
+
+static irqreturn_t hampshire_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct hampshire *phampshire = serio_get_drvdata(serio);
+
+ phampshire->data[phampshire->idx] = data;
+
+ if (HAMPSHIRE_RESPONSE_BEGIN_BYTE & phampshire->data[0])
+ hampshire_process_data(phampshire);
+ else
+ dev_dbg(&serio->dev, "unknown/unsynchronized data: %x\n",
+ phampshire->data[0]);
+
+ return IRQ_HANDLED;
+}
+
+static void hampshire_disconnect(struct serio *serio)
+{
+ struct hampshire *phampshire = serio_get_drvdata(serio);
+
+ input_get_device(phampshire->dev);
+ input_unregister_device(phampshire->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(phampshire->dev);
+ kfree(phampshire);
+}
+
+/*
+ * hampshire_connect() is the routine that is called when someone adds a
+ * new serio device that supports hampshire protocol and registers it as
+ * an input device. This is usually accomplished using inputattach.
+ */
+
+static int hampshire_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct hampshire *phampshire;
+ struct input_dev *input_dev;
+ int err;
+
+ phampshire = kzalloc(sizeof(struct hampshire), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!phampshire || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ phampshire->serio = serio;
+ phampshire->dev = input_dev;
+ snprintf(phampshire->phys, sizeof(phampshire->phys),
+ "%s/input0", serio->phys);
+
+ input_dev->name = "Hampshire Serial TouchScreen";
+ input_dev->phys = phampshire->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_HAMPSHIRE;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0001;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(phampshire->dev, ABS_X,
+ HAMPSHIRE_MIN_XC, HAMPSHIRE_MAX_XC, 0, 0);
+ input_set_abs_params(phampshire->dev, ABS_Y,
+ HAMPSHIRE_MIN_YC, HAMPSHIRE_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, phampshire);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(phampshire->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(phampshire);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id hampshire_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_HAMPSHIRE,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, hampshire_serio_ids);
+
+static struct serio_driver hampshire_drv = {
+ .driver = {
+ .name = "hampshire",
+ },
+ .description = DRIVER_DESC,
+ .id_table = hampshire_serio_ids,
+ .interrupt = hampshire_interrupt,
+ .connect = hampshire_connect,
+ .disconnect = hampshire_disconnect,
+};
+
+module_serio_driver(hampshire_drv);
diff --git a/drivers/input/touchscreen/hideep.c b/drivers/input/touchscreen/hideep.c
new file mode 100644
index 000000000..e9547ee29
--- /dev/null
+++ b/drivers/input/touchscreen/hideep.c
@@ -0,0 +1,1122 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2012-2017 Hideep, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/regulator/consumer.h>
+#include <asm/unaligned.h>
+
+#define HIDEEP_TS_NAME "HiDeep Touchscreen"
+#define HIDEEP_I2C_NAME "hideep_ts"
+
+#define HIDEEP_MT_MAX 10
+#define HIDEEP_KEY_MAX 3
+
+/* count(2) + touch data(100) + key data(6) */
+#define HIDEEP_MAX_EVENT 108UL
+
+#define HIDEEP_TOUCH_EVENT_INDEX 2
+#define HIDEEP_KEY_EVENT_INDEX 102
+
+/* Touch & key event */
+#define HIDEEP_EVENT_ADDR 0x240
+
+/* command list */
+#define HIDEEP_RESET_CMD 0x9800
+
+/* event bit */
+#define HIDEEP_MT_RELEASED BIT(4)
+#define HIDEEP_KEY_PRESSED BIT(7)
+#define HIDEEP_KEY_FIRST_PRESSED BIT(8)
+#define HIDEEP_KEY_PRESSED_MASK (HIDEEP_KEY_PRESSED | \
+ HIDEEP_KEY_FIRST_PRESSED)
+
+#define HIDEEP_KEY_IDX_MASK 0x0f
+
+/* For NVM */
+#define HIDEEP_YRAM_BASE 0x40000000
+#define HIDEEP_PERIPHERAL_BASE 0x50000000
+#define HIDEEP_ESI_BASE (HIDEEP_PERIPHERAL_BASE + 0x00000000)
+#define HIDEEP_FLASH_BASE (HIDEEP_PERIPHERAL_BASE + 0x01000000)
+#define HIDEEP_SYSCON_BASE (HIDEEP_PERIPHERAL_BASE + 0x02000000)
+
+#define HIDEEP_SYSCON_MOD_CON (HIDEEP_SYSCON_BASE + 0x0000)
+#define HIDEEP_SYSCON_SPC_CON (HIDEEP_SYSCON_BASE + 0x0004)
+#define HIDEEP_SYSCON_CLK_CON (HIDEEP_SYSCON_BASE + 0x0008)
+#define HIDEEP_SYSCON_CLK_ENA (HIDEEP_SYSCON_BASE + 0x000C)
+#define HIDEEP_SYSCON_RST_CON (HIDEEP_SYSCON_BASE + 0x0010)
+#define HIDEEP_SYSCON_WDT_CON (HIDEEP_SYSCON_BASE + 0x0014)
+#define HIDEEP_SYSCON_WDT_CNT (HIDEEP_SYSCON_BASE + 0x0018)
+#define HIDEEP_SYSCON_PWR_CON (HIDEEP_SYSCON_BASE + 0x0020)
+#define HIDEEP_SYSCON_PGM_ID (HIDEEP_SYSCON_BASE + 0x00F4)
+
+#define HIDEEP_FLASH_CON (HIDEEP_FLASH_BASE + 0x0000)
+#define HIDEEP_FLASH_STA (HIDEEP_FLASH_BASE + 0x0004)
+#define HIDEEP_FLASH_CFG (HIDEEP_FLASH_BASE + 0x0008)
+#define HIDEEP_FLASH_TIM (HIDEEP_FLASH_BASE + 0x000C)
+#define HIDEEP_FLASH_CACHE_CFG (HIDEEP_FLASH_BASE + 0x0010)
+#define HIDEEP_FLASH_PIO_SIG (HIDEEP_FLASH_BASE + 0x400000)
+
+#define HIDEEP_ESI_TX_INVALID (HIDEEP_ESI_BASE + 0x0008)
+
+#define HIDEEP_PERASE 0x00040000
+#define HIDEEP_WRONLY 0x00100000
+
+#define HIDEEP_NVM_MASK_OFS 0x0000000C
+#define HIDEEP_NVM_DEFAULT_PAGE 0
+#define HIDEEP_NVM_SFR_WPAGE 1
+#define HIDEEP_NVM_SFR_RPAGE 2
+
+#define HIDEEP_PIO_SIG 0x00400000
+#define HIDEEP_PROT_MODE 0x03400000
+
+#define HIDEEP_NVM_PAGE_SIZE 128
+
+#define HIDEEP_DWZ_INFO 0x000002C0
+
+struct hideep_event {
+ __le16 x;
+ __le16 y;
+ __le16 z;
+ u8 w;
+ u8 flag;
+ u8 type;
+ u8 index;
+};
+
+struct dwz_info {
+ __be32 code_start;
+ u8 code_crc[12];
+
+ __be32 c_code_start;
+ __be16 gen_ver;
+ __be16 c_code_len;
+
+ __be32 vr_start;
+ __be16 rsv0;
+ __be16 vr_len;
+
+ __be32 ft_start;
+ __be16 vr_version;
+ __be16 ft_len;
+
+ __be16 core_ver;
+ __be16 boot_ver;
+
+ __be16 release_ver;
+ __be16 custom_ver;
+
+ u8 factory_id;
+ u8 panel_type;
+ u8 model_name[6];
+
+ __be16 extra_option;
+ __be16 product_code;
+
+ __be16 vendor_id;
+ __be16 product_id;
+};
+
+struct pgm_packet {
+ struct {
+ u8 unused[3];
+ u8 len;
+ __be32 addr;
+ } header;
+ __be32 payload[HIDEEP_NVM_PAGE_SIZE / sizeof(__be32)];
+};
+
+#define HIDEEP_XFER_BUF_SIZE sizeof(struct pgm_packet)
+
+struct hideep_ts {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct regmap *reg;
+
+ struct touchscreen_properties prop;
+
+ struct gpio_desc *reset_gpio;
+
+ struct regulator *vcc_vdd;
+ struct regulator *vcc_vid;
+
+ struct mutex dev_mutex;
+
+ u32 tch_count;
+ u32 lpm_count;
+
+ /*
+ * Data buffer to read packet from the device (contacts and key
+ * states). We align it on double-word boundary to keep word-sized
+ * fields in contact data and double-word-sized fields in program
+ * packet aligned.
+ */
+ u8 xfer_buf[HIDEEP_XFER_BUF_SIZE] __aligned(4);
+
+ int key_num;
+ u32 key_codes[HIDEEP_KEY_MAX];
+
+ struct dwz_info dwz_info;
+
+ unsigned int fw_size;
+ u32 nvm_mask;
+};
+
+static int hideep_pgm_w_mem(struct hideep_ts *ts, u32 addr,
+ const __be32 *data, size_t count)
+{
+ struct pgm_packet *packet = (void *)ts->xfer_buf;
+ size_t len = count * sizeof(*data);
+ struct i2c_msg msg = {
+ .addr = ts->client->addr,
+ .len = len + sizeof(packet->header.len) +
+ sizeof(packet->header.addr),
+ .buf = &packet->header.len,
+ };
+ int ret;
+
+ if (len > HIDEEP_NVM_PAGE_SIZE)
+ return -EINVAL;
+
+ packet->header.len = 0x80 | (count - 1);
+ packet->header.addr = cpu_to_be32(addr);
+ memcpy(packet->payload, data, len);
+
+ ret = i2c_transfer(ts->client->adapter, &msg, 1);
+ if (ret != 1)
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int hideep_pgm_r_mem(struct hideep_ts *ts, u32 addr,
+ __be32 *data, size_t count)
+{
+ struct pgm_packet *packet = (void *)ts->xfer_buf;
+ size_t len = count * sizeof(*data);
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .len = sizeof(packet->header.len) +
+ sizeof(packet->header.addr),
+ .buf = &packet->header.len,
+ },
+ {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = (u8 *)data,
+ },
+ };
+ int ret;
+
+ if (len > HIDEEP_NVM_PAGE_SIZE)
+ return -EINVAL;
+
+ packet->header.len = count - 1;
+ packet->header.addr = cpu_to_be32(addr);
+
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg))
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int hideep_pgm_r_reg(struct hideep_ts *ts, u32 addr, u32 *val)
+{
+ __be32 data;
+ int error;
+
+ error = hideep_pgm_r_mem(ts, addr, &data, 1);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "read of register %#08x failed: %d\n",
+ addr, error);
+ return error;
+ }
+
+ *val = be32_to_cpu(data);
+ return 0;
+}
+
+static int hideep_pgm_w_reg(struct hideep_ts *ts, u32 addr, u32 val)
+{
+ __be32 data = cpu_to_be32(val);
+ int error;
+
+ error = hideep_pgm_w_mem(ts, addr, &data, 1);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "write to register %#08x (%#08x) failed: %d\n",
+ addr, val, error);
+ return error;
+ }
+
+ return 0;
+}
+
+#define SW_RESET_IN_PGM(clk) \
+{ \
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CNT, (clk)); \
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x03); \
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x01); \
+}
+
+#define SET_FLASH_PIO(ce) \
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CON, \
+ 0x01 | ((ce) << 1))
+
+#define SET_PIO_SIG(x, y) \
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_PIO_SIG + (x), (y))
+
+#define SET_FLASH_HWCONTROL() \
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CON, 0x00)
+
+#define NVM_W_SFR(x, y) \
+{ \
+ SET_FLASH_PIO(1); \
+ SET_PIO_SIG(x, y); \
+ SET_FLASH_PIO(0); \
+}
+
+static void hideep_pgm_set(struct hideep_ts *ts)
+{
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_WDT_CON, 0x00);
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_SPC_CON, 0x00);
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_CLK_ENA, 0xFF);
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_CLK_CON, 0x01);
+ hideep_pgm_w_reg(ts, HIDEEP_SYSCON_PWR_CON, 0x01);
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_TIM, 0x03);
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CACHE_CFG, 0x00);
+}
+
+static int hideep_pgm_get_pattern(struct hideep_ts *ts, u32 *pattern)
+{
+ u16 p1 = 0xAF39;
+ u16 p2 = 0xDF9D;
+ int error;
+
+ error = regmap_bulk_write(ts->reg, p1, &p2, 1);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "%s: regmap_bulk_write() failed with %d\n",
+ __func__, error);
+ return error;
+ }
+
+ usleep_range(1000, 1100);
+
+ /* flush invalid Tx load register */
+ error = hideep_pgm_w_reg(ts, HIDEEP_ESI_TX_INVALID, 0x01);
+ if (error)
+ return error;
+
+ error = hideep_pgm_r_reg(ts, HIDEEP_SYSCON_PGM_ID, pattern);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int hideep_enter_pgm(struct hideep_ts *ts)
+{
+ int retry_count = 10;
+ u32 pattern;
+ int error;
+
+ while (retry_count--) {
+ error = hideep_pgm_get_pattern(ts, &pattern);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "hideep_pgm_get_pattern failed: %d\n", error);
+ } else if (pattern != 0x39AF9DDF) {
+ dev_err(&ts->client->dev, "%s: bad pattern: %#08x\n",
+ __func__, pattern);
+ } else {
+ dev_dbg(&ts->client->dev, "found magic code");
+
+ hideep_pgm_set(ts);
+ usleep_range(1000, 1100);
+
+ return 0;
+ }
+ }
+
+ dev_err(&ts->client->dev, "failed to enter pgm mode\n");
+ SW_RESET_IN_PGM(1000);
+ return -EIO;
+}
+
+static int hideep_nvm_unlock(struct hideep_ts *ts)
+{
+ u32 unmask_code;
+ int error;
+
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, HIDEEP_NVM_SFR_RPAGE);
+ error = hideep_pgm_r_reg(ts, 0x0000000C, &unmask_code);
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, HIDEEP_NVM_DEFAULT_PAGE);
+ if (error)
+ return error;
+
+ /* make it unprotected code */
+ unmask_code &= ~HIDEEP_PROT_MODE;
+
+ /* compare unmask code */
+ if (unmask_code != ts->nvm_mask)
+ dev_warn(&ts->client->dev,
+ "read mask code different %#08x vs %#08x",
+ unmask_code, ts->nvm_mask);
+
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, HIDEEP_NVM_SFR_WPAGE);
+ SET_FLASH_PIO(0);
+
+ NVM_W_SFR(HIDEEP_NVM_MASK_OFS, ts->nvm_mask);
+ SET_FLASH_HWCONTROL();
+ hideep_pgm_w_reg(ts, HIDEEP_FLASH_CFG, HIDEEP_NVM_DEFAULT_PAGE);
+
+ return 0;
+}
+
+static int hideep_check_status(struct hideep_ts *ts)
+{
+ int time_out = 100;
+ int status;
+ int error;
+
+ while (time_out--) {
+ error = hideep_pgm_r_reg(ts, HIDEEP_FLASH_STA, &status);
+ if (!error && status)
+ return 0;
+
+ usleep_range(1000, 1100);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int hideep_program_page(struct hideep_ts *ts, u32 addr,
+ const __be32 *ucode, size_t xfer_count)
+{
+ u32 val;
+ int error;
+
+ error = hideep_check_status(ts);
+ if (error)
+ return -EBUSY;
+
+ addr &= ~(HIDEEP_NVM_PAGE_SIZE - 1);
+
+ SET_FLASH_PIO(0);
+ SET_FLASH_PIO(1);
+
+ /* erase page */
+ SET_PIO_SIG(HIDEEP_PERASE | addr, 0xFFFFFFFF);
+
+ SET_FLASH_PIO(0);
+
+ error = hideep_check_status(ts);
+ if (error)
+ return -EBUSY;
+
+ /* write page */
+ SET_FLASH_PIO(1);
+
+ val = be32_to_cpu(ucode[0]);
+ SET_PIO_SIG(HIDEEP_WRONLY | addr, val);
+
+ hideep_pgm_w_mem(ts, HIDEEP_FLASH_PIO_SIG | HIDEEP_WRONLY,
+ ucode, xfer_count);
+
+ val = be32_to_cpu(ucode[xfer_count - 1]);
+ SET_PIO_SIG(124, val);
+
+ SET_FLASH_PIO(0);
+
+ usleep_range(1000, 1100);
+
+ error = hideep_check_status(ts);
+ if (error)
+ return -EBUSY;
+
+ SET_FLASH_HWCONTROL();
+
+ return 0;
+}
+
+static int hideep_program_nvm(struct hideep_ts *ts,
+ const __be32 *ucode, size_t ucode_len)
+{
+ struct pgm_packet *packet_r = (void *)ts->xfer_buf;
+ __be32 *current_ucode = packet_r->payload;
+ size_t xfer_len;
+ size_t xfer_count;
+ u32 addr = 0;
+ int error;
+
+ error = hideep_nvm_unlock(ts);
+ if (error)
+ return error;
+
+ while (ucode_len > 0) {
+ xfer_len = min_t(size_t, ucode_len, HIDEEP_NVM_PAGE_SIZE);
+ xfer_count = xfer_len / sizeof(*ucode);
+
+ error = hideep_pgm_r_mem(ts, 0x00000000 + addr,
+ current_ucode, xfer_count);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "%s: failed to read page at offset %#08x: %d\n",
+ __func__, addr, error);
+ return error;
+ }
+
+ /* See if the page needs updating */
+ if (memcmp(ucode, current_ucode, xfer_len)) {
+ error = hideep_program_page(ts, addr,
+ ucode, xfer_count);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "%s: iwrite failure @%#08x: %d\n",
+ __func__, addr, error);
+ return error;
+ }
+
+ usleep_range(1000, 1100);
+ }
+
+ ucode += xfer_count;
+ addr += xfer_len;
+ ucode_len -= xfer_len;
+ }
+
+ return 0;
+}
+
+static int hideep_verify_nvm(struct hideep_ts *ts,
+ const __be32 *ucode, size_t ucode_len)
+{
+ struct pgm_packet *packet_r = (void *)ts->xfer_buf;
+ __be32 *current_ucode = packet_r->payload;
+ size_t xfer_len;
+ size_t xfer_count;
+ u32 addr = 0;
+ int i;
+ int error;
+
+ while (ucode_len > 0) {
+ xfer_len = min_t(size_t, ucode_len, HIDEEP_NVM_PAGE_SIZE);
+ xfer_count = xfer_len / sizeof(*ucode);
+
+ error = hideep_pgm_r_mem(ts, 0x00000000 + addr,
+ current_ucode, xfer_count);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "%s: failed to read page at offset %#08x: %d\n",
+ __func__, addr, error);
+ return error;
+ }
+
+ if (memcmp(ucode, current_ucode, xfer_len)) {
+ const u8 *ucode_bytes = (const u8 *)ucode;
+ const u8 *current_bytes = (const u8 *)current_ucode;
+
+ for (i = 0; i < xfer_len; i++)
+ if (ucode_bytes[i] != current_bytes[i])
+ dev_err(&ts->client->dev,
+ "%s: mismatch @%#08x: (%#02x vs %#02x)\n",
+ __func__, addr + i,
+ ucode_bytes[i],
+ current_bytes[i]);
+
+ return -EIO;
+ }
+
+ ucode += xfer_count;
+ addr += xfer_len;
+ ucode_len -= xfer_len;
+ }
+
+ return 0;
+}
+
+static int hideep_load_dwz(struct hideep_ts *ts)
+{
+ u16 product_code;
+ int error;
+
+ error = hideep_enter_pgm(ts);
+ if (error)
+ return error;
+
+ msleep(50);
+
+ error = hideep_pgm_r_mem(ts, HIDEEP_DWZ_INFO,
+ (void *)&ts->dwz_info,
+ sizeof(ts->dwz_info) / sizeof(__be32));
+
+ SW_RESET_IN_PGM(10);
+ msleep(50);
+
+ if (error) {
+ dev_err(&ts->client->dev,
+ "failed to fetch DWZ data: %d\n", error);
+ return error;
+ }
+
+ product_code = be16_to_cpu(ts->dwz_info.product_code);
+
+ switch (product_code & 0xF0) {
+ case 0x40:
+ dev_dbg(&ts->client->dev, "used crimson IC");
+ ts->fw_size = 1024 * 48;
+ ts->nvm_mask = 0x00310000;
+ break;
+ case 0x60:
+ dev_dbg(&ts->client->dev, "used lime IC");
+ ts->fw_size = 1024 * 64;
+ ts->nvm_mask = 0x0030027B;
+ break;
+ default:
+ dev_err(&ts->client->dev, "product code is wrong: %#04x",
+ product_code);
+ return -EINVAL;
+ }
+
+ dev_dbg(&ts->client->dev, "firmware release version: %#04x",
+ be16_to_cpu(ts->dwz_info.release_ver));
+
+ return 0;
+}
+
+static int hideep_flash_firmware(struct hideep_ts *ts,
+ const __be32 *ucode, size_t ucode_len)
+{
+ int retry_cnt = 3;
+ int error;
+
+ while (retry_cnt--) {
+ error = hideep_program_nvm(ts, ucode, ucode_len);
+ if (!error) {
+ error = hideep_verify_nvm(ts, ucode, ucode_len);
+ if (!error)
+ return 0;
+ }
+ }
+
+ return error;
+}
+
+static int hideep_update_firmware(struct hideep_ts *ts,
+ const __be32 *ucode, size_t ucode_len)
+{
+ int error, error2;
+
+ dev_dbg(&ts->client->dev, "starting firmware update");
+
+ /* enter program mode */
+ error = hideep_enter_pgm(ts);
+ if (error)
+ return error;
+
+ error = hideep_flash_firmware(ts, ucode, ucode_len);
+ if (error)
+ dev_err(&ts->client->dev,
+ "firmware update failed: %d\n", error);
+ else
+ dev_dbg(&ts->client->dev, "firmware updated successfully\n");
+
+ SW_RESET_IN_PGM(1000);
+
+ error2 = hideep_load_dwz(ts);
+ if (error2)
+ dev_err(&ts->client->dev,
+ "failed to load dwz after firmware update: %d\n",
+ error2);
+
+ return error ?: error2;
+}
+
+static int hideep_power_on(struct hideep_ts *ts)
+{
+ int error = 0;
+
+ error = regulator_enable(ts->vcc_vdd);
+ if (error)
+ dev_err(&ts->client->dev,
+ "failed to enable 'vdd' regulator: %d", error);
+
+ usleep_range(999, 1000);
+
+ error = regulator_enable(ts->vcc_vid);
+ if (error)
+ dev_err(&ts->client->dev,
+ "failed to enable 'vcc_vid' regulator: %d",
+ error);
+
+ msleep(30);
+
+ if (ts->reset_gpio) {
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+ } else {
+ error = regmap_write(ts->reg, HIDEEP_RESET_CMD, 0x01);
+ if (error)
+ dev_err(&ts->client->dev,
+ "failed to send 'reset' command: %d\n", error);
+ }
+
+ msleep(50);
+
+ return error;
+}
+
+static void hideep_power_off(void *data)
+{
+ struct hideep_ts *ts = data;
+
+ if (ts->reset_gpio)
+ gpiod_set_value(ts->reset_gpio, 1);
+
+ regulator_disable(ts->vcc_vid);
+ regulator_disable(ts->vcc_vdd);
+}
+
+#define __GET_MT_TOOL_TYPE(type) ((type) == 0x01 ? MT_TOOL_FINGER : MT_TOOL_PEN)
+
+static void hideep_report_slot(struct input_dev *input,
+ const struct hideep_event *event)
+{
+ input_mt_slot(input, event->index & 0x0f);
+ input_mt_report_slot_state(input,
+ __GET_MT_TOOL_TYPE(event->type),
+ !(event->flag & HIDEEP_MT_RELEASED));
+ if (!(event->flag & HIDEEP_MT_RELEASED)) {
+ input_report_abs(input, ABS_MT_POSITION_X,
+ le16_to_cpup(&event->x));
+ input_report_abs(input, ABS_MT_POSITION_Y,
+ le16_to_cpup(&event->y));
+ input_report_abs(input, ABS_MT_PRESSURE,
+ le16_to_cpup(&event->z));
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, event->w);
+ }
+}
+
+static void hideep_parse_and_report(struct hideep_ts *ts)
+{
+ const struct hideep_event *events =
+ (void *)&ts->xfer_buf[HIDEEP_TOUCH_EVENT_INDEX];
+ const u8 *keys = &ts->xfer_buf[HIDEEP_KEY_EVENT_INDEX];
+ int touch_count = ts->xfer_buf[0];
+ int key_count = ts->xfer_buf[1] & 0x0f;
+ int lpm_count = ts->xfer_buf[1] & 0xf0;
+ int i;
+
+ /* get touch event count */
+ dev_dbg(&ts->client->dev, "mt = %d, key = %d, lpm = %02x",
+ touch_count, key_count, lpm_count);
+
+ touch_count = min(touch_count, HIDEEP_MT_MAX);
+ for (i = 0; i < touch_count; i++)
+ hideep_report_slot(ts->input_dev, events + i);
+
+ key_count = min(key_count, HIDEEP_KEY_MAX);
+ for (i = 0; i < key_count; i++) {
+ u8 key_data = keys[i * 2];
+
+ input_report_key(ts->input_dev,
+ ts->key_codes[key_data & HIDEEP_KEY_IDX_MASK],
+ key_data & HIDEEP_KEY_PRESSED_MASK);
+ }
+
+ input_mt_sync_frame(ts->input_dev);
+ input_sync(ts->input_dev);
+}
+
+static irqreturn_t hideep_irq(int irq, void *handle)
+{
+ struct hideep_ts *ts = handle;
+ int error;
+
+ BUILD_BUG_ON(HIDEEP_MAX_EVENT > HIDEEP_XFER_BUF_SIZE);
+
+ error = regmap_bulk_read(ts->reg, HIDEEP_EVENT_ADDR,
+ ts->xfer_buf, HIDEEP_MAX_EVENT / 2);
+ if (error) {
+ dev_err(&ts->client->dev, "failed to read events: %d\n", error);
+ goto out;
+ }
+
+ hideep_parse_and_report(ts);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int hideep_get_axis_info(struct hideep_ts *ts)
+{
+ __le16 val[2];
+ int error;
+
+ error = regmap_bulk_read(ts->reg, 0x28, val, ARRAY_SIZE(val));
+ if (error)
+ return error;
+
+ ts->prop.max_x = le16_to_cpup(val);
+ ts->prop.max_y = le16_to_cpup(val + 1);
+
+ dev_dbg(&ts->client->dev, "X: %d, Y: %d",
+ ts->prop.max_x, ts->prop.max_y);
+
+ return 0;
+}
+
+static int hideep_init_input(struct hideep_ts *ts)
+{
+ struct device *dev = &ts->client->dev;
+ int i;
+ int error;
+
+ ts->input_dev = devm_input_allocate_device(dev);
+ if (!ts->input_dev) {
+ dev_err(dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->input_dev->name = HIDEEP_TS_NAME;
+ ts->input_dev->id.bustype = BUS_I2C;
+ input_set_drvdata(ts->input_dev, ts);
+
+ input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y);
+ input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE, 0, 65535, 0, 0);
+ input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(ts->input_dev, ABS_MT_TOOL_TYPE,
+ 0, MT_TOOL_MAX, 0, 0);
+ touchscreen_parse_properties(ts->input_dev, true, &ts->prop);
+
+ if (ts->prop.max_x == 0 || ts->prop.max_y == 0) {
+ error = hideep_get_axis_info(ts);
+ if (error)
+ return error;
+ }
+
+ error = input_mt_init_slots(ts->input_dev, HIDEEP_MT_MAX,
+ INPUT_MT_DIRECT);
+ if (error)
+ return error;
+
+ ts->key_num = device_property_count_u32(dev, "linux,keycodes");
+ if (ts->key_num > HIDEEP_KEY_MAX) {
+ dev_err(dev, "too many keys defined: %d\n",
+ ts->key_num);
+ return -EINVAL;
+ }
+
+ if (ts->key_num <= 0) {
+ dev_dbg(dev,
+ "missing or malformed 'linux,keycodes' property\n");
+ } else {
+ error = device_property_read_u32_array(dev, "linux,keycodes",
+ ts->key_codes,
+ ts->key_num);
+ if (error) {
+ dev_dbg(dev, "failed to read keymap: %d", error);
+ return error;
+ }
+
+ if (ts->key_num) {
+ ts->input_dev->keycode = ts->key_codes;
+ ts->input_dev->keycodesize = sizeof(ts->key_codes[0]);
+ ts->input_dev->keycodemax = ts->key_num;
+
+ for (i = 0; i < ts->key_num; i++)
+ input_set_capability(ts->input_dev, EV_KEY,
+ ts->key_codes[i]);
+ }
+ }
+
+ error = input_register_device(ts->input_dev);
+ if (error) {
+ dev_err(dev, "failed to register input device: %d", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static ssize_t hideep_update_fw(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hideep_ts *ts = i2c_get_clientdata(client);
+ const struct firmware *fw_entry;
+ char *fw_name;
+ int mode;
+ int error;
+
+ error = kstrtoint(buf, 0, &mode);
+ if (error)
+ return error;
+
+ fw_name = kasprintf(GFP_KERNEL, "hideep_ts_%04x.bin",
+ be16_to_cpu(ts->dwz_info.product_id));
+ if (!fw_name)
+ return -ENOMEM;
+
+ error = request_firmware(&fw_entry, fw_name, dev);
+ if (error) {
+ dev_err(dev, "failed to request firmware %s: %d",
+ fw_name, error);
+ goto out_free_fw_name;
+ }
+
+ if (fw_entry->size % sizeof(__be32)) {
+ dev_err(dev, "invalid firmware size %zu\n", fw_entry->size);
+ error = -EINVAL;
+ goto out_release_fw;
+ }
+
+ if (fw_entry->size > ts->fw_size) {
+ dev_err(dev, "fw size (%zu) is too big (memory size %d)\n",
+ fw_entry->size, ts->fw_size);
+ error = -EFBIG;
+ goto out_release_fw;
+ }
+
+ mutex_lock(&ts->dev_mutex);
+ disable_irq(client->irq);
+
+ error = hideep_update_firmware(ts, (const __be32 *)fw_entry->data,
+ fw_entry->size);
+
+ enable_irq(client->irq);
+ mutex_unlock(&ts->dev_mutex);
+
+out_release_fw:
+ release_firmware(fw_entry);
+out_free_fw_name:
+ kfree(fw_name);
+
+ return error ?: count;
+}
+
+static ssize_t hideep_fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hideep_ts *ts = i2c_get_clientdata(client);
+ ssize_t len;
+
+ mutex_lock(&ts->dev_mutex);
+ len = scnprintf(buf, PAGE_SIZE, "%04x\n",
+ be16_to_cpu(ts->dwz_info.release_ver));
+ mutex_unlock(&ts->dev_mutex);
+
+ return len;
+}
+
+static ssize_t hideep_product_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hideep_ts *ts = i2c_get_clientdata(client);
+ ssize_t len;
+
+ mutex_lock(&ts->dev_mutex);
+ len = scnprintf(buf, PAGE_SIZE, "%04x\n",
+ be16_to_cpu(ts->dwz_info.product_id));
+ mutex_unlock(&ts->dev_mutex);
+
+ return len;
+}
+
+static DEVICE_ATTR(version, 0664, hideep_fw_version_show, NULL);
+static DEVICE_ATTR(product_id, 0664, hideep_product_id_show, NULL);
+static DEVICE_ATTR(update_fw, 0664, NULL, hideep_update_fw);
+
+static struct attribute *hideep_ts_sysfs_entries[] = {
+ &dev_attr_version.attr,
+ &dev_attr_product_id.attr,
+ &dev_attr_update_fw.attr,
+ NULL,
+};
+
+static const struct attribute_group hideep_ts_attr_group = {
+ .attrs = hideep_ts_sysfs_entries,
+};
+
+static int __maybe_unused hideep_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hideep_ts *ts = i2c_get_clientdata(client);
+
+ disable_irq(client->irq);
+ hideep_power_off(ts);
+
+ return 0;
+}
+
+static int __maybe_unused hideep_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hideep_ts *ts = i2c_get_clientdata(client);
+ int error;
+
+ error = hideep_power_on(ts);
+ if (error) {
+ dev_err(&client->dev, "power on failed");
+ return error;
+ }
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(hideep_pm_ops, hideep_suspend, hideep_resume);
+
+static const struct regmap_config hideep_regmap_config = {
+ .reg_bits = 16,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+ .max_register = 0xffff,
+};
+
+static int hideep_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct hideep_ts *ts;
+ int error;
+
+ /* check i2c bus */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "check i2c device error");
+ return -ENODEV;
+ }
+
+ if (client->irq <= 0) {
+ dev_err(&client->dev, "missing irq: %d\n", client->irq);
+ return -EINVAL;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+ i2c_set_clientdata(client, ts);
+ mutex_init(&ts->dev_mutex);
+
+ ts->reg = devm_regmap_init_i2c(client, &hideep_regmap_config);
+ if (IS_ERR(ts->reg)) {
+ error = PTR_ERR(ts->reg);
+ dev_err(&client->dev,
+ "failed to initialize regmap: %d\n", error);
+ return error;
+ }
+
+ ts->vcc_vdd = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(ts->vcc_vdd))
+ return PTR_ERR(ts->vcc_vdd);
+
+ ts->vcc_vid = devm_regulator_get(&client->dev, "vid");
+ if (IS_ERR(ts->vcc_vid))
+ return PTR_ERR(ts->vcc_vid);
+
+ ts->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->reset_gpio))
+ return PTR_ERR(ts->reset_gpio);
+
+ error = hideep_power_on(ts);
+ if (error) {
+ dev_err(&client->dev, "power on failed: %d\n", error);
+ return error;
+ }
+
+ error = devm_add_action_or_reset(&client->dev, hideep_power_off, ts);
+ if (error)
+ return error;
+
+ error = hideep_load_dwz(ts);
+ if (error) {
+ dev_err(&client->dev, "failed to load dwz: %d", error);
+ return error;
+ }
+
+ error = hideep_init_input(ts);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, hideep_irq, IRQF_ONESHOT,
+ client->name, ts);
+ if (error) {
+ dev_err(&client->dev, "failed to request irq %d: %d\n",
+ client->irq, error);
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &hideep_ts_attr_group);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to add sysfs attributes: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id hideep_i2c_id[] = {
+ { HIDEEP_I2C_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, hideep_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id hideep_acpi_id[] = {
+ { "HIDP0001", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, hideep_acpi_id);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id hideep_match_table[] = {
+ { .compatible = "hideep,hideep-ts" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hideep_match_table);
+#endif
+
+static struct i2c_driver hideep_driver = {
+ .driver = {
+ .name = HIDEEP_I2C_NAME,
+ .of_match_table = of_match_ptr(hideep_match_table),
+ .acpi_match_table = ACPI_PTR(hideep_acpi_id),
+ .pm = &hideep_pm_ops,
+ },
+ .id_table = hideep_i2c_id,
+ .probe = hideep_probe,
+};
+
+module_i2c_driver(hideep_driver);
+
+MODULE_DESCRIPTION("Driver for HiDeep Touchscreen Controller");
+MODULE_AUTHOR("anthony.kim@hideep.com");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/hp680_ts_input.c b/drivers/input/touchscreen/hp680_ts_input.c
new file mode 100644
index 000000000..818f2e48b
--- /dev/null
+++ b/drivers/input/touchscreen/hp680_ts_input.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+#include <asm/adc.h>
+#include <mach/hp6xx.h>
+
+#define MODNAME "hp680_ts_input"
+
+#define HP680_TS_ABS_X_MIN 40
+#define HP680_TS_ABS_X_MAX 950
+#define HP680_TS_ABS_Y_MIN 80
+#define HP680_TS_ABS_Y_MAX 910
+
+#define PHDR 0xa400012e
+#define SCPDR 0xa4000136
+
+static void do_softint(struct work_struct *work);
+
+static struct input_dev *hp680_ts_dev;
+static DECLARE_DELAYED_WORK(work, do_softint);
+
+static void do_softint(struct work_struct *work)
+{
+ int absx = 0, absy = 0;
+ u8 scpdr;
+ int touched = 0;
+
+ if (__raw_readb(PHDR) & PHDR_TS_PEN_DOWN) {
+ scpdr = __raw_readb(SCPDR);
+ scpdr |= SCPDR_TS_SCAN_ENABLE;
+ scpdr &= ~SCPDR_TS_SCAN_Y;
+ __raw_writeb(scpdr, SCPDR);
+ udelay(30);
+
+ absy = adc_single(ADC_CHANNEL_TS_Y);
+
+ scpdr = __raw_readb(SCPDR);
+ scpdr |= SCPDR_TS_SCAN_Y;
+ scpdr &= ~SCPDR_TS_SCAN_X;
+ __raw_writeb(scpdr, SCPDR);
+ udelay(30);
+
+ absx = adc_single(ADC_CHANNEL_TS_X);
+
+ scpdr = __raw_readb(SCPDR);
+ scpdr |= SCPDR_TS_SCAN_X;
+ scpdr &= ~SCPDR_TS_SCAN_ENABLE;
+ __raw_writeb(scpdr, SCPDR);
+ udelay(100);
+ touched = __raw_readb(PHDR) & PHDR_TS_PEN_DOWN;
+ }
+
+ if (touched) {
+ input_report_key(hp680_ts_dev, BTN_TOUCH, 1);
+ input_report_abs(hp680_ts_dev, ABS_X, absx);
+ input_report_abs(hp680_ts_dev, ABS_Y, absy);
+ } else {
+ input_report_key(hp680_ts_dev, BTN_TOUCH, 0);
+ }
+
+ input_sync(hp680_ts_dev);
+ enable_irq(HP680_TS_IRQ);
+}
+
+static irqreturn_t hp680_ts_interrupt(int irq, void *dev)
+{
+ disable_irq_nosync(irq);
+ schedule_delayed_work(&work, HZ / 20);
+
+ return IRQ_HANDLED;
+}
+
+static int __init hp680_ts_init(void)
+{
+ int err;
+
+ hp680_ts_dev = input_allocate_device();
+ if (!hp680_ts_dev)
+ return -ENOMEM;
+
+ hp680_ts_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY);
+ hp680_ts_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ input_set_abs_params(hp680_ts_dev, ABS_X,
+ HP680_TS_ABS_X_MIN, HP680_TS_ABS_X_MAX, 0, 0);
+ input_set_abs_params(hp680_ts_dev, ABS_Y,
+ HP680_TS_ABS_Y_MIN, HP680_TS_ABS_Y_MAX, 0, 0);
+
+ hp680_ts_dev->name = "HP Jornada touchscreen";
+ hp680_ts_dev->phys = "hp680_ts/input0";
+
+ if (request_irq(HP680_TS_IRQ, hp680_ts_interrupt,
+ 0, MODNAME, NULL) < 0) {
+ printk(KERN_ERR "hp680_touchscreen.c: Can't allocate irq %d\n",
+ HP680_TS_IRQ);
+ err = -EBUSY;
+ goto fail1;
+ }
+
+ err = input_register_device(hp680_ts_dev);
+ if (err)
+ goto fail2;
+
+ return 0;
+
+ fail2: free_irq(HP680_TS_IRQ, NULL);
+ cancel_delayed_work_sync(&work);
+ fail1: input_free_device(hp680_ts_dev);
+ return err;
+}
+
+static void __exit hp680_ts_exit(void)
+{
+ free_irq(HP680_TS_IRQ, NULL);
+ cancel_delayed_work_sync(&work);
+ input_unregister_device(hp680_ts_dev);
+}
+
+module_init(hp680_ts_init);
+module_exit(hp680_ts_exit);
+
+MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua");
+MODULE_DESCRIPTION("HP Jornada 680 touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/htcpen.c b/drivers/input/touchscreen/htcpen.c
new file mode 100644
index 000000000..056ba7608
--- /dev/null
+++ b/drivers/input/touchscreen/htcpen.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HTC Shift touchscreen driver
+ *
+ * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org>
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/isa.h>
+#include <linux/ioport.h>
+#include <linux/dmi.h>
+
+MODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>");
+MODULE_DESCRIPTION("HTC Shift touchscreen driver");
+MODULE_LICENSE("GPL");
+
+#define HTCPEN_PORT_IRQ_CLEAR 0x068
+#define HTCPEN_PORT_INIT 0x06c
+#define HTCPEN_PORT_INDEX 0x0250
+#define HTCPEN_PORT_DATA 0x0251
+#define HTCPEN_IRQ 3
+
+#define DEVICE_ENABLE 0xa2
+#define DEVICE_DISABLE 0xa3
+
+#define X_INDEX 3
+#define Y_INDEX 5
+#define TOUCH_INDEX 0xb
+#define LSB_XY_INDEX 0xc
+#define X_AXIS_MAX 2040
+#define Y_AXIS_MAX 2040
+
+static bool invert_x;
+module_param(invert_x, bool, 0644);
+MODULE_PARM_DESC(invert_x, "If set, X axis is inverted");
+static bool invert_y;
+module_param(invert_y, bool, 0644);
+MODULE_PARM_DESC(invert_y, "If set, Y axis is inverted");
+
+static irqreturn_t htcpen_interrupt(int irq, void *handle)
+{
+ struct input_dev *htcpen_dev = handle;
+ unsigned short x, y, xy;
+
+ /* 0 = press; 1 = release */
+ outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX);
+
+ if (inb_p(HTCPEN_PORT_DATA)) {
+ input_report_key(htcpen_dev, BTN_TOUCH, 0);
+ } else {
+ outb_p(X_INDEX, HTCPEN_PORT_INDEX);
+ x = inb_p(HTCPEN_PORT_DATA);
+
+ outb_p(Y_INDEX, HTCPEN_PORT_INDEX);
+ y = inb_p(HTCPEN_PORT_DATA);
+
+ outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX);
+ xy = inb_p(HTCPEN_PORT_DATA);
+
+ /* get high resolution value of X and Y using LSB */
+ x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf));
+ y = (y * 8) + (xy & 0xf);
+ if (invert_x)
+ x = X_AXIS_MAX - x;
+ if (invert_y)
+ y = Y_AXIS_MAX - y;
+
+ if (x != X_AXIS_MAX && x != 0) {
+ input_report_key(htcpen_dev, BTN_TOUCH, 1);
+ input_report_abs(htcpen_dev, ABS_X, x);
+ input_report_abs(htcpen_dev, ABS_Y, y);
+ }
+ }
+
+ input_sync(htcpen_dev);
+
+ inb_p(HTCPEN_PORT_IRQ_CLEAR);
+
+ return IRQ_HANDLED;
+}
+
+static int htcpen_open(struct input_dev *dev)
+{
+ outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT);
+
+ return 0;
+}
+
+static void htcpen_close(struct input_dev *dev)
+{
+ outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT);
+ synchronize_irq(HTCPEN_IRQ);
+}
+
+static int htcpen_isa_probe(struct device *dev, unsigned int id)
+{
+ struct input_dev *htcpen_dev;
+ int err = -EBUSY;
+
+ if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) {
+ printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n",
+ HTCPEN_PORT_IRQ_CLEAR);
+ goto request_region1_failed;
+ }
+
+ if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) {
+ printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n",
+ HTCPEN_PORT_INIT);
+ goto request_region2_failed;
+ }
+
+ if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) {
+ printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n",
+ HTCPEN_PORT_INDEX);
+ goto request_region3_failed;
+ }
+
+ htcpen_dev = input_allocate_device();
+ if (!htcpen_dev) {
+ printk(KERN_ERR "htcpen: can't allocate device\n");
+ err = -ENOMEM;
+ goto input_alloc_failed;
+ }
+
+ htcpen_dev->name = "HTC Shift EC TouchScreen";
+ htcpen_dev->id.bustype = BUS_ISA;
+
+ htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY);
+ htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0);
+ input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0);
+
+ htcpen_dev->open = htcpen_open;
+ htcpen_dev->close = htcpen_close;
+
+ err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen",
+ htcpen_dev);
+ if (err) {
+ printk(KERN_ERR "htcpen: irq busy\n");
+ goto request_irq_failed;
+ }
+
+ inb_p(HTCPEN_PORT_IRQ_CLEAR);
+
+ err = input_register_device(htcpen_dev);
+ if (err)
+ goto input_register_failed;
+
+ dev_set_drvdata(dev, htcpen_dev);
+
+ return 0;
+
+ input_register_failed:
+ free_irq(HTCPEN_IRQ, htcpen_dev);
+ request_irq_failed:
+ input_free_device(htcpen_dev);
+ input_alloc_failed:
+ release_region(HTCPEN_PORT_INDEX, 2);
+ request_region3_failed:
+ release_region(HTCPEN_PORT_INIT, 1);
+ request_region2_failed:
+ release_region(HTCPEN_PORT_IRQ_CLEAR, 1);
+ request_region1_failed:
+ return err;
+}
+
+static void htcpen_isa_remove(struct device *dev, unsigned int id)
+{
+ struct input_dev *htcpen_dev = dev_get_drvdata(dev);
+
+ input_unregister_device(htcpen_dev);
+
+ free_irq(HTCPEN_IRQ, htcpen_dev);
+
+ release_region(HTCPEN_PORT_INDEX, 2);
+ release_region(HTCPEN_PORT_INIT, 1);
+ release_region(HTCPEN_PORT_IRQ_CLEAR, 1);
+}
+
+#ifdef CONFIG_PM
+static int htcpen_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT);
+
+ return 0;
+}
+
+static int htcpen_isa_resume(struct device *dev, unsigned int n)
+{
+ outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT);
+
+ return 0;
+}
+#endif
+
+static struct isa_driver htcpen_isa_driver = {
+ .probe = htcpen_isa_probe,
+ .remove = htcpen_isa_remove,
+#ifdef CONFIG_PM
+ .suspend = htcpen_isa_suspend,
+ .resume = htcpen_isa_resume,
+#endif
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "htcpen",
+ }
+};
+
+static const struct dmi_system_id htcshift_dmi_table[] __initconst = {
+ {
+ .ident = "Shift",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Shift"),
+ },
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, htcshift_dmi_table);
+
+static int __init htcpen_isa_init(void)
+{
+ if (!dmi_check_system(htcshift_dmi_table))
+ return -ENODEV;
+
+ return isa_register_driver(&htcpen_isa_driver, 1);
+}
+
+static void __exit htcpen_isa_exit(void)
+{
+ isa_unregister_driver(&htcpen_isa_driver);
+}
+
+module_init(htcpen_isa_init);
+module_exit(htcpen_isa_exit);
diff --git a/drivers/input/touchscreen/hycon-hy46xx.c b/drivers/input/touchscreen/hycon-hy46xx.c
new file mode 100644
index 000000000..891d04300
--- /dev/null
+++ b/drivers/input/touchscreen/hycon-hy46xx.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021
+ * Author(s): Giulio Benetti <giulio.benetti@benettiengineering.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+
+#include <asm/unaligned.h>
+
+#define HY46XX_CHKSUM_CODE 0x1
+#define HY46XX_FINGER_NUM 0x2
+#define HY46XX_CHKSUM_LEN 0x7
+#define HY46XX_THRESHOLD 0x80
+#define HY46XX_GLOVE_EN 0x84
+#define HY46XX_REPORT_SPEED 0x88
+#define HY46XX_PWR_NOISE_EN 0x89
+#define HY46XX_FILTER_DATA 0x8A
+#define HY46XX_GAIN 0x92
+#define HY46XX_EDGE_OFFSET 0x93
+#define HY46XX_RX_NR_USED 0x94
+#define HY46XX_TX_NR_USED 0x95
+#define HY46XX_PWR_MODE 0xA5
+#define HY46XX_FW_VERSION 0xA6
+#define HY46XX_LIB_VERSION 0xA7
+#define HY46XX_TP_INFO 0xA8
+#define HY46XX_TP_CHIP_ID 0xA9
+#define HY46XX_BOOT_VER 0xB0
+
+#define HY46XX_TPLEN 0x6
+#define HY46XX_REPORT_PKT_LEN 0x44
+
+#define HY46XX_MAX_SUPPORTED_POINTS 11
+
+#define TOUCH_EVENT_DOWN 0x00
+#define TOUCH_EVENT_UP 0x01
+#define TOUCH_EVENT_CONTACT 0x02
+#define TOUCH_EVENT_RESERVED 0x03
+
+struct hycon_hy46xx_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct touchscreen_properties prop;
+ struct regulator *vcc;
+
+ struct gpio_desc *reset_gpio;
+
+ struct mutex mutex;
+ struct regmap *regmap;
+
+ int threshold;
+ bool glove_enable;
+ int report_speed;
+ bool noise_filter_enable;
+ int filter_data;
+ int gain;
+ int edge_offset;
+ int rx_number_used;
+ int tx_number_used;
+ int power_mode;
+ int fw_version;
+ int lib_version;
+ int tp_information;
+ int tp_chip_id;
+ int bootloader_version;
+};
+
+static const struct regmap_config hycon_hy46xx_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static bool hycon_hy46xx_check_checksum(struct hycon_hy46xx_data *tsdata, u8 *buf)
+{
+ u8 chksum = 0;
+ int i;
+
+ for (i = 2; i < buf[HY46XX_CHKSUM_LEN]; i++)
+ chksum += buf[i];
+
+ if (chksum == buf[HY46XX_CHKSUM_CODE])
+ return true;
+
+ dev_err_ratelimited(&tsdata->client->dev,
+ "checksum error: 0x%02x expected, got 0x%02x\n",
+ chksum, buf[HY46XX_CHKSUM_CODE]);
+
+ return false;
+}
+
+static irqreturn_t hycon_hy46xx_isr(int irq, void *dev_id)
+{
+ struct hycon_hy46xx_data *tsdata = dev_id;
+ struct device *dev = &tsdata->client->dev;
+ u8 rdbuf[HY46XX_REPORT_PKT_LEN];
+ int i, x, y, id;
+ int error;
+
+ memset(rdbuf, 0, sizeof(rdbuf));
+
+ error = regmap_bulk_read(tsdata->regmap, 0, rdbuf, sizeof(rdbuf));
+ if (error) {
+ dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
+ error);
+ goto out;
+ }
+
+ if (!hycon_hy46xx_check_checksum(tsdata, rdbuf))
+ goto out;
+
+ for (i = 0; i < HY46XX_MAX_SUPPORTED_POINTS; i++) {
+ u8 *buf = &rdbuf[3 + (HY46XX_TPLEN * i)];
+ int type = buf[0] >> 6;
+
+ if (type == TOUCH_EVENT_RESERVED)
+ continue;
+
+ x = get_unaligned_be16(buf) & 0x0fff;
+ y = get_unaligned_be16(buf + 2) & 0x0fff;
+
+ id = buf[2] >> 4;
+
+ input_mt_slot(tsdata->input, id);
+ if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER,
+ type != TOUCH_EVENT_UP))
+ touchscreen_report_pos(tsdata->input, &tsdata->prop,
+ x, y, true);
+ }
+
+ input_mt_report_pointer_emulation(tsdata->input, false);
+ input_sync(tsdata->input);
+
+out:
+ return IRQ_HANDLED;
+}
+
+struct hycon_hy46xx_attribute {
+ struct device_attribute dattr;
+ size_t field_offset;
+ u8 address;
+ u8 limit_low;
+ u8 limit_high;
+};
+
+#define HYCON_ATTR_U8(_field, _mode, _address, _limit_low, _limit_high) \
+ struct hycon_hy46xx_attribute hycon_hy46xx_attr_##_field = { \
+ .dattr = __ATTR(_field, _mode, \
+ hycon_hy46xx_setting_show, \
+ hycon_hy46xx_setting_store), \
+ .field_offset = offsetof(struct hycon_hy46xx_data, _field), \
+ .address = _address, \
+ .limit_low = _limit_low, \
+ .limit_high = _limit_high, \
+ }
+
+#define HYCON_ATTR_BOOL(_field, _mode, _address) \
+ struct hycon_hy46xx_attribute hycon_hy46xx_attr_##_field = { \
+ .dattr = __ATTR(_field, _mode, \
+ hycon_hy46xx_setting_show, \
+ hycon_hy46xx_setting_store), \
+ .field_offset = offsetof(struct hycon_hy46xx_data, _field), \
+ .address = _address, \
+ .limit_low = false, \
+ .limit_high = true, \
+ }
+
+static ssize_t hycon_hy46xx_setting_show(struct device *dev,
+ struct device_attribute *dattr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hycon_hy46xx_data *tsdata = i2c_get_clientdata(client);
+ struct hycon_hy46xx_attribute *attr =
+ container_of(dattr, struct hycon_hy46xx_attribute, dattr);
+ u8 *field = (u8 *)tsdata + attr->field_offset;
+ size_t count = 0;
+ int error = 0;
+ int val;
+
+ mutex_lock(&tsdata->mutex);
+
+ error = regmap_read(tsdata->regmap, attr->address, &val);
+ if (error < 0) {
+ dev_err(&tsdata->client->dev,
+ "Failed to fetch attribute %s, error %d\n",
+ dattr->attr.name, error);
+ goto out;
+ }
+
+ if (val != *field) {
+ dev_warn(&tsdata->client->dev,
+ "%s: read (%d) and stored value (%d) differ\n",
+ dattr->attr.name, val, *field);
+ *field = val;
+ }
+
+ count = scnprintf(buf, PAGE_SIZE, "%d\n", val);
+
+out:
+ mutex_unlock(&tsdata->mutex);
+ return error ?: count;
+}
+
+static ssize_t hycon_hy46xx_setting_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hycon_hy46xx_data *tsdata = i2c_get_clientdata(client);
+ struct hycon_hy46xx_attribute *attr =
+ container_of(dattr, struct hycon_hy46xx_attribute, dattr);
+ u8 *field = (u8 *)tsdata + attr->field_offset;
+ unsigned int val;
+ int error;
+
+ mutex_lock(&tsdata->mutex);
+
+ error = kstrtouint(buf, 0, &val);
+ if (error)
+ goto out;
+
+ if (val < attr->limit_low || val > attr->limit_high) {
+ error = -ERANGE;
+ goto out;
+ }
+
+ error = regmap_write(tsdata->regmap, attr->address, val);
+ if (error < 0) {
+ dev_err(&tsdata->client->dev,
+ "Failed to update attribute %s, error: %d\n",
+ dattr->attr.name, error);
+ goto out;
+ }
+ *field = val;
+
+out:
+ mutex_unlock(&tsdata->mutex);
+ return error ?: count;
+}
+
+static HYCON_ATTR_U8(threshold, 0644, HY46XX_THRESHOLD, 0, 255);
+static HYCON_ATTR_BOOL(glove_enable, 0644, HY46XX_GLOVE_EN);
+static HYCON_ATTR_U8(report_speed, 0644, HY46XX_REPORT_SPEED, 0, 255);
+static HYCON_ATTR_BOOL(noise_filter_enable, 0644, HY46XX_PWR_NOISE_EN);
+static HYCON_ATTR_U8(filter_data, 0644, HY46XX_FILTER_DATA, 0, 5);
+static HYCON_ATTR_U8(gain, 0644, HY46XX_GAIN, 0, 5);
+static HYCON_ATTR_U8(edge_offset, 0644, HY46XX_EDGE_OFFSET, 0, 5);
+static HYCON_ATTR_U8(fw_version, 0444, HY46XX_FW_VERSION, 0, 255);
+static HYCON_ATTR_U8(lib_version, 0444, HY46XX_LIB_VERSION, 0, 255);
+static HYCON_ATTR_U8(tp_information, 0444, HY46XX_TP_INFO, 0, 255);
+static HYCON_ATTR_U8(tp_chip_id, 0444, HY46XX_TP_CHIP_ID, 0, 255);
+static HYCON_ATTR_U8(bootloader_version, 0444, HY46XX_BOOT_VER, 0, 255);
+
+static struct attribute *hycon_hy46xx_attrs[] = {
+ &hycon_hy46xx_attr_threshold.dattr.attr,
+ &hycon_hy46xx_attr_glove_enable.dattr.attr,
+ &hycon_hy46xx_attr_report_speed.dattr.attr,
+ &hycon_hy46xx_attr_noise_filter_enable.dattr.attr,
+ &hycon_hy46xx_attr_filter_data.dattr.attr,
+ &hycon_hy46xx_attr_gain.dattr.attr,
+ &hycon_hy46xx_attr_edge_offset.dattr.attr,
+ &hycon_hy46xx_attr_fw_version.dattr.attr,
+ &hycon_hy46xx_attr_lib_version.dattr.attr,
+ &hycon_hy46xx_attr_tp_information.dattr.attr,
+ &hycon_hy46xx_attr_tp_chip_id.dattr.attr,
+ &hycon_hy46xx_attr_bootloader_version.dattr.attr,
+ NULL
+};
+
+static const struct attribute_group hycon_hy46xx_attr_group = {
+ .attrs = hycon_hy46xx_attrs,
+};
+
+static void hycon_hy46xx_get_defaults(struct device *dev, struct hycon_hy46xx_data *tsdata)
+{
+ bool val_bool;
+ int error;
+ u32 val;
+
+ error = device_property_read_u32(dev, "hycon,threshold", &val);
+ if (!error) {
+ error = regmap_write(tsdata->regmap, HY46XX_THRESHOLD, val);
+ if (error < 0)
+ goto out;
+
+ tsdata->threshold = val;
+ }
+
+ val_bool = device_property_read_bool(dev, "hycon,glove-enable");
+ error = regmap_write(tsdata->regmap, HY46XX_GLOVE_EN, val_bool);
+ if (error < 0)
+ goto out;
+ tsdata->glove_enable = val_bool;
+
+ error = device_property_read_u32(dev, "hycon,report-speed-hz", &val);
+ if (!error) {
+ error = regmap_write(tsdata->regmap, HY46XX_REPORT_SPEED, val);
+ if (error < 0)
+ goto out;
+
+ tsdata->report_speed = val;
+ }
+
+ val_bool = device_property_read_bool(dev, "hycon,noise-filter-enable");
+ error = regmap_write(tsdata->regmap, HY46XX_PWR_NOISE_EN, val_bool);
+ if (error < 0)
+ goto out;
+ tsdata->noise_filter_enable = val_bool;
+
+ error = device_property_read_u32(dev, "hycon,filter-data", &val);
+ if (!error) {
+ error = regmap_write(tsdata->regmap, HY46XX_FILTER_DATA, val);
+ if (error < 0)
+ goto out;
+
+ tsdata->filter_data = val;
+ }
+
+ error = device_property_read_u32(dev, "hycon,gain", &val);
+ if (!error) {
+ error = regmap_write(tsdata->regmap, HY46XX_GAIN, val);
+ if (error < 0)
+ goto out;
+
+ tsdata->gain = val;
+ }
+
+ error = device_property_read_u32(dev, "hycon,edge-offset", &val);
+ if (!error) {
+ error = regmap_write(tsdata->regmap, HY46XX_EDGE_OFFSET, val);
+ if (error < 0)
+ goto out;
+
+ tsdata->edge_offset = val;
+ }
+
+ return;
+out:
+ dev_err(&tsdata->client->dev, "Failed to set default settings");
+}
+
+static void hycon_hy46xx_get_parameters(struct hycon_hy46xx_data *tsdata)
+{
+ int error;
+ u32 val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_THRESHOLD, &val);
+ if (error < 0)
+ goto out;
+ tsdata->threshold = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_GLOVE_EN, &val);
+ if (error < 0)
+ goto out;
+ tsdata->glove_enable = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_REPORT_SPEED, &val);
+ if (error < 0)
+ goto out;
+ tsdata->report_speed = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_PWR_NOISE_EN, &val);
+ if (error < 0)
+ goto out;
+ tsdata->noise_filter_enable = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_FILTER_DATA, &val);
+ if (error < 0)
+ goto out;
+ tsdata->filter_data = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_GAIN, &val);
+ if (error < 0)
+ goto out;
+ tsdata->gain = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_EDGE_OFFSET, &val);
+ if (error < 0)
+ goto out;
+ tsdata->edge_offset = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_RX_NR_USED, &val);
+ if (error < 0)
+ goto out;
+ tsdata->rx_number_used = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_TX_NR_USED, &val);
+ if (error < 0)
+ goto out;
+ tsdata->tx_number_used = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_PWR_MODE, &val);
+ if (error < 0)
+ goto out;
+ tsdata->power_mode = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_FW_VERSION, &val);
+ if (error < 0)
+ goto out;
+ tsdata->fw_version = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_LIB_VERSION, &val);
+ if (error < 0)
+ goto out;
+ tsdata->lib_version = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_TP_INFO, &val);
+ if (error < 0)
+ goto out;
+ tsdata->tp_information = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_TP_CHIP_ID, &val);
+ if (error < 0)
+ goto out;
+ tsdata->tp_chip_id = val;
+
+ error = regmap_read(tsdata->regmap, HY46XX_BOOT_VER, &val);
+ if (error < 0)
+ goto out;
+ tsdata->bootloader_version = val;
+
+ return;
+out:
+ dev_err(&tsdata->client->dev, "Failed to read default settings");
+}
+
+static void hycon_hy46xx_disable_regulator(void *arg)
+{
+ struct hycon_hy46xx_data *data = arg;
+
+ regulator_disable(data->vcc);
+}
+
+static int hycon_hy46xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct hycon_hy46xx_data *tsdata;
+ struct input_dev *input;
+ int error;
+
+ dev_dbg(&client->dev, "probing for HYCON HY46XX I2C\n");
+
+ tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
+ if (!tsdata)
+ return -ENOMEM;
+
+ tsdata->vcc = devm_regulator_get(&client->dev, "vcc");
+ if (IS_ERR(tsdata->vcc)) {
+ error = PTR_ERR(tsdata->vcc);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "failed to request regulator: %d\n", error);
+ return error;
+ }
+
+ error = regulator_enable(tsdata->vcc);
+ if (error < 0) {
+ dev_err(&client->dev, "failed to enable vcc: %d\n", error);
+ return error;
+ }
+
+ error = devm_add_action_or_reset(&client->dev,
+ hycon_hy46xx_disable_regulator,
+ tsdata);
+ if (error)
+ return error;
+
+ tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(tsdata->reset_gpio)) {
+ error = PTR_ERR(tsdata->reset_gpio);
+ dev_err(&client->dev,
+ "Failed to request GPIO reset pin, error %d\n", error);
+ return error;
+ }
+
+ if (tsdata->reset_gpio) {
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+ msleep(1000);
+ }
+
+ input = devm_input_allocate_device(&client->dev);
+ if (!input) {
+ dev_err(&client->dev, "failed to allocate input device.\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&tsdata->mutex);
+ tsdata->client = client;
+ tsdata->input = input;
+
+ tsdata->regmap = devm_regmap_init_i2c(client,
+ &hycon_hy46xx_i2c_regmap_config);
+ if (IS_ERR(tsdata->regmap)) {
+ dev_err(&client->dev, "regmap allocation failed\n");
+ return PTR_ERR(tsdata->regmap);
+ }
+
+ hycon_hy46xx_get_defaults(&client->dev, tsdata);
+ hycon_hy46xx_get_parameters(tsdata);
+
+ input->name = "Hycon Capacitive Touch";
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &client->dev;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, -1, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, -1, 0, 0);
+
+ touchscreen_parse_properties(input, true, &tsdata->prop);
+
+ error = input_mt_init_slots(input, HY46XX_MAX_SUPPORTED_POINTS,
+ INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(&client->dev, "Unable to init MT slots.\n");
+ return error;
+ }
+
+ i2c_set_clientdata(client, tsdata);
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, hycon_hy46xx_isr, IRQF_ONESHOT,
+ client->name, tsdata);
+ if (error) {
+ dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &hycon_hy46xx_attr_group);
+ if (error)
+ return error;
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ dev_dbg(&client->dev,
+ "HYCON HY46XX initialized: IRQ %d, Reset pin %d.\n",
+ client->irq,
+ tsdata->reset_gpio ? desc_to_gpio(tsdata->reset_gpio) : -1);
+
+ return 0;
+}
+
+static const struct i2c_device_id hycon_hy46xx_id[] = {
+ { .name = "hy4613" },
+ { .name = "hy4614" },
+ { .name = "hy4621" },
+ { .name = "hy4623" },
+ { .name = "hy4633" },
+ { .name = "hy4635" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, hycon_hy46xx_id);
+
+static const struct of_device_id hycon_hy46xx_of_match[] = {
+ { .compatible = "hycon,hy4613" },
+ { .compatible = "hycon,hy4614" },
+ { .compatible = "hycon,hy4621" },
+ { .compatible = "hycon,hy4623" },
+ { .compatible = "hycon,hy4633" },
+ { .compatible = "hycon,hy4635" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hycon_hy46xx_of_match);
+
+static struct i2c_driver hycon_hy46xx_driver = {
+ .driver = {
+ .name = "hycon_hy46xx",
+ .of_match_table = hycon_hy46xx_of_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = hycon_hy46xx_id,
+ .probe = hycon_hy46xx_probe,
+};
+
+module_i2c_driver(hycon_hy46xx_driver);
+
+MODULE_AUTHOR("Giulio Benetti <giulio.benetti@benettiengineering.com>");
+MODULE_DESCRIPTION("HYCON HY46XX I2C Touchscreen Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c
new file mode 100644
index 000000000..e9bd36adb
--- /dev/null
+++ b/drivers/input/touchscreen/ili210x.c
@@ -0,0 +1,1053 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/crc-ccitt.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/ihex.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#define ILI2XXX_POLL_PERIOD 15
+
+#define ILI210X_DATA_SIZE 64
+#define ILI211X_DATA_SIZE 43
+#define ILI251X_DATA_SIZE1 31
+#define ILI251X_DATA_SIZE2 20
+
+/* Touchscreen commands */
+#define REG_TOUCHDATA 0x10
+#define REG_PANEL_INFO 0x20
+#define REG_FIRMWARE_VERSION 0x40
+#define REG_PROTOCOL_VERSION 0x42
+#define REG_KERNEL_VERSION 0x61
+#define REG_IC_BUSY 0x80
+#define REG_IC_BUSY_NOT_BUSY 0x50
+#define REG_GET_MODE 0xc0
+#define REG_GET_MODE_AP 0x5a
+#define REG_GET_MODE_BL 0x55
+#define REG_SET_MODE_AP 0xc1
+#define REG_SET_MODE_BL 0xc2
+#define REG_WRITE_DATA 0xc3
+#define REG_WRITE_ENABLE 0xc4
+#define REG_READ_DATA_CRC 0xc7
+#define REG_CALIBRATE 0xcc
+
+#define ILI251X_FW_FILENAME "ilitek/ili251x.bin"
+
+struct ili2xxx_chip {
+ int (*read_reg)(struct i2c_client *client, u8 reg,
+ void *buf, size_t len);
+ int (*get_touch_data)(struct i2c_client *client, u8 *data);
+ bool (*parse_touch_data)(const u8 *data, unsigned int finger,
+ unsigned int *x, unsigned int *y,
+ unsigned int *z);
+ bool (*continue_polling)(const u8 *data, bool touch);
+ unsigned int max_touches;
+ unsigned int resolution;
+ bool has_calibrate_reg;
+ bool has_firmware_proto;
+ bool has_pressure_reg;
+};
+
+struct ili210x {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *reset_gpio;
+ struct touchscreen_properties prop;
+ const struct ili2xxx_chip *chip;
+ u8 version_firmware[8];
+ u8 version_kernel[5];
+ u8 version_proto[2];
+ u8 ic_mode[2];
+ bool stop;
+};
+
+static int ili210x_read_reg(struct i2c_client *client,
+ u8 reg, void *buf, size_t len)
+{
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &reg,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = buf,
+ }
+ };
+ int error, ret;
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&client->dev, "%s failed: %d\n", __func__, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ili210x_read_touch_data(struct i2c_client *client, u8 *data)
+{
+ return ili210x_read_reg(client, REG_TOUCHDATA,
+ data, ILI210X_DATA_SIZE);
+}
+
+static bool ili210x_touchdata_to_coords(const u8 *touchdata,
+ unsigned int finger,
+ unsigned int *x, unsigned int *y,
+ unsigned int *z)
+{
+ if (!(touchdata[0] & BIT(finger)))
+ return false;
+
+ *x = get_unaligned_be16(touchdata + 1 + (finger * 4) + 0);
+ *y = get_unaligned_be16(touchdata + 1 + (finger * 4) + 2);
+
+ return true;
+}
+
+static bool ili210x_check_continue_polling(const u8 *data, bool touch)
+{
+ return data[0] & 0xf3;
+}
+
+static const struct ili2xxx_chip ili210x_chip = {
+ .read_reg = ili210x_read_reg,
+ .get_touch_data = ili210x_read_touch_data,
+ .parse_touch_data = ili210x_touchdata_to_coords,
+ .continue_polling = ili210x_check_continue_polling,
+ .max_touches = 2,
+ .has_calibrate_reg = true,
+};
+
+static int ili211x_read_touch_data(struct i2c_client *client, u8 *data)
+{
+ s16 sum = 0;
+ int error;
+ int ret;
+ int i;
+
+ ret = i2c_master_recv(client, data, ILI211X_DATA_SIZE);
+ if (ret != ILI211X_DATA_SIZE) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&client->dev, "%s failed: %d\n", __func__, error);
+ return error;
+ }
+
+ /* This chip uses custom checksum at the end of data */
+ for (i = 0; i < ILI211X_DATA_SIZE - 1; i++)
+ sum = (sum + data[i]) & 0xff;
+
+ if ((-sum & 0xff) != data[ILI211X_DATA_SIZE - 1]) {
+ dev_err(&client->dev,
+ "CRC error (crc=0x%02x expected=0x%02x)\n",
+ sum, data[ILI211X_DATA_SIZE - 1]);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static bool ili211x_touchdata_to_coords(const u8 *touchdata,
+ unsigned int finger,
+ unsigned int *x, unsigned int *y,
+ unsigned int *z)
+{
+ u32 data;
+
+ data = get_unaligned_be32(touchdata + 1 + (finger * 4) + 0);
+ if (data == 0xffffffff) /* Finger up */
+ return false;
+
+ *x = ((touchdata[1 + (finger * 4) + 0] & 0xf0) << 4) |
+ touchdata[1 + (finger * 4) + 1];
+ *y = ((touchdata[1 + (finger * 4) + 0] & 0x0f) << 8) |
+ touchdata[1 + (finger * 4) + 2];
+
+ return true;
+}
+
+static bool ili211x_decline_polling(const u8 *data, bool touch)
+{
+ return false;
+}
+
+static const struct ili2xxx_chip ili211x_chip = {
+ .read_reg = ili210x_read_reg,
+ .get_touch_data = ili211x_read_touch_data,
+ .parse_touch_data = ili211x_touchdata_to_coords,
+ .continue_polling = ili211x_decline_polling,
+ .max_touches = 10,
+ .resolution = 2048,
+};
+
+static bool ili212x_touchdata_to_coords(const u8 *touchdata,
+ unsigned int finger,
+ unsigned int *x, unsigned int *y,
+ unsigned int *z)
+{
+ u16 val;
+
+ val = get_unaligned_be16(touchdata + 3 + (finger * 5) + 0);
+ if (!(val & BIT(15))) /* Touch indication */
+ return false;
+
+ *x = val & 0x3fff;
+ *y = get_unaligned_be16(touchdata + 3 + (finger * 5) + 2);
+
+ return true;
+}
+
+static bool ili212x_check_continue_polling(const u8 *data, bool touch)
+{
+ return touch;
+}
+
+static const struct ili2xxx_chip ili212x_chip = {
+ .read_reg = ili210x_read_reg,
+ .get_touch_data = ili210x_read_touch_data,
+ .parse_touch_data = ili212x_touchdata_to_coords,
+ .continue_polling = ili212x_check_continue_polling,
+ .max_touches = 10,
+ .has_calibrate_reg = true,
+};
+
+static int ili251x_read_reg_common(struct i2c_client *client,
+ u8 reg, void *buf, size_t len,
+ unsigned int delay)
+{
+ int error;
+ int ret;
+
+ ret = i2c_master_send(client, &reg, 1);
+ if (ret == 1) {
+ if (delay)
+ usleep_range(delay, delay + 500);
+
+ ret = i2c_master_recv(client, buf, len);
+ if (ret == len)
+ return 0;
+ }
+
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&client->dev, "%s failed: %d\n", __func__, error);
+ return ret;
+}
+
+static int ili251x_read_reg(struct i2c_client *client,
+ u8 reg, void *buf, size_t len)
+{
+ return ili251x_read_reg_common(client, reg, buf, len, 5000);
+}
+
+static int ili251x_read_touch_data(struct i2c_client *client, u8 *data)
+{
+ int error;
+
+ error = ili251x_read_reg_common(client, REG_TOUCHDATA,
+ data, ILI251X_DATA_SIZE1, 0);
+ if (!error && data[0] == 2) {
+ error = i2c_master_recv(client, data + ILI251X_DATA_SIZE1,
+ ILI251X_DATA_SIZE2);
+ if (error >= 0 && error != ILI251X_DATA_SIZE2)
+ error = -EIO;
+ }
+
+ return error;
+}
+
+static bool ili251x_touchdata_to_coords(const u8 *touchdata,
+ unsigned int finger,
+ unsigned int *x, unsigned int *y,
+ unsigned int *z)
+{
+ u16 val;
+
+ val = get_unaligned_be16(touchdata + 1 + (finger * 5) + 0);
+ if (!(val & BIT(15))) /* Touch indication */
+ return false;
+
+ *x = val & 0x3fff;
+ *y = get_unaligned_be16(touchdata + 1 + (finger * 5) + 2);
+ *z = touchdata[1 + (finger * 5) + 4];
+
+ return true;
+}
+
+static bool ili251x_check_continue_polling(const u8 *data, bool touch)
+{
+ return touch;
+}
+
+static const struct ili2xxx_chip ili251x_chip = {
+ .read_reg = ili251x_read_reg,
+ .get_touch_data = ili251x_read_touch_data,
+ .parse_touch_data = ili251x_touchdata_to_coords,
+ .continue_polling = ili251x_check_continue_polling,
+ .max_touches = 10,
+ .has_calibrate_reg = true,
+ .has_firmware_proto = true,
+ .has_pressure_reg = true,
+};
+
+static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata)
+{
+ struct input_dev *input = priv->input;
+ int i;
+ bool contact = false, touch;
+ unsigned int x = 0, y = 0, z = 0;
+
+ for (i = 0; i < priv->chip->max_touches; i++) {
+ touch = priv->chip->parse_touch_data(touchdata, i, &x, &y, &z);
+
+ input_mt_slot(input, i);
+ if (input_mt_report_slot_state(input, MT_TOOL_FINGER, touch)) {
+ touchscreen_report_pos(input, &priv->prop, x, y, true);
+ if (priv->chip->has_pressure_reg)
+ input_report_abs(input, ABS_MT_PRESSURE, z);
+ contact = true;
+ }
+ }
+
+ input_mt_report_pointer_emulation(input, false);
+ input_sync(input);
+
+ return contact;
+}
+
+static irqreturn_t ili210x_irq(int irq, void *irq_data)
+{
+ struct ili210x *priv = irq_data;
+ struct i2c_client *client = priv->client;
+ const struct ili2xxx_chip *chip = priv->chip;
+ u8 touchdata[ILI210X_DATA_SIZE] = { 0 };
+ bool keep_polling;
+ ktime_t time_next;
+ s64 time_delta;
+ bool touch;
+ int error;
+
+ do {
+ time_next = ktime_add_ms(ktime_get(), ILI2XXX_POLL_PERIOD);
+ error = chip->get_touch_data(client, touchdata);
+ if (error) {
+ dev_err(&client->dev,
+ "Unable to get touch data: %d\n", error);
+ break;
+ }
+
+ touch = ili210x_report_events(priv, touchdata);
+ keep_polling = chip->continue_polling(touchdata, touch);
+ if (keep_polling) {
+ time_delta = ktime_us_delta(time_next, ktime_get());
+ if (time_delta > 0)
+ usleep_range(time_delta, time_delta + 1000);
+ }
+ } while (!priv->stop && keep_polling);
+
+ return IRQ_HANDLED;
+}
+
+static int ili251x_firmware_update_resolution(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u16 resx, resy;
+ u8 rs[10];
+ int error;
+
+ /* The firmware update blob might have changed the resolution. */
+ error = priv->chip->read_reg(client, REG_PANEL_INFO, &rs, sizeof(rs));
+ if (error)
+ return error;
+
+ resx = le16_to_cpup((__le16 *)rs);
+ resy = le16_to_cpup((__le16 *)(rs + 2));
+
+ /* The value reported by the firmware is invalid. */
+ if (!resx || resx == 0xffff || !resy || resy == 0xffff)
+ return -EINVAL;
+
+ input_abs_set_max(priv->input, ABS_X, resx - 1);
+ input_abs_set_max(priv->input, ABS_Y, resy - 1);
+ input_abs_set_max(priv->input, ABS_MT_POSITION_X, resx - 1);
+ input_abs_set_max(priv->input, ABS_MT_POSITION_Y, resy - 1);
+
+ return 0;
+}
+
+static ssize_t ili251x_firmware_update_firmware_version(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int error;
+ u8 fw[8];
+
+ /* Get firmware version */
+ error = priv->chip->read_reg(client, REG_FIRMWARE_VERSION,
+ &fw, sizeof(fw));
+ if (!error)
+ memcpy(priv->version_firmware, fw, sizeof(fw));
+
+ return error;
+}
+
+static ssize_t ili251x_firmware_update_kernel_version(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int error;
+ u8 kv[5];
+
+ /* Get kernel version */
+ error = priv->chip->read_reg(client, REG_KERNEL_VERSION,
+ &kv, sizeof(kv));
+ if (!error)
+ memcpy(priv->version_kernel, kv, sizeof(kv));
+
+ return error;
+}
+
+static ssize_t ili251x_firmware_update_protocol_version(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int error;
+ u8 pv[2];
+
+ /* Get protocol version */
+ error = priv->chip->read_reg(client, REG_PROTOCOL_VERSION,
+ &pv, sizeof(pv));
+ if (!error)
+ memcpy(priv->version_proto, pv, sizeof(pv));
+
+ return error;
+}
+
+static ssize_t ili251x_firmware_update_ic_mode(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int error;
+ u8 md[2];
+
+ /* Get chip boot mode */
+ error = priv->chip->read_reg(client, REG_GET_MODE, &md, sizeof(md));
+ if (!error)
+ memcpy(priv->ic_mode, md, sizeof(md));
+
+ return error;
+}
+
+static int ili251x_firmware_update_cached_state(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int error;
+
+ if (!priv->chip->has_firmware_proto)
+ return 0;
+
+ /* Wait for firmware to boot and stabilize itself. */
+ msleep(200);
+
+ /* Firmware does report valid information. */
+ error = ili251x_firmware_update_resolution(dev);
+ if (error)
+ return error;
+
+ error = ili251x_firmware_update_firmware_version(dev);
+ if (error)
+ return error;
+
+ error = ili251x_firmware_update_kernel_version(dev);
+ if (error)
+ return error;
+
+ error = ili251x_firmware_update_protocol_version(dev);
+ if (error)
+ return error;
+
+ error = ili251x_firmware_update_ic_mode(dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static ssize_t ili251x_firmware_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 *fw = priv->version_firmware;
+
+ return sysfs_emit(buf, "%02x%02x.%02x%02x.%02x%02x.%02x%02x\n",
+ fw[0], fw[1], fw[2], fw[3],
+ fw[4], fw[5], fw[6], fw[7]);
+}
+static DEVICE_ATTR(firmware_version, 0444, ili251x_firmware_version_show, NULL);
+
+static ssize_t ili251x_kernel_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 *kv = priv->version_kernel;
+
+ return sysfs_emit(buf, "%02x.%02x.%02x.%02x.%02x\n",
+ kv[0], kv[1], kv[2], kv[3], kv[4]);
+}
+static DEVICE_ATTR(kernel_version, 0444, ili251x_kernel_version_show, NULL);
+
+static ssize_t ili251x_protocol_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 *pv = priv->version_proto;
+
+ return sysfs_emit(buf, "%02x.%02x\n", pv[0], pv[1]);
+}
+static DEVICE_ATTR(protocol_version, 0444, ili251x_protocol_version_show, NULL);
+
+static ssize_t ili251x_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 *md = priv->ic_mode;
+ char *mode = "AP";
+
+ if (md[0] == REG_GET_MODE_AP) /* Application Mode */
+ mode = "AP";
+ else if (md[0] == REG_GET_MODE_BL) /* BootLoader Mode */
+ mode = "BL";
+ else /* Unknown Mode */
+ mode = "??";
+
+ return sysfs_emit(buf, "%02x.%02x:%s\n", md[0], md[1], mode);
+}
+static DEVICE_ATTR(mode, 0444, ili251x_mode_show, NULL);
+
+static ssize_t ili210x_calibrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ unsigned long calibrate;
+ int rc;
+ u8 cmd = REG_CALIBRATE;
+
+ if (kstrtoul(buf, 10, &calibrate))
+ return -EINVAL;
+
+ if (calibrate > 1)
+ return -EINVAL;
+
+ if (calibrate) {
+ rc = i2c_master_send(priv->client, &cmd, sizeof(cmd));
+ if (rc != sizeof(cmd))
+ return -EIO;
+ }
+
+ return count;
+}
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate);
+
+static int ili251x_firmware_to_buffer(const struct firmware *fw,
+ u8 **buf, u16 *ac_end, u16 *df_end)
+{
+ const struct ihex_binrec *rec;
+ u32 fw_addr, fw_last_addr = 0;
+ u16 fw_len;
+ u8 *fw_buf;
+ int error;
+
+ /*
+ * The firmware ihex blob can never be bigger than 64 kiB, so make this
+ * simple -- allocate a 64 kiB buffer, iterate over the ihex blob records
+ * once, copy them all into this buffer at the right locations, and then
+ * do all operations on this linear buffer.
+ */
+ fw_buf = kzalloc(SZ_64K, GFP_KERNEL);
+ if (!fw_buf)
+ return -ENOMEM;
+
+ rec = (const struct ihex_binrec *)fw->data;
+ while (rec) {
+ fw_addr = be32_to_cpu(rec->addr);
+ fw_len = be16_to_cpu(rec->len);
+
+ /* The last 32 Byte firmware block can be 0xffe0 */
+ if (fw_addr + fw_len > SZ_64K || fw_addr > SZ_64K - 32) {
+ error = -EFBIG;
+ goto err_big;
+ }
+
+ /* Find the last address before DF start address, that is AC end */
+ if (fw_addr == 0xf000)
+ *ac_end = fw_last_addr;
+ fw_last_addr = fw_addr + fw_len;
+
+ memcpy(fw_buf + fw_addr, rec->data, fw_len);
+ rec = ihex_next_binrec(rec);
+ }
+
+ /* DF end address is the last address in the firmware blob */
+ *df_end = fw_addr + fw_len;
+ *buf = fw_buf;
+ return 0;
+
+err_big:
+ kfree(fw_buf);
+ return error;
+}
+
+/* Switch mode between Application and BootLoader */
+static int ili251x_switch_ic_mode(struct i2c_client *client, u8 cmd_mode)
+{
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 cmd_wren[3] = { REG_WRITE_ENABLE, 0x5a, 0xa5 };
+ u8 md[2];
+ int error;
+
+ error = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md));
+ if (error)
+ return error;
+ /* Mode already set */
+ if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) ||
+ (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL))
+ return 0;
+
+ /* Unlock writes */
+ error = i2c_master_send(client, cmd_wren, sizeof(cmd_wren));
+ if (error != sizeof(cmd_wren))
+ return -EINVAL;
+
+ mdelay(20);
+
+ /* Select mode (BootLoader or Application) */
+ error = i2c_master_send(client, &cmd_mode, 1);
+ if (error != 1)
+ return -EINVAL;
+
+ mdelay(200); /* Reboot into bootloader takes a lot of time ... */
+
+ /* Read back mode */
+ error = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md));
+ if (error)
+ return error;
+ /* Check if mode is correct now. */
+ if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) ||
+ (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL))
+ return 0;
+
+ return -EINVAL;
+}
+
+static int ili251x_firmware_busy(struct i2c_client *client)
+{
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int error, i = 0;
+ u8 data;
+
+ do {
+ /* The read_reg already contains suitable delay */
+ error = priv->chip->read_reg(client, REG_IC_BUSY, &data, 1);
+ if (error)
+ return error;
+ if (i++ == 100000)
+ return -ETIMEDOUT;
+ } while (data != REG_IC_BUSY_NOT_BUSY);
+
+ return 0;
+}
+
+static int ili251x_firmware_write_to_ic(struct device *dev, u8 *fwbuf,
+ u16 start, u16 end, u8 dataflash)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 cmd_crc = REG_READ_DATA_CRC;
+ u8 crcrb[4] = { 0 };
+ u8 fw_data[33];
+ u16 fw_addr;
+ int error;
+
+ /*
+ * The DF (dataflash) needs 2 bytes offset for unknown reasons,
+ * the AC (application) has 2 bytes CRC16-CCITT at the end.
+ */
+ u16 crc = crc_ccitt(0, fwbuf + start + (dataflash ? 2 : 0),
+ end - start - 2);
+
+ /* Unlock write to either AC (application) or DF (dataflash) area */
+ u8 cmd_wr[10] = {
+ REG_WRITE_ENABLE, 0x5a, 0xa5, dataflash,
+ (end >> 16) & 0xff, (end >> 8) & 0xff, end & 0xff,
+ (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff
+ };
+
+ error = i2c_master_send(client, cmd_wr, sizeof(cmd_wr));
+ if (error != sizeof(cmd_wr))
+ return -EINVAL;
+
+ error = ili251x_firmware_busy(client);
+ if (error)
+ return error;
+
+ for (fw_addr = start; fw_addr < end; fw_addr += 32) {
+ fw_data[0] = REG_WRITE_DATA;
+ memcpy(&(fw_data[1]), fwbuf + fw_addr, 32);
+ error = i2c_master_send(client, fw_data, 33);
+ if (error != sizeof(fw_data))
+ return error;
+ error = ili251x_firmware_busy(client);
+ if (error)
+ return error;
+ }
+
+ error = i2c_master_send(client, &cmd_crc, 1);
+ if (error != 1)
+ return -EINVAL;
+
+ error = ili251x_firmware_busy(client);
+ if (error)
+ return error;
+
+ error = priv->chip->read_reg(client, REG_READ_DATA_CRC,
+ &crcrb, sizeof(crcrb));
+ if (error)
+ return error;
+
+ /* Check CRC readback */
+ if ((crcrb[0] != (crc & 0xff)) || crcrb[1] != ((crc >> 8) & 0xff))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ili251x_firmware_reset(struct i2c_client *client)
+{
+ u8 cmd_reset[2] = { 0xf2, 0x01 };
+ int error;
+
+ error = i2c_master_send(client, cmd_reset, sizeof(cmd_reset));
+ if (error != sizeof(cmd_reset))
+ return -EINVAL;
+
+ return ili251x_firmware_busy(client);
+}
+
+static void ili210x_hardware_reset(struct gpio_desc *reset_gpio)
+{
+ /* Reset the controller */
+ gpiod_set_value_cansleep(reset_gpio, 1);
+ usleep_range(12000, 15000);
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ msleep(300);
+}
+
+static ssize_t ili210x_firmware_update_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ const char *fwname = ILI251X_FW_FILENAME;
+ const struct firmware *fw;
+ u16 ac_end, df_end;
+ u8 *fwbuf;
+ int error;
+ int i;
+
+ error = request_ihex_firmware(&fw, fwname, dev);
+ if (error) {
+ dev_err(dev, "Failed to request firmware %s, error=%d\n",
+ fwname, error);
+ return error;
+ }
+
+ error = ili251x_firmware_to_buffer(fw, &fwbuf, &ac_end, &df_end);
+ release_firmware(fw);
+ if (error)
+ return error;
+
+ /*
+ * Disable touchscreen IRQ, so that we would not get spurious touch
+ * interrupt during firmware update, and so that the IRQ handler won't
+ * trigger and interfere with the firmware update. There is no bit in
+ * the touch controller to disable the IRQs during update, so we have
+ * to do it this way here.
+ */
+ disable_irq(client->irq);
+
+ dev_dbg(dev, "Firmware update started, firmware=%s\n", fwname);
+
+ ili210x_hardware_reset(priv->reset_gpio);
+
+ error = ili251x_firmware_reset(client);
+ if (error)
+ goto exit;
+
+ /* This may not succeed on first try, so re-try a few times. */
+ for (i = 0; i < 5; i++) {
+ error = ili251x_switch_ic_mode(client, REG_SET_MODE_BL);
+ if (!error)
+ break;
+ }
+
+ if (error)
+ goto exit;
+
+ dev_dbg(dev, "IC is now in BootLoader mode\n");
+
+ msleep(200); /* The bootloader seems to need some time too. */
+
+ error = ili251x_firmware_write_to_ic(dev, fwbuf, 0xf000, df_end, 1);
+ if (error) {
+ dev_err(dev, "DF firmware update failed, error=%d\n", error);
+ goto exit;
+ }
+
+ dev_dbg(dev, "DataFlash firmware written\n");
+
+ error = ili251x_firmware_write_to_ic(dev, fwbuf, 0x2000, ac_end, 0);
+ if (error) {
+ dev_err(dev, "AC firmware update failed, error=%d\n", error);
+ goto exit;
+ }
+
+ dev_dbg(dev, "Application firmware written\n");
+
+ /* This may not succeed on first try, so re-try a few times. */
+ for (i = 0; i < 5; i++) {
+ error = ili251x_switch_ic_mode(client, REG_SET_MODE_AP);
+ if (!error)
+ break;
+ }
+
+ if (error)
+ goto exit;
+
+ dev_dbg(dev, "IC is now in Application mode\n");
+
+ error = ili251x_firmware_update_cached_state(dev);
+ if (error)
+ goto exit;
+
+ error = count;
+
+exit:
+ ili210x_hardware_reset(priv->reset_gpio);
+ dev_dbg(dev, "Firmware update ended, error=%i\n", error);
+ enable_irq(client->irq);
+ kfree(fwbuf);
+ return error;
+}
+
+static DEVICE_ATTR(firmware_update, 0200, NULL, ili210x_firmware_update_store);
+
+static struct attribute *ili210x_attributes[] = {
+ &dev_attr_calibrate.attr,
+ &dev_attr_firmware_update.attr,
+ &dev_attr_firmware_version.attr,
+ &dev_attr_kernel_version.attr,
+ &dev_attr_protocol_version.attr,
+ &dev_attr_mode.attr,
+ NULL,
+};
+
+static umode_t ili210x_attributes_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+
+ /* Calibrate is present on all ILI2xxx which have calibrate register */
+ if (attr == &dev_attr_calibrate.attr)
+ return priv->chip->has_calibrate_reg ? attr->mode : 0;
+
+ /* Firmware/Kernel/Protocol/BootMode is implememted only for ILI251x */
+ if (!priv->chip->has_firmware_proto)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group ili210x_attr_group = {
+ .attrs = ili210x_attributes,
+ .is_visible = ili210x_attributes_visible,
+};
+
+static void ili210x_power_down(void *data)
+{
+ struct gpio_desc *reset_gpio = data;
+
+ gpiod_set_value_cansleep(reset_gpio, 1);
+}
+
+static void ili210x_stop(void *data)
+{
+ struct ili210x *priv = data;
+
+ /* Tell ISR to quit even if there is a contact. */
+ priv->stop = true;
+}
+
+static int ili210x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ const struct ili2xxx_chip *chip;
+ struct ili210x *priv;
+ struct gpio_desc *reset_gpio;
+ struct input_dev *input;
+ int error;
+ unsigned int max_xy;
+
+ dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver");
+
+ chip = device_get_match_data(dev);
+ if (!chip && id)
+ chip = (const struct ili2xxx_chip *)id->driver_data;
+ if (!chip) {
+ dev_err(&client->dev, "unknown device model\n");
+ return -ENODEV;
+ }
+
+ if (client->irq <= 0) {
+ dev_err(dev, "No IRQ!\n");
+ return -EINVAL;
+ }
+
+ reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+
+ if (reset_gpio) {
+ error = devm_add_action_or_reset(dev, ili210x_power_down,
+ reset_gpio);
+ if (error)
+ return error;
+
+ ili210x_hardware_reset(reset_gpio);
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ priv->client = client;
+ priv->input = input;
+ priv->reset_gpio = reset_gpio;
+ priv->chip = chip;
+ i2c_set_clientdata(client, priv);
+
+ /* Setup input device */
+ input->name = "ILI210x Touchscreen";
+ input->id.bustype = BUS_I2C;
+
+ /* Multi touch */
+ max_xy = (chip->resolution ?: SZ_64K) - 1;
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_xy, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_xy, 0, 0);
+ if (priv->chip->has_pressure_reg)
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xa, 0, 0);
+ error = ili251x_firmware_update_cached_state(dev);
+ if (error) {
+ dev_err(dev, "Unable to cache firmware information, err: %d\n",
+ error);
+ return error;
+ }
+ touchscreen_parse_properties(input, true, &priv->prop);
+
+ error = input_mt_init_slots(input, priv->chip->max_touches,
+ INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(dev, "Unable to set up slots, err: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq,
+ IRQF_ONESHOT, client->name, priv);
+ if (error) {
+ dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",
+ error);
+ return error;
+ }
+
+ error = devm_add_action_or_reset(dev, ili210x_stop, priv);
+ if (error)
+ return error;
+
+ error = devm_device_add_group(dev, &ili210x_attr_group);
+ if (error) {
+ dev_err(dev, "Unable to create sysfs attributes, err: %d\n",
+ error);
+ return error;
+ }
+
+ error = input_register_device(priv->input);
+ if (error) {
+ dev_err(dev, "Cannot register input device, err: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id ili210x_i2c_id[] = {
+ { "ili210x", (long)&ili210x_chip },
+ { "ili2117", (long)&ili211x_chip },
+ { "ili2120", (long)&ili212x_chip },
+ { "ili251x", (long)&ili251x_chip },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id);
+
+static const struct of_device_id ili210x_dt_ids[] = {
+ { .compatible = "ilitek,ili210x", .data = &ili210x_chip },
+ { .compatible = "ilitek,ili2117", .data = &ili211x_chip },
+ { .compatible = "ilitek,ili2120", .data = &ili212x_chip },
+ { .compatible = "ilitek,ili251x", .data = &ili251x_chip },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ili210x_dt_ids);
+
+static struct i2c_driver ili210x_ts_driver = {
+ .driver = {
+ .name = "ili210x_i2c",
+ .of_match_table = ili210x_dt_ids,
+ },
+ .id_table = ili210x_i2c_id,
+ .probe = ili210x_i2c_probe,
+};
+
+module_i2c_driver(ili210x_ts_driver);
+
+MODULE_AUTHOR("Olivier Sobrie <olivier@sobrie.be>");
+MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/ilitek_ts_i2c.c b/drivers/input/touchscreen/ilitek_ts_i2c.c
new file mode 100644
index 000000000..c5d259c76
--- /dev/null
+++ b/drivers/input/touchscreen/ilitek_ts_i2c.c
@@ -0,0 +1,690 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ILITEK Touch IC driver for 23XX, 25XX and Lego series
+ *
+ * Copyright (C) 2011 ILI Technology Corporation.
+ * Copyright (C) 2020 Luca Hsu <luca_hsu@ilitek.com>
+ * Copyright (C) 2021 Joe Hung <joe_hung@ilitek.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/input/touchscreen.h>
+#include <asm/unaligned.h>
+
+
+#define ILITEK_TS_NAME "ilitek_ts"
+#define BL_V1_8 0x108
+#define BL_V1_7 0x107
+#define BL_V1_6 0x106
+
+#define ILITEK_TP_CMD_GET_TP_RES 0x20
+#define ILITEK_TP_CMD_GET_SCRN_RES 0x21
+#define ILITEK_TP_CMD_SET_IC_SLEEP 0x30
+#define ILITEK_TP_CMD_SET_IC_WAKE 0x31
+#define ILITEK_TP_CMD_GET_FW_VER 0x40
+#define ILITEK_TP_CMD_GET_PRL_VER 0x42
+#define ILITEK_TP_CMD_GET_MCU_VER 0x61
+#define ILITEK_TP_CMD_GET_IC_MODE 0xC0
+
+#define REPORT_COUNT_ADDRESS 61
+#define ILITEK_SUPPORT_MAX_POINT 40
+
+struct ilitek_protocol_info {
+ u16 ver;
+ u8 ver_major;
+};
+
+struct ilitek_ts_data {
+ struct i2c_client *client;
+ struct gpio_desc *reset_gpio;
+ struct input_dev *input_dev;
+ struct touchscreen_properties prop;
+
+ const struct ilitek_protocol_map *ptl_cb_func;
+ struct ilitek_protocol_info ptl;
+
+ char product_id[30];
+ u16 mcu_ver;
+ u8 ic_mode;
+ u8 firmware_ver[8];
+
+ s32 reset_time;
+ s32 screen_max_x;
+ s32 screen_max_y;
+ s32 screen_min_x;
+ s32 screen_min_y;
+ s32 max_tp;
+};
+
+struct ilitek_protocol_map {
+ u16 cmd;
+ const char *name;
+ int (*func)(struct ilitek_ts_data *ts, u16 cmd, u8 *inbuf, u8 *outbuf);
+};
+
+enum ilitek_cmds {
+ /* common cmds */
+ GET_PTL_VER = 0,
+ GET_FW_VER,
+ GET_SCRN_RES,
+ GET_TP_RES,
+ GET_IC_MODE,
+ GET_MCU_VER,
+ SET_IC_SLEEP,
+ SET_IC_WAKE,
+
+ /* ALWAYS keep at the end */
+ MAX_CMD_CNT
+};
+
+/* ILITEK I2C R/W APIs */
+static int ilitek_i2c_write_and_read(struct ilitek_ts_data *ts,
+ u8 *cmd, int write_len, int delay,
+ u8 *data, int read_len)
+{
+ int error;
+ struct i2c_client *client = ts->client;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = write_len,
+ .buf = cmd,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = read_len,
+ .buf = data,
+ },
+ };
+
+ if (delay == 0 && write_len > 0 && read_len > 0) {
+ error = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (error < 0)
+ return error;
+ } else {
+ if (write_len > 0) {
+ error = i2c_transfer(client->adapter, msgs, 1);
+ if (error < 0)
+ return error;
+ }
+ if (delay > 0)
+ mdelay(delay);
+
+ if (read_len > 0) {
+ error = i2c_transfer(client->adapter, msgs + 1, 1);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+/* ILITEK ISR APIs */
+static void ilitek_touch_down(struct ilitek_ts_data *ts, unsigned int id,
+ unsigned int x, unsigned int y)
+{
+ struct input_dev *input = ts->input_dev;
+
+ input_mt_slot(input, id);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+ touchscreen_report_pos(input, &ts->prop, x, y, true);
+}
+
+static int ilitek_process_and_report_v6(struct ilitek_ts_data *ts)
+{
+ int error = 0;
+ u8 buf[512];
+ int packet_len = 5;
+ int packet_max_point = 10;
+ int report_max_point;
+ int i, count;
+ struct input_dev *input = ts->input_dev;
+ struct device *dev = &ts->client->dev;
+ unsigned int x, y, status, id;
+
+ error = ilitek_i2c_write_and_read(ts, NULL, 0, 0, buf, 64);
+ if (error) {
+ dev_err(dev, "get touch info failed, err:%d\n", error);
+ goto err_sync_frame;
+ }
+
+ report_max_point = buf[REPORT_COUNT_ADDRESS];
+ if (report_max_point > ts->max_tp) {
+ dev_err(dev, "FW report max point:%d > panel info. max:%d\n",
+ report_max_point, ts->max_tp);
+ error = -EINVAL;
+ goto err_sync_frame;
+ }
+
+ count = DIV_ROUND_UP(report_max_point, packet_max_point);
+ for (i = 1; i < count; i++) {
+ error = ilitek_i2c_write_and_read(ts, NULL, 0, 0,
+ buf + i * 64, 64);
+ if (error) {
+ dev_err(dev, "get touch info. failed, cnt:%d, err:%d\n",
+ count, error);
+ goto err_sync_frame;
+ }
+ }
+
+ for (i = 0; i < report_max_point; i++) {
+ status = buf[i * packet_len + 1] & 0x40;
+ if (!status)
+ continue;
+
+ id = buf[i * packet_len + 1] & 0x3F;
+
+ x = get_unaligned_le16(buf + i * packet_len + 2);
+ y = get_unaligned_le16(buf + i * packet_len + 4);
+
+ if (x > ts->screen_max_x || x < ts->screen_min_x ||
+ y > ts->screen_max_y || y < ts->screen_min_y) {
+ dev_warn(dev, "invalid position, X[%d,%u,%d], Y[%d,%u,%d]\n",
+ ts->screen_min_x, x, ts->screen_max_x,
+ ts->screen_min_y, y, ts->screen_max_y);
+ continue;
+ }
+
+ ilitek_touch_down(ts, id, x, y);
+ }
+
+err_sync_frame:
+ input_mt_sync_frame(input);
+ input_sync(input);
+ return error;
+}
+
+/* APIs of cmds for ILITEK Touch IC */
+static int api_protocol_set_cmd(struct ilitek_ts_data *ts,
+ u16 idx, u8 *inbuf, u8 *outbuf)
+{
+ u16 cmd;
+ int error;
+
+ if (idx >= MAX_CMD_CNT)
+ return -EINVAL;
+
+ cmd = ts->ptl_cb_func[idx].cmd;
+ error = ts->ptl_cb_func[idx].func(ts, cmd, inbuf, outbuf);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int api_protocol_get_ptl_ver(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ int error;
+ u8 buf[64];
+
+ buf[0] = cmd;
+ error = ilitek_i2c_write_and_read(ts, buf, 1, 5, outbuf, 3);
+ if (error)
+ return error;
+
+ ts->ptl.ver = get_unaligned_be16(outbuf);
+ ts->ptl.ver_major = outbuf[0];
+
+ return 0;
+}
+
+static int api_protocol_get_mcu_ver(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ int error;
+ u8 buf[64];
+
+ buf[0] = cmd;
+ error = ilitek_i2c_write_and_read(ts, buf, 1, 5, outbuf, 32);
+ if (error)
+ return error;
+
+ ts->mcu_ver = get_unaligned_le16(outbuf);
+ memset(ts->product_id, 0, sizeof(ts->product_id));
+ memcpy(ts->product_id, outbuf + 6, 26);
+
+ return 0;
+}
+
+static int api_protocol_get_fw_ver(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ int error;
+ u8 buf[64];
+
+ buf[0] = cmd;
+ error = ilitek_i2c_write_and_read(ts, buf, 1, 5, outbuf, 8);
+ if (error)
+ return error;
+
+ memcpy(ts->firmware_ver, outbuf, 8);
+
+ return 0;
+}
+
+static int api_protocol_get_scrn_res(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ int error;
+ u8 buf[64];
+
+ buf[0] = cmd;
+ error = ilitek_i2c_write_and_read(ts, buf, 1, 5, outbuf, 8);
+ if (error)
+ return error;
+
+ ts->screen_min_x = get_unaligned_le16(outbuf);
+ ts->screen_min_y = get_unaligned_le16(outbuf + 2);
+ ts->screen_max_x = get_unaligned_le16(outbuf + 4);
+ ts->screen_max_y = get_unaligned_le16(outbuf + 6);
+
+ return 0;
+}
+
+static int api_protocol_get_tp_res(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ int error;
+ u8 buf[64];
+
+ buf[0] = cmd;
+ error = ilitek_i2c_write_and_read(ts, buf, 1, 5, outbuf, 15);
+ if (error)
+ return error;
+
+ ts->max_tp = outbuf[8];
+ if (ts->max_tp > ILITEK_SUPPORT_MAX_POINT) {
+ dev_err(&ts->client->dev, "Invalid MAX_TP:%d from FW\n",
+ ts->max_tp);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int api_protocol_get_ic_mode(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ int error;
+ u8 buf[64];
+
+ buf[0] = cmd;
+ error = ilitek_i2c_write_and_read(ts, buf, 1, 5, outbuf, 2);
+ if (error)
+ return error;
+
+ ts->ic_mode = outbuf[0];
+ return 0;
+}
+
+static int api_protocol_set_ic_sleep(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ u8 buf[64];
+
+ buf[0] = cmd;
+ return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0);
+}
+
+static int api_protocol_set_ic_wake(struct ilitek_ts_data *ts,
+ u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+ u8 buf[64];
+
+ buf[0] = cmd;
+ return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0);
+}
+
+static const struct ilitek_protocol_map ptl_func_map[] = {
+ /* common cmds */
+ [GET_PTL_VER] = {
+ ILITEK_TP_CMD_GET_PRL_VER, "GET_PTL_VER",
+ api_protocol_get_ptl_ver
+ },
+ [GET_FW_VER] = {
+ ILITEK_TP_CMD_GET_FW_VER, "GET_FW_VER",
+ api_protocol_get_fw_ver
+ },
+ [GET_SCRN_RES] = {
+ ILITEK_TP_CMD_GET_SCRN_RES, "GET_SCRN_RES",
+ api_protocol_get_scrn_res
+ },
+ [GET_TP_RES] = {
+ ILITEK_TP_CMD_GET_TP_RES, "GET_TP_RES",
+ api_protocol_get_tp_res
+ },
+ [GET_IC_MODE] = {
+ ILITEK_TP_CMD_GET_IC_MODE, "GET_IC_MODE",
+ api_protocol_get_ic_mode
+ },
+ [GET_MCU_VER] = {
+ ILITEK_TP_CMD_GET_MCU_VER, "GET_MOD_VER",
+ api_protocol_get_mcu_ver
+ },
+ [SET_IC_SLEEP] = {
+ ILITEK_TP_CMD_SET_IC_SLEEP, "SET_IC_SLEEP",
+ api_protocol_set_ic_sleep
+ },
+ [SET_IC_WAKE] = {
+ ILITEK_TP_CMD_SET_IC_WAKE, "SET_IC_WAKE",
+ api_protocol_set_ic_wake
+ },
+};
+
+/* Probe APIs */
+static void ilitek_reset(struct ilitek_ts_data *ts, int delay)
+{
+ if (ts->reset_gpio) {
+ gpiod_set_value(ts->reset_gpio, 1);
+ mdelay(10);
+ gpiod_set_value(ts->reset_gpio, 0);
+ mdelay(delay);
+ }
+}
+
+static int ilitek_protocol_init(struct ilitek_ts_data *ts)
+{
+ int error;
+ u8 outbuf[64];
+
+ ts->ptl_cb_func = ptl_func_map;
+ ts->reset_time = 600;
+
+ error = api_protocol_set_cmd(ts, GET_PTL_VER, NULL, outbuf);
+ if (error)
+ return error;
+
+ /* Protocol v3 is not support currently */
+ if (ts->ptl.ver_major == 0x3 ||
+ ts->ptl.ver == BL_V1_6 ||
+ ts->ptl.ver == BL_V1_7)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ilitek_read_tp_info(struct ilitek_ts_data *ts, bool boot)
+{
+ u8 outbuf[256];
+ int error;
+
+ error = api_protocol_set_cmd(ts, GET_PTL_VER, NULL, outbuf);
+ if (error)
+ return error;
+
+ error = api_protocol_set_cmd(ts, GET_MCU_VER, NULL, outbuf);
+ if (error)
+ return error;
+
+ error = api_protocol_set_cmd(ts, GET_FW_VER, NULL, outbuf);
+ if (error)
+ return error;
+
+ if (boot) {
+ error = api_protocol_set_cmd(ts, GET_SCRN_RES, NULL,
+ outbuf);
+ if (error)
+ return error;
+ }
+
+ error = api_protocol_set_cmd(ts, GET_TP_RES, NULL, outbuf);
+ if (error)
+ return error;
+
+ error = api_protocol_set_cmd(ts, GET_IC_MODE, NULL, outbuf);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int ilitek_input_dev_init(struct device *dev, struct ilitek_ts_data *ts)
+{
+ int error;
+ struct input_dev *input;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ ts->input_dev = input;
+ input->name = ILITEK_TS_NAME;
+ input->id.bustype = BUS_I2C;
+
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+
+ input_set_abs_params(input, ABS_MT_POSITION_X,
+ ts->screen_min_x, ts->screen_max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y,
+ ts->screen_min_y, ts->screen_max_y, 0, 0);
+
+ touchscreen_parse_properties(input, true, &ts->prop);
+
+ error = input_mt_init_slots(input, ts->max_tp,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(dev, "initialize MT slots failed, err:%d\n", error);
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "register input device failed, err:%d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static irqreturn_t ilitek_i2c_isr(int irq, void *dev_id)
+{
+ struct ilitek_ts_data *ts = dev_id;
+ int error;
+
+ error = ilitek_process_and_report_v6(ts);
+ if (error < 0) {
+ dev_err(&ts->client->dev, "[%s] err:%d\n", __func__, error);
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t firmware_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ilitek_ts_data *ts = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE,
+ "fw version: [%02X%02X.%02X%02X.%02X%02X.%02X%02X]\n",
+ ts->firmware_ver[0], ts->firmware_ver[1],
+ ts->firmware_ver[2], ts->firmware_ver[3],
+ ts->firmware_ver[4], ts->firmware_ver[5],
+ ts->firmware_ver[6], ts->firmware_ver[7]);
+}
+static DEVICE_ATTR_RO(firmware_version);
+
+static ssize_t product_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ilitek_ts_data *ts = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "product id: [%04X], module: [%s]\n",
+ ts->mcu_ver, ts->product_id);
+}
+static DEVICE_ATTR_RO(product_id);
+
+static struct attribute *ilitek_sysfs_attrs[] = {
+ &dev_attr_firmware_version.attr,
+ &dev_attr_product_id.attr,
+ NULL
+};
+
+static struct attribute_group ilitek_attrs_group = {
+ .attrs = ilitek_sysfs_attrs,
+};
+
+static int ilitek_ts_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ilitek_ts_data *ts;
+ struct device *dev = &client->dev;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c check functionality failed\n");
+ return -ENXIO;
+ }
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+ i2c_set_clientdata(client, ts);
+
+ ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+ dev_err(dev, "request gpiod failed: %d", error);
+ return error;
+ }
+
+ ilitek_reset(ts, 1000);
+
+ error = ilitek_protocol_init(ts);
+ if (error) {
+ dev_err(dev, "protocol init failed: %d", error);
+ return error;
+ }
+
+ error = ilitek_read_tp_info(ts, true);
+ if (error) {
+ dev_err(dev, "read tp info failed: %d", error);
+ return error;
+ }
+
+ error = ilitek_input_dev_init(dev, ts);
+ if (error) {
+ dev_err(dev, "input dev init failed: %d", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, ts->client->irq,
+ NULL, ilitek_i2c_isr, IRQF_ONESHOT,
+ "ilitek_touch_irq", ts);
+ if (error) {
+ dev_err(dev, "request threaded irq failed: %d\n", error);
+ return error;
+ }
+
+ error = devm_device_add_group(dev, &ilitek_attrs_group);
+ if (error) {
+ dev_err(dev, "sysfs create group failed: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused ilitek_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ilitek_ts_data *ts = i2c_get_clientdata(client);
+ int error;
+
+ disable_irq(client->irq);
+
+ if (!device_may_wakeup(dev)) {
+ error = api_protocol_set_cmd(ts, SET_IC_SLEEP, NULL, NULL);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused ilitek_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ilitek_ts_data *ts = i2c_get_clientdata(client);
+ int error;
+
+ if (!device_may_wakeup(dev)) {
+ error = api_protocol_set_cmd(ts, SET_IC_WAKE, NULL, NULL);
+ if (error)
+ return error;
+
+ ilitek_reset(ts, ts->reset_time);
+ }
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ilitek_pm_ops, ilitek_suspend, ilitek_resume);
+
+static const struct i2c_device_id ilitek_ts_i2c_id[] = {
+ { ILITEK_TS_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ilitek_ts_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id ilitekts_acpi_id[] = {
+ { "ILTK0001", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, ilitekts_acpi_id);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id ilitek_ts_i2c_match[] = {
+ {.compatible = "ilitek,ili2130",},
+ {.compatible = "ilitek,ili2131",},
+ {.compatible = "ilitek,ili2132",},
+ {.compatible = "ilitek,ili2316",},
+ {.compatible = "ilitek,ili2322",},
+ {.compatible = "ilitek,ili2323",},
+ {.compatible = "ilitek,ili2326",},
+ {.compatible = "ilitek,ili2520",},
+ {.compatible = "ilitek,ili2521",},
+ { },
+};
+MODULE_DEVICE_TABLE(of, ilitek_ts_i2c_match);
+#endif
+
+static struct i2c_driver ilitek_ts_i2c_driver = {
+ .driver = {
+ .name = ILITEK_TS_NAME,
+ .pm = &ilitek_pm_ops,
+ .of_match_table = of_match_ptr(ilitek_ts_i2c_match),
+ .acpi_match_table = ACPI_PTR(ilitekts_acpi_id),
+ },
+ .probe = ilitek_ts_i2c_probe,
+ .id_table = ilitek_ts_i2c_id,
+};
+module_i2c_driver(ilitek_ts_i2c_driver);
+
+MODULE_AUTHOR("ILITEK");
+MODULE_DESCRIPTION("ILITEK I2C Touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/imagis.c b/drivers/input/touchscreen/imagis.c
new file mode 100644
index 000000000..e2697e6c6
--- /dev/null
+++ b/drivers/input/touchscreen/imagis.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#define IST3038C_HIB_ACCESS (0x800B << 16)
+#define IST3038C_DIRECT_ACCESS BIT(31)
+#define IST3038C_REG_CHIPID 0x40001000
+#define IST3038C_REG_HIB_BASE 0x30000100
+#define IST3038C_REG_TOUCH_STATUS (IST3038C_REG_HIB_BASE | IST3038C_HIB_ACCESS)
+#define IST3038C_REG_TOUCH_COORD (IST3038C_REG_HIB_BASE | IST3038C_HIB_ACCESS | 0x8)
+#define IST3038C_REG_INTR_MESSAGE (IST3038C_REG_HIB_BASE | IST3038C_HIB_ACCESS | 0x4)
+#define IST3038C_WHOAMI 0x38c
+#define IST3038C_CHIP_ON_DELAY_MS 60
+#define IST3038C_I2C_RETRY_COUNT 3
+#define IST3038C_MAX_FINGER_NUM 10
+#define IST3038C_X_MASK GENMASK(23, 12)
+#define IST3038C_X_SHIFT 12
+#define IST3038C_Y_MASK GENMASK(11, 0)
+#define IST3038C_AREA_MASK GENMASK(27, 24)
+#define IST3038C_AREA_SHIFT 24
+#define IST3038C_FINGER_COUNT_MASK GENMASK(15, 12)
+#define IST3038C_FINGER_COUNT_SHIFT 12
+#define IST3038C_FINGER_STATUS_MASK GENMASK(9, 0)
+
+struct imagis_ts {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct touchscreen_properties prop;
+ struct regulator_bulk_data supplies[2];
+};
+
+static int imagis_i2c_read_reg(struct imagis_ts *ts,
+ unsigned int reg, u32 *data)
+{
+ __be32 ret_be;
+ __be32 reg_be = cpu_to_be32(reg);
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .buf = (unsigned char *)&reg_be,
+ .len = sizeof(reg_be),
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .buf = (unsigned char *)&ret_be,
+ .len = sizeof(ret_be),
+ },
+ };
+ int ret, error;
+ int retry = IST3038C_I2C_RETRY_COUNT;
+
+ /* Retry in case the controller fails to respond */
+ do {
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret == ARRAY_SIZE(msg)) {
+ *data = be32_to_cpu(ret_be);
+ return 0;
+ }
+
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "%s - i2c_transfer failed: %d (%d)\n",
+ __func__, error, ret);
+ } while (--retry);
+
+ return error;
+}
+
+static irqreturn_t imagis_interrupt(int irq, void *dev_id)
+{
+ struct imagis_ts *ts = dev_id;
+ u32 intr_message, finger_status;
+ unsigned int finger_count, finger_pressed;
+ int i;
+ int error;
+
+ error = imagis_i2c_read_reg(ts, IST3038C_REG_INTR_MESSAGE,
+ &intr_message);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "failed to read the interrupt message: %d\n", error);
+ goto out;
+ }
+
+ finger_count = (intr_message & IST3038C_FINGER_COUNT_MASK) >>
+ IST3038C_FINGER_COUNT_SHIFT;
+ if (finger_count > IST3038C_MAX_FINGER_NUM) {
+ dev_err(&ts->client->dev,
+ "finger count %d is more than maximum supported\n",
+ finger_count);
+ goto out;
+ }
+
+ finger_pressed = intr_message & IST3038C_FINGER_STATUS_MASK;
+
+ for (i = 0; i < finger_count; i++) {
+ error = imagis_i2c_read_reg(ts,
+ IST3038C_REG_TOUCH_COORD + (i * 4),
+ &finger_status);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "failed to read coordinates for finger %d: %d\n",
+ i, error);
+ goto out;
+ }
+
+ input_mt_slot(ts->input_dev, i);
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER,
+ finger_pressed & BIT(i));
+ touchscreen_report_pos(ts->input_dev, &ts->prop,
+ (finger_status & IST3038C_X_MASK) >>
+ IST3038C_X_SHIFT,
+ finger_status & IST3038C_Y_MASK, 1);
+ input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR,
+ (finger_status & IST3038C_AREA_MASK) >>
+ IST3038C_AREA_SHIFT);
+ }
+
+ input_mt_sync_frame(ts->input_dev);
+ input_sync(ts->input_dev);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static void imagis_power_off(void *_ts)
+{
+ struct imagis_ts *ts = _ts;
+
+ regulator_bulk_disable(ARRAY_SIZE(ts->supplies), ts->supplies);
+}
+
+static int imagis_power_on(struct imagis_ts *ts)
+{
+ int error;
+
+ error = regulator_bulk_enable(ARRAY_SIZE(ts->supplies), ts->supplies);
+ if (error)
+ return error;
+
+ msleep(IST3038C_CHIP_ON_DELAY_MS);
+
+ return 0;
+}
+
+static int imagis_start(struct imagis_ts *ts)
+{
+ int error;
+
+ error = imagis_power_on(ts);
+ if (error)
+ return error;
+
+ enable_irq(ts->client->irq);
+
+ return 0;
+}
+
+static int imagis_stop(struct imagis_ts *ts)
+{
+ disable_irq(ts->client->irq);
+
+ imagis_power_off(ts);
+
+ return 0;
+}
+
+static int imagis_input_open(struct input_dev *dev)
+{
+ struct imagis_ts *ts = input_get_drvdata(dev);
+
+ return imagis_start(ts);
+}
+
+static void imagis_input_close(struct input_dev *dev)
+{
+ struct imagis_ts *ts = input_get_drvdata(dev);
+
+ imagis_stop(ts);
+}
+
+static int imagis_init_input_dev(struct imagis_ts *ts)
+{
+ struct input_dev *input_dev;
+ int error;
+
+ input_dev = devm_input_allocate_device(&ts->client->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ ts->input_dev = input_dev;
+
+ input_dev->name = "Imagis capacitive touchscreen";
+ input_dev->phys = "input/ts";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->open = imagis_input_open;
+ input_dev->close = imagis_input_close;
+
+ input_set_drvdata(input_dev, ts);
+
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(input_dev, true, &ts->prop);
+ if (!ts->prop.max_x || !ts->prop.max_y) {
+ dev_err(&ts->client->dev,
+ "Touchscreen-size-x and/or touchscreen-size-y not set in dts\n");
+ return -EINVAL;
+ }
+
+ error = input_mt_init_slots(input_dev,
+ IST3038C_MAX_FINGER_NUM,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Failed to initialize MT slots: %d", error);
+ return error;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Failed to register input device: %d", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int imagis_init_regulators(struct imagis_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+
+ ts->supplies[0].supply = "vdd";
+ ts->supplies[1].supply = "vddio";
+ return devm_regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(ts->supplies),
+ ts->supplies);
+}
+
+static int imagis_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct imagis_ts *ts;
+ int chip_id, error;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = i2c;
+
+ error = imagis_init_regulators(ts);
+ if (error) {
+ dev_err(dev, "regulator init error: %d\n", error);
+ return error;
+ }
+
+ error = imagis_power_on(ts);
+ if (error) {
+ dev_err(dev, "failed to enable regulators: %d\n", error);
+ return error;
+ }
+
+ error = devm_add_action_or_reset(dev, imagis_power_off, ts);
+ if (error) {
+ dev_err(dev, "failed to install poweroff action: %d\n", error);
+ return error;
+ }
+
+ error = imagis_i2c_read_reg(ts,
+ IST3038C_REG_CHIPID | IST3038C_DIRECT_ACCESS,
+ &chip_id);
+ if (error) {
+ dev_err(dev, "chip ID read failure: %d\n", error);
+ return error;
+ }
+
+ if (chip_id != IST3038C_WHOAMI) {
+ dev_err(dev, "unknown chip ID: 0x%x\n", chip_id);
+ return -EINVAL;
+ }
+
+ error = devm_request_threaded_irq(dev, i2c->irq,
+ NULL, imagis_interrupt,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "imagis-touchscreen", ts);
+ if (error) {
+ dev_err(dev, "IRQ %d allocation failure: %d\n",
+ i2c->irq, error);
+ return error;
+ }
+
+ error = imagis_init_input_dev(ts);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int __maybe_unused imagis_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct imagis_ts *ts = i2c_get_clientdata(client);
+ int retval = 0;
+
+ mutex_lock(&ts->input_dev->mutex);
+
+ if (input_device_enabled(ts->input_dev))
+ retval = imagis_stop(ts);
+
+ mutex_unlock(&ts->input_dev->mutex);
+
+ return retval;
+}
+
+static int __maybe_unused imagis_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct imagis_ts *ts = i2c_get_clientdata(client);
+ int retval = 0;
+
+ mutex_lock(&ts->input_dev->mutex);
+
+ if (input_device_enabled(ts->input_dev))
+ retval = imagis_start(ts);
+
+ mutex_unlock(&ts->input_dev->mutex);
+
+ return retval;
+}
+
+static SIMPLE_DEV_PM_OPS(imagis_pm_ops, imagis_suspend, imagis_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id imagis_of_match[] = {
+ { .compatible = "imagis,ist3038c", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imagis_of_match);
+#endif
+
+static struct i2c_driver imagis_ts_driver = {
+ .driver = {
+ .name = "imagis-touchscreen",
+ .pm = &imagis_pm_ops,
+ .of_match_table = of_match_ptr(imagis_of_match),
+ },
+ .probe_new = imagis_probe,
+};
+
+module_i2c_driver(imagis_ts_driver);
+
+MODULE_DESCRIPTION("Imagis IST3038C Touchscreen Driver");
+MODULE_AUTHOR("Markuss Broks <markuss.broks@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/imx6ul_tsc.c b/drivers/input/touchscreen/imx6ul_tsc.c
new file mode 100644
index 000000000..2d4facf70
--- /dev/null
+++ b/drivers/input/touchscreen/imx6ul_tsc.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale i.MX6UL touchscreen controller driver
+//
+// Copyright (C) 2015 Freescale Semiconductor, Inc.
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+
+/* ADC configuration registers field define */
+#define ADC_AIEN (0x1 << 7)
+#define ADC_CONV_DISABLE 0x1F
+#define ADC_AVGE (0x1 << 5)
+#define ADC_CAL (0x1 << 7)
+#define ADC_CALF 0x2
+#define ADC_12BIT_MODE (0x2 << 2)
+#define ADC_CONV_MODE_MASK (0x3 << 2)
+#define ADC_IPG_CLK 0x00
+#define ADC_INPUT_CLK_MASK 0x3
+#define ADC_CLK_DIV_8 (0x03 << 5)
+#define ADC_CLK_DIV_MASK (0x3 << 5)
+#define ADC_SHORT_SAMPLE_MODE (0x0 << 4)
+#define ADC_SAMPLE_MODE_MASK (0x1 << 4)
+#define ADC_HARDWARE_TRIGGER (0x1 << 13)
+#define ADC_AVGS_SHIFT 14
+#define ADC_AVGS_MASK (0x3 << 14)
+#define SELECT_CHANNEL_4 0x04
+#define SELECT_CHANNEL_1 0x01
+#define DISABLE_CONVERSION_INT (0x0 << 7)
+
+/* ADC registers */
+#define REG_ADC_HC0 0x00
+#define REG_ADC_HC1 0x04
+#define REG_ADC_HC2 0x08
+#define REG_ADC_HC3 0x0C
+#define REG_ADC_HC4 0x10
+#define REG_ADC_HS 0x14
+#define REG_ADC_R0 0x18
+#define REG_ADC_CFG 0x2C
+#define REG_ADC_GC 0x30
+#define REG_ADC_GS 0x34
+
+#define ADC_TIMEOUT msecs_to_jiffies(100)
+
+/* TSC registers */
+#define REG_TSC_BASIC_SETING 0x00
+#define REG_TSC_PRE_CHARGE_TIME 0x10
+#define REG_TSC_FLOW_CONTROL 0x20
+#define REG_TSC_MEASURE_VALUE 0x30
+#define REG_TSC_INT_EN 0x40
+#define REG_TSC_INT_SIG_EN 0x50
+#define REG_TSC_INT_STATUS 0x60
+#define REG_TSC_DEBUG_MODE 0x70
+#define REG_TSC_DEBUG_MODE2 0x80
+
+/* TSC configuration registers field define */
+#define DETECT_4_WIRE_MODE (0x0 << 4)
+#define AUTO_MEASURE 0x1
+#define MEASURE_SIGNAL 0x1
+#define DETECT_SIGNAL (0x1 << 4)
+#define VALID_SIGNAL (0x1 << 8)
+#define MEASURE_INT_EN 0x1
+#define MEASURE_SIG_EN 0x1
+#define VALID_SIG_EN (0x1 << 8)
+#define DE_GLITCH_2 (0x2 << 29)
+#define START_SENSE (0x1 << 12)
+#define TSC_DISABLE (0x1 << 16)
+#define DETECT_MODE 0x2
+
+struct imx6ul_tsc {
+ struct device *dev;
+ struct input_dev *input;
+ void __iomem *tsc_regs;
+ void __iomem *adc_regs;
+ struct clk *tsc_clk;
+ struct clk *adc_clk;
+ struct gpio_desc *xnur_gpio;
+
+ u32 measure_delay_time;
+ u32 pre_charge_time;
+ bool average_enable;
+ u32 average_select;
+
+ struct completion completion;
+};
+
+/*
+ * TSC module need ADC to get the measure value. So
+ * before config TSC, we should initialize ADC module.
+ */
+static int imx6ul_adc_init(struct imx6ul_tsc *tsc)
+{
+ u32 adc_hc = 0;
+ u32 adc_gc;
+ u32 adc_gs;
+ u32 adc_cfg;
+ unsigned long timeout;
+
+ reinit_completion(&tsc->completion);
+
+ adc_cfg = readl(tsc->adc_regs + REG_ADC_CFG);
+ adc_cfg &= ~(ADC_CONV_MODE_MASK | ADC_INPUT_CLK_MASK);
+ adc_cfg |= ADC_12BIT_MODE | ADC_IPG_CLK;
+ adc_cfg &= ~(ADC_CLK_DIV_MASK | ADC_SAMPLE_MODE_MASK);
+ adc_cfg |= ADC_CLK_DIV_8 | ADC_SHORT_SAMPLE_MODE;
+ if (tsc->average_enable) {
+ adc_cfg &= ~ADC_AVGS_MASK;
+ adc_cfg |= (tsc->average_select) << ADC_AVGS_SHIFT;
+ }
+ adc_cfg &= ~ADC_HARDWARE_TRIGGER;
+ writel(adc_cfg, tsc->adc_regs + REG_ADC_CFG);
+
+ /* enable calibration interrupt */
+ adc_hc |= ADC_AIEN;
+ adc_hc |= ADC_CONV_DISABLE;
+ writel(adc_hc, tsc->adc_regs + REG_ADC_HC0);
+
+ /* start ADC calibration */
+ adc_gc = readl(tsc->adc_regs + REG_ADC_GC);
+ adc_gc |= ADC_CAL;
+ if (tsc->average_enable)
+ adc_gc |= ADC_AVGE;
+ writel(adc_gc, tsc->adc_regs + REG_ADC_GC);
+
+ timeout = wait_for_completion_timeout
+ (&tsc->completion, ADC_TIMEOUT);
+ if (timeout == 0) {
+ dev_err(tsc->dev, "Timeout for adc calibration\n");
+ return -ETIMEDOUT;
+ }
+
+ adc_gs = readl(tsc->adc_regs + REG_ADC_GS);
+ if (adc_gs & ADC_CALF) {
+ dev_err(tsc->dev, "ADC calibration failed\n");
+ return -EINVAL;
+ }
+
+ /* TSC need the ADC work in hardware trigger */
+ adc_cfg = readl(tsc->adc_regs + REG_ADC_CFG);
+ adc_cfg |= ADC_HARDWARE_TRIGGER;
+ writel(adc_cfg, tsc->adc_regs + REG_ADC_CFG);
+
+ return 0;
+}
+
+/*
+ * This is a TSC workaround. Currently TSC misconnect two
+ * ADC channels, this function remap channel configure for
+ * hardware trigger.
+ */
+static void imx6ul_tsc_channel_config(struct imx6ul_tsc *tsc)
+{
+ u32 adc_hc0, adc_hc1, adc_hc2, adc_hc3, adc_hc4;
+
+ adc_hc0 = DISABLE_CONVERSION_INT;
+ writel(adc_hc0, tsc->adc_regs + REG_ADC_HC0);
+
+ adc_hc1 = DISABLE_CONVERSION_INT | SELECT_CHANNEL_4;
+ writel(adc_hc1, tsc->adc_regs + REG_ADC_HC1);
+
+ adc_hc2 = DISABLE_CONVERSION_INT;
+ writel(adc_hc2, tsc->adc_regs + REG_ADC_HC2);
+
+ adc_hc3 = DISABLE_CONVERSION_INT | SELECT_CHANNEL_1;
+ writel(adc_hc3, tsc->adc_regs + REG_ADC_HC3);
+
+ adc_hc4 = DISABLE_CONVERSION_INT;
+ writel(adc_hc4, tsc->adc_regs + REG_ADC_HC4);
+}
+
+/*
+ * TSC setting, confige the pre-charge time and measure delay time.
+ * different touch screen may need different pre-charge time and
+ * measure delay time.
+ */
+static void imx6ul_tsc_set(struct imx6ul_tsc *tsc)
+{
+ u32 basic_setting = 0;
+ u32 start;
+
+ basic_setting |= tsc->measure_delay_time << 8;
+ basic_setting |= DETECT_4_WIRE_MODE | AUTO_MEASURE;
+ writel(basic_setting, tsc->tsc_regs + REG_TSC_BASIC_SETING);
+
+ writel(DE_GLITCH_2, tsc->tsc_regs + REG_TSC_DEBUG_MODE2);
+
+ writel(tsc->pre_charge_time, tsc->tsc_regs + REG_TSC_PRE_CHARGE_TIME);
+ writel(MEASURE_INT_EN, tsc->tsc_regs + REG_TSC_INT_EN);
+ writel(MEASURE_SIG_EN | VALID_SIG_EN,
+ tsc->tsc_regs + REG_TSC_INT_SIG_EN);
+
+ /* start sense detection */
+ start = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
+ start |= START_SENSE;
+ start &= ~TSC_DISABLE;
+ writel(start, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
+}
+
+static int imx6ul_tsc_init(struct imx6ul_tsc *tsc)
+{
+ int err;
+
+ err = imx6ul_adc_init(tsc);
+ if (err)
+ return err;
+ imx6ul_tsc_channel_config(tsc);
+ imx6ul_tsc_set(tsc);
+
+ return 0;
+}
+
+static void imx6ul_tsc_disable(struct imx6ul_tsc *tsc)
+{
+ u32 tsc_flow;
+ u32 adc_cfg;
+
+ /* TSC controller enters to idle status */
+ tsc_flow = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
+ tsc_flow |= TSC_DISABLE;
+ writel(tsc_flow, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
+
+ /* ADC controller enters to stop mode */
+ adc_cfg = readl(tsc->adc_regs + REG_ADC_HC0);
+ adc_cfg |= ADC_CONV_DISABLE;
+ writel(adc_cfg, tsc->adc_regs + REG_ADC_HC0);
+}
+
+/* Delay some time (max 2ms), wait the pre-charge done. */
+static bool tsc_wait_detect_mode(struct imx6ul_tsc *tsc)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(2);
+ u32 state_machine;
+ u32 debug_mode2;
+
+ do {
+ if (time_after(jiffies, timeout))
+ return false;
+
+ usleep_range(200, 400);
+ debug_mode2 = readl(tsc->tsc_regs + REG_TSC_DEBUG_MODE2);
+ state_machine = (debug_mode2 >> 20) & 0x7;
+ } while (state_machine != DETECT_MODE);
+
+ usleep_range(200, 400);
+ return true;
+}
+
+static irqreturn_t tsc_irq_fn(int irq, void *dev_id)
+{
+ struct imx6ul_tsc *tsc = dev_id;
+ u32 status;
+ u32 value;
+ u32 x, y;
+ u32 start;
+
+ status = readl(tsc->tsc_regs + REG_TSC_INT_STATUS);
+
+ /* write 1 to clear the bit measure-signal */
+ writel(MEASURE_SIGNAL | DETECT_SIGNAL,
+ tsc->tsc_regs + REG_TSC_INT_STATUS);
+
+ /* It's a HW self-clean bit. Set this bit and start sense detection */
+ start = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
+ start |= START_SENSE;
+ writel(start, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
+
+ if (status & MEASURE_SIGNAL) {
+ value = readl(tsc->tsc_regs + REG_TSC_MEASURE_VALUE);
+ x = (value >> 16) & 0x0fff;
+ y = value & 0x0fff;
+
+ /*
+ * In detect mode, we can get the xnur gpio value,
+ * otherwise assume contact is stiull active.
+ */
+ if (!tsc_wait_detect_mode(tsc) ||
+ gpiod_get_value_cansleep(tsc->xnur_gpio)) {
+ input_report_key(tsc->input, BTN_TOUCH, 1);
+ input_report_abs(tsc->input, ABS_X, x);
+ input_report_abs(tsc->input, ABS_Y, y);
+ } else {
+ input_report_key(tsc->input, BTN_TOUCH, 0);
+ }
+
+ input_sync(tsc->input);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t adc_irq_fn(int irq, void *dev_id)
+{
+ struct imx6ul_tsc *tsc = dev_id;
+ u32 coco;
+
+ coco = readl(tsc->adc_regs + REG_ADC_HS);
+ if (coco & 0x01) {
+ readl(tsc->adc_regs + REG_ADC_R0);
+ complete(&tsc->completion);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int imx6ul_tsc_start(struct imx6ul_tsc *tsc)
+{
+ int err;
+
+ err = clk_prepare_enable(tsc->adc_clk);
+ if (err) {
+ dev_err(tsc->dev,
+ "Could not prepare or enable the adc clock: %d\n",
+ err);
+ return err;
+ }
+
+ err = clk_prepare_enable(tsc->tsc_clk);
+ if (err) {
+ dev_err(tsc->dev,
+ "Could not prepare or enable the tsc clock: %d\n",
+ err);
+ goto disable_adc_clk;
+ }
+
+ err = imx6ul_tsc_init(tsc);
+ if (err)
+ goto disable_tsc_clk;
+
+ return 0;
+
+disable_tsc_clk:
+ clk_disable_unprepare(tsc->tsc_clk);
+disable_adc_clk:
+ clk_disable_unprepare(tsc->adc_clk);
+ return err;
+}
+
+static void imx6ul_tsc_stop(struct imx6ul_tsc *tsc)
+{
+ imx6ul_tsc_disable(tsc);
+
+ clk_disable_unprepare(tsc->tsc_clk);
+ clk_disable_unprepare(tsc->adc_clk);
+}
+
+
+static int imx6ul_tsc_open(struct input_dev *input_dev)
+{
+ struct imx6ul_tsc *tsc = input_get_drvdata(input_dev);
+
+ return imx6ul_tsc_start(tsc);
+}
+
+static void imx6ul_tsc_close(struct input_dev *input_dev)
+{
+ struct imx6ul_tsc *tsc = input_get_drvdata(input_dev);
+
+ imx6ul_tsc_stop(tsc);
+}
+
+static int imx6ul_tsc_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct imx6ul_tsc *tsc;
+ struct input_dev *input_dev;
+ int err;
+ int tsc_irq;
+ int adc_irq;
+ u32 average_samples;
+
+ tsc = devm_kzalloc(&pdev->dev, sizeof(*tsc), GFP_KERNEL);
+ if (!tsc)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "iMX6UL Touchscreen Controller";
+ input_dev->id.bustype = BUS_HOST;
+
+ input_dev->open = imx6ul_tsc_open;
+ input_dev->close = imx6ul_tsc_close;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, 0, 0xFFF, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 0xFFF, 0, 0);
+
+ input_set_drvdata(input_dev, tsc);
+
+ tsc->dev = &pdev->dev;
+ tsc->input = input_dev;
+ init_completion(&tsc->completion);
+
+ tsc->xnur_gpio = devm_gpiod_get(&pdev->dev, "xnur", GPIOD_IN);
+ if (IS_ERR(tsc->xnur_gpio)) {
+ err = PTR_ERR(tsc->xnur_gpio);
+ dev_err(&pdev->dev,
+ "failed to request GPIO tsc_X- (xnur): %d\n", err);
+ return err;
+ }
+
+ tsc->tsc_regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tsc->tsc_regs)) {
+ err = PTR_ERR(tsc->tsc_regs);
+ dev_err(&pdev->dev, "failed to remap tsc memory: %d\n", err);
+ return err;
+ }
+
+ tsc->adc_regs = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(tsc->adc_regs)) {
+ err = PTR_ERR(tsc->adc_regs);
+ dev_err(&pdev->dev, "failed to remap adc memory: %d\n", err);
+ return err;
+ }
+
+ tsc->tsc_clk = devm_clk_get(&pdev->dev, "tsc");
+ if (IS_ERR(tsc->tsc_clk)) {
+ err = PTR_ERR(tsc->tsc_clk);
+ dev_err(&pdev->dev, "failed getting tsc clock: %d\n", err);
+ return err;
+ }
+
+ tsc->adc_clk = devm_clk_get(&pdev->dev, "adc");
+ if (IS_ERR(tsc->adc_clk)) {
+ err = PTR_ERR(tsc->adc_clk);
+ dev_err(&pdev->dev, "failed getting adc clock: %d\n", err);
+ return err;
+ }
+
+ tsc_irq = platform_get_irq(pdev, 0);
+ if (tsc_irq < 0)
+ return tsc_irq;
+
+ adc_irq = platform_get_irq(pdev, 1);
+ if (adc_irq < 0)
+ return adc_irq;
+
+ err = devm_request_threaded_irq(tsc->dev, tsc_irq,
+ NULL, tsc_irq_fn, IRQF_ONESHOT,
+ dev_name(&pdev->dev), tsc);
+ if (err) {
+ dev_err(&pdev->dev,
+ "failed requesting tsc irq %d: %d\n",
+ tsc_irq, err);
+ return err;
+ }
+
+ err = devm_request_irq(tsc->dev, adc_irq, adc_irq_fn, 0,
+ dev_name(&pdev->dev), tsc);
+ if (err) {
+ dev_err(&pdev->dev,
+ "failed requesting adc irq %d: %d\n",
+ adc_irq, err);
+ return err;
+ }
+
+ err = of_property_read_u32(np, "measure-delay-time",
+ &tsc->measure_delay_time);
+ if (err)
+ tsc->measure_delay_time = 0xffff;
+
+ err = of_property_read_u32(np, "pre-charge-time",
+ &tsc->pre_charge_time);
+ if (err)
+ tsc->pre_charge_time = 0xfff;
+
+ err = of_property_read_u32(np, "touchscreen-average-samples",
+ &average_samples);
+ if (err)
+ average_samples = 1;
+
+ switch (average_samples) {
+ case 1:
+ tsc->average_enable = false;
+ tsc->average_select = 0; /* value unused; initialize anyway */
+ break;
+ case 4:
+ case 8:
+ case 16:
+ case 32:
+ tsc->average_enable = true;
+ tsc->average_select = ilog2(average_samples) - 2;
+ break;
+ default:
+ dev_err(&pdev->dev,
+ "touchscreen-average-samples (%u) must be 1, 4, 8, 16 or 32\n",
+ average_samples);
+ return -EINVAL;
+ }
+
+ err = input_register_device(tsc->input);
+ if (err) {
+ dev_err(&pdev->dev,
+ "failed to register input device: %d\n", err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, tsc);
+ return 0;
+}
+
+static int __maybe_unused imx6ul_tsc_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct imx6ul_tsc *tsc = platform_get_drvdata(pdev);
+ struct input_dev *input_dev = tsc->input;
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ imx6ul_tsc_stop(tsc);
+
+ mutex_unlock(&input_dev->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused imx6ul_tsc_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct imx6ul_tsc *tsc = platform_get_drvdata(pdev);
+ struct input_dev *input_dev = tsc->input;
+ int retval = 0;
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ retval = imx6ul_tsc_start(tsc);
+
+ mutex_unlock(&input_dev->mutex);
+
+ return retval;
+}
+
+static SIMPLE_DEV_PM_OPS(imx6ul_tsc_pm_ops,
+ imx6ul_tsc_suspend, imx6ul_tsc_resume);
+
+static const struct of_device_id imx6ul_tsc_match[] = {
+ { .compatible = "fsl,imx6ul-tsc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx6ul_tsc_match);
+
+static struct platform_driver imx6ul_tsc_driver = {
+ .driver = {
+ .name = "imx6ul-tsc",
+ .of_match_table = imx6ul_tsc_match,
+ .pm = &imx6ul_tsc_pm_ops,
+ },
+ .probe = imx6ul_tsc_probe,
+};
+module_platform_driver(imx6ul_tsc_driver);
+
+MODULE_AUTHOR("Haibo Chen <haibo.chen@freescale.com>");
+MODULE_DESCRIPTION("Freescale i.MX6UL Touchscreen controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c
new file mode 100644
index 000000000..1d7e4c396
--- /dev/null
+++ b/drivers/input/touchscreen/inexio.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * iNexio serial touchscreen driver
+ *
+ * Copyright (c) 2008 Richard Lemon
+ * Based on the mtouch driver (c) Vojtech Pavlik and Dan Streetman
+ */
+
+
+/*
+ * 2008/06/19 Richard Lemon <richard@codelemon.com>
+ * Copied mtouch.c and edited for iNexio protocol
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "iNexio serial touchscreen driver"
+
+MODULE_AUTHOR("Richard Lemon <richard@codelemon.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define INEXIO_FORMAT_TOUCH_BIT 0x01
+#define INEXIO_FORMAT_LENGTH 5
+#define INEXIO_RESPONSE_BEGIN_BYTE 0x80
+
+/* todo: check specs for max length of all responses */
+#define INEXIO_MAX_LENGTH 16
+
+#define INEXIO_MIN_XC 0
+#define INEXIO_MAX_XC 0x3fff
+#define INEXIO_MIN_YC 0
+#define INEXIO_MAX_YC 0x3fff
+
+#define INEXIO_GET_XC(data) (((data[1])<<7) | data[2])
+#define INEXIO_GET_YC(data) (((data[3])<<7) | data[4])
+#define INEXIO_GET_TOUCHED(data) (INEXIO_FORMAT_TOUCH_BIT & data[0])
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct inexio {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[INEXIO_MAX_LENGTH];
+ char phys[32];
+};
+
+static void inexio_process_data(struct inexio *pinexio)
+{
+ struct input_dev *dev = pinexio->dev;
+
+ if (INEXIO_FORMAT_LENGTH == ++pinexio->idx) {
+ input_report_abs(dev, ABS_X, INEXIO_GET_XC(pinexio->data));
+ input_report_abs(dev, ABS_Y, INEXIO_GET_YC(pinexio->data));
+ input_report_key(dev, BTN_TOUCH, INEXIO_GET_TOUCHED(pinexio->data));
+ input_sync(dev);
+
+ pinexio->idx = 0;
+ }
+}
+
+static irqreturn_t inexio_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct inexio *pinexio = serio_get_drvdata(serio);
+
+ pinexio->data[pinexio->idx] = data;
+
+ if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0])
+ inexio_process_data(pinexio);
+ else
+ printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * inexio_disconnect() is the opposite of inexio_connect()
+ */
+
+static void inexio_disconnect(struct serio *serio)
+{
+ struct inexio *pinexio = serio_get_drvdata(serio);
+
+ input_get_device(pinexio->dev);
+ input_unregister_device(pinexio->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(pinexio->dev);
+ kfree(pinexio);
+}
+
+/*
+ * inexio_connect() is the routine that is called when someone adds a
+ * new serio device that supports iNexio protocol and registers it as
+ * an input device. This is usually accomplished using inputattach.
+ */
+
+static int inexio_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct inexio *pinexio;
+ struct input_dev *input_dev;
+ int err;
+
+ pinexio = kzalloc(sizeof(struct inexio), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!pinexio || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ pinexio->serio = serio;
+ pinexio->dev = input_dev;
+ snprintf(pinexio->phys, sizeof(pinexio->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "iNexio Serial TouchScreen";
+ input_dev->phys = pinexio->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_INEXIO;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0001;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(pinexio->dev, ABS_X, INEXIO_MIN_XC, INEXIO_MAX_XC, 0, 0);
+ input_set_abs_params(pinexio->dev, ABS_Y, INEXIO_MIN_YC, INEXIO_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, pinexio);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(pinexio->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(pinexio);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id inexio_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_INEXIO,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, inexio_serio_ids);
+
+static struct serio_driver inexio_drv = {
+ .driver = {
+ .name = "inexio",
+ },
+ .description = DRIVER_DESC,
+ .id_table = inexio_serio_ids,
+ .interrupt = inexio_interrupt,
+ .connect = inexio_connect,
+ .disconnect = inexio_disconnect,
+};
+
+module_serio_driver(inexio_drv);
diff --git a/drivers/input/touchscreen/ipaq-micro-ts.c b/drivers/input/touchscreen/ipaq-micro-ts.c
new file mode 100644
index 000000000..0eb5689fe
--- /dev/null
+++ b/drivers/input/touchscreen/ipaq-micro-ts.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * h3600 atmel micro companion support, touchscreen subdevice
+ * Author : Alessandro Gardich <gremlin@gremlin.it>
+ * Author : Dmitry Artamonow <mad_soft@inbox.ru>
+ * Author : Linus Walleij <linus.walleij@linaro.org>
+ */
+
+#include <asm/byteorder.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mfd/ipaq-micro.h>
+
+struct touchscreen_data {
+ struct input_dev *input;
+ struct ipaq_micro *micro;
+};
+
+static void micro_ts_receive(void *data, int len, unsigned char *msg)
+{
+ struct touchscreen_data *ts = data;
+
+ if (len == 4) {
+ input_report_abs(ts->input, ABS_X,
+ be16_to_cpup((__be16 *) &msg[2]));
+ input_report_abs(ts->input, ABS_Y,
+ be16_to_cpup((__be16 *) &msg[0]));
+ input_report_key(ts->input, BTN_TOUCH, 1);
+ input_sync(ts->input);
+ } else if (len == 0) {
+ input_report_abs(ts->input, ABS_X, 0);
+ input_report_abs(ts->input, ABS_Y, 0);
+ input_report_key(ts->input, BTN_TOUCH, 0);
+ input_sync(ts->input);
+ }
+}
+
+static void micro_ts_toggle_receive(struct touchscreen_data *ts, bool enable)
+{
+ struct ipaq_micro *micro = ts->micro;
+
+ spin_lock_irq(&micro->lock);
+
+ if (enable) {
+ micro->ts = micro_ts_receive;
+ micro->ts_data = ts;
+ } else {
+ micro->ts = NULL;
+ micro->ts_data = NULL;
+ }
+
+ spin_unlock_irq(&ts->micro->lock);
+}
+
+static int micro_ts_open(struct input_dev *input)
+{
+ struct touchscreen_data *ts = input_get_drvdata(input);
+
+ micro_ts_toggle_receive(ts, true);
+
+ return 0;
+}
+
+static void micro_ts_close(struct input_dev *input)
+{
+ struct touchscreen_data *ts = input_get_drvdata(input);
+
+ micro_ts_toggle_receive(ts, false);
+}
+
+static int micro_ts_probe(struct platform_device *pdev)
+{
+ struct ipaq_micro *micro = dev_get_drvdata(pdev->dev.parent);
+ struct touchscreen_data *ts;
+ int error;
+
+ ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->micro = micro;
+
+ ts->input = devm_input_allocate_device(&pdev->dev);
+ if (!ts->input) {
+ dev_err(&pdev->dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->input->name = "ipaq micro ts";
+ ts->input->open = micro_ts_open;
+ ts->input->close = micro_ts_close;
+
+ input_set_drvdata(ts->input, ts);
+
+ input_set_capability(ts->input, EV_KEY, BTN_TOUCH);
+ input_set_capability(ts->input, EV_ABS, ABS_X);
+ input_set_capability(ts->input, EV_ABS, ABS_Y);
+ input_set_abs_params(ts->input, ABS_X, 0, 1023, 0, 0);
+ input_set_abs_params(ts->input, ABS_Y, 0, 1023, 0, 0);
+
+ error = input_register_device(ts->input);
+ if (error) {
+ dev_err(&pdev->dev, "error registering touch input\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, ts);
+
+ dev_info(&pdev->dev, "iPAQ micro touchscreen\n");
+
+ return 0;
+}
+
+static int __maybe_unused micro_ts_suspend(struct device *dev)
+{
+ struct touchscreen_data *ts = dev_get_drvdata(dev);
+
+ micro_ts_toggle_receive(ts, false);
+
+ return 0;
+}
+
+static int __maybe_unused micro_ts_resume(struct device *dev)
+{
+ struct touchscreen_data *ts = dev_get_drvdata(dev);
+ struct input_dev *input = ts->input;
+
+ mutex_lock(&input->mutex);
+
+ if (input_device_enabled(input))
+ micro_ts_toggle_receive(ts, true);
+
+ mutex_unlock(&input->mutex);
+
+ return 0;
+}
+
+static const struct dev_pm_ops micro_ts_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(micro_ts_suspend, micro_ts_resume)
+};
+
+static struct platform_driver micro_ts_device_driver = {
+ .driver = {
+ .name = "ipaq-micro-ts",
+ .pm = &micro_ts_dev_pm_ops,
+ },
+ .probe = micro_ts_probe,
+};
+module_platform_driver(micro_ts_device_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("driver for iPAQ Atmel micro touchscreen");
+MODULE_ALIAS("platform:ipaq-micro-ts");
diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c
new file mode 100644
index 000000000..34c4cca57
--- /dev/null
+++ b/drivers/input/touchscreen/iqs5xx.c
@@ -0,0 +1,1103 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS550/572/525 Trackpad/Touchscreen Controller
+ *
+ * Copyright (C) 2018 Jeff LaBundy <jeff@labundy.com>
+ *
+ * These devices require firmware exported from a PC-based configuration tool
+ * made available by the vendor. Firmware files may be pushed to the device's
+ * nonvolatile memory by writing the filename to the 'fw_file' sysfs control.
+ *
+ * Link to PC-based configuration tool and datasheet: https://www.azoteq.com/
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#define IQS5XX_FW_FILE_LEN 64
+#define IQS5XX_NUM_RETRIES 10
+#define IQS5XX_NUM_CONTACTS 5
+#define IQS5XX_WR_BYTES_MAX 2
+
+#define IQS5XX_PROD_NUM_IQS550 40
+#define IQS5XX_PROD_NUM_IQS572 58
+#define IQS5XX_PROD_NUM_IQS525 52
+
+#define IQS5XX_SHOW_RESET BIT(7)
+#define IQS5XX_ACK_RESET BIT(7)
+
+#define IQS5XX_SUSPEND BIT(0)
+#define IQS5XX_RESUME 0
+
+#define IQS5XX_SETUP_COMPLETE BIT(6)
+#define IQS5XX_WDT BIT(5)
+#define IQS5XX_ALP_REATI BIT(3)
+#define IQS5XX_REATI BIT(2)
+
+#define IQS5XX_TP_EVENT BIT(2)
+#define IQS5XX_EVENT_MODE BIT(0)
+
+#define IQS5XX_PROD_NUM 0x0000
+#define IQS5XX_SYS_INFO0 0x000F
+#define IQS5XX_SYS_INFO1 0x0010
+#define IQS5XX_SYS_CTRL0 0x0431
+#define IQS5XX_SYS_CTRL1 0x0432
+#define IQS5XX_SYS_CFG0 0x058E
+#define IQS5XX_SYS_CFG1 0x058F
+#define IQS5XX_X_RES 0x066E
+#define IQS5XX_Y_RES 0x0670
+#define IQS5XX_EXP_FILE 0x0677
+#define IQS5XX_CHKSM 0x83C0
+#define IQS5XX_APP 0x8400
+#define IQS5XX_CSTM 0xBE00
+#define IQS5XX_PMAP_END 0xBFFF
+#define IQS5XX_END_COMM 0xEEEE
+
+#define IQS5XX_CHKSM_LEN (IQS5XX_APP - IQS5XX_CHKSM)
+#define IQS5XX_APP_LEN (IQS5XX_CSTM - IQS5XX_APP)
+#define IQS5XX_CSTM_LEN (IQS5XX_PMAP_END + 1 - IQS5XX_CSTM)
+#define IQS5XX_PMAP_LEN (IQS5XX_PMAP_END + 1 - IQS5XX_CHKSM)
+
+#define IQS5XX_REC_HDR_LEN 4
+#define IQS5XX_REC_LEN_MAX 255
+#define IQS5XX_REC_TYPE_DATA 0x00
+#define IQS5XX_REC_TYPE_EOF 0x01
+
+#define IQS5XX_BL_ADDR_MASK 0x40
+#define IQS5XX_BL_CMD_VER 0x00
+#define IQS5XX_BL_CMD_READ 0x01
+#define IQS5XX_BL_CMD_EXEC 0x02
+#define IQS5XX_BL_CMD_CRC 0x03
+#define IQS5XX_BL_BLK_LEN_MAX 64
+#define IQS5XX_BL_ID 0x0200
+#define IQS5XX_BL_STATUS_NONE 0xEE
+#define IQS5XX_BL_CRC_PASS 0x00
+#define IQS5XX_BL_CRC_FAIL 0x01
+#define IQS5XX_BL_ATTEMPTS 3
+
+struct iqs5xx_dev_id_info {
+ __be16 prod_num;
+ __be16 proj_num;
+ u8 major_ver;
+ u8 minor_ver;
+ u8 bl_status;
+} __packed;
+
+struct iqs5xx_ihex_rec {
+ char start;
+ char len[2];
+ char addr[4];
+ char type[2];
+ char data[2];
+} __packed;
+
+struct iqs5xx_touch_data {
+ __be16 abs_x;
+ __be16 abs_y;
+ __be16 strength;
+ u8 area;
+} __packed;
+
+struct iqs5xx_status {
+ u8 sys_info[2];
+ u8 num_active;
+ __be16 rel_x;
+ __be16 rel_y;
+ struct iqs5xx_touch_data touch_data[IQS5XX_NUM_CONTACTS];
+} __packed;
+
+struct iqs5xx_private {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *reset_gpio;
+ struct touchscreen_properties prop;
+ struct mutex lock;
+ struct iqs5xx_dev_id_info dev_id_info;
+ u8 exp_file[2];
+};
+
+static int iqs5xx_read_burst(struct i2c_client *client,
+ u16 reg, void *val, u16 len)
+{
+ __be16 reg_buf = cpu_to_be16(reg);
+ int ret, i;
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = sizeof(reg_buf),
+ .buf = (u8 *)&reg_buf,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = (u8 *)val,
+ },
+ };
+
+ /*
+ * The first addressing attempt outside of a communication window fails
+ * and must be retried, after which the device clock stretches until it
+ * is available.
+ */
+ for (i = 0; i < IQS5XX_NUM_RETRIES; i++) {
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret == ARRAY_SIZE(msg))
+ return 0;
+
+ usleep_range(200, 300);
+ }
+
+ if (ret >= 0)
+ ret = -EIO;
+
+ dev_err(&client->dev, "Failed to read from address 0x%04X: %d\n",
+ reg, ret);
+
+ return ret;
+}
+
+static int iqs5xx_read_word(struct i2c_client *client, u16 reg, u16 *val)
+{
+ __be16 val_buf;
+ int error;
+
+ error = iqs5xx_read_burst(client, reg, &val_buf, sizeof(val_buf));
+ if (error)
+ return error;
+
+ *val = be16_to_cpu(val_buf);
+
+ return 0;
+}
+
+static int iqs5xx_write_burst(struct i2c_client *client,
+ u16 reg, const void *val, u16 len)
+{
+ int ret, i;
+ u16 mlen = sizeof(reg) + len;
+ u8 mbuf[sizeof(reg) + IQS5XX_WR_BYTES_MAX];
+
+ if (len > IQS5XX_WR_BYTES_MAX)
+ return -EINVAL;
+
+ put_unaligned_be16(reg, mbuf);
+ memcpy(mbuf + sizeof(reg), val, len);
+
+ /*
+ * The first addressing attempt outside of a communication window fails
+ * and must be retried, after which the device clock stretches until it
+ * is available.
+ */
+ for (i = 0; i < IQS5XX_NUM_RETRIES; i++) {
+ ret = i2c_master_send(client, mbuf, mlen);
+ if (ret == mlen)
+ return 0;
+
+ usleep_range(200, 300);
+ }
+
+ if (ret >= 0)
+ ret = -EIO;
+
+ dev_err(&client->dev, "Failed to write to address 0x%04X: %d\n",
+ reg, ret);
+
+ return ret;
+}
+
+static int iqs5xx_write_word(struct i2c_client *client, u16 reg, u16 val)
+{
+ __be16 val_buf = cpu_to_be16(val);
+
+ return iqs5xx_write_burst(client, reg, &val_buf, sizeof(val_buf));
+}
+
+static int iqs5xx_write_byte(struct i2c_client *client, u16 reg, u8 val)
+{
+ return iqs5xx_write_burst(client, reg, &val, sizeof(val));
+}
+
+static void iqs5xx_reset(struct i2c_client *client)
+{
+ struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client);
+
+ gpiod_set_value_cansleep(iqs5xx->reset_gpio, 1);
+ usleep_range(200, 300);
+
+ gpiod_set_value_cansleep(iqs5xx->reset_gpio, 0);
+}
+
+static int iqs5xx_bl_cmd(struct i2c_client *client, u8 bl_cmd, u16 bl_addr)
+{
+ struct i2c_msg msg;
+ int ret;
+ u8 mbuf[sizeof(bl_cmd) + sizeof(bl_addr)];
+
+ msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK;
+ msg.flags = 0;
+ msg.len = sizeof(bl_cmd);
+ msg.buf = mbuf;
+
+ *mbuf = bl_cmd;
+
+ switch (bl_cmd) {
+ case IQS5XX_BL_CMD_VER:
+ case IQS5XX_BL_CMD_CRC:
+ case IQS5XX_BL_CMD_EXEC:
+ break;
+ case IQS5XX_BL_CMD_READ:
+ msg.len += sizeof(bl_addr);
+ put_unaligned_be16(bl_addr, mbuf + sizeof(bl_cmd));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1)
+ goto msg_fail;
+
+ switch (bl_cmd) {
+ case IQS5XX_BL_CMD_VER:
+ msg.len = sizeof(u16);
+ break;
+ case IQS5XX_BL_CMD_CRC:
+ msg.len = sizeof(u8);
+ /*
+ * This delay saves the bus controller the trouble of having to
+ * tolerate a relatively long clock-stretching period while the
+ * CRC is calculated.
+ */
+ msleep(50);
+ break;
+ case IQS5XX_BL_CMD_EXEC:
+ usleep_range(10000, 10100);
+ fallthrough;
+ default:
+ return 0;
+ }
+
+ msg.flags = I2C_M_RD;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1)
+ goto msg_fail;
+
+ if (bl_cmd == IQS5XX_BL_CMD_VER &&
+ get_unaligned_be16(mbuf) != IQS5XX_BL_ID) {
+ dev_err(&client->dev, "Unrecognized bootloader ID: 0x%04X\n",
+ get_unaligned_be16(mbuf));
+ return -EINVAL;
+ }
+
+ if (bl_cmd == IQS5XX_BL_CMD_CRC && *mbuf != IQS5XX_BL_CRC_PASS) {
+ dev_err(&client->dev, "Bootloader CRC failed\n");
+ return -EIO;
+ }
+
+ return 0;
+
+msg_fail:
+ if (ret >= 0)
+ ret = -EIO;
+
+ if (bl_cmd != IQS5XX_BL_CMD_VER)
+ dev_err(&client->dev,
+ "Unsuccessful bootloader command 0x%02X: %d\n",
+ bl_cmd, ret);
+
+ return ret;
+}
+
+static int iqs5xx_bl_open(struct i2c_client *client)
+{
+ int error, i, j;
+
+ /*
+ * The device opens a bootloader polling window for 2 ms following the
+ * release of reset. If the host cannot establish communication during
+ * this time frame, it must cycle reset again.
+ */
+ for (i = 0; i < IQS5XX_BL_ATTEMPTS; i++) {
+ iqs5xx_reset(client);
+ usleep_range(350, 400);
+
+ for (j = 0; j < IQS5XX_NUM_RETRIES; j++) {
+ error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_VER, 0);
+ if (!error)
+ usleep_range(10000, 10100);
+ else if (error != -EINVAL)
+ continue;
+
+ return error;
+ }
+ }
+
+ dev_err(&client->dev, "Failed to open bootloader: %d\n", error);
+
+ return error;
+}
+
+static int iqs5xx_bl_write(struct i2c_client *client,
+ u16 bl_addr, u8 *pmap_data, u16 pmap_len)
+{
+ struct i2c_msg msg;
+ int ret, i;
+ u8 mbuf[sizeof(bl_addr) + IQS5XX_BL_BLK_LEN_MAX];
+
+ if (pmap_len % IQS5XX_BL_BLK_LEN_MAX)
+ return -EINVAL;
+
+ msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK;
+ msg.flags = 0;
+ msg.len = sizeof(mbuf);
+ msg.buf = mbuf;
+
+ for (i = 0; i < pmap_len; i += IQS5XX_BL_BLK_LEN_MAX) {
+ put_unaligned_be16(bl_addr + i, mbuf);
+ memcpy(mbuf + sizeof(bl_addr), pmap_data + i,
+ sizeof(mbuf) - sizeof(bl_addr));
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1)
+ goto msg_fail;
+
+ usleep_range(10000, 10100);
+ }
+
+ return 0;
+
+msg_fail:
+ if (ret >= 0)
+ ret = -EIO;
+
+ dev_err(&client->dev, "Failed to write block at address 0x%04X: %d\n",
+ bl_addr + i, ret);
+
+ return ret;
+}
+
+static int iqs5xx_bl_verify(struct i2c_client *client,
+ u16 bl_addr, u8 *pmap_data, u16 pmap_len)
+{
+ struct i2c_msg msg;
+ int ret, i;
+ u8 bl_data[IQS5XX_BL_BLK_LEN_MAX];
+
+ if (pmap_len % IQS5XX_BL_BLK_LEN_MAX)
+ return -EINVAL;
+
+ msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK;
+ msg.flags = I2C_M_RD;
+ msg.len = sizeof(bl_data);
+ msg.buf = bl_data;
+
+ for (i = 0; i < pmap_len; i += IQS5XX_BL_BLK_LEN_MAX) {
+ ret = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_READ, bl_addr + i);
+ if (ret)
+ return ret;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1)
+ goto msg_fail;
+
+ if (memcmp(bl_data, pmap_data + i, sizeof(bl_data))) {
+ dev_err(&client->dev,
+ "Failed to verify block at address 0x%04X\n",
+ bl_addr + i);
+ return -EIO;
+ }
+ }
+
+ return 0;
+
+msg_fail:
+ if (ret >= 0)
+ ret = -EIO;
+
+ dev_err(&client->dev, "Failed to read block at address 0x%04X: %d\n",
+ bl_addr + i, ret);
+
+ return ret;
+}
+
+static int iqs5xx_set_state(struct i2c_client *client, u8 state)
+{
+ struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client);
+ int error1, error2;
+
+ if (!iqs5xx->dev_id_info.bl_status)
+ return 0;
+
+ mutex_lock(&iqs5xx->lock);
+
+ /*
+ * Addressing the device outside of a communication window prompts it
+ * to assert the RDY output, so disable the interrupt line to prevent
+ * the handler from servicing a false interrupt.
+ */
+ disable_irq(client->irq);
+
+ error1 = iqs5xx_write_byte(client, IQS5XX_SYS_CTRL1, state);
+ error2 = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0);
+
+ usleep_range(50, 100);
+ enable_irq(client->irq);
+
+ mutex_unlock(&iqs5xx->lock);
+
+ if (error1)
+ return error1;
+
+ return error2;
+}
+
+static int iqs5xx_open(struct input_dev *input)
+{
+ struct iqs5xx_private *iqs5xx = input_get_drvdata(input);
+
+ return iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME);
+}
+
+static void iqs5xx_close(struct input_dev *input)
+{
+ struct iqs5xx_private *iqs5xx = input_get_drvdata(input);
+
+ iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND);
+}
+
+static int iqs5xx_axis_init(struct i2c_client *client)
+{
+ struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client);
+ struct touchscreen_properties *prop = &iqs5xx->prop;
+ struct input_dev *input = iqs5xx->input;
+ u16 max_x, max_y;
+ int error;
+
+ if (!input) {
+ input = devm_input_allocate_device(&client->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->open = iqs5xx_open;
+ input->close = iqs5xx_close;
+
+ input_set_drvdata(input, iqs5xx);
+ iqs5xx->input = input;
+ }
+
+ error = iqs5xx_read_word(client, IQS5XX_X_RES, &max_x);
+ if (error)
+ return error;
+
+ error = iqs5xx_read_word(client, IQS5XX_Y_RES, &max_y);
+ if (error)
+ return error;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, U16_MAX, 0, 0);
+
+ touchscreen_parse_properties(input, true, prop);
+
+ /*
+ * The device reserves 0xFFFF for coordinates that correspond to slots
+ * which are not in a state of touch.
+ */
+ if (prop->max_x >= U16_MAX || prop->max_y >= U16_MAX) {
+ dev_err(&client->dev, "Invalid touchscreen size: %u*%u\n",
+ prop->max_x, prop->max_y);
+ return -EINVAL;
+ }
+
+ if (prop->max_x != max_x) {
+ error = iqs5xx_write_word(client, IQS5XX_X_RES, prop->max_x);
+ if (error)
+ return error;
+ }
+
+ if (prop->max_y != max_y) {
+ error = iqs5xx_write_word(client, IQS5XX_Y_RES, prop->max_y);
+ if (error)
+ return error;
+ }
+
+ error = input_mt_init_slots(input, IQS5XX_NUM_CONTACTS,
+ INPUT_MT_DIRECT);
+ if (error)
+ dev_err(&client->dev, "Failed to initialize slots: %d\n",
+ error);
+
+ return error;
+}
+
+static int iqs5xx_dev_init(struct i2c_client *client)
+{
+ struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client);
+ struct iqs5xx_dev_id_info *dev_id_info;
+ int error;
+ u8 buf[sizeof(*dev_id_info) + 1];
+
+ error = iqs5xx_read_burst(client, IQS5XX_PROD_NUM,
+ &buf[1], sizeof(*dev_id_info));
+ if (error)
+ return iqs5xx_bl_open(client);
+
+ /*
+ * A000 and B000 devices use 8-bit and 16-bit addressing, respectively.
+ * Querying an A000 device's version information with 16-bit addressing
+ * gives the appearance that the data is shifted by one byte; a nonzero
+ * leading array element suggests this could be the case (in which case
+ * the missing zero is prepended).
+ */
+ buf[0] = 0;
+ dev_id_info = (struct iqs5xx_dev_id_info *)&buf[buf[1] ? 0 : 1];
+
+ switch (be16_to_cpu(dev_id_info->prod_num)) {
+ case IQS5XX_PROD_NUM_IQS550:
+ case IQS5XX_PROD_NUM_IQS572:
+ case IQS5XX_PROD_NUM_IQS525:
+ break;
+ default:
+ dev_err(&client->dev, "Unrecognized product number: %u\n",
+ be16_to_cpu(dev_id_info->prod_num));
+ return -EINVAL;
+ }
+
+ /*
+ * With the product number recognized yet shifted by one byte, open the
+ * bootloader and wait for user space to convert the A000 device into a
+ * B000 device via new firmware.
+ */
+ if (buf[1]) {
+ dev_err(&client->dev, "Opening bootloader for A000 device\n");
+ return iqs5xx_bl_open(client);
+ }
+
+ error = iqs5xx_read_burst(client, IQS5XX_EXP_FILE,
+ iqs5xx->exp_file, sizeof(iqs5xx->exp_file));
+ if (error)
+ return error;
+
+ error = iqs5xx_axis_init(client);
+ if (error)
+ return error;
+
+ error = iqs5xx_write_byte(client, IQS5XX_SYS_CTRL0, IQS5XX_ACK_RESET);
+ if (error)
+ return error;
+
+ error = iqs5xx_write_byte(client, IQS5XX_SYS_CFG0,
+ IQS5XX_SETUP_COMPLETE | IQS5XX_WDT |
+ IQS5XX_ALP_REATI | IQS5XX_REATI);
+ if (error)
+ return error;
+
+ error = iqs5xx_write_byte(client, IQS5XX_SYS_CFG1,
+ IQS5XX_TP_EVENT | IQS5XX_EVENT_MODE);
+ if (error)
+ return error;
+
+ error = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0);
+ if (error)
+ return error;
+
+ iqs5xx->dev_id_info = *dev_id_info;
+
+ /*
+ * The following delay allows ATI to complete before the open and close
+ * callbacks are free to elicit I2C communication. Any attempts to read
+ * from or write to the device during this time may face extended clock
+ * stretching and prompt the I2C controller to report an error.
+ */
+ msleep(250);
+
+ return 0;
+}
+
+static irqreturn_t iqs5xx_irq(int irq, void *data)
+{
+ struct iqs5xx_private *iqs5xx = data;
+ struct iqs5xx_status status;
+ struct i2c_client *client = iqs5xx->client;
+ struct input_dev *input = iqs5xx->input;
+ int error, i;
+
+ /*
+ * This check is purely a precaution, as the device does not assert the
+ * RDY output during bootloader mode. If the device operates outside of
+ * bootloader mode, the input device is guaranteed to be allocated.
+ */
+ if (!iqs5xx->dev_id_info.bl_status)
+ return IRQ_NONE;
+
+ error = iqs5xx_read_burst(client, IQS5XX_SYS_INFO0,
+ &status, sizeof(status));
+ if (error)
+ return IRQ_NONE;
+
+ if (status.sys_info[0] & IQS5XX_SHOW_RESET) {
+ dev_err(&client->dev, "Unexpected device reset\n");
+
+ error = iqs5xx_dev_init(client);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to re-initialize device: %d\n", error);
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(status.touch_data); i++) {
+ struct iqs5xx_touch_data *touch_data = &status.touch_data[i];
+ u16 pressure = be16_to_cpu(touch_data->strength);
+
+ input_mt_slot(input, i);
+ if (input_mt_report_slot_state(input, MT_TOOL_FINGER,
+ pressure != 0)) {
+ touchscreen_report_pos(input, &iqs5xx->prop,
+ be16_to_cpu(touch_data->abs_x),
+ be16_to_cpu(touch_data->abs_y),
+ true);
+ input_report_abs(input, ABS_MT_PRESSURE, pressure);
+ }
+ }
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+
+ error = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0);
+ if (error)
+ return IRQ_NONE;
+
+ /*
+ * Once the communication window is closed, a small delay is added to
+ * ensure the device's RDY output has been deasserted by the time the
+ * interrupt handler returns.
+ */
+ usleep_range(50, 100);
+
+ return IRQ_HANDLED;
+}
+
+static int iqs5xx_fw_file_parse(struct i2c_client *client,
+ const char *fw_file, u8 *pmap)
+{
+ const struct firmware *fw;
+ struct iqs5xx_ihex_rec *rec;
+ size_t pos = 0;
+ int error, i;
+ u16 rec_num = 1;
+ u16 rec_addr;
+ u8 rec_len, rec_type, rec_chksm, chksm;
+ u8 rec_hdr[IQS5XX_REC_HDR_LEN];
+ u8 rec_data[IQS5XX_REC_LEN_MAX];
+
+ /*
+ * Firmware exported from the vendor's configuration tool deviates from
+ * standard ihex as follows: (1) the checksum for records corresponding
+ * to user-exported settings is not recalculated, and (2) an address of
+ * 0xFFFF is used for the EOF record.
+ *
+ * Because the ihex2fw tool tolerates neither (1) nor (2), the slightly
+ * nonstandard ihex firmware is parsed directly by the driver.
+ */
+ error = request_firmware(&fw, fw_file, &client->dev);
+ if (error) {
+ dev_err(&client->dev, "Failed to request firmware %s: %d\n",
+ fw_file, error);
+ return error;
+ }
+
+ do {
+ if (pos + sizeof(*rec) > fw->size) {
+ dev_err(&client->dev, "Insufficient firmware size\n");
+ error = -EINVAL;
+ break;
+ }
+ rec = (struct iqs5xx_ihex_rec *)(fw->data + pos);
+ pos += sizeof(*rec);
+
+ if (rec->start != ':') {
+ dev_err(&client->dev, "Invalid start at record %u\n",
+ rec_num);
+ error = -EINVAL;
+ break;
+ }
+
+ error = hex2bin(rec_hdr, rec->len, sizeof(rec_hdr));
+ if (error) {
+ dev_err(&client->dev, "Invalid header at record %u\n",
+ rec_num);
+ break;
+ }
+
+ rec_len = *rec_hdr;
+ rec_addr = get_unaligned_be16(rec_hdr + sizeof(rec_len));
+ rec_type = *(rec_hdr + sizeof(rec_len) + sizeof(rec_addr));
+
+ if (pos + rec_len * 2 > fw->size) {
+ dev_err(&client->dev, "Insufficient firmware size\n");
+ error = -EINVAL;
+ break;
+ }
+ pos += (rec_len * 2);
+
+ error = hex2bin(rec_data, rec->data, rec_len);
+ if (error) {
+ dev_err(&client->dev, "Invalid data at record %u\n",
+ rec_num);
+ break;
+ }
+
+ error = hex2bin(&rec_chksm,
+ rec->data + rec_len * 2, sizeof(rec_chksm));
+ if (error) {
+ dev_err(&client->dev, "Invalid checksum at record %u\n",
+ rec_num);
+ break;
+ }
+
+ chksm = 0;
+ for (i = 0; i < sizeof(rec_hdr); i++)
+ chksm += rec_hdr[i];
+ for (i = 0; i < rec_len; i++)
+ chksm += rec_data[i];
+ chksm = ~chksm + 1;
+
+ if (chksm != rec_chksm && rec_addr < IQS5XX_CSTM) {
+ dev_err(&client->dev,
+ "Incorrect checksum at record %u\n",
+ rec_num);
+ error = -EINVAL;
+ break;
+ }
+
+ switch (rec_type) {
+ case IQS5XX_REC_TYPE_DATA:
+ if (rec_addr < IQS5XX_CHKSM ||
+ rec_addr > IQS5XX_PMAP_END) {
+ dev_err(&client->dev,
+ "Invalid address at record %u\n",
+ rec_num);
+ error = -EINVAL;
+ } else {
+ memcpy(pmap + rec_addr - IQS5XX_CHKSM,
+ rec_data, rec_len);
+ }
+ break;
+ case IQS5XX_REC_TYPE_EOF:
+ break;
+ default:
+ dev_err(&client->dev, "Invalid type at record %u\n",
+ rec_num);
+ error = -EINVAL;
+ }
+
+ if (error)
+ break;
+
+ rec_num++;
+ while (pos < fw->size) {
+ if (*(fw->data + pos) == ':')
+ break;
+ pos++;
+ }
+ } while (rec_type != IQS5XX_REC_TYPE_EOF);
+
+ release_firmware(fw);
+
+ return error;
+}
+
+static int iqs5xx_fw_file_write(struct i2c_client *client, const char *fw_file)
+{
+ struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client);
+ int error, error_init = 0;
+ u8 *pmap;
+
+ pmap = kzalloc(IQS5XX_PMAP_LEN, GFP_KERNEL);
+ if (!pmap)
+ return -ENOMEM;
+
+ error = iqs5xx_fw_file_parse(client, fw_file, pmap);
+ if (error)
+ goto err_kfree;
+
+ mutex_lock(&iqs5xx->lock);
+
+ /*
+ * Disable the interrupt line in case the first attempt(s) to enter the
+ * bootloader don't happen quickly enough, in which case the device may
+ * assert the RDY output until the next attempt.
+ */
+ disable_irq(client->irq);
+
+ iqs5xx->dev_id_info.bl_status = 0;
+
+ error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_VER, 0);
+ if (error) {
+ error = iqs5xx_bl_open(client);
+ if (error)
+ goto err_reset;
+ }
+
+ error = iqs5xx_bl_write(client, IQS5XX_CHKSM, pmap, IQS5XX_PMAP_LEN);
+ if (error)
+ goto err_reset;
+
+ error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_CRC, 0);
+ if (error)
+ goto err_reset;
+
+ error = iqs5xx_bl_verify(client, IQS5XX_CSTM,
+ pmap + IQS5XX_CHKSM_LEN + IQS5XX_APP_LEN,
+ IQS5XX_CSTM_LEN);
+
+err_reset:
+ iqs5xx_reset(client);
+ usleep_range(15000, 15100);
+
+ error_init = iqs5xx_dev_init(client);
+ if (!iqs5xx->dev_id_info.bl_status)
+ error_init = error_init ? : -EINVAL;
+
+ enable_irq(client->irq);
+
+ mutex_unlock(&iqs5xx->lock);
+
+err_kfree:
+ kfree(pmap);
+
+ return error ? : error_init;
+}
+
+static ssize_t fw_file_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs5xx->client;
+ size_t len = count;
+ bool input_reg = !iqs5xx->input;
+ char fw_file[IQS5XX_FW_FILE_LEN + 1];
+ int error;
+
+ if (!len)
+ return -EINVAL;
+
+ if (buf[len - 1] == '\n')
+ len--;
+
+ if (len > IQS5XX_FW_FILE_LEN)
+ return -ENAMETOOLONG;
+
+ memcpy(fw_file, buf, len);
+ fw_file[len] = '\0';
+
+ error = iqs5xx_fw_file_write(client, fw_file);
+ if (error)
+ return error;
+
+ /*
+ * If the input device was not allocated already, it is guaranteed to
+ * be allocated by this point and can finally be registered.
+ */
+ if (input_reg) {
+ error = input_register_device(iqs5xx->input);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register device: %d\n",
+ error);
+ return error;
+ }
+ }
+
+ return count;
+}
+
+static ssize_t fw_info_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev);
+
+ if (!iqs5xx->dev_id_info.bl_status)
+ return -ENODATA;
+
+ return scnprintf(buf, PAGE_SIZE, "%u.%u.%u.%u:%u.%u\n",
+ be16_to_cpu(iqs5xx->dev_id_info.prod_num),
+ be16_to_cpu(iqs5xx->dev_id_info.proj_num),
+ iqs5xx->dev_id_info.major_ver,
+ iqs5xx->dev_id_info.minor_ver,
+ iqs5xx->exp_file[0], iqs5xx->exp_file[1]);
+}
+
+static DEVICE_ATTR_WO(fw_file);
+static DEVICE_ATTR_RO(fw_info);
+
+static struct attribute *iqs5xx_attrs[] = {
+ &dev_attr_fw_file.attr,
+ &dev_attr_fw_info.attr,
+ NULL,
+};
+
+static umode_t iqs5xx_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int i)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_fw_file.attr &&
+ (iqs5xx->dev_id_info.bl_status == IQS5XX_BL_STATUS_NONE ||
+ !iqs5xx->reset_gpio))
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group iqs5xx_attr_group = {
+ .is_visible = iqs5xx_attr_is_visible,
+ .attrs = iqs5xx_attrs,
+};
+
+static int __maybe_unused iqs5xx_suspend(struct device *dev)
+{
+ struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev);
+ struct input_dev *input = iqs5xx->input;
+ int error = 0;
+
+ if (!input || device_may_wakeup(dev))
+ return error;
+
+ mutex_lock(&input->mutex);
+
+ if (input_device_enabled(input))
+ error = iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND);
+
+ mutex_unlock(&input->mutex);
+
+ return error;
+}
+
+static int __maybe_unused iqs5xx_resume(struct device *dev)
+{
+ struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev);
+ struct input_dev *input = iqs5xx->input;
+ int error = 0;
+
+ if (!input || device_may_wakeup(dev))
+ return error;
+
+ mutex_lock(&input->mutex);
+
+ if (input_device_enabled(input))
+ error = iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME);
+
+ mutex_unlock(&input->mutex);
+
+ return error;
+}
+
+static SIMPLE_DEV_PM_OPS(iqs5xx_pm, iqs5xx_suspend, iqs5xx_resume);
+
+static int iqs5xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct iqs5xx_private *iqs5xx;
+ int error;
+
+ iqs5xx = devm_kzalloc(&client->dev, sizeof(*iqs5xx), GFP_KERNEL);
+ if (!iqs5xx)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, iqs5xx);
+ iqs5xx->client = client;
+
+ iqs5xx->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(iqs5xx->reset_gpio)) {
+ error = PTR_ERR(iqs5xx->reset_gpio);
+ dev_err(&client->dev, "Failed to request GPIO: %d\n", error);
+ return error;
+ }
+
+ mutex_init(&iqs5xx->lock);
+
+ error = iqs5xx_dev_init(client);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, iqs5xx_irq, IRQF_ONESHOT,
+ client->name, iqs5xx);
+ if (error) {
+ dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &iqs5xx_attr_group);
+ if (error) {
+ dev_err(&client->dev, "Failed to add attributes: %d\n", error);
+ return error;
+ }
+
+ if (iqs5xx->input) {
+ error = input_register_device(iqs5xx->input);
+ if (error)
+ dev_err(&client->dev,
+ "Failed to register device: %d\n",
+ error);
+ }
+
+ return error;
+}
+
+static const struct i2c_device_id iqs5xx_id[] = {
+ { "iqs550", 0 },
+ { "iqs572", 1 },
+ { "iqs525", 2 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, iqs5xx_id);
+
+static const struct of_device_id iqs5xx_of_match[] = {
+ { .compatible = "azoteq,iqs550" },
+ { .compatible = "azoteq,iqs572" },
+ { .compatible = "azoteq,iqs525" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, iqs5xx_of_match);
+
+static struct i2c_driver iqs5xx_i2c_driver = {
+ .driver = {
+ .name = "iqs5xx",
+ .of_match_table = iqs5xx_of_match,
+ .pm = &iqs5xx_pm,
+ },
+ .id_table = iqs5xx_id,
+ .probe = iqs5xx_probe,
+};
+module_i2c_driver(iqs5xx_i2c_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS550/572/525 Trackpad/Touchscreen Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c
new file mode 100644
index 000000000..974521102
--- /dev/null
+++ b/drivers/input/touchscreen/jornada720_ts.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/input/touchscreen/jornada720_ts.c
+ *
+ * Copyright (C) 2007 Kristoffer Ericson <Kristoffer.Ericson@gmail.com>
+ *
+ * Copyright (C) 2006 Filip Zyzniewski <filip.zyzniewski@tefnet.pl>
+ * based on HP Jornada 56x touchscreen driver by Alex Lange <chicken@handhelds.org>
+ *
+ * HP Jornada 710/720/729 Touchscreen Driver
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include <mach/jornada720.h>
+
+MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>");
+MODULE_DESCRIPTION("HP Jornada 710/720/728 touchscreen driver");
+MODULE_LICENSE("GPL v2");
+
+struct jornada_ts {
+ struct input_dev *dev;
+ struct gpio_desc *gpio;
+ int x_data[4]; /* X sample values */
+ int y_data[4]; /* Y sample values */
+};
+
+static void jornada720_ts_collect_data(struct jornada_ts *jornada_ts)
+{
+ /* 3 low word X samples */
+ jornada_ts->x_data[0] = jornada_ssp_byte(TXDUMMY);
+ jornada_ts->x_data[1] = jornada_ssp_byte(TXDUMMY);
+ jornada_ts->x_data[2] = jornada_ssp_byte(TXDUMMY);
+
+ /* 3 low word Y samples */
+ jornada_ts->y_data[0] = jornada_ssp_byte(TXDUMMY);
+ jornada_ts->y_data[1] = jornada_ssp_byte(TXDUMMY);
+ jornada_ts->y_data[2] = jornada_ssp_byte(TXDUMMY);
+
+ /* combined x samples bits */
+ jornada_ts->x_data[3] = jornada_ssp_byte(TXDUMMY);
+
+ /* combined y samples bits */
+ jornada_ts->y_data[3] = jornada_ssp_byte(TXDUMMY);
+}
+
+static int jornada720_ts_average(int coords[4])
+{
+ int coord, high_bits = coords[3];
+
+ coord = coords[0] | ((high_bits & 0x03) << 8);
+ coord += coords[1] | ((high_bits & 0x0c) << 6);
+ coord += coords[2] | ((high_bits & 0x30) << 4);
+
+ return coord / 3;
+}
+
+static irqreturn_t jornada720_ts_interrupt(int irq, void *dev_id)
+{
+ struct platform_device *pdev = dev_id;
+ struct jornada_ts *jornada_ts = platform_get_drvdata(pdev);
+ struct input_dev *input = jornada_ts->dev;
+ int x, y;
+
+ /* If gpio is high then report pen up */
+ if (gpiod_get_value(jornada_ts->gpio)) {
+ input_report_key(input, BTN_TOUCH, 0);
+ input_sync(input);
+ } else {
+ jornada_ssp_start();
+
+ /* proper reply to request is always TXDUMMY */
+ if (jornada_ssp_inout(GETTOUCHSAMPLES) == TXDUMMY) {
+ jornada720_ts_collect_data(jornada_ts);
+
+ x = jornada720_ts_average(jornada_ts->x_data);
+ y = jornada720_ts_average(jornada_ts->y_data);
+
+ input_report_key(input, BTN_TOUCH, 1);
+ input_report_abs(input, ABS_X, x);
+ input_report_abs(input, ABS_Y, y);
+ input_sync(input);
+ }
+
+ jornada_ssp_end();
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int jornada720_ts_probe(struct platform_device *pdev)
+{
+ struct jornada_ts *jornada_ts;
+ struct input_dev *input_dev;
+ int error, irq;
+
+ jornada_ts = devm_kzalloc(&pdev->dev, sizeof(*jornada_ts), GFP_KERNEL);
+ if (!jornada_ts)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, jornada_ts);
+
+ jornada_ts->gpio = devm_gpiod_get(&pdev->dev, "penup", GPIOD_IN);
+ if (IS_ERR(jornada_ts->gpio))
+ return PTR_ERR(jornada_ts->gpio);
+
+ irq = gpiod_to_irq(jornada_ts->gpio);
+ if (irq <= 0)
+ return irq < 0 ? irq : -EINVAL;
+
+ jornada_ts->dev = input_dev;
+
+ input_dev->name = "HP Jornada 7xx Touchscreen";
+ input_dev->phys = "jornadats/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->dev.parent = &pdev->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, 270, 3900, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 180, 3700, 0, 0);
+
+ error = devm_request_irq(&pdev->dev, irq, jornada720_ts_interrupt,
+ IRQF_TRIGGER_RISING,
+ "HP7XX Touchscreen driver", pdev);
+ if (error) {
+ dev_err(&pdev->dev, "HP7XX TS : Unable to acquire irq!\n");
+ return error;
+ }
+
+ error = input_register_device(jornada_ts->dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:jornada_ts");
+
+static struct platform_driver jornada720_ts_driver = {
+ .probe = jornada720_ts_probe,
+ .driver = {
+ .name = "jornada_ts",
+ },
+};
+module_platform_driver(jornada720_ts_driver);
diff --git a/drivers/input/touchscreen/lpc32xx_ts.c b/drivers/input/touchscreen/lpc32xx_ts.c
new file mode 100644
index 000000000..15b5cb763
--- /dev/null
+++ b/drivers/input/touchscreen/lpc32xx_ts.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LPC32xx built-in touchscreen driver
+ *
+ * Copyright (C) 2010 NXP Semiconductors
+ */
+
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+/*
+ * Touchscreen controller register offsets
+ */
+#define LPC32XX_TSC_STAT 0x00
+#define LPC32XX_TSC_SEL 0x04
+#define LPC32XX_TSC_CON 0x08
+#define LPC32XX_TSC_FIFO 0x0C
+#define LPC32XX_TSC_DTR 0x10
+#define LPC32XX_TSC_RTR 0x14
+#define LPC32XX_TSC_UTR 0x18
+#define LPC32XX_TSC_TTR 0x1C
+#define LPC32XX_TSC_DXP 0x20
+#define LPC32XX_TSC_MIN_X 0x24
+#define LPC32XX_TSC_MAX_X 0x28
+#define LPC32XX_TSC_MIN_Y 0x2C
+#define LPC32XX_TSC_MAX_Y 0x30
+#define LPC32XX_TSC_AUX_UTR 0x34
+#define LPC32XX_TSC_AUX_MIN 0x38
+#define LPC32XX_TSC_AUX_MAX 0x3C
+
+#define LPC32XX_TSC_STAT_FIFO_OVRRN BIT(8)
+#define LPC32XX_TSC_STAT_FIFO_EMPTY BIT(7)
+
+#define LPC32XX_TSC_SEL_DEFVAL 0x0284
+
+#define LPC32XX_TSC_ADCCON_IRQ_TO_FIFO_4 (0x1 << 11)
+#define LPC32XX_TSC_ADCCON_X_SAMPLE_SIZE(s) ((10 - (s)) << 7)
+#define LPC32XX_TSC_ADCCON_Y_SAMPLE_SIZE(s) ((10 - (s)) << 4)
+#define LPC32XX_TSC_ADCCON_POWER_UP BIT(2)
+#define LPC32XX_TSC_ADCCON_AUTO_EN BIT(0)
+
+#define LPC32XX_TSC_FIFO_TS_P_LEVEL BIT(31)
+#define LPC32XX_TSC_FIFO_NORMALIZE_X_VAL(x) (((x) & 0x03FF0000) >> 16)
+#define LPC32XX_TSC_FIFO_NORMALIZE_Y_VAL(y) ((y) & 0x000003FF)
+
+#define LPC32XX_TSC_ADCDAT_VALUE_MASK 0x000003FF
+
+#define LPC32XX_TSC_MIN_XY_VAL 0x0
+#define LPC32XX_TSC_MAX_XY_VAL 0x3FF
+
+#define MOD_NAME "ts-lpc32xx"
+
+#define tsc_readl(dev, reg) \
+ __raw_readl((dev)->tsc_base + (reg))
+#define tsc_writel(dev, reg, val) \
+ __raw_writel((val), (dev)->tsc_base + (reg))
+
+struct lpc32xx_tsc {
+ struct input_dev *dev;
+ void __iomem *tsc_base;
+ int irq;
+ struct clk *clk;
+};
+
+static void lpc32xx_fifo_clear(struct lpc32xx_tsc *tsc)
+{
+ while (!(tsc_readl(tsc, LPC32XX_TSC_STAT) &
+ LPC32XX_TSC_STAT_FIFO_EMPTY))
+ tsc_readl(tsc, LPC32XX_TSC_FIFO);
+}
+
+static irqreturn_t lpc32xx_ts_interrupt(int irq, void *dev_id)
+{
+ u32 tmp, rv[4], xs[4], ys[4];
+ int idx;
+ struct lpc32xx_tsc *tsc = dev_id;
+ struct input_dev *input = tsc->dev;
+
+ tmp = tsc_readl(tsc, LPC32XX_TSC_STAT);
+
+ if (tmp & LPC32XX_TSC_STAT_FIFO_OVRRN) {
+ /* FIFO overflow - throw away samples */
+ lpc32xx_fifo_clear(tsc);
+ return IRQ_HANDLED;
+ }
+
+ /*
+ * Gather and normalize 4 samples. Pen-up events may have less
+ * than 4 samples, but its ok to pop 4 and let the last sample
+ * pen status check drop the samples.
+ */
+ idx = 0;
+ while (idx < 4 &&
+ !(tsc_readl(tsc, LPC32XX_TSC_STAT) &
+ LPC32XX_TSC_STAT_FIFO_EMPTY)) {
+ tmp = tsc_readl(tsc, LPC32XX_TSC_FIFO);
+ xs[idx] = LPC32XX_TSC_ADCDAT_VALUE_MASK -
+ LPC32XX_TSC_FIFO_NORMALIZE_X_VAL(tmp);
+ ys[idx] = LPC32XX_TSC_ADCDAT_VALUE_MASK -
+ LPC32XX_TSC_FIFO_NORMALIZE_Y_VAL(tmp);
+ rv[idx] = tmp;
+ idx++;
+ }
+
+ /* Data is only valid if pen is still down in last sample */
+ if (!(rv[3] & LPC32XX_TSC_FIFO_TS_P_LEVEL) && idx == 4) {
+ /* Use average of 2nd and 3rd sample for position */
+ input_report_abs(input, ABS_X, (xs[1] + xs[2]) / 2);
+ input_report_abs(input, ABS_Y, (ys[1] + ys[2]) / 2);
+ input_report_key(input, BTN_TOUCH, 1);
+ } else {
+ input_report_key(input, BTN_TOUCH, 0);
+ }
+
+ input_sync(input);
+
+ return IRQ_HANDLED;
+}
+
+static void lpc32xx_stop_tsc(struct lpc32xx_tsc *tsc)
+{
+ /* Disable auto mode */
+ tsc_writel(tsc, LPC32XX_TSC_CON,
+ tsc_readl(tsc, LPC32XX_TSC_CON) &
+ ~LPC32XX_TSC_ADCCON_AUTO_EN);
+
+ clk_disable_unprepare(tsc->clk);
+}
+
+static int lpc32xx_setup_tsc(struct lpc32xx_tsc *tsc)
+{
+ u32 tmp;
+ int err;
+
+ err = clk_prepare_enable(tsc->clk);
+ if (err)
+ return err;
+
+ tmp = tsc_readl(tsc, LPC32XX_TSC_CON) & ~LPC32XX_TSC_ADCCON_POWER_UP;
+
+ /* Set the TSC FIFO depth to 4 samples @ 10-bits per sample (max) */
+ tmp = LPC32XX_TSC_ADCCON_IRQ_TO_FIFO_4 |
+ LPC32XX_TSC_ADCCON_X_SAMPLE_SIZE(10) |
+ LPC32XX_TSC_ADCCON_Y_SAMPLE_SIZE(10);
+ tsc_writel(tsc, LPC32XX_TSC_CON, tmp);
+
+ /* These values are all preset */
+ tsc_writel(tsc, LPC32XX_TSC_SEL, LPC32XX_TSC_SEL_DEFVAL);
+ tsc_writel(tsc, LPC32XX_TSC_MIN_X, LPC32XX_TSC_MIN_XY_VAL);
+ tsc_writel(tsc, LPC32XX_TSC_MAX_X, LPC32XX_TSC_MAX_XY_VAL);
+ tsc_writel(tsc, LPC32XX_TSC_MIN_Y, LPC32XX_TSC_MIN_XY_VAL);
+ tsc_writel(tsc, LPC32XX_TSC_MAX_Y, LPC32XX_TSC_MAX_XY_VAL);
+
+ /* Aux support is not used */
+ tsc_writel(tsc, LPC32XX_TSC_AUX_UTR, 0);
+ tsc_writel(tsc, LPC32XX_TSC_AUX_MIN, 0);
+ tsc_writel(tsc, LPC32XX_TSC_AUX_MAX, 0);
+
+ /*
+ * Set sample rate to about 240Hz per X/Y pair. A single measurement
+ * consists of 4 pairs which gives about a 60Hz sample rate based on
+ * a stable 32768Hz clock source. Values are in clocks.
+ * Rate is (32768 / (RTR + XCONV + RTR + YCONV + DXP + TTR + UTR) / 4
+ */
+ tsc_writel(tsc, LPC32XX_TSC_RTR, 0x2);
+ tsc_writel(tsc, LPC32XX_TSC_DTR, 0x2);
+ tsc_writel(tsc, LPC32XX_TSC_TTR, 0x10);
+ tsc_writel(tsc, LPC32XX_TSC_DXP, 0x4);
+ tsc_writel(tsc, LPC32XX_TSC_UTR, 88);
+
+ lpc32xx_fifo_clear(tsc);
+
+ /* Enable automatic ts event capture */
+ tsc_writel(tsc, LPC32XX_TSC_CON, tmp | LPC32XX_TSC_ADCCON_AUTO_EN);
+
+ return 0;
+}
+
+static int lpc32xx_ts_open(struct input_dev *dev)
+{
+ struct lpc32xx_tsc *tsc = input_get_drvdata(dev);
+
+ return lpc32xx_setup_tsc(tsc);
+}
+
+static void lpc32xx_ts_close(struct input_dev *dev)
+{
+ struct lpc32xx_tsc *tsc = input_get_drvdata(dev);
+
+ lpc32xx_stop_tsc(tsc);
+}
+
+static int lpc32xx_ts_probe(struct platform_device *pdev)
+{
+ struct lpc32xx_tsc *tsc;
+ struct input_dev *input;
+ struct resource *res;
+ resource_size_t size;
+ int irq;
+ int error;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Can't get memory resource\n");
+ return -ENOENT;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
+ input = input_allocate_device();
+ if (!tsc || !input) {
+ dev_err(&pdev->dev, "failed allocating memory\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ tsc->dev = input;
+ tsc->irq = irq;
+
+ size = resource_size(res);
+
+ if (!request_mem_region(res->start, size, pdev->name)) {
+ dev_err(&pdev->dev, "TSC registers are not free\n");
+ error = -EBUSY;
+ goto err_free_mem;
+ }
+
+ tsc->tsc_base = ioremap(res->start, size);
+ if (!tsc->tsc_base) {
+ dev_err(&pdev->dev, "Can't map memory\n");
+ error = -ENOMEM;
+ goto err_release_mem;
+ }
+
+ tsc->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(tsc->clk)) {
+ dev_err(&pdev->dev, "failed getting clock\n");
+ error = PTR_ERR(tsc->clk);
+ goto err_unmap;
+ }
+
+ input->name = MOD_NAME;
+ input->phys = "lpc32xx/input0";
+ input->id.bustype = BUS_HOST;
+ input->id.vendor = 0x0001;
+ input->id.product = 0x0002;
+ input->id.version = 0x0100;
+ input->dev.parent = &pdev->dev;
+ input->open = lpc32xx_ts_open;
+ input->close = lpc32xx_ts_close;
+
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(input, ABS_X, LPC32XX_TSC_MIN_XY_VAL,
+ LPC32XX_TSC_MAX_XY_VAL, 0, 0);
+ input_set_abs_params(input, ABS_Y, LPC32XX_TSC_MIN_XY_VAL,
+ LPC32XX_TSC_MAX_XY_VAL, 0, 0);
+
+ input_set_drvdata(input, tsc);
+
+ error = request_irq(tsc->irq, lpc32xx_ts_interrupt,
+ 0, pdev->name, tsc);
+ if (error) {
+ dev_err(&pdev->dev, "failed requesting interrupt\n");
+ goto err_put_clock;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&pdev->dev, "failed registering input device\n");
+ goto err_free_irq;
+ }
+
+ platform_set_drvdata(pdev, tsc);
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+
+err_free_irq:
+ free_irq(tsc->irq, tsc);
+err_put_clock:
+ clk_put(tsc->clk);
+err_unmap:
+ iounmap(tsc->tsc_base);
+err_release_mem:
+ release_mem_region(res->start, size);
+err_free_mem:
+ input_free_device(input);
+ kfree(tsc);
+
+ return error;
+}
+
+static int lpc32xx_ts_remove(struct platform_device *pdev)
+{
+ struct lpc32xx_tsc *tsc = platform_get_drvdata(pdev);
+ struct resource *res;
+
+ free_irq(tsc->irq, tsc);
+
+ input_unregister_device(tsc->dev);
+
+ clk_put(tsc->clk);
+
+ iounmap(tsc->tsc_base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+
+ kfree(tsc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int lpc32xx_ts_suspend(struct device *dev)
+{
+ struct lpc32xx_tsc *tsc = dev_get_drvdata(dev);
+ struct input_dev *input = tsc->dev;
+
+ /*
+ * Suspend and resume can be called when the device hasn't been
+ * enabled. If there are no users that have the device open, then
+ * avoid calling the TSC stop and start functions as the TSC
+ * isn't yet clocked.
+ */
+ mutex_lock(&input->mutex);
+
+ if (input_device_enabled(input)) {
+ if (device_may_wakeup(dev))
+ enable_irq_wake(tsc->irq);
+ else
+ lpc32xx_stop_tsc(tsc);
+ }
+
+ mutex_unlock(&input->mutex);
+
+ return 0;
+}
+
+static int lpc32xx_ts_resume(struct device *dev)
+{
+ struct lpc32xx_tsc *tsc = dev_get_drvdata(dev);
+ struct input_dev *input = tsc->dev;
+
+ mutex_lock(&input->mutex);
+
+ if (input_device_enabled(input)) {
+ if (device_may_wakeup(dev))
+ disable_irq_wake(tsc->irq);
+ else
+ lpc32xx_setup_tsc(tsc);
+ }
+
+ mutex_unlock(&input->mutex);
+
+ return 0;
+}
+
+static const struct dev_pm_ops lpc32xx_ts_pm_ops = {
+ .suspend = lpc32xx_ts_suspend,
+ .resume = lpc32xx_ts_resume,
+};
+#define LPC32XX_TS_PM_OPS (&lpc32xx_ts_pm_ops)
+#else
+#define LPC32XX_TS_PM_OPS NULL
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id lpc32xx_tsc_of_match[] = {
+ { .compatible = "nxp,lpc3220-tsc", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, lpc32xx_tsc_of_match);
+#endif
+
+static struct platform_driver lpc32xx_ts_driver = {
+ .probe = lpc32xx_ts_probe,
+ .remove = lpc32xx_ts_remove,
+ .driver = {
+ .name = MOD_NAME,
+ .pm = LPC32XX_TS_PM_OPS,
+ .of_match_table = of_match_ptr(lpc32xx_tsc_of_match),
+ },
+};
+module_platform_driver(lpc32xx_ts_driver);
+
+MODULE_AUTHOR("Kevin Wells <kevin.wells@nxp.com");
+MODULE_DESCRIPTION("LPC32XX TSC Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lpc32xx_ts");
diff --git a/drivers/input/touchscreen/mainstone-wm97xx.c b/drivers/input/touchscreen/mainstone-wm97xx.c
new file mode 100644
index 000000000..c39f49720
--- /dev/null
+++ b/drivers/input/touchscreen/mainstone-wm97xx.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * mainstone-wm97xx.c -- Mainstone Continuous Touch screen driver for
+ * Wolfson WM97xx AC97 Codecs.
+ *
+ * Copyright 2004, 2007 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ *
+ * Notes:
+ * This is a wm97xx extended touch driver to capture touch
+ * data in a continuous manner on the Intel XScale architecture
+ *
+ * Features:
+ * - codecs supported:- WM9705, WM9712, WM9713
+ * - processors supported:- Intel XScale PXA25x, PXA26x, PXA27x
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/soc/pxa/cpu.h>
+#include <linux/wm97xx.h>
+
+#include <sound/pxa2xx-lib.h>
+
+#include <asm/mach-types.h>
+
+struct continuous {
+ u16 id; /* codec id */
+ u8 code; /* continuous code */
+ u8 reads; /* number of coord reads per read cycle */
+ u32 speed; /* number of coords per second */
+};
+
+#define WM_READS(sp) ((sp / HZ) + 1)
+
+static const struct continuous cinfo[] = {
+ { WM9705_ID2, 0, WM_READS(94), 94 },
+ { WM9705_ID2, 1, WM_READS(188), 188 },
+ { WM9705_ID2, 2, WM_READS(375), 375 },
+ { WM9705_ID2, 3, WM_READS(750), 750 },
+ { WM9712_ID2, 0, WM_READS(94), 94 },
+ { WM9712_ID2, 1, WM_READS(188), 188 },
+ { WM9712_ID2, 2, WM_READS(375), 375 },
+ { WM9712_ID2, 3, WM_READS(750), 750 },
+ { WM9713_ID2, 0, WM_READS(94), 94 },
+ { WM9713_ID2, 1, WM_READS(120), 120 },
+ { WM9713_ID2, 2, WM_READS(154), 154 },
+ { WM9713_ID2, 3, WM_READS(188), 188 },
+};
+
+/* continuous speed index */
+static int sp_idx;
+static struct gpio_desc *gpiod_irq;
+
+/*
+ * Pen sampling frequency (Hz) in continuous mode.
+ */
+static int cont_rate = 200;
+module_param(cont_rate, int, 0);
+MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)");
+
+/*
+ * Pen down detection.
+ *
+ * This driver can either poll or use an interrupt to indicate a pen down
+ * event. If the irq request fails then it will fall back to polling mode.
+ */
+static int pen_int;
+module_param(pen_int, int, 0);
+MODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)");
+
+/*
+ * Pressure readback.
+ *
+ * Set to 1 to read back pen down pressure
+ */
+static int pressure;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)");
+
+/*
+ * AC97 touch data slot.
+ *
+ * Touch screen readback data ac97 slot
+ */
+static int ac97_touch_slot = 5;
+module_param(ac97_touch_slot, int, 0);
+MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number");
+
+
+/* flush AC97 slot 5 FIFO on pxa machines */
+static void wm97xx_acc_pen_up(struct wm97xx *wm)
+{
+ unsigned int count;
+
+ msleep(1);
+
+ if (cpu_is_pxa27x()) {
+ while (pxa2xx_ac97_read_misr() & (1 << 2))
+ pxa2xx_ac97_read_modr();
+ } else if (cpu_is_pxa3xx()) {
+ for (count = 0; count < 16; count++)
+ pxa2xx_ac97_read_modr();
+ }
+}
+
+static int wm97xx_acc_pen_down(struct wm97xx *wm)
+{
+ u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES;
+ int reads = 0;
+ static u16 last, tries;
+
+ /* When the AC97 queue has been drained we need to allow time
+ * to buffer up samples otherwise we end up spinning polling
+ * for samples. The controller can't have a suitably low
+ * threshold set to use the notifications it gives.
+ */
+ msleep(1);
+
+ if (tries > 5) {
+ tries = 0;
+ return RC_PENUP;
+ }
+
+ x = pxa2xx_ac97_read_modr();
+ if (x == last) {
+ tries++;
+ return RC_AGAIN;
+ }
+ last = x;
+ do {
+ if (reads)
+ x = pxa2xx_ac97_read_modr();
+ y = pxa2xx_ac97_read_modr();
+ if (pressure)
+ p = pxa2xx_ac97_read_modr();
+
+ dev_dbg(wm->dev, "Raw coordinates: x=%x, y=%x, p=%x\n",
+ x, y, p);
+
+ /* are samples valid */
+ if ((x & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_X ||
+ (y & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_Y ||
+ (p & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_PRES)
+ goto up;
+
+ /* coordinate is good */
+ tries = 0;
+ input_report_abs(wm->input_dev, ABS_X, x & 0xfff);
+ input_report_abs(wm->input_dev, ABS_Y, y & 0xfff);
+ input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff);
+ input_report_key(wm->input_dev, BTN_TOUCH, (p != 0));
+ input_sync(wm->input_dev);
+ reads++;
+ } while (reads < cinfo[sp_idx].reads);
+up:
+ return RC_PENDOWN | RC_AGAIN;
+}
+
+static int wm97xx_acc_startup(struct wm97xx *wm)
+{
+ int idx = 0, ret = 0;
+
+ /* check we have a codec */
+ if (wm->ac97 == NULL)
+ return -ENODEV;
+
+ /* Go you big red fire engine */
+ for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) {
+ if (wm->id != cinfo[idx].id)
+ continue;
+ sp_idx = idx;
+ if (cont_rate <= cinfo[idx].speed)
+ break;
+ }
+ wm->acc_rate = cinfo[sp_idx].code;
+ wm->acc_slot = ac97_touch_slot;
+ dev_info(wm->dev,
+ "mainstone accelerated touchscreen driver, %d samples/sec\n",
+ cinfo[sp_idx].speed);
+
+ /* IRQ driven touchscreen is used on Palm hardware */
+ if (machine_is_palmt5() || machine_is_palmtx() || machine_is_palmld()) {
+ pen_int = 1;
+ /* There is some obscure mutant of WM9712 interbred with WM9713
+ * used on Palm HW */
+ wm->variant = WM97xx_WM1613;
+ } else if (machine_is_zylonite()) {
+ pen_int = 1;
+ }
+
+ if (pen_int) {
+ gpiod_irq = gpiod_get(wm->dev, "touch", GPIOD_IN);
+ if (IS_ERR(gpiod_irq))
+ pen_int = 0;
+ }
+
+ if (pen_int) {
+ wm->pen_irq = gpiod_to_irq(gpiod_irq);
+ irq_set_irq_type(wm->pen_irq, IRQ_TYPE_EDGE_BOTH);
+ }
+
+ /* codec specific irq config */
+ if (pen_int) {
+ switch (wm->id) {
+ case WM9705_ID2:
+ break;
+ case WM9712_ID2:
+ case WM9713_ID2:
+ /* use PEN_DOWN GPIO 13 to assert IRQ on GPIO line 2 */
+ wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN,
+ WM97XX_GPIO_POL_HIGH,
+ WM97XX_GPIO_STICKY,
+ WM97XX_GPIO_WAKE);
+ wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT,
+ WM97XX_GPIO_POL_HIGH,
+ WM97XX_GPIO_NOTSTICKY,
+ WM97XX_GPIO_NOWAKE);
+ break;
+ default:
+ dev_err(wm->dev,
+ "pen down irq not supported on this device\n");
+ pen_int = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void wm97xx_acc_shutdown(struct wm97xx *wm)
+{
+ /* codec specific deconfig */
+ if (pen_int) {
+ if (gpiod_irq)
+ gpiod_put(gpiod_irq);
+ wm->pen_irq = 0;
+ }
+}
+
+static struct wm97xx_mach_ops mainstone_mach_ops = {
+ .acc_enabled = 1,
+ .acc_pen_up = wm97xx_acc_pen_up,
+ .acc_pen_down = wm97xx_acc_pen_down,
+ .acc_startup = wm97xx_acc_startup,
+ .acc_shutdown = wm97xx_acc_shutdown,
+ .irq_gpio = WM97XX_GPIO_2,
+};
+
+static int mainstone_wm97xx_probe(struct platform_device *pdev)
+{
+ struct wm97xx *wm = platform_get_drvdata(pdev);
+
+ return wm97xx_register_mach_ops(wm, &mainstone_mach_ops);
+}
+
+static int mainstone_wm97xx_remove(struct platform_device *pdev)
+{
+ struct wm97xx *wm = platform_get_drvdata(pdev);
+
+ wm97xx_unregister_mach_ops(wm);
+
+ return 0;
+}
+
+static struct platform_driver mainstone_wm97xx_driver = {
+ .probe = mainstone_wm97xx_probe,
+ .remove = mainstone_wm97xx_remove,
+ .driver = {
+ .name = "wm97xx-touch",
+ },
+};
+module_platform_driver(mainstone_wm97xx_driver);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>");
+MODULE_DESCRIPTION("wm97xx continuous touch driver for mainstone");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/max11801_ts.c b/drivers/input/touchscreen/max11801_ts.c
new file mode 100644
index 000000000..f15713aae
--- /dev/null
+++ b/drivers/input/touchscreen/max11801_ts.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MAXI MAX11801 - A Resistive touch screen controller with
+ * i2c interface
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ * Author: Zhang Jiejing <jiejing.zhang@freescale.com>
+ *
+ * Based on mcs5000_ts.c
+ */
+
+/*
+ * This driver aims to support the series of MAXI touch chips max11801
+ * through max11803. The main difference between these 4 chips can be
+ * found in the table below:
+ * -----------------------------------------------------
+ * | CHIP | AUTO MODE SUPPORT(FIFO) | INTERFACE |
+ * |----------------------------------------------------|
+ * | max11800 | YES | SPI |
+ * | max11801 | YES | I2C |
+ * | max11802 | NO | SPI |
+ * | max11803 | NO | I2C |
+ * ------------------------------------------------------
+ *
+ * Currently, this driver only supports max11801.
+ *
+ * Data Sheet:
+ * http://www.maxim-ic.com/datasheet/index.mvp/id/5943
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+
+/* Register Address define */
+#define GENERNAL_STATUS_REG 0x00
+#define GENERNAL_CONF_REG 0x01
+#define MESURE_RES_CONF_REG 0x02
+#define MESURE_AVER_CONF_REG 0x03
+#define ADC_SAMPLE_TIME_CONF_REG 0x04
+#define PANEL_SETUPTIME_CONF_REG 0x05
+#define DELAY_CONVERSION_CONF_REG 0x06
+#define TOUCH_DETECT_PULLUP_CONF_REG 0x07
+#define AUTO_MODE_TIME_CONF_REG 0x08 /* only for max11800/max11801 */
+#define APERTURE_CONF_REG 0x09 /* only for max11800/max11801 */
+#define AUX_MESURE_CONF_REG 0x0a
+#define OP_MODE_CONF_REG 0x0b
+
+/* FIFO is found only in max11800 and max11801 */
+#define FIFO_RD_CMD (0x50 << 1)
+#define MAX11801_FIFO_INT (1 << 2)
+#define MAX11801_FIFO_OVERFLOW (1 << 3)
+
+#define XY_BUFSIZE 4
+#define XY_BUF_OFFSET 4
+
+#define MAX11801_MAX_X 0xfff
+#define MAX11801_MAX_Y 0xfff
+
+#define MEASURE_TAG_OFFSET 2
+#define MEASURE_TAG_MASK (3 << MEASURE_TAG_OFFSET)
+#define EVENT_TAG_OFFSET 0
+#define EVENT_TAG_MASK (3 << EVENT_TAG_OFFSET)
+#define MEASURE_X_TAG (0 << MEASURE_TAG_OFFSET)
+#define MEASURE_Y_TAG (1 << MEASURE_TAG_OFFSET)
+
+/* These are the state of touch event state machine */
+enum {
+ EVENT_INIT,
+ EVENT_MIDDLE,
+ EVENT_RELEASE,
+ EVENT_FIFO_END
+};
+
+struct max11801_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+};
+
+static u8 read_register(struct i2c_client *client, int addr)
+{
+ /* XXX: The chip ignores LSB of register address */
+ return i2c_smbus_read_byte_data(client, addr << 1);
+}
+
+static int max11801_write_reg(struct i2c_client *client, int addr, int data)
+{
+ /* XXX: The chip ignores LSB of register address */
+ return i2c_smbus_write_byte_data(client, addr << 1, data);
+}
+
+static irqreturn_t max11801_ts_interrupt(int irq, void *dev_id)
+{
+ struct max11801_data *data = dev_id;
+ struct i2c_client *client = data->client;
+ int status, i, ret;
+ u8 buf[XY_BUFSIZE];
+ int x = -1;
+ int y = -1;
+
+ status = read_register(data->client, GENERNAL_STATUS_REG);
+
+ if (status & (MAX11801_FIFO_INT | MAX11801_FIFO_OVERFLOW)) {
+ status = read_register(data->client, GENERNAL_STATUS_REG);
+
+ ret = i2c_smbus_read_i2c_block_data(client, FIFO_RD_CMD,
+ XY_BUFSIZE, buf);
+
+ /*
+ * We should get 4 bytes buffer that contains X,Y
+ * and event tag
+ */
+ if (ret < XY_BUFSIZE)
+ goto out;
+
+ for (i = 0; i < XY_BUFSIZE; i += XY_BUFSIZE / 2) {
+ if ((buf[i + 1] & MEASURE_TAG_MASK) == MEASURE_X_TAG)
+ x = (buf[i] << XY_BUF_OFFSET) +
+ (buf[i + 1] >> XY_BUF_OFFSET);
+ else if ((buf[i + 1] & MEASURE_TAG_MASK) == MEASURE_Y_TAG)
+ y = (buf[i] << XY_BUF_OFFSET) +
+ (buf[i + 1] >> XY_BUF_OFFSET);
+ }
+
+ if ((buf[1] & EVENT_TAG_MASK) != (buf[3] & EVENT_TAG_MASK))
+ goto out;
+
+ switch (buf[1] & EVENT_TAG_MASK) {
+ case EVENT_INIT:
+ case EVENT_MIDDLE:
+ input_report_abs(data->input_dev, ABS_X, x);
+ input_report_abs(data->input_dev, ABS_Y, y);
+ input_event(data->input_dev, EV_KEY, BTN_TOUCH, 1);
+ input_sync(data->input_dev);
+ break;
+
+ case EVENT_RELEASE:
+ input_event(data->input_dev, EV_KEY, BTN_TOUCH, 0);
+ input_sync(data->input_dev);
+ break;
+
+ case EVENT_FIFO_END:
+ break;
+ }
+ }
+out:
+ return IRQ_HANDLED;
+}
+
+static void max11801_ts_phy_init(struct max11801_data *data)
+{
+ struct i2c_client *client = data->client;
+
+ /* Average X,Y, take 16 samples, average eight media sample */
+ max11801_write_reg(client, MESURE_AVER_CONF_REG, 0xff);
+ /* X,Y panel setup time set to 20us */
+ max11801_write_reg(client, PANEL_SETUPTIME_CONF_REG, 0x11);
+ /* Rough pullup time (2uS), Fine pullup time (10us) */
+ max11801_write_reg(client, TOUCH_DETECT_PULLUP_CONF_REG, 0x10);
+ /* Auto mode init period = 5ms , scan period = 5ms*/
+ max11801_write_reg(client, AUTO_MODE_TIME_CONF_REG, 0xaa);
+ /* Aperture X,Y set to +- 4LSB */
+ max11801_write_reg(client, APERTURE_CONF_REG, 0x33);
+ /* Enable Power, enable Automode, enable Aperture, enable Average X,Y */
+ max11801_write_reg(client, OP_MODE_CONF_REG, 0x36);
+}
+
+static int max11801_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct max11801_data *data;
+ struct input_dev *input_dev;
+ int error;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!data || !input_dev) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ data->client = client;
+ data->input_dev = input_dev;
+
+ input_dev->name = "max11801_ts";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+ input_set_abs_params(input_dev, ABS_X, 0, MAX11801_MAX_X, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MAX11801_MAX_Y, 0, 0);
+
+ max11801_ts_phy_init(data);
+
+ error = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ max11801_ts_interrupt,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "max11801_ts", data);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = input_register_device(data->input_dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct i2c_device_id max11801_ts_id[] = {
+ {"max11801", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max11801_ts_id);
+
+static const struct of_device_id max11801_ts_dt_ids[] = {
+ { .compatible = "maxim,max11801" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max11801_ts_dt_ids);
+
+static struct i2c_driver max11801_ts_driver = {
+ .driver = {
+ .name = "max11801_ts",
+ .of_match_table = max11801_ts_dt_ids,
+ },
+ .id_table = max11801_ts_id,
+ .probe = max11801_ts_probe,
+};
+
+module_i2c_driver(max11801_ts_driver);
+
+MODULE_AUTHOR("Zhang Jiejing <jiejing.zhang@freescale.com>");
+MODULE_DESCRIPTION("Touchscreen driver for MAXI MAX11801 controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/mc13783_ts.c b/drivers/input/touchscreen/mc13783_ts.c
new file mode 100644
index 000000000..ae0d978c8
--- /dev/null
+++ b/drivers/input/touchscreen/mc13783_ts.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Freescale Semiconductor MC13783 touchscreen.
+ *
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2009 Sascha Hauer, Pengutronix
+ *
+ * Initial development of this code was funded by
+ * Phytec Messtechnik GmbH, http://www.phytec.de/
+ */
+#include <linux/platform_device.h>
+#include <linux/mfd/mc13783.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+
+#define MC13783_TS_NAME "mc13783-ts"
+
+#define DEFAULT_SAMPLE_TOLERANCE 300
+
+static unsigned int sample_tolerance = DEFAULT_SAMPLE_TOLERANCE;
+module_param(sample_tolerance, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(sample_tolerance,
+ "If the minimal and maximal value read out for one axis (out "
+ "of three) differ by this value (default: "
+ __stringify(DEFAULT_SAMPLE_TOLERANCE) ") or more, the reading "
+ "is supposed to be wrong and is discarded. Set to 0 to "
+ "disable this check.");
+
+struct mc13783_ts_priv {
+ struct input_dev *idev;
+ struct mc13xxx *mc13xxx;
+ struct delayed_work work;
+ unsigned int sample[4];
+ struct mc13xxx_ts_platform_data *touch;
+};
+
+static irqreturn_t mc13783_ts_handler(int irq, void *data)
+{
+ struct mc13783_ts_priv *priv = data;
+
+ mc13xxx_irq_ack(priv->mc13xxx, irq);
+
+ /*
+ * Kick off reading coordinates. Note that if work happens already
+ * be queued for future execution (it rearms itself) it will not
+ * be rescheduled for immediate execution here. However the rearm
+ * delay is HZ / 50 which is acceptable.
+ */
+ schedule_delayed_work(&priv->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+#define sort3(a0, a1, a2) ({ \
+ if (a0 > a1) \
+ swap(a0, a1); \
+ if (a1 > a2) \
+ swap(a1, a2); \
+ if (a0 > a1) \
+ swap(a0, a1); \
+ })
+
+static void mc13783_ts_report_sample(struct mc13783_ts_priv *priv)
+{
+ struct input_dev *idev = priv->idev;
+ int x0, x1, x2, y0, y1, y2;
+ int cr0, cr1;
+
+ /*
+ * the values are 10-bit wide only, but the two least significant
+ * bits are for future 12 bit use and reading yields 0
+ */
+ x0 = priv->sample[0] & 0xfff;
+ x1 = priv->sample[1] & 0xfff;
+ x2 = priv->sample[2] & 0xfff;
+ y0 = priv->sample[3] & 0xfff;
+ y1 = (priv->sample[0] >> 12) & 0xfff;
+ y2 = (priv->sample[1] >> 12) & 0xfff;
+ cr0 = (priv->sample[2] >> 12) & 0xfff;
+ cr1 = (priv->sample[3] >> 12) & 0xfff;
+
+ dev_dbg(&idev->dev,
+ "x: (% 4d,% 4d,% 4d) y: (% 4d, % 4d,% 4d) cr: (% 4d, % 4d)\n",
+ x0, x1, x2, y0, y1, y2, cr0, cr1);
+
+ sort3(x0, x1, x2);
+ sort3(y0, y1, y2);
+
+ cr0 = (cr0 + cr1) / 2;
+
+ if (!cr0 || !sample_tolerance ||
+ (x2 - x0 < sample_tolerance &&
+ y2 - y0 < sample_tolerance)) {
+ /* report the median coordinate and average pressure */
+ if (cr0) {
+ input_report_abs(idev, ABS_X, x1);
+ input_report_abs(idev, ABS_Y, y1);
+
+ dev_dbg(&idev->dev, "report (%d, %d, %d)\n",
+ x1, y1, 0x1000 - cr0);
+ schedule_delayed_work(&priv->work, HZ / 50);
+ } else {
+ dev_dbg(&idev->dev, "report release\n");
+ }
+
+ input_report_abs(idev, ABS_PRESSURE,
+ cr0 ? 0x1000 - cr0 : cr0);
+ input_report_key(idev, BTN_TOUCH, cr0);
+ input_sync(idev);
+ } else {
+ dev_dbg(&idev->dev, "discard event\n");
+ }
+}
+
+static void mc13783_ts_work(struct work_struct *work)
+{
+ struct mc13783_ts_priv *priv =
+ container_of(work, struct mc13783_ts_priv, work.work);
+ unsigned int mode = MC13XXX_ADC_MODE_TS;
+ unsigned int channel = 12;
+
+ if (mc13xxx_adc_do_conversion(priv->mc13xxx,
+ mode, channel,
+ priv->touch->ato, priv->touch->atox,
+ priv->sample) == 0)
+ mc13783_ts_report_sample(priv);
+}
+
+static int mc13783_ts_open(struct input_dev *dev)
+{
+ struct mc13783_ts_priv *priv = input_get_drvdata(dev);
+ int ret;
+
+ mc13xxx_lock(priv->mc13xxx);
+
+ mc13xxx_irq_ack(priv->mc13xxx, MC13XXX_IRQ_TS);
+
+ ret = mc13xxx_irq_request(priv->mc13xxx, MC13XXX_IRQ_TS,
+ mc13783_ts_handler, MC13783_TS_NAME, priv);
+ if (ret)
+ goto out;
+
+ ret = mc13xxx_reg_rmw(priv->mc13xxx, MC13XXX_ADC0,
+ MC13XXX_ADC0_TSMOD_MASK, MC13XXX_ADC0_TSMOD0);
+ if (ret)
+ mc13xxx_irq_free(priv->mc13xxx, MC13XXX_IRQ_TS, priv);
+out:
+ mc13xxx_unlock(priv->mc13xxx);
+ return ret;
+}
+
+static void mc13783_ts_close(struct input_dev *dev)
+{
+ struct mc13783_ts_priv *priv = input_get_drvdata(dev);
+
+ mc13xxx_lock(priv->mc13xxx);
+ mc13xxx_reg_rmw(priv->mc13xxx, MC13XXX_ADC0,
+ MC13XXX_ADC0_TSMOD_MASK, 0);
+ mc13xxx_irq_free(priv->mc13xxx, MC13XXX_IRQ_TS, priv);
+ mc13xxx_unlock(priv->mc13xxx);
+
+ cancel_delayed_work_sync(&priv->work);
+}
+
+static int __init mc13783_ts_probe(struct platform_device *pdev)
+{
+ struct mc13783_ts_priv *priv;
+ struct input_dev *idev;
+ int ret = -ENOMEM;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ idev = input_allocate_device();
+ if (!priv || !idev)
+ goto err_free_mem;
+
+ INIT_DELAYED_WORK(&priv->work, mc13783_ts_work);
+ priv->mc13xxx = dev_get_drvdata(pdev->dev.parent);
+ priv->idev = idev;
+ priv->touch = dev_get_platdata(&pdev->dev);
+ if (!priv->touch) {
+ dev_err(&pdev->dev, "missing platform data\n");
+ ret = -ENODEV;
+ goto err_free_mem;
+ }
+
+ idev->name = MC13783_TS_NAME;
+ idev->dev.parent = &pdev->dev;
+
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0, 0xfff, 0, 0);
+
+ idev->open = mc13783_ts_open;
+ idev->close = mc13783_ts_close;
+
+ input_set_drvdata(idev, priv);
+
+ ret = input_register_device(priv->idev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "register input device failed with %d\n", ret);
+ goto err_free_mem;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ return 0;
+
+err_free_mem:
+ input_free_device(idev);
+ kfree(priv);
+ return ret;
+}
+
+static int mc13783_ts_remove(struct platform_device *pdev)
+{
+ struct mc13783_ts_priv *priv = platform_get_drvdata(pdev);
+
+ input_unregister_device(priv->idev);
+ kfree(priv);
+
+ return 0;
+}
+
+static struct platform_driver mc13783_ts_driver = {
+ .remove = mc13783_ts_remove,
+ .driver = {
+ .name = MC13783_TS_NAME,
+ },
+};
+
+module_platform_driver_probe(mc13783_ts_driver, mc13783_ts_probe);
+
+MODULE_DESCRIPTION("MC13783 input touchscreen driver");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" MC13783_TS_NAME);
diff --git a/drivers/input/touchscreen/mcs5000_ts.c b/drivers/input/touchscreen/mcs5000_ts.c
new file mode 100644
index 000000000..5376d8f74
--- /dev/null
+++ b/drivers/input/touchscreen/mcs5000_ts.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * mcs5000_ts.c - Touchscreen driver for MELFAS MCS-5000 controller
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ * Based on wm97xx-core.c
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/irq.h>
+#include <linux/platform_data/mcs.h>
+#include <linux/slab.h>
+
+/* Registers */
+#define MCS5000_TS_STATUS 0x00
+#define STATUS_OFFSET 0
+#define STATUS_NO (0 << STATUS_OFFSET)
+#define STATUS_INIT (1 << STATUS_OFFSET)
+#define STATUS_SENSING (2 << STATUS_OFFSET)
+#define STATUS_COORD (3 << STATUS_OFFSET)
+#define STATUS_GESTURE (4 << STATUS_OFFSET)
+#define ERROR_OFFSET 4
+#define ERROR_NO (0 << ERROR_OFFSET)
+#define ERROR_POWER_ON_RESET (1 << ERROR_OFFSET)
+#define ERROR_INT_RESET (2 << ERROR_OFFSET)
+#define ERROR_EXT_RESET (3 << ERROR_OFFSET)
+#define ERROR_INVALID_REG_ADDRESS (8 << ERROR_OFFSET)
+#define ERROR_INVALID_REG_VALUE (9 << ERROR_OFFSET)
+
+#define MCS5000_TS_OP_MODE 0x01
+#define RESET_OFFSET 0
+#define RESET_NO (0 << RESET_OFFSET)
+#define RESET_EXT_SOFT (1 << RESET_OFFSET)
+#define OP_MODE_OFFSET 1
+#define OP_MODE_SLEEP (0 << OP_MODE_OFFSET)
+#define OP_MODE_ACTIVE (1 << OP_MODE_OFFSET)
+#define GESTURE_OFFSET 4
+#define GESTURE_DISABLE (0 << GESTURE_OFFSET)
+#define GESTURE_ENABLE (1 << GESTURE_OFFSET)
+#define PROXIMITY_OFFSET 5
+#define PROXIMITY_DISABLE (0 << PROXIMITY_OFFSET)
+#define PROXIMITY_ENABLE (1 << PROXIMITY_OFFSET)
+#define SCAN_MODE_OFFSET 6
+#define SCAN_MODE_INTERRUPT (0 << SCAN_MODE_OFFSET)
+#define SCAN_MODE_POLLING (1 << SCAN_MODE_OFFSET)
+#define REPORT_RATE_OFFSET 7
+#define REPORT_RATE_40 (0 << REPORT_RATE_OFFSET)
+#define REPORT_RATE_80 (1 << REPORT_RATE_OFFSET)
+
+#define MCS5000_TS_SENS_CTL 0x02
+#define MCS5000_TS_FILTER_CTL 0x03
+#define PRI_FILTER_OFFSET 0
+#define SEC_FILTER_OFFSET 4
+
+#define MCS5000_TS_X_SIZE_UPPER 0x08
+#define MCS5000_TS_X_SIZE_LOWER 0x09
+#define MCS5000_TS_Y_SIZE_UPPER 0x0A
+#define MCS5000_TS_Y_SIZE_LOWER 0x0B
+
+#define MCS5000_TS_INPUT_INFO 0x10
+#define INPUT_TYPE_OFFSET 0
+#define INPUT_TYPE_NONTOUCH (0 << INPUT_TYPE_OFFSET)
+#define INPUT_TYPE_SINGLE (1 << INPUT_TYPE_OFFSET)
+#define INPUT_TYPE_DUAL (2 << INPUT_TYPE_OFFSET)
+#define INPUT_TYPE_PALM (3 << INPUT_TYPE_OFFSET)
+#define INPUT_TYPE_PROXIMITY (7 << INPUT_TYPE_OFFSET)
+#define GESTURE_CODE_OFFSET 3
+#define GESTURE_CODE_NO (0 << GESTURE_CODE_OFFSET)
+
+#define MCS5000_TS_X_POS_UPPER 0x11
+#define MCS5000_TS_X_POS_LOWER 0x12
+#define MCS5000_TS_Y_POS_UPPER 0x13
+#define MCS5000_TS_Y_POS_LOWER 0x14
+#define MCS5000_TS_Z_POS 0x15
+#define MCS5000_TS_WIDTH 0x16
+#define MCS5000_TS_GESTURE_VAL 0x17
+#define MCS5000_TS_MODULE_REV 0x20
+#define MCS5000_TS_FIRMWARE_VER 0x21
+
+/* Touchscreen absolute values */
+#define MCS5000_MAX_XC 0x3ff
+#define MCS5000_MAX_YC 0x3ff
+
+enum mcs5000_ts_read_offset {
+ READ_INPUT_INFO,
+ READ_X_POS_UPPER,
+ READ_X_POS_LOWER,
+ READ_Y_POS_UPPER,
+ READ_Y_POS_LOWER,
+ READ_BLOCK_SIZE,
+};
+
+/* Each client has this additional data */
+struct mcs5000_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ const struct mcs_platform_data *platform_data;
+};
+
+static irqreturn_t mcs5000_ts_interrupt(int irq, void *dev_id)
+{
+ struct mcs5000_ts_data *data = dev_id;
+ struct i2c_client *client = data->client;
+ u8 buffer[READ_BLOCK_SIZE];
+ int err;
+ int x;
+ int y;
+
+ err = i2c_smbus_read_i2c_block_data(client, MCS5000_TS_INPUT_INFO,
+ READ_BLOCK_SIZE, buffer);
+ if (err < 0) {
+ dev_err(&client->dev, "%s, err[%d]\n", __func__, err);
+ goto out;
+ }
+
+ switch (buffer[READ_INPUT_INFO]) {
+ case INPUT_TYPE_NONTOUCH:
+ input_report_key(data->input_dev, BTN_TOUCH, 0);
+ input_sync(data->input_dev);
+ break;
+
+ case INPUT_TYPE_SINGLE:
+ x = (buffer[READ_X_POS_UPPER] << 8) | buffer[READ_X_POS_LOWER];
+ y = (buffer[READ_Y_POS_UPPER] << 8) | buffer[READ_Y_POS_LOWER];
+
+ input_report_key(data->input_dev, BTN_TOUCH, 1);
+ input_report_abs(data->input_dev, ABS_X, x);
+ input_report_abs(data->input_dev, ABS_Y, y);
+ input_sync(data->input_dev);
+ break;
+
+ case INPUT_TYPE_DUAL:
+ /* TODO */
+ break;
+
+ case INPUT_TYPE_PALM:
+ /* TODO */
+ break;
+
+ case INPUT_TYPE_PROXIMITY:
+ /* TODO */
+ break;
+
+ default:
+ dev_err(&client->dev, "Unknown ts input type %d\n",
+ buffer[READ_INPUT_INFO]);
+ break;
+ }
+
+ out:
+ return IRQ_HANDLED;
+}
+
+static void mcs5000_ts_phys_init(struct mcs5000_ts_data *data,
+ const struct mcs_platform_data *platform_data)
+{
+ struct i2c_client *client = data->client;
+
+ /* Touch reset & sleep mode */
+ i2c_smbus_write_byte_data(client, MCS5000_TS_OP_MODE,
+ RESET_EXT_SOFT | OP_MODE_SLEEP);
+
+ /* Touch size */
+ i2c_smbus_write_byte_data(client, MCS5000_TS_X_SIZE_UPPER,
+ platform_data->x_size >> 8);
+ i2c_smbus_write_byte_data(client, MCS5000_TS_X_SIZE_LOWER,
+ platform_data->x_size & 0xff);
+ i2c_smbus_write_byte_data(client, MCS5000_TS_Y_SIZE_UPPER,
+ platform_data->y_size >> 8);
+ i2c_smbus_write_byte_data(client, MCS5000_TS_Y_SIZE_LOWER,
+ platform_data->y_size & 0xff);
+
+ /* Touch active mode & 80 report rate */
+ i2c_smbus_write_byte_data(data->client, MCS5000_TS_OP_MODE,
+ OP_MODE_ACTIVE | REPORT_RATE_80);
+}
+
+static int mcs5000_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct mcs_platform_data *pdata;
+ struct mcs5000_ts_data *data;
+ struct input_dev *input_dev;
+ int error;
+
+ pdata = dev_get_platdata(&client->dev);
+ if (!pdata)
+ return -EINVAL;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ data->client = client;
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = "MELFAS MCS-5000 Touchscreen";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+ input_set_abs_params(input_dev, ABS_X, 0, MCS5000_MAX_XC, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MCS5000_MAX_YC, 0, 0);
+
+ data->input_dev = input_dev;
+
+ if (pdata->cfg_pin)
+ pdata->cfg_pin();
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, mcs5000_ts_interrupt,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "mcs5000_ts", data);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = input_register_device(data->input_dev);
+ if (error) {
+ dev_err(&client->dev, "Failed to register input device\n");
+ return error;
+ }
+
+ mcs5000_ts_phys_init(data, pdata);
+ i2c_set_clientdata(client, data);
+
+ return 0;
+}
+
+static int __maybe_unused mcs5000_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ /* Touch sleep mode */
+ i2c_smbus_write_byte_data(client, MCS5000_TS_OP_MODE, OP_MODE_SLEEP);
+
+ return 0;
+}
+
+static int __maybe_unused mcs5000_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mcs5000_ts_data *data = i2c_get_clientdata(client);
+ const struct mcs_platform_data *pdata = dev_get_platdata(dev);
+
+ mcs5000_ts_phys_init(data, pdata);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mcs5000_ts_pm, mcs5000_ts_suspend, mcs5000_ts_resume);
+
+static const struct i2c_device_id mcs5000_ts_id[] = {
+ { "mcs5000_ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mcs5000_ts_id);
+
+static struct i2c_driver mcs5000_ts_driver = {
+ .probe = mcs5000_ts_probe,
+ .driver = {
+ .name = "mcs5000_ts",
+ .pm = &mcs5000_ts_pm,
+ },
+ .id_table = mcs5000_ts_id,
+};
+
+module_i2c_driver(mcs5000_ts_driver);
+
+/* Module information */
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_DESCRIPTION("Touchscreen driver for MELFAS MCS-5000 controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/melfas_mip4.c b/drivers/input/touchscreen/melfas_mip4.c
new file mode 100644
index 000000000..83f4be05e
--- /dev/null
+++ b/drivers/input/touchscreen/melfas_mip4.c
@@ -0,0 +1,1605 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MELFAS MIP4 Touchscreen
+ *
+ * Copyright (C) 2016 MELFAS Inc.
+ *
+ * Author : Sangwon Jee <jeesw@melfas.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#define MIP4_DEVICE_NAME "mip4_ts"
+
+/*****************************************************************
+ * Protocol
+ * Version : MIP 4.0 Rev 5.4
+ *****************************************************************/
+
+/* Address */
+#define MIP4_R0_BOOT 0x00
+#define MIP4_R1_BOOT_MODE 0x01
+#define MIP4_R1_BOOT_BUF_ADDR 0x10
+#define MIP4_R1_BOOT_STATUS 0x20
+#define MIP4_R1_BOOT_CMD 0x30
+#define MIP4_R1_BOOT_TARGET_ADDR 0x40
+#define MIP4_R1_BOOT_SIZE 0x44
+
+#define MIP4_R0_INFO 0x01
+#define MIP4_R1_INFO_PRODUCT_NAME 0x00
+#define MIP4_R1_INFO_RESOLUTION_X 0x10
+#define MIP4_R1_INFO_RESOLUTION_Y 0x12
+#define MIP4_R1_INFO_NODE_NUM_X 0x14
+#define MIP4_R1_INFO_NODE_NUM_Y 0x15
+#define MIP4_R1_INFO_KEY_NUM 0x16
+#define MIP4_R1_INFO_PRESSURE_NUM 0x17
+#define MIP4_R1_INFO_LENGTH_X 0x18
+#define MIP4_R1_INFO_LENGTH_Y 0x1A
+#define MIP4_R1_INFO_PPM_X 0x1C
+#define MIP4_R1_INFO_PPM_Y 0x1D
+#define MIP4_R1_INFO_VERSION_BOOT 0x20
+#define MIP4_R1_INFO_VERSION_CORE 0x22
+#define MIP4_R1_INFO_VERSION_APP 0x24
+#define MIP4_R1_INFO_VERSION_PARAM 0x26
+#define MIP4_R1_INFO_SECT_BOOT_START 0x30
+#define MIP4_R1_INFO_SECT_BOOT_END 0x31
+#define MIP4_R1_INFO_SECT_CORE_START 0x32
+#define MIP4_R1_INFO_SECT_CORE_END 0x33
+#define MIP4_R1_INFO_SECT_APP_START 0x34
+#define MIP4_R1_INFO_SECT_APP_END 0x35
+#define MIP4_R1_INFO_SECT_PARAM_START 0x36
+#define MIP4_R1_INFO_SECT_PARAM_END 0x37
+#define MIP4_R1_INFO_BUILD_DATE 0x40
+#define MIP4_R1_INFO_BUILD_TIME 0x44
+#define MIP4_R1_INFO_CHECKSUM_PRECALC 0x48
+#define MIP4_R1_INFO_CHECKSUM_REALTIME 0x4A
+#define MIP4_R1_INFO_PROTOCOL_NAME 0x50
+#define MIP4_R1_INFO_PROTOCOL_VERSION 0x58
+#define MIP4_R1_INFO_IC_ID 0x70
+#define MIP4_R1_INFO_IC_NAME 0x71
+#define MIP4_R1_INFO_IC_VENDOR_ID 0x75
+#define MIP4_R1_INFO_IC_HW_CATEGORY 0x77
+#define MIP4_R1_INFO_CONTACT_THD_SCR 0x78
+#define MIP4_R1_INFO_CONTACT_THD_KEY 0x7A
+#define MIP4_R1_INFO_PID 0x7C
+#define MIP4_R1_INFO_VID 0x7E
+#define MIP4_R1_INFO_SLAVE_ADDR 0x80
+
+#define MIP4_R0_EVENT 0x02
+#define MIP4_R1_EVENT_SUPPORTED_FUNC 0x00
+#define MIP4_R1_EVENT_FORMAT 0x04
+#define MIP4_R1_EVENT_SIZE 0x06
+#define MIP4_R1_EVENT_PACKET_INFO 0x10
+#define MIP4_R1_EVENT_PACKET_DATA 0x11
+
+#define MIP4_R0_CTRL 0x06
+#define MIP4_R1_CTRL_READY_STATUS 0x00
+#define MIP4_R1_CTRL_EVENT_READY 0x01
+#define MIP4_R1_CTRL_MODE 0x10
+#define MIP4_R1_CTRL_EVENT_TRIGGER_TYPE 0x11
+#define MIP4_R1_CTRL_RECALIBRATE 0x12
+#define MIP4_R1_CTRL_POWER_STATE 0x13
+#define MIP4_R1_CTRL_GESTURE_TYPE 0x14
+#define MIP4_R1_CTRL_DISABLE_ESD_ALERT 0x18
+#define MIP4_R1_CTRL_CHARGER_MODE 0x19
+#define MIP4_R1_CTRL_HIGH_SENS_MODE 0x1A
+#define MIP4_R1_CTRL_WINDOW_MODE 0x1B
+#define MIP4_R1_CTRL_PALM_REJECTION 0x1C
+#define MIP4_R1_CTRL_EDGE_CORRECTION 0x1D
+#define MIP4_R1_CTRL_ENTER_GLOVE_MODE 0x1E
+#define MIP4_R1_CTRL_I2C_ON_LPM 0x1F
+#define MIP4_R1_CTRL_GESTURE_DEBUG 0x20
+#define MIP4_R1_CTRL_PALM_EVENT 0x22
+#define MIP4_R1_CTRL_PROXIMITY_SENSING 0x23
+
+/* Value */
+#define MIP4_BOOT_MODE_BOOT 0x01
+#define MIP4_BOOT_MODE_APP 0x02
+
+#define MIP4_BOOT_STATUS_BUSY 0x05
+#define MIP4_BOOT_STATUS_ERROR 0x0E
+#define MIP4_BOOT_STATUS_DONE 0xA0
+
+#define MIP4_BOOT_CMD_MASS_ERASE 0x15
+#define MIP4_BOOT_CMD_PROGRAM 0x54
+#define MIP4_BOOT_CMD_ERASE 0x8F
+#define MIP4_BOOT_CMD_WRITE 0xA5
+#define MIP4_BOOT_CMD_READ 0xC2
+
+#define MIP4_EVENT_INPUT_TYPE_KEY 0
+#define MIP4_EVENT_INPUT_TYPE_SCREEN 1
+#define MIP4_EVENT_INPUT_TYPE_PROXIMITY 2
+
+#define I2C_RETRY_COUNT 3 /* 2~ */
+
+#define MIP4_BUF_SIZE 128
+#define MIP4_MAX_FINGERS 10
+#define MIP4_MAX_KEYS 4
+
+#define MIP4_TOUCH_MAJOR_MIN 0
+#define MIP4_TOUCH_MAJOR_MAX 255
+#define MIP4_TOUCH_MINOR_MIN 0
+#define MIP4_TOUCH_MINOR_MAX 255
+#define MIP4_PRESSURE_MIN 0
+#define MIP4_PRESSURE_MAX 255
+
+#define MIP4_FW_NAME "melfas_mip4.fw"
+#define MIP4_FW_UPDATE_DEBUG 0 /* 0 (default) or 1 */
+
+struct mip4_fw_version {
+ u16 boot;
+ u16 core;
+ u16 app;
+ u16 param;
+};
+
+struct mip4_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *gpio_ce;
+
+ char phys[32];
+ char product_name[16];
+ u16 product_id;
+ char ic_name[4];
+ char fw_name[32];
+
+ unsigned int max_x;
+ unsigned int max_y;
+ u8 node_x;
+ u8 node_y;
+ u8 node_key;
+ unsigned int ppm_x;
+ unsigned int ppm_y;
+
+ struct mip4_fw_version fw_version;
+
+ unsigned int event_size;
+ unsigned int event_format;
+
+ unsigned int key_num;
+ unsigned short key_code[MIP4_MAX_KEYS];
+
+ bool wake_irq_enabled;
+
+ u8 buf[MIP4_BUF_SIZE];
+};
+
+static int mip4_i2c_xfer(struct mip4_ts *ts,
+ char *write_buf, unsigned int write_len,
+ char *read_buf, unsigned int read_len)
+{
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .buf = write_buf,
+ .len = write_len,
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .buf = read_buf,
+ .len = read_len,
+ },
+ };
+ int retry = I2C_RETRY_COUNT;
+ int res;
+ int error;
+
+ do {
+ res = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (res == ARRAY_SIZE(msg))
+ return 0;
+
+ error = res < 0 ? res : -EIO;
+ dev_err(&ts->client->dev,
+ "%s - i2c_transfer failed: %d (%d)\n",
+ __func__, error, res);
+ } while (--retry);
+
+ return error;
+}
+
+static void mip4_parse_fw_version(const u8 *buf, struct mip4_fw_version *v)
+{
+ v->boot = get_unaligned_le16(buf + 0);
+ v->core = get_unaligned_le16(buf + 2);
+ v->app = get_unaligned_le16(buf + 4);
+ v->param = get_unaligned_le16(buf + 6);
+}
+
+/*
+ * Read chip firmware version
+ */
+static int mip4_get_fw_version(struct mip4_ts *ts)
+{
+ u8 cmd[] = { MIP4_R0_INFO, MIP4_R1_INFO_VERSION_BOOT };
+ u8 buf[sizeof(ts->fw_version)];
+ int error;
+
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, sizeof(buf));
+ if (error) {
+ memset(&ts->fw_version, 0xff, sizeof(ts->fw_version));
+ return error;
+ }
+
+ mip4_parse_fw_version(buf, &ts->fw_version);
+
+ return 0;
+}
+
+/*
+ * Fetch device characteristics
+ */
+static int mip4_query_device(struct mip4_ts *ts)
+{
+ union i2c_smbus_data dummy;
+ int error;
+ u8 cmd[2];
+ u8 buf[14];
+
+ /*
+ * Make sure there is something at this address as we do not
+ * consider subsequent failures as fatal.
+ */
+ if (i2c_smbus_xfer(ts->client->adapter, ts->client->addr,
+ 0, I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) {
+ dev_err(&ts->client->dev, "nothing at this address\n");
+ return -ENXIO;
+ }
+
+ /* Product name */
+ cmd[0] = MIP4_R0_INFO;
+ cmd[1] = MIP4_R1_INFO_PRODUCT_NAME;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd),
+ ts->product_name, sizeof(ts->product_name));
+ if (error)
+ dev_warn(&ts->client->dev,
+ "Failed to retrieve product name: %d\n", error);
+ else
+ dev_dbg(&ts->client->dev, "product name: %.*s\n",
+ (int)sizeof(ts->product_name), ts->product_name);
+
+ /* Product ID */
+ cmd[0] = MIP4_R0_INFO;
+ cmd[1] = MIP4_R1_INFO_PID;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 2);
+ if (error) {
+ dev_warn(&ts->client->dev,
+ "Failed to retrieve product id: %d\n", error);
+ } else {
+ ts->product_id = get_unaligned_le16(&buf[0]);
+ dev_dbg(&ts->client->dev, "product id: %04X\n", ts->product_id);
+ }
+
+ /* Firmware name */
+ snprintf(ts->fw_name, sizeof(ts->fw_name),
+ "melfas_mip4_%04X.fw", ts->product_id);
+ dev_dbg(&ts->client->dev, "firmware name: %s\n", ts->fw_name);
+
+ /* IC name */
+ cmd[0] = MIP4_R0_INFO;
+ cmd[1] = MIP4_R1_INFO_IC_NAME;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd),
+ ts->ic_name, sizeof(ts->ic_name));
+ if (error)
+ dev_warn(&ts->client->dev,
+ "Failed to retrieve IC name: %d\n", error);
+ else
+ dev_dbg(&ts->client->dev, "IC name: %.*s\n",
+ (int)sizeof(ts->ic_name), ts->ic_name);
+
+ /* Firmware version */
+ error = mip4_get_fw_version(ts);
+ if (error)
+ dev_warn(&ts->client->dev,
+ "Failed to retrieve FW version: %d\n", error);
+ else
+ dev_dbg(&ts->client->dev, "F/W Version: %04X %04X %04X %04X\n",
+ ts->fw_version.boot, ts->fw_version.core,
+ ts->fw_version.app, ts->fw_version.param);
+
+ /* Resolution */
+ cmd[0] = MIP4_R0_INFO;
+ cmd[1] = MIP4_R1_INFO_RESOLUTION_X;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 14);
+ if (error) {
+ dev_warn(&ts->client->dev,
+ "Failed to retrieve touchscreen parameters: %d\n",
+ error);
+ } else {
+ ts->max_x = get_unaligned_le16(&buf[0]);
+ ts->max_y = get_unaligned_le16(&buf[2]);
+ dev_dbg(&ts->client->dev, "max_x: %d, max_y: %d\n",
+ ts->max_x, ts->max_y);
+
+ ts->node_x = buf[4];
+ ts->node_y = buf[5];
+ ts->node_key = buf[6];
+ dev_dbg(&ts->client->dev,
+ "node_x: %d, node_y: %d, node_key: %d\n",
+ ts->node_x, ts->node_y, ts->node_key);
+
+ ts->ppm_x = buf[12];
+ ts->ppm_y = buf[13];
+ dev_dbg(&ts->client->dev, "ppm_x: %d, ppm_y: %d\n",
+ ts->ppm_x, ts->ppm_y);
+
+ /* Key ts */
+ if (ts->node_key > 0)
+ ts->key_num = ts->node_key;
+ }
+
+ /* Protocol */
+ cmd[0] = MIP4_R0_EVENT;
+ cmd[1] = MIP4_R1_EVENT_SUPPORTED_FUNC;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 7);
+ if (error) {
+ dev_warn(&ts->client->dev,
+ "Failed to retrieve device type: %d\n", error);
+ ts->event_format = 0xff;
+ } else {
+ ts->event_format = get_unaligned_le16(&buf[4]);
+ ts->event_size = buf[6];
+ dev_dbg(&ts->client->dev, "event_format: %d, event_size: %d\n",
+ ts->event_format, ts->event_size);
+
+ if (ts->event_format == 2 || ts->event_format > 3)
+ dev_warn(&ts->client->dev,
+ "Unknown event format %d\n", ts->event_format);
+ }
+
+ return 0;
+}
+
+static int mip4_power_on(struct mip4_ts *ts)
+{
+ if (ts->gpio_ce) {
+ gpiod_set_value_cansleep(ts->gpio_ce, 1);
+
+ /* Booting delay : 200~300ms */
+ usleep_range(200 * 1000, 300 * 1000);
+ }
+
+ return 0;
+}
+
+static void mip4_power_off(struct mip4_ts *ts)
+{
+ if (ts->gpio_ce)
+ gpiod_set_value_cansleep(ts->gpio_ce, 0);
+}
+
+/*
+ * Clear touch input event status
+ */
+static void mip4_clear_input(struct mip4_ts *ts)
+{
+ int i;
+
+ /* Screen */
+ for (i = 0; i < MIP4_MAX_FINGERS; i++) {
+ input_mt_slot(ts->input, i);
+ input_mt_report_slot_inactive(ts->input);
+ }
+
+ /* Keys */
+ for (i = 0; i < ts->key_num; i++)
+ input_report_key(ts->input, ts->key_code[i], 0);
+
+ input_sync(ts->input);
+}
+
+static int mip4_enable(struct mip4_ts *ts)
+{
+ int error;
+
+ error = mip4_power_on(ts);
+ if (error)
+ return error;
+
+ enable_irq(ts->client->irq);
+
+ return 0;
+}
+
+static void mip4_disable(struct mip4_ts *ts)
+{
+ disable_irq(ts->client->irq);
+
+ mip4_power_off(ts);
+
+ mip4_clear_input(ts);
+}
+
+/*****************************************************************
+ * Input handling
+ *****************************************************************/
+
+static void mip4_report_keys(struct mip4_ts *ts, u8 *packet)
+{
+ u8 key;
+ bool down;
+
+ switch (ts->event_format) {
+ case 0:
+ case 1:
+ key = packet[0] & 0x0F;
+ down = packet[0] & 0x80;
+ break;
+
+ case 3:
+ default:
+ key = packet[0] & 0x0F;
+ down = packet[1] & 0x01;
+ break;
+ }
+
+ /* Report key event */
+ if (key >= 1 && key <= ts->key_num) {
+ unsigned short keycode = ts->key_code[key - 1];
+
+ dev_dbg(&ts->client->dev,
+ "Key - ID: %d, keycode: %d, state: %d\n",
+ key, keycode, down);
+
+ input_event(ts->input, EV_MSC, MSC_SCAN, keycode);
+ input_report_key(ts->input, keycode, down);
+
+ } else {
+ dev_err(&ts->client->dev, "Unknown key: %d\n", key);
+ }
+}
+
+static void mip4_report_touch(struct mip4_ts *ts, u8 *packet)
+{
+ int id;
+ bool __always_unused hover;
+ bool __always_unused palm;
+ bool state;
+ u16 x, y;
+ u8 __always_unused pressure_stage = 0;
+ u8 pressure;
+ u8 __always_unused size;
+ u8 touch_major;
+ u8 touch_minor;
+
+ switch (ts->event_format) {
+ case 0:
+ case 1:
+ /* Touch only */
+ state = packet[0] & BIT(7);
+ hover = packet[0] & BIT(5);
+ palm = packet[0] & BIT(4);
+ id = (packet[0] & 0x0F) - 1;
+ x = ((packet[1] & 0x0F) << 8) | packet[2];
+ y = (((packet[1] >> 4) & 0x0F) << 8) |
+ packet[3];
+ pressure = packet[4];
+ size = packet[5];
+ if (ts->event_format == 0) {
+ touch_major = packet[5];
+ touch_minor = packet[5];
+ } else {
+ touch_major = packet[6];
+ touch_minor = packet[7];
+ }
+ break;
+
+ case 3:
+ default:
+ /* Touch + Force(Pressure) */
+ id = (packet[0] & 0x0F) - 1;
+ hover = packet[1] & BIT(2);
+ palm = packet[1] & BIT(1);
+ state = packet[1] & BIT(0);
+ x = ((packet[2] & 0x0F) << 8) | packet[3];
+ y = (((packet[2] >> 4) & 0x0F) << 8) |
+ packet[4];
+ size = packet[6];
+ pressure_stage = (packet[7] & 0xF0) >> 4;
+ pressure = ((packet[7] & 0x0F) << 8) |
+ packet[8];
+ touch_major = packet[9];
+ touch_minor = packet[10];
+ break;
+ }
+
+ dev_dbg(&ts->client->dev,
+ "Screen - Slot: %d State: %d X: %04d Y: %04d Z: %d\n",
+ id, state, x, y, pressure);
+
+ if (unlikely(id < 0 || id >= MIP4_MAX_FINGERS)) {
+ dev_err(&ts->client->dev, "Screen - invalid slot ID: %d\n", id);
+ } else if (state) {
+ /* Press or Move event */
+ input_mt_slot(ts->input, id);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+ input_report_abs(ts->input, ABS_MT_POSITION_X, x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
+ input_report_abs(ts->input, ABS_MT_PRESSURE, pressure);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, touch_major);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, touch_minor);
+ } else {
+ /* Release event */
+ input_mt_slot(ts->input, id);
+ input_mt_report_slot_inactive(ts->input);
+ }
+
+ input_mt_sync_frame(ts->input);
+}
+
+static int mip4_handle_packet(struct mip4_ts *ts, u8 *packet)
+{
+ u8 type;
+
+ switch (ts->event_format) {
+ case 0:
+ case 1:
+ type = (packet[0] & 0x40) >> 6;
+ break;
+
+ case 3:
+ type = (packet[0] & 0xF0) >> 4;
+ break;
+
+ default:
+ /* Should not happen unless we have corrupted firmware */
+ return -EINVAL;
+ }
+
+ dev_dbg(&ts->client->dev, "Type: %d\n", type);
+
+ /* Report input event */
+ switch (type) {
+ case MIP4_EVENT_INPUT_TYPE_KEY:
+ mip4_report_keys(ts, packet);
+ break;
+
+ case MIP4_EVENT_INPUT_TYPE_SCREEN:
+ mip4_report_touch(ts, packet);
+ break;
+
+ default:
+ dev_err(&ts->client->dev, "Unknown event type: %d\n", type);
+ break;
+ }
+
+ return 0;
+}
+
+static irqreturn_t mip4_interrupt(int irq, void *dev_id)
+{
+ struct mip4_ts *ts = dev_id;
+ struct i2c_client *client = ts->client;
+ unsigned int i;
+ int error;
+ u8 cmd[2];
+ u8 size;
+ bool alert;
+
+ /* Read packet info */
+ cmd[0] = MIP4_R0_EVENT;
+ cmd[1] = MIP4_R1_EVENT_PACKET_INFO;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->buf, 1);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read packet info: %d\n", error);
+ goto out;
+ }
+
+ size = ts->buf[0] & 0x7F;
+ alert = ts->buf[0] & BIT(7);
+ dev_dbg(&client->dev, "packet size: %d, alert: %d\n", size, alert);
+
+ /* Check size */
+ if (!size) {
+ dev_err(&client->dev, "Empty packet\n");
+ goto out;
+ }
+
+ /* Read packet data */
+ cmd[0] = MIP4_R0_EVENT;
+ cmd[1] = MIP4_R1_EVENT_PACKET_DATA;
+ error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->buf, size);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read packet data: %d\n", error);
+ goto out;
+ }
+
+ if (alert) {
+ dev_dbg(&client->dev, "Alert: %d\n", ts->buf[0]);
+ } else {
+ for (i = 0; i < size; i += ts->event_size) {
+ error = mip4_handle_packet(ts, &ts->buf[i]);
+ if (error)
+ break;
+ }
+
+ input_sync(ts->input);
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int mip4_input_open(struct input_dev *dev)
+{
+ struct mip4_ts *ts = input_get_drvdata(dev);
+
+ return mip4_enable(ts);
+}
+
+static void mip4_input_close(struct input_dev *dev)
+{
+ struct mip4_ts *ts = input_get_drvdata(dev);
+
+ mip4_disable(ts);
+}
+
+/*****************************************************************
+ * Firmware update
+ *****************************************************************/
+
+/* Firmware Info */
+#define MIP4_BL_PAGE_SIZE 512 /* 512 */
+#define MIP4_BL_PACKET_SIZE 512 /* 512, 256, 128, 64, ... */
+
+/*
+ * Firmware binary tail info
+ */
+
+struct mip4_bin_tail {
+ u8 tail_mark[4];
+ u8 chip_name[4];
+
+ __le32 bin_start_addr;
+ __le32 bin_length;
+
+ __le16 ver_boot;
+ __le16 ver_core;
+ __le16 ver_app;
+ __le16 ver_param;
+
+ u8 boot_start;
+ u8 boot_end;
+ u8 core_start;
+ u8 core_end;
+ u8 app_start;
+ u8 app_end;
+ u8 param_start;
+ u8 param_end;
+
+ u8 checksum_type;
+ u8 hw_category;
+
+ __le16 param_id;
+ __le32 param_length;
+ __le32 build_date;
+ __le32 build_time;
+
+ __le32 reserved1;
+ __le32 reserved2;
+ __le16 reserved3;
+ __le16 tail_size;
+ __le32 crc;
+} __packed;
+
+#define MIP4_BIN_TAIL_MARK "MBT\001"
+#define MIP4_BIN_TAIL_SIZE (sizeof(struct mip4_bin_tail))
+
+/*
+* Bootloader - Read status
+*/
+static int mip4_bl_read_status(struct mip4_ts *ts)
+{
+ u8 cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_STATUS };
+ u8 result;
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .buf = cmd,
+ .len = sizeof(cmd),
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .buf = &result,
+ .len = sizeof(result),
+ },
+ };
+ int ret;
+ int error;
+ int retry = 1000;
+
+ do {
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to read bootloader status: %d\n",
+ error);
+ return error;
+ }
+
+ switch (result) {
+ case MIP4_BOOT_STATUS_DONE:
+ dev_dbg(&ts->client->dev, "%s - done\n", __func__);
+ return 0;
+
+ case MIP4_BOOT_STATUS_ERROR:
+ dev_err(&ts->client->dev, "Bootloader failure\n");
+ return -EIO;
+
+ case MIP4_BOOT_STATUS_BUSY:
+ dev_dbg(&ts->client->dev, "%s - Busy\n", __func__);
+ error = -EBUSY;
+ break;
+
+ default:
+ dev_err(&ts->client->dev,
+ "Unexpected bootloader status: %#02x\n",
+ result);
+ error = -EINVAL;
+ break;
+ }
+
+ usleep_range(1000, 2000);
+ } while (--retry);
+
+ return error;
+}
+
+/*
+* Bootloader - Change mode
+*/
+static int mip4_bl_change_mode(struct mip4_ts *ts, u8 mode)
+{
+ u8 mode_chg_cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_MODE, mode };
+ u8 mode_read_cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_MODE };
+ u8 result;
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .buf = mode_read_cmd,
+ .len = sizeof(mode_read_cmd),
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .buf = &result,
+ .len = sizeof(result),
+ },
+ };
+ int retry = 10;
+ int ret;
+ int error;
+
+ do {
+ /* Send mode change command */
+ ret = i2c_master_send(ts->client,
+ mode_chg_cmd, sizeof(mode_chg_cmd));
+ if (ret != sizeof(mode_chg_cmd)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send %d mode change: %d (%d)\n",
+ mode, error, ret);
+ return error;
+ }
+
+ dev_dbg(&ts->client->dev,
+ "Sent mode change request (mode: %d)\n", mode);
+
+ /* Wait */
+ msleep(1000);
+
+ /* Verify target mode */
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to read device mode: %d\n", error);
+ return error;
+ }
+
+ dev_dbg(&ts->client->dev,
+ "Current device mode: %d, want: %d\n", result, mode);
+
+ if (result == mode)
+ return 0;
+
+ } while (--retry);
+
+ return -EIO;
+}
+
+/*
+ * Bootloader - Start bootloader mode
+ */
+static int mip4_bl_enter(struct mip4_ts *ts)
+{
+ return mip4_bl_change_mode(ts, MIP4_BOOT_MODE_BOOT);
+}
+
+/*
+ * Bootloader - Exit bootloader mode
+ */
+static int mip4_bl_exit(struct mip4_ts *ts)
+{
+ return mip4_bl_change_mode(ts, MIP4_BOOT_MODE_APP);
+}
+
+static int mip4_bl_get_address(struct mip4_ts *ts, u16 *buf_addr)
+{
+ u8 cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_BUF_ADDR };
+ u8 result[sizeof(u16)];
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .buf = cmd,
+ .len = sizeof(cmd),
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .buf = result,
+ .len = sizeof(result),
+ },
+ };
+ int ret;
+ int error;
+
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to retrieve bootloader buffer address: %d\n",
+ error);
+ return error;
+ }
+
+ *buf_addr = get_unaligned_le16(result);
+ dev_dbg(&ts->client->dev,
+ "Bootloader buffer address %#04x\n", *buf_addr);
+
+ return 0;
+}
+
+static int mip4_bl_program_page(struct mip4_ts *ts, int offset,
+ const u8 *data, int length, u16 buf_addr)
+{
+ u8 cmd[6];
+ u8 *data_buf;
+ u16 buf_offset;
+ int ret;
+ int error;
+
+ dev_dbg(&ts->client->dev, "Writing page @%#06x (%d)\n",
+ offset, length);
+
+ if (length > MIP4_BL_PAGE_SIZE || length % MIP4_BL_PACKET_SIZE) {
+ dev_err(&ts->client->dev,
+ "Invalid page length: %d\n", length);
+ return -EINVAL;
+ }
+
+ data_buf = kmalloc(2 + MIP4_BL_PACKET_SIZE, GFP_KERNEL);
+ if (!data_buf)
+ return -ENOMEM;
+
+ /* Addr */
+ cmd[0] = MIP4_R0_BOOT;
+ cmd[1] = MIP4_R1_BOOT_TARGET_ADDR;
+ put_unaligned_le32(offset, &cmd[2]);
+ ret = i2c_master_send(ts->client, cmd, 6);
+ if (ret != 6) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send write page address: %d\n", error);
+ goto out;
+ }
+
+ /* Size */
+ cmd[0] = MIP4_R0_BOOT;
+ cmd[1] = MIP4_R1_BOOT_SIZE;
+ put_unaligned_le32(length, &cmd[2]);
+ ret = i2c_master_send(ts->client, cmd, 6);
+ if (ret != 6) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send write page size: %d\n", error);
+ goto out;
+ }
+
+ /* Data */
+ for (buf_offset = 0;
+ buf_offset < length;
+ buf_offset += MIP4_BL_PACKET_SIZE) {
+ dev_dbg(&ts->client->dev,
+ "writing chunk at %#04x (size %d)\n",
+ buf_offset, MIP4_BL_PACKET_SIZE);
+ put_unaligned_be16(buf_addr + buf_offset, data_buf);
+ memcpy(&data_buf[2], &data[buf_offset], MIP4_BL_PACKET_SIZE);
+ ret = i2c_master_send(ts->client,
+ data_buf, 2 + MIP4_BL_PACKET_SIZE);
+ if (ret != 2 + MIP4_BL_PACKET_SIZE) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to read chunk at %#04x (size %d): %d\n",
+ buf_offset, MIP4_BL_PACKET_SIZE, error);
+ goto out;
+ }
+ }
+
+ /* Command */
+ cmd[0] = MIP4_R0_BOOT;
+ cmd[1] = MIP4_R1_BOOT_CMD;
+ cmd[2] = MIP4_BOOT_CMD_PROGRAM;
+ ret = i2c_master_send(ts->client, cmd, 3);
+ if (ret != 3) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send 'write' command: %d\n", error);
+ goto out;
+ }
+
+ /* Status */
+ error = mip4_bl_read_status(ts);
+
+out:
+ kfree(data_buf);
+ return error ? error : 0;
+}
+
+static int mip4_bl_verify_page(struct mip4_ts *ts, int offset,
+ const u8 *data, int length, int buf_addr)
+{
+ u8 cmd[8];
+ u8 *read_buf;
+ int buf_offset;
+ struct i2c_msg msg[] = {
+ {
+ .addr = ts->client->addr,
+ .flags = 0,
+ .buf = cmd,
+ .len = 2,
+ }, {
+ .addr = ts->client->addr,
+ .flags = I2C_M_RD,
+ .len = MIP4_BL_PACKET_SIZE,
+ },
+ };
+ int ret;
+ int error;
+
+ dev_dbg(&ts->client->dev, "Validating page @%#06x (%d)\n",
+ offset, length);
+
+ /* Addr */
+ cmd[0] = MIP4_R0_BOOT;
+ cmd[1] = MIP4_R1_BOOT_TARGET_ADDR;
+ put_unaligned_le32(offset, &cmd[2]);
+ ret = i2c_master_send(ts->client, cmd, 6);
+ if (ret != 6) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send read page address: %d\n", error);
+ return error;
+ }
+
+ /* Size */
+ cmd[0] = MIP4_R0_BOOT;
+ cmd[1] = MIP4_R1_BOOT_SIZE;
+ put_unaligned_le32(length, &cmd[2]);
+ ret = i2c_master_send(ts->client, cmd, 6);
+ if (ret != 6) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send read page size: %d\n", error);
+ return error;
+ }
+
+ /* Command */
+ cmd[0] = MIP4_R0_BOOT;
+ cmd[1] = MIP4_R1_BOOT_CMD;
+ cmd[2] = MIP4_BOOT_CMD_READ;
+ ret = i2c_master_send(ts->client, cmd, 3);
+ if (ret != 3) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to send 'read' command: %d\n", error);
+ return error;
+ }
+
+ /* Status */
+ error = mip4_bl_read_status(ts);
+ if (error)
+ return error;
+
+ /* Read */
+ msg[1].buf = read_buf = kmalloc(MIP4_BL_PACKET_SIZE, GFP_KERNEL);
+ if (!read_buf)
+ return -ENOMEM;
+
+ for (buf_offset = 0;
+ buf_offset < length;
+ buf_offset += MIP4_BL_PACKET_SIZE) {
+ dev_dbg(&ts->client->dev,
+ "reading chunk at %#04x (size %d)\n",
+ buf_offset, MIP4_BL_PACKET_SIZE);
+ put_unaligned_be16(buf_addr + buf_offset, cmd);
+ ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "Failed to read chunk at %#04x (size %d): %d\n",
+ buf_offset, MIP4_BL_PACKET_SIZE, error);
+ break;
+ }
+
+ if (memcmp(&data[buf_offset], read_buf, MIP4_BL_PACKET_SIZE)) {
+ dev_err(&ts->client->dev,
+ "Failed to validate chunk at %#04x (size %d)\n",
+ buf_offset, MIP4_BL_PACKET_SIZE);
+#if MIP4_FW_UPDATE_DEBUG
+ print_hex_dump(KERN_DEBUG,
+ MIP4_DEVICE_NAME " F/W File: ",
+ DUMP_PREFIX_OFFSET, 16, 1,
+ data + offset, MIP4_BL_PACKET_SIZE,
+ false);
+ print_hex_dump(KERN_DEBUG,
+ MIP4_DEVICE_NAME " F/W Chip: ",
+ DUMP_PREFIX_OFFSET, 16, 1,
+ read_buf, MIP4_BL_PAGE_SIZE, false);
+#endif
+ error = -EINVAL;
+ break;
+ }
+ }
+
+ kfree(read_buf);
+ return error ? error : 0;
+}
+
+/*
+ * Flash chip firmware
+ */
+static int mip4_flash_fw(struct mip4_ts *ts,
+ const u8 *fw_data, u32 fw_size, u32 fw_offset)
+{
+ struct i2c_client *client = ts->client;
+ int offset;
+ u16 buf_addr;
+ int error, error2;
+
+ /* Enter bootloader mode */
+ dev_dbg(&client->dev, "Entering bootloader mode\n");
+
+ error = mip4_bl_enter(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to enter bootloader mode: %d\n",
+ error);
+ return error;
+ }
+
+ /* Read info */
+ error = mip4_bl_get_address(ts, &buf_addr);
+ if (error)
+ goto exit_bl;
+
+ /* Program & Verify */
+ dev_dbg(&client->dev,
+ "Program & Verify, page size: %d, packet size: %d\n",
+ MIP4_BL_PAGE_SIZE, MIP4_BL_PACKET_SIZE);
+
+ for (offset = fw_offset;
+ offset < fw_offset + fw_size;
+ offset += MIP4_BL_PAGE_SIZE) {
+ /* Program */
+ error = mip4_bl_program_page(ts, offset, fw_data + offset,
+ MIP4_BL_PAGE_SIZE, buf_addr);
+ if (error)
+ break;
+
+ /* Verify */
+ error = mip4_bl_verify_page(ts, offset, fw_data + offset,
+ MIP4_BL_PAGE_SIZE, buf_addr);
+ if (error)
+ break;
+ }
+
+exit_bl:
+ /* Exit bootloader mode */
+ dev_dbg(&client->dev, "Exiting bootloader mode\n");
+
+ error2 = mip4_bl_exit(ts);
+ if (error2) {
+ dev_err(&client->dev,
+ "Failed to exit bootloader mode: %d\n", error2);
+ if (!error)
+ error = error2;
+ }
+
+ /* Reset chip */
+ mip4_power_off(ts);
+ mip4_power_on(ts);
+
+ mip4_query_device(ts);
+
+ /* Refresh device parameters */
+ input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
+ input_set_abs_params(ts->input, ABS_X, 0, ts->max_x, 0, 0);
+ input_set_abs_params(ts->input, ABS_Y, 0, ts->max_y, 0, 0);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->ppm_x);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->ppm_y);
+ input_abs_set_res(ts->input, ABS_X, ts->ppm_x);
+ input_abs_set_res(ts->input, ABS_Y, ts->ppm_y);
+
+ return error ? error : 0;
+}
+
+static int mip4_parse_firmware(struct mip4_ts *ts, const struct firmware *fw,
+ u32 *fw_offset_start, u32 *fw_size,
+ const struct mip4_bin_tail **pfw_info)
+{
+ const struct mip4_bin_tail *fw_info;
+ struct mip4_fw_version fw_version;
+ u16 tail_size;
+
+ if (fw->size < MIP4_BIN_TAIL_SIZE) {
+ dev_err(&ts->client->dev,
+ "Invalid firmware, size mismatch (tail %zd vs %zd)\n",
+ MIP4_BIN_TAIL_SIZE, fw->size);
+ return -EINVAL;
+ }
+
+ fw_info = (const void *)&fw->data[fw->size - MIP4_BIN_TAIL_SIZE];
+
+#if MIP4_FW_UPDATE_DEBUG
+ print_hex_dump(KERN_ERR, MIP4_DEVICE_NAME " Bin Info: ",
+ DUMP_PREFIX_OFFSET, 16, 1, *fw_info, tail_size, false);
+#endif
+
+ tail_size = get_unaligned_le16(&fw_info->tail_size);
+ if (tail_size != MIP4_BIN_TAIL_SIZE) {
+ dev_err(&ts->client->dev,
+ "wrong tail size: %d (expected %zd)\n",
+ tail_size, MIP4_BIN_TAIL_SIZE);
+ return -EINVAL;
+ }
+
+ /* Check bin format */
+ if (memcmp(fw_info->tail_mark, MIP4_BIN_TAIL_MARK,
+ sizeof(fw_info->tail_mark))) {
+ dev_err(&ts->client->dev,
+ "unable to locate tail marker (%*ph vs %*ph)\n",
+ (int)sizeof(fw_info->tail_mark), fw_info->tail_mark,
+ (int)sizeof(fw_info->tail_mark), MIP4_BIN_TAIL_MARK);
+ return -EINVAL;
+ }
+
+ *fw_offset_start = get_unaligned_le32(&fw_info->bin_start_addr);
+ *fw_size = get_unaligned_le32(&fw_info->bin_length);
+
+ dev_dbg(&ts->client->dev,
+ "F/W Data offset: %#08x, size: %d\n",
+ *fw_offset_start, *fw_size);
+
+ if (*fw_size % MIP4_BL_PAGE_SIZE) {
+ dev_err(&ts->client->dev,
+ "encoded fw length %d is not multiple of pages (%d)\n",
+ *fw_size, MIP4_BL_PAGE_SIZE);
+ return -EINVAL;
+ }
+
+ if (fw->size != *fw_offset_start + *fw_size) {
+ dev_err(&ts->client->dev,
+ "Wrong firmware size, expected %d bytes, got %zd\n",
+ *fw_offset_start + *fw_size, fw->size);
+ return -EINVAL;
+ }
+
+ mip4_parse_fw_version((const u8 *)&fw_info->ver_boot, &fw_version);
+
+ dev_dbg(&ts->client->dev,
+ "F/W file version %04X %04X %04X %04X\n",
+ fw_version.boot, fw_version.core,
+ fw_version.app, fw_version.param);
+
+ dev_dbg(&ts->client->dev, "F/W chip version: %04X %04X %04X %04X\n",
+ ts->fw_version.boot, ts->fw_version.core,
+ ts->fw_version.app, ts->fw_version.param);
+
+ /* Check F/W type */
+ if (fw_version.boot != 0xEEEE && fw_version.boot != 0xFFFF &&
+ fw_version.core == 0xEEEE &&
+ fw_version.app == 0xEEEE &&
+ fw_version.param == 0xEEEE) {
+ dev_dbg(&ts->client->dev, "F/W type: Bootloader\n");
+ } else if (fw_version.boot == 0xEEEE &&
+ fw_version.core != 0xEEEE && fw_version.core != 0xFFFF &&
+ fw_version.app != 0xEEEE && fw_version.app != 0xFFFF &&
+ fw_version.param != 0xEEEE && fw_version.param != 0xFFFF) {
+ dev_dbg(&ts->client->dev, "F/W type: Main\n");
+ } else {
+ dev_err(&ts->client->dev, "Wrong firmware type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mip4_execute_fw_update(struct mip4_ts *ts, const struct firmware *fw)
+{
+ const struct mip4_bin_tail *fw_info;
+ u32 fw_start_offset;
+ u32 fw_size;
+ int retires = 3;
+ int error;
+
+ error = mip4_parse_firmware(ts, fw,
+ &fw_start_offset, &fw_size, &fw_info);
+ if (error)
+ return error;
+
+ if (input_device_enabled(ts->input)) {
+ disable_irq(ts->client->irq);
+ } else {
+ error = mip4_power_on(ts);
+ if (error)
+ return error;
+ }
+
+ /* Update firmware */
+ do {
+ error = mip4_flash_fw(ts, fw->data, fw_size, fw_start_offset);
+ if (!error)
+ break;
+ } while (--retires);
+
+ if (error)
+ dev_err(&ts->client->dev,
+ "Failed to flash firmware: %d\n", error);
+
+ /* Enable IRQ */
+ if (input_device_enabled(ts->input))
+ enable_irq(ts->client->irq);
+ else
+ mip4_power_off(ts);
+
+ return error ? error : 0;
+}
+
+static ssize_t mip4_sysfs_fw_update(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ const struct firmware *fw;
+ int error;
+
+ error = request_firmware(&fw, ts->fw_name, dev);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Failed to retrieve firmware %s: %d\n",
+ ts->fw_name, error);
+ return error;
+ }
+
+ /*
+ * Take input mutex to prevent racing with itself and also with
+ * userspace opening and closing the device and also suspend/resume
+ * transitions.
+ */
+ mutex_lock(&ts->input->mutex);
+
+ error = mip4_execute_fw_update(ts, fw);
+
+ mutex_unlock(&ts->input->mutex);
+
+ release_firmware(fw);
+
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Firmware update failed: %d\n", error);
+ return error;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mip4_sysfs_fw_update);
+
+static ssize_t mip4_sysfs_read_fw_version(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ size_t count;
+
+ /* Take lock to prevent racing with firmware update */
+ mutex_lock(&ts->input->mutex);
+
+ count = snprintf(buf, PAGE_SIZE, "%04X %04X %04X %04X\n",
+ ts->fw_version.boot, ts->fw_version.core,
+ ts->fw_version.app, ts->fw_version.param);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(fw_version, S_IRUGO, mip4_sysfs_read_fw_version, NULL);
+
+static ssize_t mip4_sysfs_read_hw_version(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ size_t count;
+
+ /* Take lock to prevent racing with firmware update */
+ mutex_lock(&ts->input->mutex);
+
+ /*
+ * product_name shows the name or version of the hardware
+ * paired with current firmware in the chip.
+ */
+ count = snprintf(buf, PAGE_SIZE, "%.*s\n",
+ (int)sizeof(ts->product_name), ts->product_name);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(hw_version, S_IRUGO, mip4_sysfs_read_hw_version, NULL);
+
+static ssize_t mip4_sysfs_read_product_id(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ size_t count;
+
+ mutex_lock(&ts->input->mutex);
+
+ count = snprintf(buf, PAGE_SIZE, "%04X\n", ts->product_id);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(product_id, S_IRUGO, mip4_sysfs_read_product_id, NULL);
+
+static ssize_t mip4_sysfs_read_ic_name(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ size_t count;
+
+ mutex_lock(&ts->input->mutex);
+
+ count = snprintf(buf, PAGE_SIZE, "%.*s\n",
+ (int)sizeof(ts->ic_name), ts->ic_name);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(ic_name, S_IRUGO, mip4_sysfs_read_ic_name, NULL);
+
+static struct attribute *mip4_attrs[] = {
+ &dev_attr_fw_version.attr,
+ &dev_attr_hw_version.attr,
+ &dev_attr_product_id.attr,
+ &dev_attr_ic_name.attr,
+ &dev_attr_update_fw.attr,
+ NULL,
+};
+
+static const struct attribute_group mip4_attr_group = {
+ .attrs = mip4_attrs,
+};
+
+static int mip4_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct mip4_ts *ts;
+ struct input_dev *input;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "Not supported I2C adapter\n");
+ return -ENXIO;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(&client->dev);
+ if (!input)
+ return -ENOMEM;
+
+ ts->client = client;
+ ts->input = input;
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input0", dev_name(&client->dev));
+
+ ts->gpio_ce = devm_gpiod_get_optional(&client->dev,
+ "ce", GPIOD_OUT_LOW);
+ if (IS_ERR(ts->gpio_ce)) {
+ error = PTR_ERR(ts->gpio_ce);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get gpio: %d\n", error);
+ return error;
+ }
+
+ error = mip4_power_on(ts);
+ if (error)
+ return error;
+ error = mip4_query_device(ts);
+ mip4_power_off(ts);
+ if (error)
+ return error;
+
+ input->name = "MELFAS MIP4 Touchscreen";
+ input->phys = ts->phys;
+
+ input->id.bustype = BUS_I2C;
+ input->id.vendor = 0x13c5;
+ input->id.product = ts->product_id;
+
+ input->open = mip4_input_open;
+ input->close = mip4_input_close;
+
+ input_set_drvdata(input, ts);
+
+ input->keycode = ts->key_code;
+ input->keycodesize = sizeof(*ts->key_code);
+ input->keycodemax = ts->key_num;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE,
+ MIP4_PRESSURE_MIN, MIP4_PRESSURE_MAX, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR,
+ MIP4_TOUCH_MAJOR_MIN, MIP4_TOUCH_MAJOR_MAX, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR,
+ MIP4_TOUCH_MINOR_MIN, MIP4_TOUCH_MINOR_MAX, 0, 0);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->ppm_x);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->ppm_y);
+
+ error = input_mt_init_slots(input, MIP4_MAX_FINGERS, INPUT_MT_DIRECT);
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, ts);
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, mip4_interrupt,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ MIP4_DEVICE_NAME, ts);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to request interrupt %d: %d\n",
+ client->irq, error);
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &mip4_attr_group);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to create sysfs attribute group: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused mip4_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+
+ mutex_lock(&input->mutex);
+
+ if (device_may_wakeup(dev))
+ ts->wake_irq_enabled = enable_irq_wake(client->irq) == 0;
+ else if (input_device_enabled(input))
+ mip4_disable(ts);
+
+ mutex_unlock(&input->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused mip4_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mip4_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+
+ mutex_lock(&input->mutex);
+
+ if (ts->wake_irq_enabled)
+ disable_irq_wake(client->irq);
+ else if (input_device_enabled(input))
+ mip4_enable(ts);
+
+ mutex_unlock(&input->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mip4_pm_ops, mip4_suspend, mip4_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id mip4_of_match[] = {
+ { .compatible = "melfas,"MIP4_DEVICE_NAME, },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mip4_of_match);
+#endif
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id mip4_acpi_match[] = {
+ { "MLFS0000", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, mip4_acpi_match);
+#endif
+
+static const struct i2c_device_id mip4_i2c_ids[] = {
+ { MIP4_DEVICE_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, mip4_i2c_ids);
+
+static struct i2c_driver mip4_driver = {
+ .id_table = mip4_i2c_ids,
+ .probe = mip4_probe,
+ .driver = {
+ .name = MIP4_DEVICE_NAME,
+ .of_match_table = of_match_ptr(mip4_of_match),
+ .acpi_match_table = ACPI_PTR(mip4_acpi_match),
+ .pm = &mip4_pm_ops,
+ },
+};
+module_i2c_driver(mip4_driver);
+
+MODULE_DESCRIPTION("MELFAS MIP4 Touchscreen");
+MODULE_AUTHOR("Sangwon Jee <jeesw@melfas.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/migor_ts.c b/drivers/input/touchscreen/migor_ts.c
new file mode 100644
index 000000000..79cd660d8
--- /dev/null
+++ b/drivers/input/touchscreen/migor_ts.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Touch Screen driver for Renesas MIGO-R Platform
+ *
+ * Copyright (c) 2008 Magnus Damm
+ * Copyright (c) 2007 Ujjwal Pande <ujjwal@kenati.com>,
+ * Kenati Technologies Pvt Ltd.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <linux/i2c.h>
+#include <linux/timer.h>
+
+#define EVENT_PENDOWN 1
+#define EVENT_REPEAT 2
+#define EVENT_PENUP 3
+
+struct migor_ts_priv {
+ struct i2c_client *client;
+ struct input_dev *input;
+ int irq;
+};
+
+static const u_int8_t migor_ts_ena_seq[17] = { 0x33, 0x22, 0x11,
+ 0x01, 0x06, 0x07, };
+static const u_int8_t migor_ts_dis_seq[17] = { };
+
+static irqreturn_t migor_ts_isr(int irq, void *dev_id)
+{
+ struct migor_ts_priv *priv = dev_id;
+ unsigned short xpos, ypos;
+ unsigned char event;
+ u_int8_t buf[16];
+
+ /*
+ * The touch screen controller chip is hooked up to the CPU
+ * using I2C and a single interrupt line. The interrupt line
+ * is pulled low whenever someone taps the screen. To deassert
+ * the interrupt line we need to acknowledge the interrupt by
+ * communicating with the controller over the slow i2c bus.
+ *
+ * Since I2C bus controller may sleep we are using threaded
+ * IRQ here.
+ */
+
+ memset(buf, 0, sizeof(buf));
+
+ /* Set Index 0 */
+ buf[0] = 0;
+ if (i2c_master_send(priv->client, buf, 1) != 1) {
+ dev_err(&priv->client->dev, "Unable to write i2c index\n");
+ goto out;
+ }
+
+ /* Now do Page Read */
+ if (i2c_master_recv(priv->client, buf, sizeof(buf)) != sizeof(buf)) {
+ dev_err(&priv->client->dev, "Unable to read i2c page\n");
+ goto out;
+ }
+
+ ypos = ((buf[9] & 0x03) << 8 | buf[8]);
+ xpos = ((buf[11] & 0x03) << 8 | buf[10]);
+ event = buf[12];
+
+ switch (event) {
+ case EVENT_PENDOWN:
+ case EVENT_REPEAT:
+ input_report_key(priv->input, BTN_TOUCH, 1);
+ input_report_abs(priv->input, ABS_X, ypos); /*X-Y swap*/
+ input_report_abs(priv->input, ABS_Y, xpos);
+ input_sync(priv->input);
+ break;
+
+ case EVENT_PENUP:
+ input_report_key(priv->input, BTN_TOUCH, 0);
+ input_sync(priv->input);
+ break;
+ }
+
+ out:
+ return IRQ_HANDLED;
+}
+
+static int migor_ts_open(struct input_dev *dev)
+{
+ struct migor_ts_priv *priv = input_get_drvdata(dev);
+ struct i2c_client *client = priv->client;
+ int count;
+
+ /* enable controller */
+ count = i2c_master_send(client, migor_ts_ena_seq,
+ sizeof(migor_ts_ena_seq));
+ if (count != sizeof(migor_ts_ena_seq)) {
+ dev_err(&client->dev, "Unable to enable touchscreen.\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static void migor_ts_close(struct input_dev *dev)
+{
+ struct migor_ts_priv *priv = input_get_drvdata(dev);
+ struct i2c_client *client = priv->client;
+
+ disable_irq(priv->irq);
+
+ /* disable controller */
+ i2c_master_send(client, migor_ts_dis_seq, sizeof(migor_ts_dis_seq));
+
+ enable_irq(priv->irq);
+}
+
+static int migor_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *idp)
+{
+ struct migor_ts_priv *priv;
+ struct input_dev *input;
+ int error;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ input = input_allocate_device();
+ if (!priv || !input) {
+ dev_err(&client->dev, "failed to allocate memory\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ priv->client = client;
+ priv->input = input;
+ priv->irq = client->irq;
+
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+
+ __set_bit(BTN_TOUCH, input->keybit);
+
+ input_set_abs_params(input, ABS_X, 95, 955, 0, 0);
+ input_set_abs_params(input, ABS_Y, 85, 935, 0, 0);
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &client->dev;
+
+ input->open = migor_ts_open;
+ input->close = migor_ts_close;
+
+ input_set_drvdata(input, priv);
+
+ error = request_threaded_irq(priv->irq, NULL, migor_ts_isr,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ client->name, priv);
+ if (error) {
+ dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
+ goto err_free_mem;
+ }
+
+ error = input_register_device(input);
+ if (error)
+ goto err_free_irq;
+
+ i2c_set_clientdata(client, priv);
+ device_init_wakeup(&client->dev, 1);
+
+ return 0;
+
+ err_free_irq:
+ free_irq(priv->irq, priv);
+ err_free_mem:
+ input_free_device(input);
+ kfree(priv);
+ return error;
+}
+
+static void migor_ts_remove(struct i2c_client *client)
+{
+ struct migor_ts_priv *priv = i2c_get_clientdata(client);
+
+ free_irq(priv->irq, priv);
+ input_unregister_device(priv->input);
+ kfree(priv);
+
+ dev_set_drvdata(&client->dev, NULL);
+}
+
+static int __maybe_unused migor_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct migor_ts_priv *priv = i2c_get_clientdata(client);
+
+ if (device_may_wakeup(&client->dev))
+ enable_irq_wake(priv->irq);
+
+ return 0;
+}
+
+static int __maybe_unused migor_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct migor_ts_priv *priv = i2c_get_clientdata(client);
+
+ if (device_may_wakeup(&client->dev))
+ disable_irq_wake(priv->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(migor_ts_pm, migor_ts_suspend, migor_ts_resume);
+
+static const struct i2c_device_id migor_ts_id[] = {
+ { "migor_ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, migor_ts_id);
+
+static struct i2c_driver migor_ts_driver = {
+ .driver = {
+ .name = "migor_ts",
+ .pm = &migor_ts_pm,
+ },
+ .probe = migor_ts_probe,
+ .remove = migor_ts_remove,
+ .id_table = migor_ts_id,
+};
+
+module_i2c_driver(migor_ts_driver);
+
+MODULE_DESCRIPTION("MigoR Touchscreen driver");
+MODULE_AUTHOR("Magnus Damm <damm@opensource.se>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/mk712.c b/drivers/input/touchscreen/mk712.c
new file mode 100644
index 000000000..753d9cc1d
--- /dev/null
+++ b/drivers/input/touchscreen/mk712.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ICS MK712 touchscreen controller driver
+ *
+ * Copyright (c) 1999-2002 Transmeta Corporation
+ * Copyright (c) 2005 Rick Koch <n1gp@hotmail.com>
+ * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
+ */
+
+
+/*
+ * This driver supports the ICS MicroClock MK712 TouchScreen controller,
+ * found in Gateway AOL Connected Touchpad computers.
+ *
+ * Documentation for ICS MK712 can be found at:
+ * https://www.idt.com/general-parts/mk712-touch-screen-controller
+ */
+
+/*
+ * 1999-12-18: original version, Daniel Quinlan
+ * 1999-12-19: added anti-jitter code, report pen-up events, fixed mk712_poll
+ * to use queue_empty, Nathan Laredo
+ * 1999-12-20: improved random point rejection, Nathan Laredo
+ * 2000-01-05: checked in new anti-jitter code, changed mouse protocol, fixed
+ * queue code, added module options, other fixes, Daniel Quinlan
+ * 2002-03-15: Clean up for kernel merge <alan@redhat.com>
+ * Fixed multi open race, fixed memory checks, fixed resource
+ * allocation, fixed close/powerdown bug, switched to new init
+ * 2005-01-18: Ported to 2.6 from 2.4.28, Rick Koch
+ * 2005-02-05: Rewritten for the input layer, Vojtech Pavlik
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <asm/io.h>
+
+MODULE_AUTHOR("Daniel Quinlan <quinlan@pathname.com>, Vojtech Pavlik <vojtech@suse.cz>");
+MODULE_DESCRIPTION("ICS MicroClock MK712 TouchScreen driver");
+MODULE_LICENSE("GPL");
+
+static unsigned int mk712_io = 0x260; /* Also 0x200, 0x208, 0x300 */
+module_param_hw_named(io, mk712_io, uint, ioport, 0);
+MODULE_PARM_DESC(io, "I/O base address of MK712 touchscreen controller");
+
+static unsigned int mk712_irq = 10; /* Also 12, 14, 15 */
+module_param_hw_named(irq, mk712_irq, uint, irq, 0);
+MODULE_PARM_DESC(irq, "IRQ of MK712 touchscreen controller");
+
+/* eight 8-bit registers */
+#define MK712_STATUS 0
+#define MK712_X 2
+#define MK712_Y 4
+#define MK712_CONTROL 6
+#define MK712_RATE 7
+
+/* status */
+#define MK712_STATUS_TOUCH 0x10
+#define MK712_CONVERSION_COMPLETE 0x80
+
+/* control */
+#define MK712_ENABLE_INT 0x01
+#define MK712_INT_ON_CONVERSION_COMPLETE 0x02
+#define MK712_INT_ON_CHANGE_IN_TOUCH_STATUS 0x04
+#define MK712_ENABLE_PERIODIC_CONVERSIONS 0x10
+#define MK712_READ_ONE_POINT 0x20
+#define MK712_POWERUP 0x40
+
+static struct input_dev *mk712_dev;
+static DEFINE_SPINLOCK(mk712_lock);
+
+static irqreturn_t mk712_interrupt(int irq, void *dev_id)
+{
+ unsigned char status;
+ static int debounce = 1;
+ static unsigned short last_x;
+ static unsigned short last_y;
+
+ spin_lock(&mk712_lock);
+
+ status = inb(mk712_io + MK712_STATUS);
+
+ if (~status & MK712_CONVERSION_COMPLETE) {
+ debounce = 1;
+ goto end;
+ }
+
+ if (~status & MK712_STATUS_TOUCH) {
+ debounce = 1;
+ input_report_key(mk712_dev, BTN_TOUCH, 0);
+ goto end;
+ }
+
+ if (debounce) {
+ debounce = 0;
+ goto end;
+ }
+
+ input_report_key(mk712_dev, BTN_TOUCH, 1);
+ input_report_abs(mk712_dev, ABS_X, last_x);
+ input_report_abs(mk712_dev, ABS_Y, last_y);
+
+ end:
+ last_x = inw(mk712_io + MK712_X) & 0x0fff;
+ last_y = inw(mk712_io + MK712_Y) & 0x0fff;
+ input_sync(mk712_dev);
+ spin_unlock(&mk712_lock);
+ return IRQ_HANDLED;
+}
+
+static int mk712_open(struct input_dev *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mk712_lock, flags);
+
+ outb(0, mk712_io + MK712_CONTROL); /* Reset */
+
+ outb(MK712_ENABLE_INT | MK712_INT_ON_CONVERSION_COMPLETE |
+ MK712_INT_ON_CHANGE_IN_TOUCH_STATUS |
+ MK712_ENABLE_PERIODIC_CONVERSIONS |
+ MK712_POWERUP, mk712_io + MK712_CONTROL);
+
+ outb(10, mk712_io + MK712_RATE); /* 187 points per second */
+
+ spin_unlock_irqrestore(&mk712_lock, flags);
+
+ return 0;
+}
+
+static void mk712_close(struct input_dev *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mk712_lock, flags);
+
+ outb(0, mk712_io + MK712_CONTROL);
+
+ spin_unlock_irqrestore(&mk712_lock, flags);
+}
+
+static int __init mk712_init(void)
+{
+ int err;
+
+ if (!request_region(mk712_io, 8, "mk712")) {
+ printk(KERN_WARNING "mk712: unable to get IO region\n");
+ return -ENODEV;
+ }
+
+ outb(0, mk712_io + MK712_CONTROL);
+
+ if ((inw(mk712_io + MK712_X) & 0xf000) || /* Sanity check */
+ (inw(mk712_io + MK712_Y) & 0xf000) ||
+ (inw(mk712_io + MK712_STATUS) & 0xf333)) {
+ printk(KERN_WARNING "mk712: device not present\n");
+ err = -ENODEV;
+ goto fail1;
+ }
+
+ mk712_dev = input_allocate_device();
+ if (!mk712_dev) {
+ printk(KERN_ERR "mk712: not enough memory\n");
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ mk712_dev->name = "ICS MicroClock MK712 TouchScreen";
+ mk712_dev->phys = "isa0260/input0";
+ mk712_dev->id.bustype = BUS_ISA;
+ mk712_dev->id.vendor = 0x0005;
+ mk712_dev->id.product = 0x0001;
+ mk712_dev->id.version = 0x0100;
+
+ mk712_dev->open = mk712_open;
+ mk712_dev->close = mk712_close;
+
+ mk712_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ mk712_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(mk712_dev, ABS_X, 0, 0xfff, 88, 0);
+ input_set_abs_params(mk712_dev, ABS_Y, 0, 0xfff, 88, 0);
+
+ if (request_irq(mk712_irq, mk712_interrupt, 0, "mk712", mk712_dev)) {
+ printk(KERN_WARNING "mk712: unable to get IRQ\n");
+ err = -EBUSY;
+ goto fail1;
+ }
+
+ err = input_register_device(mk712_dev);
+ if (err)
+ goto fail2;
+
+ return 0;
+
+ fail2: free_irq(mk712_irq, mk712_dev);
+ fail1: input_free_device(mk712_dev);
+ release_region(mk712_io, 8);
+ return err;
+}
+
+static void __exit mk712_exit(void)
+{
+ input_unregister_device(mk712_dev);
+ free_irq(mk712_irq, mk712_dev);
+ release_region(mk712_io, 8);
+}
+
+module_init(mk712_init);
+module_exit(mk712_exit);
diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c
new file mode 100644
index 000000000..9fa3b0e42
--- /dev/null
+++ b/drivers/input/touchscreen/mms114.c
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: GPL-2.0
+// Melfas MMS114/MMS136/MMS152 touchscreen device driver
+//
+// Copyright (c) 2012 Samsung Electronics Co., Ltd.
+// Author: Joonyoung Shim <jy0922.shim@samsung.com>
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Write only registers */
+#define MMS114_MODE_CONTROL 0x01
+#define MMS114_OPERATION_MODE_MASK 0xE
+#define MMS114_ACTIVE BIT(1)
+
+#define MMS114_XY_RESOLUTION_H 0x02
+#define MMS114_X_RESOLUTION 0x03
+#define MMS114_Y_RESOLUTION 0x04
+#define MMS114_CONTACT_THRESHOLD 0x05
+#define MMS114_MOVING_THRESHOLD 0x06
+
+/* Read only registers */
+#define MMS114_PACKET_SIZE 0x0F
+#define MMS114_INFORMATION 0x10
+#define MMS114_TSP_REV 0xF0
+
+#define MMS152_FW_REV 0xE1
+#define MMS152_COMPAT_GROUP 0xF2
+
+/* Minimum delay time is 50us between stop and start signal of i2c */
+#define MMS114_I2C_DELAY 50
+
+/* 200ms needs after power on */
+#define MMS114_POWERON_DELAY 200
+
+/* Touchscreen absolute values */
+#define MMS114_MAX_AREA 0xff
+
+#define MMS114_MAX_TOUCH 10
+#define MMS114_EVENT_SIZE 8
+#define MMS136_EVENT_SIZE 6
+
+/* Touch type */
+#define MMS114_TYPE_NONE 0
+#define MMS114_TYPE_TOUCHSCREEN 1
+#define MMS114_TYPE_TOUCHKEY 2
+
+enum mms_type {
+ TYPE_MMS114 = 114,
+ TYPE_MMS134S = 134,
+ TYPE_MMS136 = 136,
+ TYPE_MMS152 = 152,
+ TYPE_MMS345L = 345,
+};
+
+struct mms114_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct regulator *core_reg;
+ struct regulator *io_reg;
+ struct touchscreen_properties props;
+ enum mms_type type;
+ unsigned int contact_threshold;
+ unsigned int moving_threshold;
+
+ /* Use cache data for mode control register(write only) */
+ u8 cache_mode_control;
+};
+
+struct mms114_touch {
+ u8 id:4, reserved_bit4:1, type:2, pressed:1;
+ u8 x_hi:4, y_hi:4;
+ u8 x_lo;
+ u8 y_lo;
+ u8 width;
+ u8 strength;
+ u8 reserved[2];
+} __packed;
+
+static int __mms114_read_reg(struct mms114_data *data, unsigned int reg,
+ unsigned int len, u8 *val)
+{
+ struct i2c_client *client = data->client;
+ struct i2c_msg xfer[2];
+ u8 buf = reg & 0xff;
+ int error;
+
+ if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL)
+ BUG();
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = client->flags & I2C_M_TEN;
+ xfer[0].len = 1;
+ xfer[0].buf = &buf;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD;
+ xfer[1].len = len;
+ xfer[1].buf = val;
+
+ error = i2c_transfer(client->adapter, xfer, 2);
+ if (error != 2) {
+ dev_err(&client->dev,
+ "%s: i2c transfer failed (%d)\n", __func__, error);
+ return error < 0 ? error : -EIO;
+ }
+ udelay(MMS114_I2C_DELAY);
+
+ return 0;
+}
+
+static int mms114_read_reg(struct mms114_data *data, unsigned int reg)
+{
+ u8 val;
+ int error;
+
+ if (reg == MMS114_MODE_CONTROL)
+ return data->cache_mode_control;
+
+ error = __mms114_read_reg(data, reg, 1, &val);
+ return error < 0 ? error : val;
+}
+
+static int mms114_write_reg(struct mms114_data *data, unsigned int reg,
+ unsigned int val)
+{
+ struct i2c_client *client = data->client;
+ u8 buf[2];
+ int error;
+
+ buf[0] = reg & 0xff;
+ buf[1] = val & 0xff;
+
+ error = i2c_master_send(client, buf, 2);
+ if (error != 2) {
+ dev_err(&client->dev,
+ "%s: i2c send failed (%d)\n", __func__, error);
+ return error < 0 ? error : -EIO;
+ }
+ udelay(MMS114_I2C_DELAY);
+
+ if (reg == MMS114_MODE_CONTROL)
+ data->cache_mode_control = val;
+
+ return 0;
+}
+
+static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *touch)
+{
+ struct i2c_client *client = data->client;
+ struct input_dev *input_dev = data->input_dev;
+ unsigned int id;
+ unsigned int x;
+ unsigned int y;
+
+ if (touch->id > MMS114_MAX_TOUCH) {
+ dev_err(&client->dev, "Wrong touch id (%d)\n", touch->id);
+ return;
+ }
+
+ if (touch->type != MMS114_TYPE_TOUCHSCREEN) {
+ dev_err(&client->dev, "Wrong touch type (%d)\n", touch->type);
+ return;
+ }
+
+ id = touch->id - 1;
+ x = touch->x_lo | touch->x_hi << 8;
+ y = touch->y_lo | touch->y_hi << 8;
+
+ dev_dbg(&client->dev,
+ "id: %d, type: %d, pressed: %d, x: %d, y: %d, width: %d, strength: %d\n",
+ id, touch->type, touch->pressed,
+ x, y, touch->width, touch->strength);
+
+ input_mt_slot(input_dev, id);
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, touch->pressed);
+
+ if (touch->pressed) {
+ touchscreen_report_pos(input_dev, &data->props, x, y, true);
+ input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width);
+ input_report_abs(input_dev, ABS_MT_PRESSURE, touch->strength);
+ }
+}
+
+static irqreturn_t mms114_interrupt(int irq, void *dev_id)
+{
+ struct mms114_data *data = dev_id;
+ struct input_dev *input_dev = data->input_dev;
+ struct mms114_touch touch[MMS114_MAX_TOUCH];
+ int packet_size;
+ int touch_size;
+ int index;
+ int error;
+
+ mutex_lock(&input_dev->mutex);
+ if (!input_device_enabled(input_dev)) {
+ mutex_unlock(&input_dev->mutex);
+ goto out;
+ }
+ mutex_unlock(&input_dev->mutex);
+
+ packet_size = mms114_read_reg(data, MMS114_PACKET_SIZE);
+ if (packet_size <= 0)
+ goto out;
+
+ /* MMS136 has slightly different event size */
+ if (data->type == TYPE_MMS134S || data->type == TYPE_MMS136)
+ touch_size = packet_size / MMS136_EVENT_SIZE;
+ else
+ touch_size = packet_size / MMS114_EVENT_SIZE;
+
+ error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size,
+ (u8 *)touch);
+ if (error < 0)
+ goto out;
+
+ for (index = 0; index < touch_size; index++)
+ mms114_process_mt(data, touch + index);
+
+ input_mt_report_pointer_emulation(data->input_dev, true);
+ input_sync(data->input_dev);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int mms114_set_active(struct mms114_data *data, bool active)
+{
+ int val;
+
+ val = mms114_read_reg(data, MMS114_MODE_CONTROL);
+ if (val < 0)
+ return val;
+
+ val &= ~MMS114_OPERATION_MODE_MASK;
+
+ /* If active is false, sleep mode */
+ if (active)
+ val |= MMS114_ACTIVE;
+
+ return mms114_write_reg(data, MMS114_MODE_CONTROL, val);
+}
+
+static int mms114_get_version(struct mms114_data *data)
+{
+ struct device *dev = &data->client->dev;
+ u8 buf[6];
+ int group;
+ int error;
+
+ switch (data->type) {
+ case TYPE_MMS345L:
+ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf);
+ if (error)
+ return error;
+
+ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n",
+ buf[0], buf[1], buf[2]);
+ break;
+
+ case TYPE_MMS152:
+ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf);
+ if (error)
+ return error;
+
+ group = i2c_smbus_read_byte_data(data->client,
+ MMS152_COMPAT_GROUP);
+ if (group < 0)
+ return group;
+
+ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n",
+ buf[0], buf[1], buf[2], group);
+ break;
+
+ case TYPE_MMS114:
+ case TYPE_MMS134S:
+ case TYPE_MMS136:
+ error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf);
+ if (error)
+ return error;
+
+ dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n",
+ buf[0], buf[1], buf[3]);
+ break;
+ }
+
+ return 0;
+}
+
+static int mms114_setup_regs(struct mms114_data *data)
+{
+ const struct touchscreen_properties *props = &data->props;
+ int val;
+ int error;
+
+ error = mms114_get_version(data);
+ if (error < 0)
+ return error;
+
+ /* MMS114, MMS134S and MMS136 have configuration and power on registers */
+ if (data->type != TYPE_MMS114 && data->type != TYPE_MMS134S &&
+ data->type != TYPE_MMS136)
+ return 0;
+
+ error = mms114_set_active(data, true);
+ if (error < 0)
+ return error;
+
+ val = (props->max_x >> 8) & 0xf;
+ val |= ((props->max_y >> 8) & 0xf) << 4;
+ error = mms114_write_reg(data, MMS114_XY_RESOLUTION_H, val);
+ if (error < 0)
+ return error;
+
+ val = props->max_x & 0xff;
+ error = mms114_write_reg(data, MMS114_X_RESOLUTION, val);
+ if (error < 0)
+ return error;
+
+ val = props->max_x & 0xff;
+ error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val);
+ if (error < 0)
+ return error;
+
+ if (data->contact_threshold) {
+ error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD,
+ data->contact_threshold);
+ if (error < 0)
+ return error;
+ }
+
+ if (data->moving_threshold) {
+ error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD,
+ data->moving_threshold);
+ if (error < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int mms114_start(struct mms114_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+
+ error = regulator_enable(data->core_reg);
+ if (error) {
+ dev_err(&client->dev, "Failed to enable avdd: %d\n", error);
+ return error;
+ }
+
+ error = regulator_enable(data->io_reg);
+ if (error) {
+ dev_err(&client->dev, "Failed to enable vdd: %d\n", error);
+ regulator_disable(data->core_reg);
+ return error;
+ }
+
+ msleep(MMS114_POWERON_DELAY);
+
+ error = mms114_setup_regs(data);
+ if (error < 0) {
+ regulator_disable(data->io_reg);
+ regulator_disable(data->core_reg);
+ return error;
+ }
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static void mms114_stop(struct mms114_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+
+ disable_irq(client->irq);
+
+ error = regulator_disable(data->io_reg);
+ if (error)
+ dev_warn(&client->dev, "Failed to disable vdd: %d\n", error);
+
+ error = regulator_disable(data->core_reg);
+ if (error)
+ dev_warn(&client->dev, "Failed to disable avdd: %d\n", error);
+}
+
+static int mms114_input_open(struct input_dev *dev)
+{
+ struct mms114_data *data = input_get_drvdata(dev);
+
+ return mms114_start(data);
+}
+
+static void mms114_input_close(struct input_dev *dev)
+{
+ struct mms114_data *data = input_get_drvdata(dev);
+
+ mms114_stop(data);
+}
+
+static int mms114_parse_legacy_bindings(struct mms114_data *data)
+{
+ struct device *dev = &data->client->dev;
+ struct touchscreen_properties *props = &data->props;
+
+ if (device_property_read_u32(dev, "x-size", &props->max_x)) {
+ dev_dbg(dev, "failed to get legacy x-size property\n");
+ return -EINVAL;
+ }
+
+ if (device_property_read_u32(dev, "y-size", &props->max_y)) {
+ dev_dbg(dev, "failed to get legacy y-size property\n");
+ return -EINVAL;
+ }
+
+ device_property_read_u32(dev, "contact-threshold",
+ &data->contact_threshold);
+ device_property_read_u32(dev, "moving-threshold",
+ &data->moving_threshold);
+
+ if (device_property_read_bool(dev, "x-invert"))
+ props->invert_x = true;
+ if (device_property_read_bool(dev, "y-invert"))
+ props->invert_y = true;
+
+ props->swap_x_y = false;
+
+ return 0;
+}
+
+static int mms114_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mms114_data *data;
+ struct input_dev *input_dev;
+ const void *match_data;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "Not supported I2C adapter\n");
+ return -ENODEV;
+ }
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mms114_data),
+ GFP_KERNEL);
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!data || !input_dev) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ data->client = client;
+ data->input_dev = input_dev;
+
+ match_data = device_get_match_data(&client->dev);
+ if (!match_data)
+ return -EINVAL;
+
+ data->type = (enum mms_type)match_data;
+
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y);
+ input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
+ 0, MMS114_MAX_AREA, 0, 0);
+
+ touchscreen_parse_properties(input_dev, true, &data->props);
+ if (!data->props.max_x || !data->props.max_y) {
+ dev_dbg(&client->dev,
+ "missing X/Y size properties, trying legacy bindings\n");
+ error = mms114_parse_legacy_bindings(data);
+ if (error)
+ return error;
+
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+ 0, data->props.max_x, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+ 0, data->props.max_y, 0, 0);
+ }
+
+ if (data->type == TYPE_MMS114 || data->type == TYPE_MMS134S ||
+ data->type == TYPE_MMS136) {
+ /*
+ * The firmware handles movement and pressure fuzz, so
+ * don't duplicate that in software.
+ */
+ data->moving_threshold = input_abs_get_fuzz(input_dev,
+ ABS_MT_POSITION_X);
+ data->contact_threshold = input_abs_get_fuzz(input_dev,
+ ABS_MT_PRESSURE);
+ input_abs_set_fuzz(input_dev, ABS_MT_POSITION_X, 0);
+ input_abs_set_fuzz(input_dev, ABS_MT_POSITION_Y, 0);
+ input_abs_set_fuzz(input_dev, ABS_MT_PRESSURE, 0);
+ }
+
+ input_dev->name = devm_kasprintf(&client->dev, GFP_KERNEL,
+ "MELFAS MMS%d Touchscreen",
+ data->type);
+ if (!input_dev->name)
+ return -ENOMEM;
+
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+ input_dev->open = mms114_input_open;
+ input_dev->close = mms114_input_close;
+
+ error = input_mt_init_slots(input_dev, MMS114_MAX_TOUCH,
+ INPUT_MT_DIRECT);
+ if (error)
+ return error;
+
+ input_set_drvdata(input_dev, data);
+ i2c_set_clientdata(client, data);
+
+ data->core_reg = devm_regulator_get(&client->dev, "avdd");
+ if (IS_ERR(data->core_reg)) {
+ error = PTR_ERR(data->core_reg);
+ dev_err(&client->dev,
+ "Unable to get the Core regulator (%d)\n", error);
+ return error;
+ }
+
+ data->io_reg = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->io_reg)) {
+ error = PTR_ERR(data->io_reg);
+ dev_err(&client->dev,
+ "Unable to get the IO regulator (%d)\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, mms114_interrupt,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ dev_name(&client->dev), data);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = input_register_device(data->input_dev);
+ if (error) {
+ dev_err(&client->dev, "Failed to register input device\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused mms114_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mms114_data *data = i2c_get_clientdata(client);
+ struct input_dev *input_dev = data->input_dev;
+ int id;
+
+ /* Release all touch */
+ for (id = 0; id < MMS114_MAX_TOUCH; id++) {
+ input_mt_slot(input_dev, id);
+ input_mt_report_slot_inactive(input_dev);
+ }
+
+ input_mt_report_pointer_emulation(input_dev, true);
+ input_sync(input_dev);
+
+ mutex_lock(&input_dev->mutex);
+ if (input_device_enabled(input_dev))
+ mms114_stop(data);
+ mutex_unlock(&input_dev->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused mms114_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mms114_data *data = i2c_get_clientdata(client);
+ struct input_dev *input_dev = data->input_dev;
+ int error;
+
+ mutex_lock(&input_dev->mutex);
+ if (input_device_enabled(input_dev)) {
+ error = mms114_start(data);
+ if (error < 0) {
+ mutex_unlock(&input_dev->mutex);
+ return error;
+ }
+ }
+ mutex_unlock(&input_dev->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume);
+
+static const struct i2c_device_id mms114_id[] = {
+ { "mms114", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mms114_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id mms114_dt_match[] = {
+ {
+ .compatible = "melfas,mms114",
+ .data = (void *)TYPE_MMS114,
+ }, {
+ .compatible = "melfas,mms134s",
+ .data = (void *)TYPE_MMS134S,
+ }, {
+ .compatible = "melfas,mms136",
+ .data = (void *)TYPE_MMS136,
+ }, {
+ .compatible = "melfas,mms152",
+ .data = (void *)TYPE_MMS152,
+ }, {
+ .compatible = "melfas,mms345l",
+ .data = (void *)TYPE_MMS345L,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mms114_dt_match);
+#endif
+
+static struct i2c_driver mms114_driver = {
+ .driver = {
+ .name = "mms114",
+ .pm = &mms114_pm_ops,
+ .of_match_table = of_match_ptr(mms114_dt_match),
+ },
+ .probe = mms114_probe,
+ .id_table = mms114_id,
+};
+
+module_i2c_driver(mms114_driver);
+
+/* Module information */
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_DESCRIPTION("MELFAS mms114 Touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/msg2638.c b/drivers/input/touchscreen/msg2638.c
new file mode 100644
index 000000000..75536bc88
--- /dev/null
+++ b/drivers/input/touchscreen/msg2638.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for MStar msg2638 touchscreens
+ *
+ * Copyright (c) 2021 Vincent Knecht <vincent.knecht@mailoo.org>
+ *
+ * Checksum and IRQ handler based on mstar_drv_common.c and
+ * mstar_drv_mutual_fw_control.c
+ * Copyright (c) 2006-2012 MStar Semiconductor, Inc.
+ *
+ * Driver structure based on zinitix.c by Michael Srba <Michael.Srba@seznam.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define MODE_DATA_RAW 0x5A
+
+#define MAX_SUPPORTED_FINGER_NUM 5
+
+#define CHIP_ON_DELAY_MS 15
+#define FIRMWARE_ON_DELAY_MS 50
+#define RESET_DELAY_MIN_US 10000
+#define RESET_DELAY_MAX_US 11000
+
+struct packet {
+ u8 xy_hi; /* higher bits of x and y coordinates */
+ u8 x_low;
+ u8 y_low;
+ u8 pressure;
+};
+
+struct touch_event {
+ u8 mode;
+ struct packet pkt[MAX_SUPPORTED_FINGER_NUM];
+ u8 proximity;
+ u8 checksum;
+};
+
+struct msg2638_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct touchscreen_properties prop;
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset_gpiod;
+};
+
+static u8 msg2638_checksum(u8 *data, u32 length)
+{
+ s32 sum = 0;
+ u32 i;
+
+ for (i = 0; i < length; i++)
+ sum += data[i];
+
+ return (u8)((-sum) & 0xFF);
+}
+
+static irqreturn_t msg2638_ts_irq_handler(int irq, void *msg2638_handler)
+{
+ struct msg2638_ts_data *msg2638 = msg2638_handler;
+ struct i2c_client *client = msg2638->client;
+ struct input_dev *input = msg2638->input_dev;
+ struct touch_event touch_event;
+ u32 len = sizeof(touch_event);
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(touch_event),
+ .buf = (u8 *)&touch_event,
+ },
+ };
+ struct packet *p;
+ u16 x, y;
+ int ret;
+ int i;
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ dev_err(&client->dev,
+ "Failed I2C transfer in irq handler: %d\n",
+ ret < 0 ? ret : -EIO);
+ goto out;
+ }
+
+ if (touch_event.mode != MODE_DATA_RAW)
+ goto out;
+
+ if (msg2638_checksum((u8 *)&touch_event, len - 1) !=
+ touch_event.checksum) {
+ dev_err(&client->dev, "Failed checksum!\n");
+ goto out;
+ }
+
+ for (i = 0; i < MAX_SUPPORTED_FINGER_NUM; i++) {
+ p = &touch_event.pkt[i];
+
+ /* Ignore non-pressed finger data */
+ if (p->xy_hi == 0xFF && p->x_low == 0xFF && p->y_low == 0xFF)
+ continue;
+
+ x = (((p->xy_hi & 0xF0) << 4) | p->x_low);
+ y = (((p->xy_hi & 0x0F) << 8) | p->y_low);
+
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ touchscreen_report_pos(input, &msg2638->prop, x, y, true);
+ }
+
+ input_mt_sync_frame(msg2638->input_dev);
+ input_sync(msg2638->input_dev);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static void msg2638_reset(struct msg2638_ts_data *msg2638)
+{
+ gpiod_set_value_cansleep(msg2638->reset_gpiod, 1);
+ usleep_range(RESET_DELAY_MIN_US, RESET_DELAY_MAX_US);
+ gpiod_set_value_cansleep(msg2638->reset_gpiod, 0);
+ msleep(FIRMWARE_ON_DELAY_MS);
+}
+
+static int msg2638_start(struct msg2638_ts_data *msg2638)
+{
+ int error;
+
+ error = regulator_bulk_enable(ARRAY_SIZE(msg2638->supplies),
+ msg2638->supplies);
+ if (error) {
+ dev_err(&msg2638->client->dev,
+ "Failed to enable regulators: %d\n", error);
+ return error;
+ }
+
+ msleep(CHIP_ON_DELAY_MS);
+
+ msg2638_reset(msg2638);
+
+ enable_irq(msg2638->client->irq);
+
+ return 0;
+}
+
+static int msg2638_stop(struct msg2638_ts_data *msg2638)
+{
+ int error;
+
+ disable_irq(msg2638->client->irq);
+
+ error = regulator_bulk_disable(ARRAY_SIZE(msg2638->supplies),
+ msg2638->supplies);
+ if (error) {
+ dev_err(&msg2638->client->dev,
+ "Failed to disable regulators: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int msg2638_input_open(struct input_dev *dev)
+{
+ struct msg2638_ts_data *msg2638 = input_get_drvdata(dev);
+
+ return msg2638_start(msg2638);
+}
+
+static void msg2638_input_close(struct input_dev *dev)
+{
+ struct msg2638_ts_data *msg2638 = input_get_drvdata(dev);
+
+ msg2638_stop(msg2638);
+}
+
+static int msg2638_init_input_dev(struct msg2638_ts_data *msg2638)
+{
+ struct device *dev = &msg2638->client->dev;
+ struct input_dev *input_dev;
+ int error;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev) {
+ dev_err(dev, "Failed to allocate input device.\n");
+ return -ENOMEM;
+ }
+
+ input_set_drvdata(input_dev, msg2638);
+ msg2638->input_dev = input_dev;
+
+ input_dev->name = "MStar TouchScreen";
+ input_dev->phys = "input/ts";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->open = msg2638_input_open;
+ input_dev->close = msg2638_input_close;
+
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y);
+
+ touchscreen_parse_properties(input_dev, true, &msg2638->prop);
+ if (!msg2638->prop.max_x || !msg2638->prop.max_y) {
+ dev_err(dev, "touchscreen-size-x and/or touchscreen-size-y not set in properties\n");
+ return -EINVAL;
+ }
+
+ error = input_mt_init_slots(input_dev, MAX_SUPPORTED_FINGER_NUM,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(dev, "Failed to initialize MT slots: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(dev, "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int msg2638_ts_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct msg2638_ts_data *msg2638;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "Failed to assert adapter's support for plain I2C.\n");
+ return -ENXIO;
+ }
+
+ msg2638 = devm_kzalloc(dev, sizeof(*msg2638), GFP_KERNEL);
+ if (!msg2638)
+ return -ENOMEM;
+
+ msg2638->client = client;
+ i2c_set_clientdata(client, msg2638);
+
+ msg2638->supplies[0].supply = "vdd";
+ msg2638->supplies[1].supply = "vddio";
+ error = devm_regulator_bulk_get(dev, ARRAY_SIZE(msg2638->supplies),
+ msg2638->supplies);
+ if (error) {
+ dev_err(dev, "Failed to get regulators: %d\n", error);
+ return error;
+ }
+
+ msg2638->reset_gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(msg2638->reset_gpiod)) {
+ error = PTR_ERR(msg2638->reset_gpiod);
+ dev_err(dev, "Failed to request reset GPIO: %d\n", error);
+ return error;
+ }
+
+ error = msg2638_init_input_dev(msg2638);
+ if (error) {
+ dev_err(dev, "Failed to initialize input device: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, msg2638_ts_irq_handler,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ client->name, msg2638);
+ if (error) {
+ dev_err(dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused msg2638_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct msg2638_ts_data *msg2638 = i2c_get_clientdata(client);
+
+ mutex_lock(&msg2638->input_dev->mutex);
+
+ if (input_device_enabled(msg2638->input_dev))
+ msg2638_stop(msg2638);
+
+ mutex_unlock(&msg2638->input_dev->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused msg2638_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct msg2638_ts_data *msg2638 = i2c_get_clientdata(client);
+ int ret = 0;
+
+ mutex_lock(&msg2638->input_dev->mutex);
+
+ if (input_device_enabled(msg2638->input_dev))
+ ret = msg2638_start(msg2638);
+
+ mutex_unlock(&msg2638->input_dev->mutex);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(msg2638_pm_ops, msg2638_suspend, msg2638_resume);
+
+static const struct of_device_id msg2638_of_match[] = {
+ { .compatible = "mstar,msg2638" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, msg2638_of_match);
+
+static struct i2c_driver msg2638_ts_driver = {
+ .probe_new = msg2638_ts_probe,
+ .driver = {
+ .name = "MStar-TS",
+ .pm = &msg2638_pm_ops,
+ .of_match_table = msg2638_of_match,
+ },
+};
+module_i2c_driver(msg2638_ts_driver);
+
+MODULE_AUTHOR("Vincent Knecht <vincent.knecht@mailoo.org>");
+MODULE_DESCRIPTION("MStar MSG2638 touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/mtouch.c b/drivers/input/touchscreen/mtouch.c
new file mode 100644
index 000000000..28e449eea
--- /dev/null
+++ b/drivers/input/touchscreen/mtouch.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MicroTouch (3M) serial touchscreen driver
+ *
+ * Copyright (c) 2004 Vojtech Pavlik
+ */
+
+
+/*
+ * 2005/02/19 Dan Streetman <ddstreet@ieee.org>
+ * Copied elo.c and edited for MicroTouch protocol
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "MicroTouch serial touchscreen driver"
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define MTOUCH_FORMAT_TABLET_STATUS_BIT 0x80
+#define MTOUCH_FORMAT_TABLET_TOUCH_BIT 0x40
+#define MTOUCH_FORMAT_TABLET_LENGTH 5
+#define MTOUCH_RESPONSE_BEGIN_BYTE 0x01
+#define MTOUCH_RESPONSE_END_BYTE 0x0d
+
+/* todo: check specs for max length of all responses */
+#define MTOUCH_MAX_LENGTH 16
+
+#define MTOUCH_MIN_XC 0
+#define MTOUCH_MAX_XC 0x3fff
+#define MTOUCH_MIN_YC 0
+#define MTOUCH_MAX_YC 0x3fff
+
+#define MTOUCH_GET_XC(data) (((data[2])<<7) | data[1])
+#define MTOUCH_GET_YC(data) (((data[4])<<7) | data[3])
+#define MTOUCH_GET_TOUCHED(data) (MTOUCH_FORMAT_TABLET_TOUCH_BIT & data[0])
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct mtouch {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[MTOUCH_MAX_LENGTH];
+ char phys[32];
+};
+
+static void mtouch_process_format_tablet(struct mtouch *mtouch)
+{
+ struct input_dev *dev = mtouch->dev;
+
+ if (MTOUCH_FORMAT_TABLET_LENGTH == ++mtouch->idx) {
+ input_report_abs(dev, ABS_X, MTOUCH_GET_XC(mtouch->data));
+ input_report_abs(dev, ABS_Y, MTOUCH_MAX_YC - MTOUCH_GET_YC(mtouch->data));
+ input_report_key(dev, BTN_TOUCH, MTOUCH_GET_TOUCHED(mtouch->data));
+ input_sync(dev);
+
+ mtouch->idx = 0;
+ }
+}
+
+static void mtouch_process_response(struct mtouch *mtouch)
+{
+ if (MTOUCH_RESPONSE_END_BYTE == mtouch->data[mtouch->idx++]) {
+ /* FIXME - process response */
+ mtouch->idx = 0;
+ } else if (MTOUCH_MAX_LENGTH == mtouch->idx) {
+ printk(KERN_ERR "mtouch.c: too many response bytes\n");
+ mtouch->idx = 0;
+ }
+}
+
+static irqreturn_t mtouch_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct mtouch *mtouch = serio_get_drvdata(serio);
+
+ mtouch->data[mtouch->idx] = data;
+
+ if (MTOUCH_FORMAT_TABLET_STATUS_BIT & mtouch->data[0])
+ mtouch_process_format_tablet(mtouch);
+ else if (MTOUCH_RESPONSE_BEGIN_BYTE == mtouch->data[0])
+ mtouch_process_response(mtouch);
+ else
+ printk(KERN_DEBUG "mtouch.c: unknown/unsynchronized data from device, byte %x\n",mtouch->data[0]);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * mtouch_disconnect() is the opposite of mtouch_connect()
+ */
+
+static void mtouch_disconnect(struct serio *serio)
+{
+ struct mtouch *mtouch = serio_get_drvdata(serio);
+
+ input_get_device(mtouch->dev);
+ input_unregister_device(mtouch->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(mtouch->dev);
+ kfree(mtouch);
+}
+
+/*
+ * mtouch_connect() is the routine that is called when someone adds a
+ * new serio device that supports MicroTouch (Format Tablet) protocol and registers it as
+ * an input device.
+ */
+
+static int mtouch_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct mtouch *mtouch;
+ struct input_dev *input_dev;
+ int err;
+
+ mtouch = kzalloc(sizeof(struct mtouch), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!mtouch || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ mtouch->serio = serio;
+ mtouch->dev = input_dev;
+ snprintf(mtouch->phys, sizeof(mtouch->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "MicroTouch Serial TouchScreen";
+ input_dev->phys = mtouch->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_MICROTOUCH;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(mtouch->dev, ABS_X, MTOUCH_MIN_XC, MTOUCH_MAX_XC, 0, 0);
+ input_set_abs_params(mtouch->dev, ABS_Y, MTOUCH_MIN_YC, MTOUCH_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, mtouch);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(mtouch->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(mtouch);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id mtouch_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_MICROTOUCH,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, mtouch_serio_ids);
+
+static struct serio_driver mtouch_drv = {
+ .driver = {
+ .name = "mtouch",
+ },
+ .description = DRIVER_DESC,
+ .id_table = mtouch_serio_ids,
+ .interrupt = mtouch_interrupt,
+ .connect = mtouch_connect,
+ .disconnect = mtouch_disconnect,
+};
+
+module_serio_driver(mtouch_drv);
diff --git a/drivers/input/touchscreen/mxs-lradc-ts.c b/drivers/input/touchscreen/mxs-lradc-ts.c
new file mode 100644
index 000000000..9e36fee38
--- /dev/null
+++ b/drivers/input/touchscreen/mxs-lradc-ts.c
@@ -0,0 +1,703 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Freescale MXS LRADC touchscreen driver
+ *
+ * Copyright (c) 2012 DENX Software Engineering, GmbH.
+ * Copyright (c) 2017 Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
+ *
+ * Authors:
+ * Marek Vasut <marex@denx.de>
+ * Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/mxs-lradc.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+static const char * const mxs_lradc_ts_irq_names[] = {
+ "mxs-lradc-touchscreen",
+ "mxs-lradc-channel6",
+ "mxs-lradc-channel7",
+};
+
+/*
+ * Touchscreen handling
+ */
+enum mxs_lradc_ts_plate {
+ LRADC_TOUCH = 0,
+ LRADC_SAMPLE_X,
+ LRADC_SAMPLE_Y,
+ LRADC_SAMPLE_PRESSURE,
+ LRADC_SAMPLE_VALID,
+};
+
+struct mxs_lradc_ts {
+ struct mxs_lradc *lradc;
+ struct device *dev;
+
+ void __iomem *base;
+ /*
+ * When the touchscreen is enabled, we give it two private virtual
+ * channels: #6 and #7. This means that only 6 virtual channels (instead
+ * of 8) will be available for buffered capture.
+ */
+#define TOUCHSCREEN_VCHANNEL1 7
+#define TOUCHSCREEN_VCHANNEL2 6
+
+ struct input_dev *ts_input;
+
+ enum mxs_lradc_ts_plate cur_plate; /* state machine */
+ bool ts_valid;
+ unsigned int ts_x_pos;
+ unsigned int ts_y_pos;
+ unsigned int ts_pressure;
+
+ /* handle touchscreen's physical behaviour */
+ /* samples per coordinate */
+ unsigned int over_sample_cnt;
+ /* time clocks between samples */
+ unsigned int over_sample_delay;
+ /* time in clocks to wait after the plates where switched */
+ unsigned int settling_delay;
+ spinlock_t lock;
+};
+
+struct state_info {
+ u32 mask;
+ u32 bit;
+ u32 x_plate;
+ u32 y_plate;
+ u32 pressure;
+};
+
+static struct state_info info[] = {
+ {LRADC_CTRL0_MX23_PLATE_MASK, LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE,
+ LRADC_CTRL0_MX23_XP | LRADC_CTRL0_MX23_XM,
+ LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_YM,
+ LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XM},
+ {LRADC_CTRL0_MX28_PLATE_MASK, LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE,
+ LRADC_CTRL0_MX28_XPPSW | LRADC_CTRL0_MX28_XNNSW,
+ LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_YNNSW,
+ LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW}
+};
+
+static bool mxs_lradc_check_touch_event(struct mxs_lradc_ts *ts)
+{
+ return !!(readl(ts->base + LRADC_STATUS) &
+ LRADC_STATUS_TOUCH_DETECT_RAW);
+}
+
+static void mxs_lradc_map_ts_channel(struct mxs_lradc_ts *ts, unsigned int vch,
+ unsigned int ch)
+{
+ writel(LRADC_CTRL4_LRADCSELECT_MASK(vch),
+ ts->base + LRADC_CTRL4 + STMP_OFFSET_REG_CLR);
+ writel(LRADC_CTRL4_LRADCSELECT(vch, ch),
+ ts->base + LRADC_CTRL4 + STMP_OFFSET_REG_SET);
+}
+
+static void mxs_lradc_setup_ts_channel(struct mxs_lradc_ts *ts, unsigned int ch)
+{
+ /*
+ * prepare for oversampling conversion
+ *
+ * from the datasheet:
+ * "The ACCUMULATE bit in the appropriate channel register
+ * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0;
+ * otherwise, the IRQs will not fire."
+ */
+ writel(LRADC_CH_ACCUMULATE |
+ LRADC_CH_NUM_SAMPLES(ts->over_sample_cnt - 1),
+ ts->base + LRADC_CH(ch));
+
+ /* from the datasheet:
+ * "Software must clear this register in preparation for a
+ * multi-cycle accumulation.
+ */
+ writel(LRADC_CH_VALUE_MASK,
+ ts->base + LRADC_CH(ch) + STMP_OFFSET_REG_CLR);
+
+ /*
+ * prepare the delay/loop unit according to the oversampling count
+ *
+ * from the datasheet:
+ * "The DELAY fields in HW_LRADC_DELAY0, HW_LRADC_DELAY1,
+ * HW_LRADC_DELAY2, and HW_LRADC_DELAY3 must be non-zero; otherwise,
+ * the LRADC will not trigger the delay group."
+ */
+ writel(LRADC_DELAY_TRIGGER(1 << ch) | LRADC_DELAY_TRIGGER_DELAYS(0) |
+ LRADC_DELAY_LOOP(ts->over_sample_cnt - 1) |
+ LRADC_DELAY_DELAY(ts->over_sample_delay - 1),
+ ts->base + LRADC_DELAY(3));
+
+ writel(LRADC_CTRL1_LRADC_IRQ(ch),
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+
+ /*
+ * after changing the touchscreen plates setting
+ * the signals need some initial time to settle. Start the
+ * SoC's delay unit and start the conversion later
+ * and automatically.
+ */
+ writel(LRADC_DELAY_TRIGGER(0) | LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) |
+ LRADC_DELAY_KICK | LRADC_DELAY_DELAY(ts->settling_delay),
+ ts->base + LRADC_DELAY(2));
+}
+
+/*
+ * Pressure detection is special:
+ * We want to do both required measurements for the pressure detection in
+ * one turn. Use the hardware features to chain both conversions and let the
+ * hardware report one interrupt if both conversions are done
+ */
+static void mxs_lradc_setup_ts_pressure(struct mxs_lradc_ts *ts,
+ unsigned int ch1, unsigned int ch2)
+{
+ u32 reg;
+
+ /*
+ * prepare for oversampling conversion
+ *
+ * from the datasheet:
+ * "The ACCUMULATE bit in the appropriate channel register
+ * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0;
+ * otherwise, the IRQs will not fire."
+ */
+ reg = LRADC_CH_ACCUMULATE |
+ LRADC_CH_NUM_SAMPLES(ts->over_sample_cnt - 1);
+ writel(reg, ts->base + LRADC_CH(ch1));
+ writel(reg, ts->base + LRADC_CH(ch2));
+
+ /* from the datasheet:
+ * "Software must clear this register in preparation for a
+ * multi-cycle accumulation.
+ */
+ writel(LRADC_CH_VALUE_MASK,
+ ts->base + LRADC_CH(ch1) + STMP_OFFSET_REG_CLR);
+ writel(LRADC_CH_VALUE_MASK,
+ ts->base + LRADC_CH(ch2) + STMP_OFFSET_REG_CLR);
+
+ /* prepare the delay/loop unit according to the oversampling count */
+ writel(LRADC_DELAY_TRIGGER(1 << ch1) | LRADC_DELAY_TRIGGER(1 << ch2) |
+ LRADC_DELAY_TRIGGER_DELAYS(0) |
+ LRADC_DELAY_LOOP(ts->over_sample_cnt - 1) |
+ LRADC_DELAY_DELAY(ts->over_sample_delay - 1),
+ ts->base + LRADC_DELAY(3));
+
+ writel(LRADC_CTRL1_LRADC_IRQ(ch2),
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+
+ /*
+ * after changing the touchscreen plates setting
+ * the signals need some initial time to settle. Start the
+ * SoC's delay unit and start the conversion later
+ * and automatically.
+ */
+ writel(LRADC_DELAY_TRIGGER(0) | LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) |
+ LRADC_DELAY_KICK | LRADC_DELAY_DELAY(ts->settling_delay),
+ ts->base + LRADC_DELAY(2));
+}
+
+static unsigned int mxs_lradc_ts_read_raw_channel(struct mxs_lradc_ts *ts,
+ unsigned int channel)
+{
+ u32 reg;
+ unsigned int num_samples, val;
+
+ reg = readl(ts->base + LRADC_CH(channel));
+ if (reg & LRADC_CH_ACCUMULATE)
+ num_samples = ts->over_sample_cnt;
+ else
+ num_samples = 1;
+
+ val = (reg & LRADC_CH_VALUE_MASK) >> LRADC_CH_VALUE_OFFSET;
+ return val / num_samples;
+}
+
+static unsigned int mxs_lradc_read_ts_pressure(struct mxs_lradc_ts *ts,
+ unsigned int ch1, unsigned int ch2)
+{
+ u32 reg, mask;
+ unsigned int pressure, m1, m2;
+
+ mask = LRADC_CTRL1_LRADC_IRQ(ch1) | LRADC_CTRL1_LRADC_IRQ(ch2);
+ reg = readl(ts->base + LRADC_CTRL1) & mask;
+
+ while (reg != mask) {
+ reg = readl(ts->base + LRADC_CTRL1) & mask;
+ dev_dbg(ts->dev, "One channel is still busy: %X\n", reg);
+ }
+
+ m1 = mxs_lradc_ts_read_raw_channel(ts, ch1);
+ m2 = mxs_lradc_ts_read_raw_channel(ts, ch2);
+
+ if (m2 == 0) {
+ dev_warn(ts->dev, "Cannot calculate pressure\n");
+ return 1 << (LRADC_RESOLUTION - 1);
+ }
+
+ /* simply scale the value from 0 ... max ADC resolution */
+ pressure = m1;
+ pressure *= (1 << LRADC_RESOLUTION);
+ pressure /= m2;
+
+ dev_dbg(ts->dev, "Pressure = %u\n", pressure);
+ return pressure;
+}
+
+#define TS_CH_XP 2
+#define TS_CH_YP 3
+#define TS_CH_XM 4
+#define TS_CH_YM 5
+
+/*
+ * YP(open)--+-------------+
+ * | |--+
+ * | | |
+ * YM(-)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(weak+) XM(open)
+ *
+ * "weak+" means 200k Ohm VDDIO
+ * (-) means GND
+ */
+static void mxs_lradc_setup_touch_detection(struct mxs_lradc_ts *ts)
+{
+ struct mxs_lradc *lradc = ts->lradc;
+
+ /*
+ * In order to detect a touch event the 'touch detect enable' bit
+ * enables:
+ * - a weak pullup to the X+ connector
+ * - a strong ground at the Y- connector
+ */
+ writel(info[lradc->soc].mask,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
+ writel(info[lradc->soc].bit,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET);
+}
+
+/*
+ * YP(meas)--+-------------+
+ * | |--+
+ * | | |
+ * YM(open)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(+) XM(-)
+ *
+ * (+) means here 1.85 V
+ * (-) means here GND
+ */
+static void mxs_lradc_prepare_x_pos(struct mxs_lradc_ts *ts)
+{
+ struct mxs_lradc *lradc = ts->lradc;
+
+ writel(info[lradc->soc].mask,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
+ writel(info[lradc->soc].x_plate,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET);
+
+ ts->cur_plate = LRADC_SAMPLE_X;
+ mxs_lradc_map_ts_channel(ts, TOUCHSCREEN_VCHANNEL1, TS_CH_YP);
+ mxs_lradc_setup_ts_channel(ts, TOUCHSCREEN_VCHANNEL1);
+}
+
+/*
+ * YP(+)--+-------------+
+ * | |--+
+ * | | |
+ * YM(-)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(open) XM(meas)
+ *
+ * (+) means here 1.85 V
+ * (-) means here GND
+ */
+static void mxs_lradc_prepare_y_pos(struct mxs_lradc_ts *ts)
+{
+ struct mxs_lradc *lradc = ts->lradc;
+
+ writel(info[lradc->soc].mask,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
+ writel(info[lradc->soc].y_plate,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET);
+
+ ts->cur_plate = LRADC_SAMPLE_Y;
+ mxs_lradc_map_ts_channel(ts, TOUCHSCREEN_VCHANNEL1, TS_CH_XM);
+ mxs_lradc_setup_ts_channel(ts, TOUCHSCREEN_VCHANNEL1);
+}
+
+/*
+ * YP(+)--+-------------+
+ * | |--+
+ * | | |
+ * YM(meas)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(meas) XM(-)
+ *
+ * (+) means here 1.85 V
+ * (-) means here GND
+ */
+static void mxs_lradc_prepare_pressure(struct mxs_lradc_ts *ts)
+{
+ struct mxs_lradc *lradc = ts->lradc;
+
+ writel(info[lradc->soc].mask,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
+ writel(info[lradc->soc].pressure,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET);
+
+ ts->cur_plate = LRADC_SAMPLE_PRESSURE;
+ mxs_lradc_map_ts_channel(ts, TOUCHSCREEN_VCHANNEL1, TS_CH_YM);
+ mxs_lradc_map_ts_channel(ts, TOUCHSCREEN_VCHANNEL2, TS_CH_XP);
+ mxs_lradc_setup_ts_pressure(ts, TOUCHSCREEN_VCHANNEL2,
+ TOUCHSCREEN_VCHANNEL1);
+}
+
+static void mxs_lradc_enable_touch_detection(struct mxs_lradc_ts *ts)
+{
+ mxs_lradc_setup_touch_detection(ts);
+
+ ts->cur_plate = LRADC_TOUCH;
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ | LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET);
+}
+
+static void mxs_lradc_start_touch_event(struct mxs_lradc_ts *ts)
+{
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+ writel(LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1),
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET);
+ /*
+ * start with the Y-pos, because it uses nearly the same plate
+ * settings like the touch detection
+ */
+ mxs_lradc_prepare_y_pos(ts);
+}
+
+static void mxs_lradc_report_ts_event(struct mxs_lradc_ts *ts)
+{
+ input_report_abs(ts->ts_input, ABS_X, ts->ts_x_pos);
+ input_report_abs(ts->ts_input, ABS_Y, ts->ts_y_pos);
+ input_report_abs(ts->ts_input, ABS_PRESSURE, ts->ts_pressure);
+ input_report_key(ts->ts_input, BTN_TOUCH, 1);
+ input_sync(ts->ts_input);
+}
+
+static void mxs_lradc_complete_touch_event(struct mxs_lradc_ts *ts)
+{
+ mxs_lradc_setup_touch_detection(ts);
+ ts->cur_plate = LRADC_SAMPLE_VALID;
+ /*
+ * start a dummy conversion to burn time to settle the signals
+ * note: we are not interested in the conversion's value
+ */
+ writel(0, ts->base + LRADC_CH(TOUCHSCREEN_VCHANNEL1));
+ writel(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
+ LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2),
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+ writel(LRADC_DELAY_TRIGGER(1 << TOUCHSCREEN_VCHANNEL1) |
+ LRADC_DELAY_KICK | LRADC_DELAY_DELAY(10),
+ ts->base + LRADC_DELAY(2));
+}
+
+/*
+ * in order to avoid false measurements, report only samples where
+ * the surface is still touched after the position measurement
+ */
+static void mxs_lradc_finish_touch_event(struct mxs_lradc_ts *ts, bool valid)
+{
+ /* if it is still touched, report the sample */
+ if (valid && mxs_lradc_check_touch_event(ts)) {
+ ts->ts_valid = true;
+ mxs_lradc_report_ts_event(ts);
+ }
+
+ /* if it is even still touched, continue with the next measurement */
+ if (mxs_lradc_check_touch_event(ts)) {
+ mxs_lradc_prepare_y_pos(ts);
+ return;
+ }
+
+ if (ts->ts_valid) {
+ /* signal the release */
+ ts->ts_valid = false;
+ input_report_key(ts->ts_input, BTN_TOUCH, 0);
+ input_sync(ts->ts_input);
+ }
+
+ /* if it is released, wait for the next touch via IRQ */
+ ts->cur_plate = LRADC_TOUCH;
+ writel(0, ts->base + LRADC_DELAY(2));
+ writel(0, ts->base + LRADC_DELAY(3));
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ |
+ LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) |
+ LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1),
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET);
+}
+
+/* touchscreen's state machine */
+static void mxs_lradc_handle_touch(struct mxs_lradc_ts *ts)
+{
+ switch (ts->cur_plate) {
+ case LRADC_TOUCH:
+ if (mxs_lradc_check_touch_event(ts))
+ mxs_lradc_start_touch_event(ts);
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+ return;
+
+ case LRADC_SAMPLE_Y:
+ ts->ts_y_pos =
+ mxs_lradc_ts_read_raw_channel(ts, TOUCHSCREEN_VCHANNEL1);
+ mxs_lradc_prepare_x_pos(ts);
+ return;
+
+ case LRADC_SAMPLE_X:
+ ts->ts_x_pos =
+ mxs_lradc_ts_read_raw_channel(ts, TOUCHSCREEN_VCHANNEL1);
+ mxs_lradc_prepare_pressure(ts);
+ return;
+
+ case LRADC_SAMPLE_PRESSURE:
+ ts->ts_pressure =
+ mxs_lradc_read_ts_pressure(ts,
+ TOUCHSCREEN_VCHANNEL2,
+ TOUCHSCREEN_VCHANNEL1);
+ mxs_lradc_complete_touch_event(ts);
+ return;
+
+ case LRADC_SAMPLE_VALID:
+ mxs_lradc_finish_touch_event(ts, 1);
+ break;
+ }
+}
+
+/* IRQ Handling */
+static irqreturn_t mxs_lradc_ts_handle_irq(int irq, void *data)
+{
+ struct mxs_lradc_ts *ts = data;
+ struct mxs_lradc *lradc = ts->lradc;
+ unsigned long reg = readl(ts->base + LRADC_CTRL1);
+ u32 clr_irq = mxs_lradc_irq_mask(lradc);
+ const u32 ts_irq_mask =
+ LRADC_CTRL1_TOUCH_DETECT_IRQ |
+ LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
+ LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2);
+ unsigned long flags;
+
+ if (!(reg & mxs_lradc_irq_mask(lradc)))
+ return IRQ_NONE;
+
+ if (reg & ts_irq_mask) {
+ spin_lock_irqsave(&ts->lock, flags);
+ mxs_lradc_handle_touch(ts);
+ spin_unlock_irqrestore(&ts->lock, flags);
+ /* Make sure we don't clear the next conversion's interrupt. */
+ clr_irq &= ~(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
+ LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2));
+ writel(reg & clr_irq,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mxs_lradc_ts_open(struct input_dev *dev)
+{
+ struct mxs_lradc_ts *ts = input_get_drvdata(dev);
+
+ /* Enable the touch-detect circuitry. */
+ mxs_lradc_enable_touch_detection(ts);
+
+ return 0;
+}
+
+static void mxs_lradc_ts_stop(struct mxs_lradc_ts *ts)
+{
+ int i;
+ struct mxs_lradc *lradc = ts->lradc;
+
+ /* stop all interrupts from firing */
+ writel(LRADC_CTRL1_TOUCH_DETECT_IRQ_EN |
+ LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) |
+ LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL2),
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+
+ /* Power-down touchscreen touch-detect circuitry. */
+ writel(info[lradc->soc].mask,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
+
+ writel(lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET,
+ ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
+
+ for (i = 1; i < LRADC_MAX_DELAY_CHANS; i++)
+ writel(0, ts->base + LRADC_DELAY(i));
+}
+
+static void mxs_lradc_ts_close(struct input_dev *dev)
+{
+ struct mxs_lradc_ts *ts = input_get_drvdata(dev);
+
+ mxs_lradc_ts_stop(ts);
+}
+
+static void mxs_lradc_ts_hw_init(struct mxs_lradc_ts *ts)
+{
+ struct mxs_lradc *lradc = ts->lradc;
+
+ /* Configure the touchscreen type */
+ if (lradc->soc == IMX28_LRADC) {
+ writel(LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
+
+ if (lradc->touchscreen_wire == MXS_LRADC_TOUCHSCREEN_5WIRE)
+ writel(LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE,
+ ts->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET);
+ }
+}
+
+static int mxs_lradc_ts_register(struct mxs_lradc_ts *ts)
+{
+ struct input_dev *input;
+ struct device *dev = ts->dev;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "mxs-lradc-ts";
+ input->id.bustype = BUS_HOST;
+ input->open = mxs_lradc_ts_open;
+ input->close = mxs_lradc_ts_close;
+
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input, ABS_X, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0);
+ input_set_abs_params(input, ABS_PRESSURE, 0, LRADC_SINGLE_SAMPLE_MASK,
+ 0, 0);
+
+ ts->ts_input = input;
+ input_set_drvdata(input, ts);
+
+ return input_register_device(input);
+}
+
+static int mxs_lradc_ts_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->parent->of_node;
+ struct mxs_lradc *lradc = dev_get_drvdata(dev->parent);
+ struct mxs_lradc_ts *ts;
+ int ret, irq, virq, i;
+ u32 ts_wires = 0, adapt;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ts);
+
+ ts->lradc = lradc;
+ ts->dev = dev;
+ spin_lock_init(&ts->lock);
+
+ ts->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->base))
+ return PTR_ERR(ts->base);
+
+ ret = of_property_read_u32(node, "fsl,lradc-touchscreen-wires",
+ &ts_wires);
+ if (ret)
+ return ret;
+
+ if (of_property_read_u32(node, "fsl,ave-ctrl", &adapt)) {
+ ts->over_sample_cnt = 4;
+ } else {
+ if (adapt >= 1 && adapt <= 32) {
+ ts->over_sample_cnt = adapt;
+ } else {
+ dev_err(ts->dev, "Invalid sample count (%u)\n",
+ adapt);
+ return -EINVAL;
+ }
+ }
+
+ if (of_property_read_u32(node, "fsl,ave-delay", &adapt)) {
+ ts->over_sample_delay = 2;
+ } else {
+ if (adapt >= 2 && adapt <= LRADC_DELAY_DELAY_MASK + 1) {
+ ts->over_sample_delay = adapt;
+ } else {
+ dev_err(ts->dev, "Invalid sample delay (%u)\n",
+ adapt);
+ return -EINVAL;
+ }
+ }
+
+ if (of_property_read_u32(node, "fsl,settling", &adapt)) {
+ ts->settling_delay = 10;
+ } else {
+ if (adapt >= 1 && adapt <= LRADC_DELAY_DELAY_MASK) {
+ ts->settling_delay = adapt;
+ } else {
+ dev_err(ts->dev, "Invalid settling delay (%u)\n",
+ adapt);
+ return -EINVAL;
+ }
+ }
+
+ ret = stmp_reset_block(ts->base);
+ if (ret)
+ return ret;
+
+ mxs_lradc_ts_hw_init(ts);
+
+ for (i = 0; i < 3; i++) {
+ irq = platform_get_irq_byname(pdev, mxs_lradc_ts_irq_names[i]);
+ if (irq < 0)
+ return irq;
+
+ virq = irq_of_parse_and_map(node, irq);
+
+ mxs_lradc_ts_stop(ts);
+
+ ret = devm_request_irq(dev, virq,
+ mxs_lradc_ts_handle_irq,
+ 0, mxs_lradc_ts_irq_names[i], ts);
+ if (ret)
+ return ret;
+ }
+
+ return mxs_lradc_ts_register(ts);
+}
+
+static struct platform_driver mxs_lradc_ts_driver = {
+ .driver = {
+ .name = "mxs-lradc-ts",
+ },
+ .probe = mxs_lradc_ts_probe,
+};
+module_platform_driver(mxs_lradc_ts_driver);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("Freescale MXS LRADC touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mxs-lradc-ts");
diff --git a/drivers/input/touchscreen/pcap_ts.c b/drivers/input/touchscreen/pcap_ts.c
new file mode 100644
index 000000000..b2da0194e
--- /dev/null
+++ b/drivers/input/touchscreen/pcap_ts.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Motorola PCAP2 touchscreen as found in the EZX phone platform.
+ *
+ * Copyright (C) 2006 Harald Welte <laforge@openezx.org>
+ * Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+
+struct pcap_ts {
+ struct pcap_chip *pcap;
+ struct input_dev *input;
+ struct delayed_work work;
+ u16 x, y;
+ u16 pressure;
+ u8 read_state;
+};
+
+#define SAMPLE_DELAY 20 /* msecs */
+
+#define X_AXIS_MIN 0
+#define X_AXIS_MAX 1023
+#define Y_AXIS_MAX X_AXIS_MAX
+#define Y_AXIS_MIN X_AXIS_MIN
+#define PRESSURE_MAX X_AXIS_MAX
+#define PRESSURE_MIN X_AXIS_MIN
+
+static void pcap_ts_read_xy(void *data, u16 res[2])
+{
+ struct pcap_ts *pcap_ts = data;
+
+ switch (pcap_ts->read_state) {
+ case PCAP_ADC_TS_M_PRESSURE:
+ /* pressure reading is unreliable */
+ if (res[0] > PRESSURE_MIN && res[0] < PRESSURE_MAX)
+ pcap_ts->pressure = res[0];
+ pcap_ts->read_state = PCAP_ADC_TS_M_XY;
+ schedule_delayed_work(&pcap_ts->work, 0);
+ break;
+ case PCAP_ADC_TS_M_XY:
+ pcap_ts->y = res[0];
+ pcap_ts->x = res[1];
+ if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
+ pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
+ /* pen has been released */
+ input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
+ input_report_key(pcap_ts->input, BTN_TOUCH, 0);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
+ schedule_delayed_work(&pcap_ts->work, 0);
+ } else {
+ /* pen is touching the screen */
+ input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
+ input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
+ input_report_key(pcap_ts->input, BTN_TOUCH, 1);
+ input_report_abs(pcap_ts->input, ABS_PRESSURE,
+ pcap_ts->pressure);
+
+ /* switch back to pressure read mode */
+ pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
+ schedule_delayed_work(&pcap_ts->work,
+ msecs_to_jiffies(SAMPLE_DELAY));
+ }
+ input_sync(pcap_ts->input);
+ break;
+ default:
+ dev_warn(&pcap_ts->input->dev,
+ "pcap_ts: Warning, unhandled read_state %d\n",
+ pcap_ts->read_state);
+ break;
+ }
+}
+
+static void pcap_ts_work(struct work_struct *work)
+{
+ struct delayed_work *dw = to_delayed_work(work);
+ struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work);
+ u8 ch[2];
+
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+
+ if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY)
+ return;
+
+ /* start adc conversion */
+ ch[0] = PCAP_ADC_CH_TS_X1;
+ ch[1] = PCAP_ADC_CH_TS_Y1;
+ pcap_adc_async(pcap_ts->pcap, PCAP_ADC_BANK_1, 0, ch,
+ pcap_ts_read_xy, pcap_ts);
+}
+
+static irqreturn_t pcap_ts_event_touch(int pirq, void *data)
+{
+ struct pcap_ts *pcap_ts = data;
+
+ if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) {
+ pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
+ schedule_delayed_work(&pcap_ts->work, 0);
+ }
+ return IRQ_HANDLED;
+}
+
+static int pcap_ts_open(struct input_dev *dev)
+{
+ struct pcap_ts *pcap_ts = input_get_drvdata(dev);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
+ schedule_delayed_work(&pcap_ts->work, 0);
+
+ return 0;
+}
+
+static void pcap_ts_close(struct input_dev *dev)
+{
+ struct pcap_ts *pcap_ts = input_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&pcap_ts->work);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+}
+
+static int pcap_ts_probe(struct platform_device *pdev)
+{
+ struct input_dev *input_dev;
+ struct pcap_ts *pcap_ts;
+ int err = -ENOMEM;
+
+ pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+ if (!pcap_ts)
+ return err;
+
+ pcap_ts->pcap = dev_get_drvdata(pdev->dev.parent);
+ platform_set_drvdata(pdev, pcap_ts);
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ goto fail;
+
+ INIT_DELAYED_WORK(&pcap_ts->work, pcap_ts_work);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+
+ pcap_ts->input = input_dev;
+ input_set_drvdata(input_dev, pcap_ts);
+
+ input_dev->name = "pcap-touchscreen";
+ input_dev->phys = "pcap_ts/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->id.vendor = 0x0001;
+ input_dev->id.product = 0x0002;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &pdev->dev;
+ input_dev->open = pcap_ts_open;
+ input_dev->close = pcap_ts_close;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+ PRESSURE_MAX, 0, 0);
+
+ err = input_register_device(pcap_ts->input);
+ if (err)
+ goto fail_allocate;
+
+ err = request_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS),
+ pcap_ts_event_touch, 0, "Touch Screen", pcap_ts);
+ if (err)
+ goto fail_register;
+
+ return 0;
+
+fail_register:
+ input_unregister_device(input_dev);
+ goto fail;
+fail_allocate:
+ input_free_device(input_dev);
+fail:
+ kfree(pcap_ts);
+
+ return err;
+}
+
+static int pcap_ts_remove(struct platform_device *pdev)
+{
+ struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+ free_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), pcap_ts);
+ cancel_delayed_work_sync(&pcap_ts->work);
+
+ input_unregister_device(pcap_ts->input);
+
+ kfree(pcap_ts);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pcap_ts_suspend(struct device *dev)
+{
+ struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
+
+ pcap_set_ts_bits(pcap_ts->pcap, PCAP_ADC_TS_REF_LOWPWR);
+ return 0;
+}
+
+static int pcap_ts_resume(struct device *dev)
+{
+ struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
+
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+ return 0;
+}
+
+static const struct dev_pm_ops pcap_ts_pm_ops = {
+ .suspend = pcap_ts_suspend,
+ .resume = pcap_ts_resume,
+};
+#define PCAP_TS_PM_OPS (&pcap_ts_pm_ops)
+#else
+#define PCAP_TS_PM_OPS NULL
+#endif
+
+static struct platform_driver pcap_ts_driver = {
+ .probe = pcap_ts_probe,
+ .remove = pcap_ts_remove,
+ .driver = {
+ .name = "pcap-ts",
+ .pm = PCAP_TS_PM_OPS,
+ },
+};
+module_platform_driver(pcap_ts_driver);
+
+MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
+MODULE_AUTHOR("Daniel Ribeiro / Harald Welte");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcap_ts");
diff --git a/drivers/input/touchscreen/penmount.c b/drivers/input/touchscreen/penmount.c
new file mode 100644
index 000000000..12abb3b36
--- /dev/null
+++ b/drivers/input/touchscreen/penmount.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Penmount serial touchscreen driver
+ *
+ * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com>
+ * Copyright (c) 2011 John Sung <penmount.touch@gmail.com>
+ *
+ * Based on ELO driver (drivers/input/touchscreen/elo.c)
+ * Copyright (c) 2004 Vojtech Pavlik
+ */
+
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "PenMount serial touchscreen driver"
+
+MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>");
+MODULE_AUTHOR("John Sung <penmount.touch@gmail.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define PM_MAX_LENGTH 6
+#define PM_MAX_MTSLOT 16
+#define PM_3000_MTSLOT 2
+#define PM_6250_MTSLOT 12
+
+/*
+ * Multi-touch slot
+ */
+
+struct mt_slot {
+ unsigned short x, y;
+ bool active; /* is the touch valid? */
+};
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct pm {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[PM_MAX_LENGTH];
+ char phys[32];
+ unsigned char packetsize;
+ unsigned char maxcontacts;
+ struct mt_slot slots[PM_MAX_MTSLOT];
+ void (*parse_packet)(struct pm *);
+};
+
+/*
+ * pm_mtevent() sends mt events and also emulates pointer movement
+ */
+
+static void pm_mtevent(struct pm *pm, struct input_dev *input)
+{
+ int i;
+
+ for (i = 0; i < pm->maxcontacts; ++i) {
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER,
+ pm->slots[i].active);
+ if (pm->slots[i].active) {
+ input_event(input, EV_ABS, ABS_MT_POSITION_X, pm->slots[i].x);
+ input_event(input, EV_ABS, ABS_MT_POSITION_Y, pm->slots[i].y);
+ }
+ }
+
+ input_mt_report_pointer_emulation(input, true);
+ input_sync(input);
+}
+
+/*
+ * pm_checkpacket() checks if data packet is valid
+ */
+
+static bool pm_checkpacket(unsigned char *packet)
+{
+ int total = 0;
+ int i;
+
+ for (i = 0; i < 5; i++)
+ total += packet[i];
+
+ return packet[5] == (unsigned char)~(total & 0xff);
+}
+
+static void pm_parse_9000(struct pm *pm)
+{
+ struct input_dev *dev = pm->dev;
+
+ if ((pm->data[0] & 0x80) && pm->packetsize == ++pm->idx) {
+ input_report_abs(dev, ABS_X, pm->data[1] * 128 + pm->data[2]);
+ input_report_abs(dev, ABS_Y, pm->data[3] * 128 + pm->data[4]);
+ input_report_key(dev, BTN_TOUCH, !!(pm->data[0] & 0x40));
+ input_sync(dev);
+ pm->idx = 0;
+ }
+}
+
+static void pm_parse_6000(struct pm *pm)
+{
+ struct input_dev *dev = pm->dev;
+
+ if ((pm->data[0] & 0xbf) == 0x30 && pm->packetsize == ++pm->idx) {
+ if (pm_checkpacket(pm->data)) {
+ input_report_abs(dev, ABS_X,
+ pm->data[2] * 256 + pm->data[1]);
+ input_report_abs(dev, ABS_Y,
+ pm->data[4] * 256 + pm->data[3]);
+ input_report_key(dev, BTN_TOUCH, pm->data[0] & 0x40);
+ input_sync(dev);
+ }
+ pm->idx = 0;
+ }
+}
+
+static void pm_parse_3000(struct pm *pm)
+{
+ struct input_dev *dev = pm->dev;
+
+ if ((pm->data[0] & 0xce) == 0x40 && pm->packetsize == ++pm->idx) {
+ if (pm_checkpacket(pm->data)) {
+ int slotnum = pm->data[0] & 0x0f;
+ pm->slots[slotnum].active = pm->data[0] & 0x30;
+ pm->slots[slotnum].x = pm->data[2] * 256 + pm->data[1];
+ pm->slots[slotnum].y = pm->data[4] * 256 + pm->data[3];
+ pm_mtevent(pm, dev);
+ }
+ pm->idx = 0;
+ }
+}
+
+static void pm_parse_6250(struct pm *pm)
+{
+ struct input_dev *dev = pm->dev;
+
+ if ((pm->data[0] & 0xb0) == 0x30 && pm->packetsize == ++pm->idx) {
+ if (pm_checkpacket(pm->data)) {
+ int slotnum = pm->data[0] & 0x0f;
+ pm->slots[slotnum].active = pm->data[0] & 0x40;
+ pm->slots[slotnum].x = pm->data[2] * 256 + pm->data[1];
+ pm->slots[slotnum].y = pm->data[4] * 256 + pm->data[3];
+ pm_mtevent(pm, dev);
+ }
+ pm->idx = 0;
+ }
+}
+
+static irqreturn_t pm_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct pm *pm = serio_get_drvdata(serio);
+
+ pm->data[pm->idx] = data;
+
+ pm->parse_packet(pm);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * pm_disconnect() is the opposite of pm_connect()
+ */
+
+static void pm_disconnect(struct serio *serio)
+{
+ struct pm *pm = serio_get_drvdata(serio);
+
+ serio_close(serio);
+
+ input_unregister_device(pm->dev);
+ kfree(pm);
+
+ serio_set_drvdata(serio, NULL);
+}
+
+/*
+ * pm_connect() is the routine that is called when someone adds a
+ * new serio device that supports PenMount protocol and registers it as
+ * an input device.
+ */
+
+static int pm_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct pm *pm;
+ struct input_dev *input_dev;
+ int max_x, max_y;
+ int err;
+
+ pm = kzalloc(sizeof(struct pm), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!pm || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ pm->serio = serio;
+ pm->dev = input_dev;
+ snprintf(pm->phys, sizeof(pm->phys), "%s/input0", serio->phys);
+ pm->maxcontacts = 1;
+
+ input_dev->name = "PenMount Serial TouchScreen";
+ input_dev->phys = pm->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_PENMOUNT;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ switch (serio->id.id) {
+ default:
+ case 0:
+ pm->packetsize = 5;
+ pm->parse_packet = pm_parse_9000;
+ input_dev->id.product = 0x9000;
+ max_x = max_y = 0x3ff;
+ break;
+
+ case 1:
+ pm->packetsize = 6;
+ pm->parse_packet = pm_parse_6000;
+ input_dev->id.product = 0x6000;
+ max_x = max_y = 0x3ff;
+ break;
+
+ case 2:
+ pm->packetsize = 6;
+ pm->parse_packet = pm_parse_3000;
+ input_dev->id.product = 0x3000;
+ max_x = max_y = 0x7ff;
+ pm->maxcontacts = PM_3000_MTSLOT;
+ break;
+
+ case 3:
+ pm->packetsize = 6;
+ pm->parse_packet = pm_parse_6250;
+ input_dev->id.product = 0x6250;
+ max_x = max_y = 0x3ff;
+ pm->maxcontacts = PM_6250_MTSLOT;
+ break;
+ }
+
+ input_set_abs_params(pm->dev, ABS_X, 0, max_x, 0, 0);
+ input_set_abs_params(pm->dev, ABS_Y, 0, max_y, 0, 0);
+
+ if (pm->maxcontacts > 1) {
+ input_mt_init_slots(pm->dev, pm->maxcontacts, 0);
+ input_set_abs_params(pm->dev,
+ ABS_MT_POSITION_X, 0, max_x, 0, 0);
+ input_set_abs_params(pm->dev,
+ ABS_MT_POSITION_Y, 0, max_y, 0, 0);
+ }
+
+ serio_set_drvdata(serio, pm);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(pm->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(pm);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id pm_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_PENMOUNT,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, pm_serio_ids);
+
+static struct serio_driver pm_drv = {
+ .driver = {
+ .name = "serio-penmount",
+ },
+ .description = DRIVER_DESC,
+ .id_table = pm_serio_ids,
+ .interrupt = pm_interrupt,
+ .connect = pm_connect,
+ .disconnect = pm_disconnect,
+};
+
+module_serio_driver(pm_drv);
diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c
new file mode 100644
index 000000000..dc148b4be
--- /dev/null
+++ b/drivers/input/touchscreen/pixcir_i2c_ts.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Pixcir I2C touchscreen controllers.
+ *
+ * Copyright (C) 2010-2011 Pixcir, Inc.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define PIXCIR_MAX_SLOTS 5 /* Max fingers supported by driver */
+
+/*
+ * Register map
+ */
+#define PIXCIR_REG_POWER_MODE 51
+#define PIXCIR_REG_INT_MODE 52
+
+/*
+ * Power modes:
+ * active: max scan speed
+ * idle: lower scan speed with automatic transition to active on touch
+ * halt: datasheet says sleep but this is more like halt as the chip
+ * clocks are cut and it can only be brought out of this mode
+ * using the RESET pin.
+ */
+enum pixcir_power_mode {
+ PIXCIR_POWER_ACTIVE,
+ PIXCIR_POWER_IDLE,
+ PIXCIR_POWER_HALT,
+};
+
+#define PIXCIR_POWER_MODE_MASK 0x03
+#define PIXCIR_POWER_ALLOW_IDLE (1UL << 2)
+
+/*
+ * Interrupt modes:
+ * periodical: interrupt is asserted periodicaly
+ * diff coordinates: interrupt is asserted when coordinates change
+ * level on touch: interrupt level asserted during touch
+ * pulse on touch: interrupt pulse asserted during touch
+ *
+ */
+enum pixcir_int_mode {
+ PIXCIR_INT_PERIODICAL,
+ PIXCIR_INT_DIFF_COORD,
+ PIXCIR_INT_LEVEL_TOUCH,
+ PIXCIR_INT_PULSE_TOUCH,
+};
+
+#define PIXCIR_INT_MODE_MASK 0x03
+#define PIXCIR_INT_ENABLE (1UL << 3)
+#define PIXCIR_INT_POL_HIGH (1UL << 2)
+
+/**
+ * struct pixcir_i2c_chip_data - chip related data
+ * @max_fingers: Max number of fingers reported simultaneously by h/w
+ * @has_hw_ids: Hardware supports finger tracking IDs
+ *
+ */
+struct pixcir_i2c_chip_data {
+ u8 max_fingers;
+ bool has_hw_ids;
+};
+
+struct pixcir_i2c_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct gpio_desc *gpio_attb;
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_enable;
+ struct gpio_desc *gpio_wake;
+ const struct pixcir_i2c_chip_data *chip;
+ struct touchscreen_properties prop;
+ bool running;
+};
+
+struct pixcir_report_data {
+ int num_touches;
+ struct input_mt_pos pos[PIXCIR_MAX_SLOTS];
+ int ids[PIXCIR_MAX_SLOTS];
+};
+
+static void pixcir_ts_parse(struct pixcir_i2c_ts_data *tsdata,
+ struct pixcir_report_data *report)
+{
+ u8 rdbuf[2 + PIXCIR_MAX_SLOTS * 5];
+ u8 wrbuf[1] = { 0 };
+ u8 *bufptr;
+ u8 touch;
+ int ret, i;
+ int readsize;
+ const struct pixcir_i2c_chip_data *chip = tsdata->chip;
+
+ memset(report, 0, sizeof(struct pixcir_report_data));
+
+ i = chip->has_hw_ids ? 1 : 0;
+ readsize = 2 + tsdata->chip->max_fingers * (4 + i);
+ if (readsize > sizeof(rdbuf))
+ readsize = sizeof(rdbuf);
+
+ ret = i2c_master_send(tsdata->client, wrbuf, sizeof(wrbuf));
+ if (ret != sizeof(wrbuf)) {
+ dev_err(&tsdata->client->dev,
+ "%s: i2c_master_send failed(), ret=%d\n",
+ __func__, ret);
+ return;
+ }
+
+ ret = i2c_master_recv(tsdata->client, rdbuf, readsize);
+ if (ret != readsize) {
+ dev_err(&tsdata->client->dev,
+ "%s: i2c_master_recv failed(), ret=%d\n",
+ __func__, ret);
+ return;
+ }
+
+ touch = rdbuf[0] & 0x7;
+ if (touch > tsdata->chip->max_fingers)
+ touch = tsdata->chip->max_fingers;
+
+ report->num_touches = touch;
+ bufptr = &rdbuf[2];
+
+ for (i = 0; i < touch; i++) {
+ touchscreen_set_mt_pos(&report->pos[i], &tsdata->prop,
+ get_unaligned_le16(bufptr),
+ get_unaligned_le16(bufptr + 2));
+ if (chip->has_hw_ids) {
+ report->ids[i] = bufptr[4];
+ bufptr = bufptr + 5;
+ } else {
+ bufptr = bufptr + 4;
+ }
+ }
+}
+
+static void pixcir_ts_report(struct pixcir_i2c_ts_data *ts,
+ struct pixcir_report_data *report)
+{
+ int slots[PIXCIR_MAX_SLOTS];
+ int n, i, slot;
+ struct device *dev = &ts->client->dev;
+ const struct pixcir_i2c_chip_data *chip = ts->chip;
+
+ n = report->num_touches;
+ if (n > PIXCIR_MAX_SLOTS)
+ n = PIXCIR_MAX_SLOTS;
+
+ if (!ts->chip->has_hw_ids)
+ input_mt_assign_slots(ts->input, slots, report->pos, n, 0);
+
+ for (i = 0; i < n; i++) {
+ if (chip->has_hw_ids) {
+ slot = input_mt_get_slot_by_key(ts->input,
+ report->ids[i]);
+ if (slot < 0) {
+ dev_dbg(dev, "no free slot for id 0x%x\n",
+ report->ids[i]);
+ continue;
+ }
+ } else {
+ slot = slots[i];
+ }
+
+ input_mt_slot(ts->input, slot);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+
+ input_report_abs(ts->input, ABS_MT_POSITION_X,
+ report->pos[i].x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y,
+ report->pos[i].y);
+
+ dev_dbg(dev, "%d: slot %d, x %d, y %d\n",
+ i, slot, report->pos[i].x, report->pos[i].y);
+ }
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+}
+
+static irqreturn_t pixcir_ts_isr(int irq, void *dev_id)
+{
+ struct pixcir_i2c_ts_data *tsdata = dev_id;
+ struct pixcir_report_data report;
+
+ while (tsdata->running) {
+ /* parse packet */
+ pixcir_ts_parse(tsdata, &report);
+
+ /* report it */
+ pixcir_ts_report(tsdata, &report);
+
+ if (gpiod_get_value_cansleep(tsdata->gpio_attb)) {
+ if (report.num_touches) {
+ /*
+ * Last report with no finger up?
+ * Do it now then.
+ */
+ input_mt_sync_frame(tsdata->input);
+ input_sync(tsdata->input);
+ }
+ break;
+ }
+
+ msleep(20);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void pixcir_reset(struct pixcir_i2c_ts_data *tsdata)
+{
+ if (!IS_ERR_OR_NULL(tsdata->gpio_reset)) {
+ gpiod_set_value_cansleep(tsdata->gpio_reset, 1);
+ ndelay(100); /* datasheet section 1.2.3 says 80ns min. */
+ gpiod_set_value_cansleep(tsdata->gpio_reset, 0);
+ /* wait for controller ready. 100ms guess. */
+ msleep(100);
+ }
+}
+
+static int pixcir_set_power_mode(struct pixcir_i2c_ts_data *ts,
+ enum pixcir_power_mode mode)
+{
+ struct device *dev = &ts->client->dev;
+ int ret;
+
+ if (mode == PIXCIR_POWER_ACTIVE || mode == PIXCIR_POWER_IDLE) {
+ if (ts->gpio_wake)
+ gpiod_set_value_cansleep(ts->gpio_wake, 1);
+ }
+
+ ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_POWER_MODE);
+ if (ret < 0) {
+ dev_err(dev, "%s: can't read reg %d : %d\n",
+ __func__, PIXCIR_REG_POWER_MODE, ret);
+ return ret;
+ }
+
+ ret &= ~PIXCIR_POWER_MODE_MASK;
+ ret |= mode;
+
+ /* Always AUTO_IDLE */
+ ret |= PIXCIR_POWER_ALLOW_IDLE;
+
+ ret = i2c_smbus_write_byte_data(ts->client, PIXCIR_REG_POWER_MODE, ret);
+ if (ret < 0) {
+ dev_err(dev, "%s: can't write reg %d : %d\n",
+ __func__, PIXCIR_REG_POWER_MODE, ret);
+ return ret;
+ }
+
+ if (mode == PIXCIR_POWER_HALT) {
+ if (ts->gpio_wake)
+ gpiod_set_value_cansleep(ts->gpio_wake, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * Set the interrupt mode for the device i.e. ATTB line behaviour
+ *
+ * @polarity : 1 for active high, 0 for active low.
+ */
+static int pixcir_set_int_mode(struct pixcir_i2c_ts_data *ts,
+ enum pixcir_int_mode mode, bool polarity)
+{
+ struct device *dev = &ts->client->dev;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_INT_MODE);
+ if (ret < 0) {
+ dev_err(dev, "%s: can't read reg %d : %d\n",
+ __func__, PIXCIR_REG_INT_MODE, ret);
+ return ret;
+ }
+
+ ret &= ~PIXCIR_INT_MODE_MASK;
+ ret |= mode;
+
+ if (polarity)
+ ret |= PIXCIR_INT_POL_HIGH;
+ else
+ ret &= ~PIXCIR_INT_POL_HIGH;
+
+ ret = i2c_smbus_write_byte_data(ts->client, PIXCIR_REG_INT_MODE, ret);
+ if (ret < 0) {
+ dev_err(dev, "%s: can't write reg %d : %d\n",
+ __func__, PIXCIR_REG_INT_MODE, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Enable/disable interrupt generation
+ */
+static int pixcir_int_enable(struct pixcir_i2c_ts_data *ts, bool enable)
+{
+ struct device *dev = &ts->client->dev;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_INT_MODE);
+ if (ret < 0) {
+ dev_err(dev, "%s: can't read reg %d : %d\n",
+ __func__, PIXCIR_REG_INT_MODE, ret);
+ return ret;
+ }
+
+ if (enable)
+ ret |= PIXCIR_INT_ENABLE;
+ else
+ ret &= ~PIXCIR_INT_ENABLE;
+
+ ret = i2c_smbus_write_byte_data(ts->client, PIXCIR_REG_INT_MODE, ret);
+ if (ret < 0) {
+ dev_err(dev, "%s: can't write reg %d : %d\n",
+ __func__, PIXCIR_REG_INT_MODE, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pixcir_start(struct pixcir_i2c_ts_data *ts)
+{
+ struct device *dev = &ts->client->dev;
+ int error;
+
+ if (ts->gpio_enable) {
+ gpiod_set_value_cansleep(ts->gpio_enable, 1);
+ msleep(100);
+ }
+
+ /* LEVEL_TOUCH interrupt with active low polarity */
+ error = pixcir_set_int_mode(ts, PIXCIR_INT_LEVEL_TOUCH, 0);
+ if (error) {
+ dev_err(dev, "Failed to set interrupt mode: %d\n", error);
+ return error;
+ }
+
+ ts->running = true;
+ mb(); /* Update status before IRQ can fire */
+
+ /* enable interrupt generation */
+ error = pixcir_int_enable(ts, true);
+ if (error) {
+ dev_err(dev, "Failed to enable interrupt generation: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int pixcir_stop(struct pixcir_i2c_ts_data *ts)
+{
+ int error;
+
+ /* Disable interrupt generation */
+ error = pixcir_int_enable(ts, false);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "Failed to disable interrupt generation: %d\n",
+ error);
+ return error;
+ }
+
+ /* Exit ISR if running, no more report parsing */
+ ts->running = false;
+ mb(); /* update status before we synchronize irq */
+
+ /* Wait till running ISR is complete */
+ synchronize_irq(ts->client->irq);
+
+ if (ts->gpio_enable)
+ gpiod_set_value_cansleep(ts->gpio_enable, 0);
+
+ return 0;
+}
+
+static int pixcir_input_open(struct input_dev *dev)
+{
+ struct pixcir_i2c_ts_data *ts = input_get_drvdata(dev);
+
+ return pixcir_start(ts);
+}
+
+static void pixcir_input_close(struct input_dev *dev)
+{
+ struct pixcir_i2c_ts_data *ts = input_get_drvdata(dev);
+
+ pixcir_stop(ts);
+}
+
+static int __maybe_unused pixcir_i2c_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pixcir_i2c_ts_data *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+
+ if (device_may_wakeup(&client->dev)) {
+ if (!input_device_enabled(input)) {
+ ret = pixcir_start(ts);
+ if (ret) {
+ dev_err(dev, "Failed to start\n");
+ goto unlock;
+ }
+ }
+ } else if (input_device_enabled(input)) {
+ ret = pixcir_stop(ts);
+ }
+
+unlock:
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static int __maybe_unused pixcir_i2c_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pixcir_i2c_ts_data *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+
+ if (device_may_wakeup(&client->dev)) {
+ if (!input_device_enabled(input)) {
+ ret = pixcir_stop(ts);
+ if (ret) {
+ dev_err(dev, "Failed to stop\n");
+ goto unlock;
+ }
+ }
+ } else if (input_device_enabled(input)) {
+ ret = pixcir_start(ts);
+ }
+
+unlock:
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(pixcir_dev_pm_ops,
+ pixcir_i2c_ts_suspend, pixcir_i2c_ts_resume);
+
+static int pixcir_i2c_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct pixcir_i2c_ts_data *tsdata;
+ struct input_dev *input;
+ int error;
+
+ tsdata = devm_kzalloc(dev, sizeof(*tsdata), GFP_KERNEL);
+ if (!tsdata)
+ return -ENOMEM;
+
+ tsdata->chip = device_get_match_data(dev);
+ if (!tsdata->chip && id)
+ tsdata->chip = (const void *)id->driver_data;
+ if (!tsdata->chip) {
+ dev_err(dev, "can't locate chip data\n");
+ return -EINVAL;
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ tsdata->client = client;
+ tsdata->input = input;
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->open = pixcir_input_open;
+ input->close = pixcir_input_close;
+
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y);
+ touchscreen_parse_properties(input, true, &tsdata->prop);
+ if (!input_abs_get_max(input, ABS_MT_POSITION_X) ||
+ !input_abs_get_max(input, ABS_MT_POSITION_Y)) {
+ dev_err(dev, "Touchscreen size is not specified\n");
+ return -EINVAL;
+ }
+
+ error = input_mt_init_slots(input, tsdata->chip->max_fingers,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(dev, "Error initializing Multi-Touch slots\n");
+ return error;
+ }
+
+ input_set_drvdata(input, tsdata);
+
+ tsdata->gpio_attb = devm_gpiod_get(dev, "attb", GPIOD_IN);
+ if (IS_ERR(tsdata->gpio_attb)) {
+ error = PTR_ERR(tsdata->gpio_attb);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to request ATTB gpio: %d\n",
+ error);
+ return error;
+ }
+
+ tsdata->gpio_reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(tsdata->gpio_reset)) {
+ error = PTR_ERR(tsdata->gpio_reset);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to request RESET gpio: %d\n",
+ error);
+ return error;
+ }
+
+ tsdata->gpio_wake = devm_gpiod_get_optional(dev, "wake",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(tsdata->gpio_wake)) {
+ error = PTR_ERR(tsdata->gpio_wake);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get wake gpio: %d\n", error);
+ return error;
+ }
+
+ tsdata->gpio_enable = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(tsdata->gpio_enable)) {
+ error = PTR_ERR(tsdata->gpio_enable);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get enable gpio: %d\n", error);
+ return error;
+ }
+
+ if (tsdata->gpio_enable)
+ msleep(100);
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL, pixcir_ts_isr,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ client->name, tsdata);
+ if (error) {
+ dev_err(dev, "failed to request irq %d\n", client->irq);
+ return error;
+ }
+
+ pixcir_reset(tsdata);
+
+ /* Always be in IDLE mode to save power, device supports auto wake */
+ error = pixcir_set_power_mode(tsdata, PIXCIR_POWER_IDLE);
+ if (error) {
+ dev_err(dev, "Failed to set IDLE mode\n");
+ return error;
+ }
+
+ /* Stop device till opened */
+ error = pixcir_stop(tsdata);
+ if (error)
+ return error;
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ i2c_set_clientdata(client, tsdata);
+
+ return 0;
+}
+
+static const struct pixcir_i2c_chip_data pixcir_ts_data = {
+ .max_fingers = 2,
+ /* no hw id support */
+};
+
+static const struct pixcir_i2c_chip_data pixcir_tangoc_data = {
+ .max_fingers = 5,
+ .has_hw_ids = true,
+};
+
+static const struct i2c_device_id pixcir_i2c_ts_id[] = {
+ { "pixcir_ts", (unsigned long) &pixcir_ts_data },
+ { "pixcir_tangoc", (unsigned long) &pixcir_tangoc_data },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pixcir_i2c_ts_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id pixcir_of_match[] = {
+ { .compatible = "pixcir,pixcir_ts", .data = &pixcir_ts_data },
+ { .compatible = "pixcir,pixcir_tangoc", .data = &pixcir_tangoc_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pixcir_of_match);
+#endif
+
+static struct i2c_driver pixcir_i2c_ts_driver = {
+ .driver = {
+ .name = "pixcir_ts",
+ .pm = &pixcir_dev_pm_ops,
+ .of_match_table = of_match_ptr(pixcir_of_match),
+ },
+ .probe = pixcir_i2c_ts_probe,
+ .id_table = pixcir_i2c_ts_id,
+};
+
+module_i2c_driver(pixcir_i2c_ts_driver);
+
+MODULE_AUTHOR("Jianchun Bian <jcbian@pixcir.com.cn>, Dequan Meng <dqmeng@pixcir.com.cn>");
+MODULE_DESCRIPTION("Pixcir I2C Touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/raspberrypi-ts.c b/drivers/input/touchscreen/raspberrypi-ts.c
new file mode 100644
index 000000000..45c575df9
--- /dev/null
+++ b/drivers/input/touchscreen/raspberrypi-ts.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Raspberry Pi firmware based touchscreen driver
+ *
+ * Copyright (C) 2015, 2017 Raspberry Pi
+ * Copyright (C) 2018 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
+ */
+
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define RPI_TS_DEFAULT_WIDTH 800
+#define RPI_TS_DEFAULT_HEIGHT 480
+
+#define RPI_TS_MAX_SUPPORTED_POINTS 10
+
+#define RPI_TS_FTS_TOUCH_DOWN 0
+#define RPI_TS_FTS_TOUCH_CONTACT 2
+
+#define RPI_TS_POLL_INTERVAL 17 /* 60fps */
+
+#define RPI_TS_NPOINTS_REG_INVALIDATE 99
+
+struct rpi_ts {
+ struct platform_device *pdev;
+ struct input_dev *input;
+ struct touchscreen_properties prop;
+
+ void __iomem *fw_regs_va;
+ dma_addr_t fw_regs_phys;
+
+ int known_ids;
+};
+
+struct rpi_ts_regs {
+ u8 device_mode;
+ u8 gesture_id;
+ u8 num_points;
+ struct rpi_ts_touch {
+ u8 xh;
+ u8 xl;
+ u8 yh;
+ u8 yl;
+ u8 pressure; /* Not supported */
+ u8 area; /* Not supported */
+ } point[RPI_TS_MAX_SUPPORTED_POINTS];
+};
+
+static void rpi_ts_poll(struct input_dev *input)
+{
+ struct rpi_ts *ts = input_get_drvdata(input);
+ struct rpi_ts_regs regs;
+ int modified_ids = 0;
+ long released_ids;
+ int event_type;
+ int touchid;
+ int x, y;
+ int i;
+
+ memcpy_fromio(&regs, ts->fw_regs_va, sizeof(regs));
+ /*
+ * We poll the memory based register copy of the touchscreen chip using
+ * the number of points register to know whether the copy has been
+ * updated (we write 99 to the memory copy, the GPU will write between
+ * 0 - 10 points)
+ */
+ iowrite8(RPI_TS_NPOINTS_REG_INVALIDATE,
+ ts->fw_regs_va + offsetof(struct rpi_ts_regs, num_points));
+
+ if (regs.num_points == RPI_TS_NPOINTS_REG_INVALIDATE ||
+ (regs.num_points == 0 && ts->known_ids == 0))
+ return;
+
+ for (i = 0; i < regs.num_points; i++) {
+ x = (((int)regs.point[i].xh & 0xf) << 8) + regs.point[i].xl;
+ y = (((int)regs.point[i].yh & 0xf) << 8) + regs.point[i].yl;
+ touchid = (regs.point[i].yh >> 4) & 0xf;
+ event_type = (regs.point[i].xh >> 6) & 0x03;
+
+ modified_ids |= BIT(touchid);
+
+ if (event_type == RPI_TS_FTS_TOUCH_DOWN ||
+ event_type == RPI_TS_FTS_TOUCH_CONTACT) {
+ input_mt_slot(input, touchid);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
+ touchscreen_report_pos(input, &ts->prop, x, y, true);
+ }
+ }
+
+ released_ids = ts->known_ids & ~modified_ids;
+ for_each_set_bit(i, &released_ids, RPI_TS_MAX_SUPPORTED_POINTS) {
+ input_mt_slot(input, i);
+ input_mt_report_slot_inactive(input);
+ modified_ids &= ~(BIT(i));
+ }
+ ts->known_ids = modified_ids;
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+}
+
+static void rpi_ts_dma_cleanup(void *data)
+{
+ struct rpi_ts *ts = data;
+ struct device *dev = &ts->pdev->dev;
+
+ dma_free_coherent(dev, PAGE_SIZE, ts->fw_regs_va, ts->fw_regs_phys);
+}
+
+static int rpi_ts_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct input_dev *input;
+ struct device_node *fw_node;
+ struct rpi_firmware *fw;
+ struct rpi_ts *ts;
+ u32 touchbuf;
+ int error;
+
+ fw_node = of_get_parent(np);
+ if (!fw_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ fw = devm_rpi_firmware_get(&pdev->dev, fw_node);
+ of_node_put(fw_node);
+ if (!fw)
+ return -EPROBE_DEFER;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+ ts->pdev = pdev;
+
+ ts->fw_regs_va = dma_alloc_coherent(dev, PAGE_SIZE, &ts->fw_regs_phys,
+ GFP_KERNEL);
+ if (!ts->fw_regs_va) {
+ dev_err(dev, "failed to dma_alloc_coherent\n");
+ return -ENOMEM;
+ }
+
+ error = devm_add_action_or_reset(dev, rpi_ts_dma_cleanup, ts);
+ if (error) {
+ dev_err(dev, "failed to devm_add_action_or_reset, %d\n", error);
+ return error;
+ }
+
+ touchbuf = (u32)ts->fw_regs_phys;
+ error = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF,
+ &touchbuf, sizeof(touchbuf));
+ if (error || touchbuf != 0) {
+ dev_warn(dev, "Failed to set touchbuf, %d\n", error);
+ return error;
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->input = input;
+ input_set_drvdata(input, ts);
+
+ input->name = "raspberrypi-ts";
+ input->id.bustype = BUS_HOST;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0,
+ RPI_TS_DEFAULT_WIDTH, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
+ RPI_TS_DEFAULT_HEIGHT, 0, 0);
+ touchscreen_parse_properties(input, true, &ts->prop);
+
+ error = input_mt_init_slots(input, RPI_TS_MAX_SUPPORTED_POINTS,
+ INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(dev, "could not init mt slots, %d\n", error);
+ return error;
+ }
+
+ error = input_setup_polling(input, rpi_ts_poll);
+ if (error) {
+ dev_err(dev, "could not set up polling mode, %d\n", error);
+ return error;
+ }
+
+ input_set_poll_interval(input, RPI_TS_POLL_INTERVAL);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "could not register input device, %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id rpi_ts_match[] = {
+ { .compatible = "raspberrypi,firmware-ts", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rpi_ts_match);
+
+static struct platform_driver rpi_ts_driver = {
+ .driver = {
+ .name = "raspberrypi-ts",
+ .of_match_table = rpi_ts_match,
+ },
+ .probe = rpi_ts_probe,
+};
+module_platform_driver(rpi_ts_driver);
+
+MODULE_AUTHOR("Gordon Hollingworth");
+MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
+MODULE_DESCRIPTION("Raspberry Pi firmware based touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/raydium_i2c_ts.c b/drivers/input/touchscreen/raydium_i2c_ts.c
new file mode 100644
index 000000000..3d9c5758d
--- /dev/null
+++ b/drivers/input/touchscreen/raydium_i2c_ts.c
@@ -0,0 +1,1295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Raydium touchscreen I2C driver.
+ *
+ * Copyright (C) 2012-2014, Raydium Semiconductor Corporation.
+ *
+ * Raydium reserves the right to make changes without further notice
+ * to the materials described herein. Raydium does not assume any
+ * liability arising out of the application described herein.
+ *
+ * Contact Raydium Semiconductor Corporation at www.rad-ic.com
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+/* Slave I2C mode */
+#define RM_BOOT_BLDR 0x02
+#define RM_BOOT_MAIN 0x03
+
+/* I2C bootoloader commands */
+#define RM_CMD_BOOT_PAGE_WRT 0x0B /* send bl page write */
+#define RM_CMD_BOOT_WRT 0x11 /* send bl write */
+#define RM_CMD_BOOT_ACK 0x22 /* send ack*/
+#define RM_CMD_BOOT_CHK 0x33 /* send data check */
+#define RM_CMD_BOOT_READ 0x44 /* send wait bl data ready*/
+
+#define RM_BOOT_RDY 0xFF /* bl data ready */
+#define RM_BOOT_CMD_READHWID 0x0E /* read hwid */
+
+/* I2C main commands */
+#define RM_CMD_QUERY_BANK 0x2B
+#define RM_CMD_DATA_BANK 0x4D
+#define RM_CMD_ENTER_SLEEP 0x4E
+#define RM_CMD_BANK_SWITCH 0xAA
+
+#define RM_RESET_MSG_ADDR 0x40000004
+
+#define RM_MAX_READ_SIZE 56
+#define RM_PACKET_CRC_SIZE 2
+
+/* Touch relative info */
+#define RM_MAX_RETRIES 3
+#define RM_RETRY_DELAY_MS 20
+#define RM_MAX_TOUCH_NUM 10
+#define RM_BOOT_DELAY_MS 100
+
+/* Offsets in contact data */
+#define RM_CONTACT_STATE_POS 0
+#define RM_CONTACT_X_POS 1
+#define RM_CONTACT_Y_POS 3
+#define RM_CONTACT_PRESSURE_POS 5
+#define RM_CONTACT_WIDTH_X_POS 6
+#define RM_CONTACT_WIDTH_Y_POS 7
+
+/* Bootloader relative info */
+#define RM_BL_WRT_CMD_SIZE 3 /* bl flash wrt cmd size */
+#define RM_BL_WRT_PKG_SIZE 32 /* bl wrt pkg size */
+#define RM_BL_WRT_LEN (RM_BL_WRT_PKG_SIZE + RM_BL_WRT_CMD_SIZE)
+#define RM_FW_PAGE_SIZE 128
+#define RM_MAX_FW_RETRIES 30
+#define RM_MAX_FW_SIZE 0xD000
+
+#define RM_POWERON_DELAY_USEC 500
+#define RM_RESET_DELAY_MSEC 50
+
+enum raydium_bl_cmd {
+ BL_HEADER = 0,
+ BL_PAGE_STR,
+ BL_PKG_IDX,
+ BL_DATA_STR,
+};
+
+enum raydium_bl_ack {
+ RAYDIUM_ACK_NULL = 0,
+ RAYDIUM_WAIT_READY,
+ RAYDIUM_PATH_READY,
+};
+
+enum raydium_boot_mode {
+ RAYDIUM_TS_MAIN = 0,
+ RAYDIUM_TS_BLDR,
+};
+
+/* Response to RM_CMD_DATA_BANK request */
+struct raydium_data_info {
+ __le32 data_bank_addr;
+ u8 pkg_size;
+ u8 tp_info_size;
+};
+
+struct raydium_info {
+ __le32 hw_ver; /*device version */
+ u8 main_ver;
+ u8 sub_ver;
+ __le16 ft_ver; /* test version */
+ u8 x_num;
+ u8 y_num;
+ __le16 x_max;
+ __le16 y_max;
+ u8 x_res; /* units/mm */
+ u8 y_res; /* units/mm */
+};
+
+/* struct raydium_data - represents state of Raydium touchscreen device */
+struct raydium_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+
+ struct regulator *avdd;
+ struct regulator *vccio;
+ struct gpio_desc *reset_gpio;
+
+ struct raydium_info info;
+
+ struct mutex sysfs_mutex;
+
+ u8 *report_data;
+
+ u32 data_bank_addr;
+ u8 report_size;
+ u8 contact_size;
+ u8 pkg_size;
+
+ enum raydium_boot_mode boot_mode;
+
+ bool wake_irq_enabled;
+};
+
+/*
+ * Header to be sent for RM_CMD_BANK_SWITCH command. This is used by
+ * raydium_i2c_{read|send} below.
+ */
+struct __packed raydium_bank_switch_header {
+ u8 cmd;
+ __be32 be_addr;
+};
+
+static int raydium_i2c_xfer(struct i2c_client *client, u32 addr,
+ struct i2c_msg *xfer, size_t xfer_count)
+{
+ int ret;
+ /*
+ * If address is greater than 255, then RM_CMD_BANK_SWITCH needs to be
+ * sent first. Else, skip the header i.e. xfer[0].
+ */
+ int xfer_start_idx = (addr > 0xff) ? 0 : 1;
+ xfer_count -= xfer_start_idx;
+
+ ret = i2c_transfer(client->adapter, &xfer[xfer_start_idx], xfer_count);
+ if (likely(ret == xfer_count))
+ return 0;
+
+ return ret < 0 ? ret : -EIO;
+}
+
+static int raydium_i2c_send(struct i2c_client *client,
+ u32 addr, const void *data, size_t len)
+{
+ int tries = 0;
+ int error;
+ u8 *tx_buf;
+ u8 reg_addr = addr & 0xff;
+
+ tx_buf = kmalloc(len + 1, GFP_KERNEL);
+ if (!tx_buf)
+ return -ENOMEM;
+
+ tx_buf[0] = reg_addr;
+ memcpy(tx_buf + 1, data, len);
+
+ do {
+ struct raydium_bank_switch_header header = {
+ .cmd = RM_CMD_BANK_SWITCH,
+ .be_addr = cpu_to_be32(addr),
+ };
+
+ /*
+ * Perform as a single i2c_transfer transaction to ensure that
+ * no other I2C transactions are initiated on the bus to any
+ * other device in between. Initiating transacations to other
+ * devices after RM_CMD_BANK_SWITCH is sent is known to cause
+ * issues. This is also why regmap infrastructure cannot be used
+ * for this driver. Regmap handles page(bank) switch and reads
+ * as separate i2c_transfer() operations. This can result in
+ * problems if the Raydium device is on a shared I2C bus.
+ */
+ struct i2c_msg xfer[] = {
+ {
+ .addr = client->addr,
+ .len = sizeof(header),
+ .buf = (u8 *)&header,
+ },
+ {
+ .addr = client->addr,
+ .len = len + 1,
+ .buf = tx_buf,
+ },
+ };
+
+ error = raydium_i2c_xfer(client, addr, xfer, ARRAY_SIZE(xfer));
+ if (likely(!error))
+ goto out;
+
+ msleep(RM_RETRY_DELAY_MS);
+ } while (++tries < RM_MAX_RETRIES);
+
+ dev_err(&client->dev, "%s failed: %d\n", __func__, error);
+out:
+ kfree(tx_buf);
+ return error;
+}
+
+static int raydium_i2c_read(struct i2c_client *client,
+ u32 addr, void *data, size_t len)
+{
+ int error;
+
+ while (len) {
+ u8 reg_addr = addr & 0xff;
+ struct raydium_bank_switch_header header = {
+ .cmd = RM_CMD_BANK_SWITCH,
+ .be_addr = cpu_to_be32(addr),
+ };
+ size_t xfer_len = min_t(size_t, len, RM_MAX_READ_SIZE);
+
+ /*
+ * Perform as a single i2c_transfer transaction to ensure that
+ * no other I2C transactions are initiated on the bus to any
+ * other device in between. Initiating transacations to other
+ * devices after RM_CMD_BANK_SWITCH is sent is known to cause
+ * issues. This is also why regmap infrastructure cannot be used
+ * for this driver. Regmap handles page(bank) switch and writes
+ * as separate i2c_transfer() operations. This can result in
+ * problems if the Raydium device is on a shared I2C bus.
+ */
+ struct i2c_msg xfer[] = {
+ {
+ .addr = client->addr,
+ .len = sizeof(header),
+ .buf = (u8 *)&header,
+ },
+ {
+ .addr = client->addr,
+ .len = 1,
+ .buf = &reg_addr,
+ },
+ {
+ .addr = client->addr,
+ .len = xfer_len,
+ .buf = data,
+ .flags = I2C_M_RD,
+ }
+ };
+
+ error = raydium_i2c_xfer(client, addr, xfer, ARRAY_SIZE(xfer));
+ if (unlikely(error))
+ return error;
+
+ len -= xfer_len;
+ data += xfer_len;
+ addr += xfer_len;
+ }
+
+ return 0;
+}
+
+static int raydium_i2c_sw_reset(struct i2c_client *client)
+{
+ const u8 soft_rst_cmd = 0x01;
+ int error;
+
+ error = raydium_i2c_send(client, RM_RESET_MSG_ADDR, &soft_rst_cmd,
+ sizeof(soft_rst_cmd));
+ if (error) {
+ dev_err(&client->dev, "software reset failed: %d\n", error);
+ return error;
+ }
+
+ msleep(RM_RESET_DELAY_MSEC);
+
+ return 0;
+}
+
+static int raydium_i2c_query_ts_bootloader_info(struct raydium_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ static const u8 get_hwid[] = { RM_BOOT_CMD_READHWID,
+ 0x10, 0xc0, 0x01, 0x00, 0x04, 0x00 };
+ u8 rbuf[5] = { 0 };
+ u32 hw_ver;
+ int error;
+
+ error = raydium_i2c_send(client, RM_CMD_BOOT_WRT,
+ get_hwid, sizeof(get_hwid));
+ if (error) {
+ dev_err(&client->dev, "WRT HWID command failed: %d\n", error);
+ return error;
+ }
+
+ error = raydium_i2c_send(client, RM_CMD_BOOT_ACK, rbuf, 1);
+ if (error) {
+ dev_err(&client->dev, "Ack HWID command failed: %d\n", error);
+ return error;
+ }
+
+ error = raydium_i2c_read(client, RM_CMD_BOOT_CHK, rbuf, sizeof(rbuf));
+ if (error) {
+ dev_err(&client->dev, "Read HWID command failed: %d (%4ph)\n",
+ error, rbuf + 1);
+ hw_ver = 0xffffffffUL;
+ } else {
+ hw_ver = get_unaligned_be32(rbuf + 1);
+ }
+
+ ts->info.hw_ver = cpu_to_le32(hw_ver);
+ ts->info.main_ver = 0xff;
+ ts->info.sub_ver = 0xff;
+
+ return error;
+}
+
+static int raydium_i2c_query_ts_info(struct raydium_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ struct raydium_data_info data_info;
+ __le32 query_bank_addr;
+
+ int error, retry_cnt;
+
+ for (retry_cnt = 0; retry_cnt < RM_MAX_RETRIES; retry_cnt++) {
+ error = raydium_i2c_read(client, RM_CMD_DATA_BANK,
+ &data_info, sizeof(data_info));
+ if (error)
+ continue;
+
+ /*
+ * Warn user if we already allocated memory for reports and
+ * then the size changed (due to firmware update?) and keep
+ * old size instead.
+ */
+ if (ts->report_data && ts->pkg_size != data_info.pkg_size) {
+ dev_warn(&client->dev,
+ "report size changes, was: %d, new: %d\n",
+ ts->pkg_size, data_info.pkg_size);
+ } else {
+ ts->pkg_size = data_info.pkg_size;
+ ts->report_size = ts->pkg_size - RM_PACKET_CRC_SIZE;
+ }
+
+ ts->contact_size = data_info.tp_info_size;
+ ts->data_bank_addr = le32_to_cpu(data_info.data_bank_addr);
+
+ dev_dbg(&client->dev,
+ "data_bank_addr: %#08x, report_size: %d, contact_size: %d\n",
+ ts->data_bank_addr, ts->report_size, ts->contact_size);
+
+ error = raydium_i2c_read(client, RM_CMD_QUERY_BANK,
+ &query_bank_addr,
+ sizeof(query_bank_addr));
+ if (error)
+ continue;
+
+ error = raydium_i2c_read(client, le32_to_cpu(query_bank_addr),
+ &ts->info, sizeof(ts->info));
+ if (error)
+ continue;
+
+ return 0;
+ }
+
+ dev_err(&client->dev, "failed to query device parameters: %d\n", error);
+ return error;
+}
+
+static int raydium_i2c_check_fw_status(struct raydium_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ static const u8 bl_ack = 0x62;
+ static const u8 main_ack = 0x66;
+ u8 buf[4];
+ int error;
+
+ error = raydium_i2c_read(client, RM_CMD_BOOT_READ, buf, sizeof(buf));
+ if (!error) {
+ if (buf[0] == bl_ack)
+ ts->boot_mode = RAYDIUM_TS_BLDR;
+ else if (buf[0] == main_ack)
+ ts->boot_mode = RAYDIUM_TS_MAIN;
+ return 0;
+ }
+
+ return error;
+}
+
+static int raydium_i2c_initialize(struct raydium_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ int error, retry_cnt;
+
+ for (retry_cnt = 0; retry_cnt < RM_MAX_RETRIES; retry_cnt++) {
+ /* Wait for Hello packet */
+ msleep(RM_BOOT_DELAY_MS);
+
+ error = raydium_i2c_check_fw_status(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to read 'hello' packet: %d\n", error);
+ continue;
+ }
+
+ if (ts->boot_mode == RAYDIUM_TS_BLDR ||
+ ts->boot_mode == RAYDIUM_TS_MAIN) {
+ break;
+ }
+ }
+
+ if (error)
+ ts->boot_mode = RAYDIUM_TS_BLDR;
+
+ if (ts->boot_mode == RAYDIUM_TS_BLDR)
+ raydium_i2c_query_ts_bootloader_info(ts);
+ else
+ raydium_i2c_query_ts_info(ts);
+
+ return error;
+}
+
+static int raydium_i2c_bl_chk_state(struct i2c_client *client,
+ enum raydium_bl_ack state)
+{
+ static const u8 ack_ok[] = { 0xFF, 0x39, 0x30, 0x30, 0x54 };
+ u8 rbuf[sizeof(ack_ok)];
+ u8 retry;
+ int error;
+
+ for (retry = 0; retry < RM_MAX_FW_RETRIES; retry++) {
+ switch (state) {
+ case RAYDIUM_ACK_NULL:
+ return 0;
+
+ case RAYDIUM_WAIT_READY:
+ error = raydium_i2c_read(client, RM_CMD_BOOT_CHK,
+ &rbuf[0], 1);
+ if (!error && rbuf[0] == RM_BOOT_RDY)
+ return 0;
+
+ break;
+
+ case RAYDIUM_PATH_READY:
+ error = raydium_i2c_read(client, RM_CMD_BOOT_CHK,
+ rbuf, sizeof(rbuf));
+ if (!error && !memcmp(rbuf, ack_ok, sizeof(ack_ok)))
+ return 0;
+
+ break;
+
+ default:
+ dev_err(&client->dev, "%s: invalid target state %d\n",
+ __func__, state);
+ return -EINVAL;
+ }
+
+ msleep(20);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int raydium_i2c_write_object(struct i2c_client *client,
+ const void *data, size_t len,
+ enum raydium_bl_ack state)
+{
+ int error;
+ static const u8 cmd[] = { 0xFF, 0x39 };
+
+ error = raydium_i2c_send(client, RM_CMD_BOOT_WRT, data, len);
+ if (error) {
+ dev_err(&client->dev, "WRT obj command failed: %d\n",
+ error);
+ return error;
+ }
+
+ error = raydium_i2c_send(client, RM_CMD_BOOT_ACK, cmd, sizeof(cmd));
+ if (error) {
+ dev_err(&client->dev, "Ack obj command failed: %d\n", error);
+ return error;
+ }
+
+ error = raydium_i2c_bl_chk_state(client, state);
+ if (error) {
+ dev_err(&client->dev, "BL check state failed: %d\n", error);
+ return error;
+ }
+ return 0;
+}
+
+static int raydium_i2c_boot_trigger(struct i2c_client *client)
+{
+ static const u8 cmd[7][6] = {
+ { 0x08, 0x0C, 0x09, 0x00, 0x50, 0xD7 },
+ { 0x08, 0x04, 0x09, 0x00, 0x50, 0xA5 },
+ { 0x08, 0x04, 0x09, 0x00, 0x50, 0x00 },
+ { 0x08, 0x04, 0x09, 0x00, 0x50, 0xA5 },
+ { 0x08, 0x0C, 0x09, 0x00, 0x50, 0x00 },
+ { 0x06, 0x01, 0x00, 0x00, 0x00, 0x00 },
+ { 0x02, 0xA2, 0x00, 0x00, 0x00, 0x00 },
+ };
+ int i;
+ int error;
+
+ for (i = 0; i < 7; i++) {
+ error = raydium_i2c_write_object(client, cmd[i], sizeof(cmd[i]),
+ RAYDIUM_WAIT_READY);
+ if (error) {
+ dev_err(&client->dev,
+ "boot trigger failed at step %d: %d\n",
+ i, error);
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+static int raydium_i2c_fw_trigger(struct i2c_client *client)
+{
+ static const u8 cmd[5][11] = {
+ { 0, 0x09, 0x71, 0x0C, 0x09, 0x00, 0x50, 0xD7, 0, 0, 0 },
+ { 0, 0x09, 0x71, 0x04, 0x09, 0x00, 0x50, 0xA5, 0, 0, 0 },
+ { 0, 0x09, 0x71, 0x04, 0x09, 0x00, 0x50, 0x00, 0, 0, 0 },
+ { 0, 0x09, 0x71, 0x04, 0x09, 0x00, 0x50, 0xA5, 0, 0, 0 },
+ { 0, 0x09, 0x71, 0x0C, 0x09, 0x00, 0x50, 0x00, 0, 0, 0 },
+ };
+ int i;
+ int error;
+
+ for (i = 0; i < 5; i++) {
+ error = raydium_i2c_write_object(client, cmd[i], sizeof(cmd[i]),
+ RAYDIUM_ACK_NULL);
+ if (error) {
+ dev_err(&client->dev,
+ "fw trigger failed at step %d: %d\n",
+ i, error);
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+static int raydium_i2c_check_path(struct i2c_client *client)
+{
+ static const u8 cmd[] = { 0x09, 0x00, 0x09, 0x00, 0x50, 0x10, 0x00 };
+ int error;
+
+ error = raydium_i2c_write_object(client, cmd, sizeof(cmd),
+ RAYDIUM_PATH_READY);
+ if (error) {
+ dev_err(&client->dev, "check path command failed: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int raydium_i2c_enter_bl(struct i2c_client *client)
+{
+ static const u8 cal_cmd[] = { 0x00, 0x01, 0x52 };
+ int error;
+
+ error = raydium_i2c_write_object(client, cal_cmd, sizeof(cal_cmd),
+ RAYDIUM_ACK_NULL);
+ if (error) {
+ dev_err(&client->dev, "enter bl command failed: %d\n", error);
+ return error;
+ }
+
+ msleep(RM_BOOT_DELAY_MS);
+ return 0;
+}
+
+static int raydium_i2c_leave_bl(struct i2c_client *client)
+{
+ static const u8 leave_cmd[] = { 0x05, 0x00 };
+ int error;
+
+ error = raydium_i2c_write_object(client, leave_cmd, sizeof(leave_cmd),
+ RAYDIUM_ACK_NULL);
+ if (error) {
+ dev_err(&client->dev, "leave bl command failed: %d\n", error);
+ return error;
+ }
+
+ msleep(RM_BOOT_DELAY_MS);
+ return 0;
+}
+
+static int raydium_i2c_write_checksum(struct i2c_client *client,
+ size_t length, u16 checksum)
+{
+ u8 checksum_cmd[] = { 0x00, 0x05, 0x6D, 0x00, 0x00, 0x00, 0x00 };
+ int error;
+
+ put_unaligned_le16(length, &checksum_cmd[3]);
+ put_unaligned_le16(checksum, &checksum_cmd[5]);
+
+ error = raydium_i2c_write_object(client,
+ checksum_cmd, sizeof(checksum_cmd),
+ RAYDIUM_ACK_NULL);
+ if (error) {
+ dev_err(&client->dev, "failed to write checksum: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int raydium_i2c_disable_watch_dog(struct i2c_client *client)
+{
+ static const u8 cmd[] = { 0x0A, 0xAA };
+ int error;
+
+ error = raydium_i2c_write_object(client, cmd, sizeof(cmd),
+ RAYDIUM_WAIT_READY);
+ if (error) {
+ dev_err(&client->dev, "disable watchdog command failed: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int raydium_i2c_fw_write_page(struct i2c_client *client,
+ u16 page_idx, const void *data, size_t len)
+{
+ u8 buf[RM_BL_WRT_LEN];
+ size_t xfer_len;
+ int error;
+ int i;
+
+ BUILD_BUG_ON((RM_FW_PAGE_SIZE % RM_BL_WRT_PKG_SIZE) != 0);
+
+ for (i = 0; i < RM_FW_PAGE_SIZE / RM_BL_WRT_PKG_SIZE; i++) {
+ buf[BL_HEADER] = RM_CMD_BOOT_PAGE_WRT;
+ buf[BL_PAGE_STR] = page_idx ? 0xff : 0;
+ buf[BL_PKG_IDX] = i + 1;
+
+ xfer_len = min_t(size_t, len, RM_BL_WRT_PKG_SIZE);
+ memcpy(&buf[BL_DATA_STR], data, xfer_len);
+ if (len < RM_BL_WRT_PKG_SIZE)
+ memset(&buf[BL_DATA_STR + xfer_len], 0xff,
+ RM_BL_WRT_PKG_SIZE - xfer_len);
+
+ error = raydium_i2c_write_object(client, buf, RM_BL_WRT_LEN,
+ RAYDIUM_WAIT_READY);
+ if (error) {
+ dev_err(&client->dev,
+ "page write command failed for page %d, chunk %d: %d\n",
+ page_idx, i, error);
+ return error;
+ }
+
+ data += xfer_len;
+ len -= xfer_len;
+ }
+
+ return error;
+}
+
+static u16 raydium_calc_chksum(const u8 *buf, u16 len)
+{
+ u16 checksum = 0;
+ u16 i;
+
+ for (i = 0; i < len; i++)
+ checksum += buf[i];
+
+ return checksum;
+}
+
+static int raydium_i2c_do_update_firmware(struct raydium_data *ts,
+ const struct firmware *fw)
+{
+ struct i2c_client *client = ts->client;
+ const void *data;
+ size_t data_len;
+ size_t len;
+ int page_nr;
+ int i;
+ int error;
+ u16 fw_checksum;
+
+ if (fw->size == 0 || fw->size > RM_MAX_FW_SIZE) {
+ dev_err(&client->dev, "Invalid firmware length\n");
+ return -EINVAL;
+ }
+
+ error = raydium_i2c_check_fw_status(ts);
+ if (error) {
+ dev_err(&client->dev, "Unable to access IC %d\n", error);
+ return error;
+ }
+
+ if (ts->boot_mode == RAYDIUM_TS_MAIN) {
+ for (i = 0; i < RM_MAX_RETRIES; i++) {
+ error = raydium_i2c_enter_bl(client);
+ if (!error) {
+ error = raydium_i2c_check_fw_status(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "unable to access IC: %d\n",
+ error);
+ return error;
+ }
+
+ if (ts->boot_mode == RAYDIUM_TS_BLDR)
+ break;
+ }
+ }
+
+ if (ts->boot_mode == RAYDIUM_TS_MAIN) {
+ dev_err(&client->dev,
+ "failed to jump to boot loader: %d\n",
+ error);
+ return -EIO;
+ }
+ }
+
+ error = raydium_i2c_disable_watch_dog(client);
+ if (error)
+ return error;
+
+ error = raydium_i2c_check_path(client);
+ if (error)
+ return error;
+
+ error = raydium_i2c_boot_trigger(client);
+ if (error) {
+ dev_err(&client->dev, "send boot trigger fail: %d\n", error);
+ return error;
+ }
+
+ msleep(RM_BOOT_DELAY_MS);
+
+ data = fw->data;
+ data_len = fw->size;
+ page_nr = 0;
+
+ while (data_len) {
+ len = min_t(size_t, data_len, RM_FW_PAGE_SIZE);
+
+ error = raydium_i2c_fw_write_page(client, page_nr++, data, len);
+ if (error)
+ return error;
+
+ msleep(20);
+
+ data += len;
+ data_len -= len;
+ }
+
+ error = raydium_i2c_leave_bl(client);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to leave boot loader: %d\n", error);
+ return error;
+ }
+
+ dev_dbg(&client->dev, "left boot loader mode\n");
+ msleep(RM_BOOT_DELAY_MS);
+
+ error = raydium_i2c_check_fw_status(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to check fw status after write: %d\n",
+ error);
+ return error;
+ }
+
+ if (ts->boot_mode != RAYDIUM_TS_MAIN) {
+ dev_err(&client->dev,
+ "failed to switch to main fw after writing firmware: %d\n",
+ error);
+ return -EINVAL;
+ }
+
+ error = raydium_i2c_fw_trigger(client);
+ if (error) {
+ dev_err(&client->dev, "failed to trigger fw: %d\n", error);
+ return error;
+ }
+
+ fw_checksum = raydium_calc_chksum(fw->data, fw->size);
+
+ error = raydium_i2c_write_checksum(client, fw->size, fw_checksum);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int raydium_i2c_fw_update(struct raydium_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ const struct firmware *fw = NULL;
+ char *fw_file;
+ int error;
+
+ fw_file = kasprintf(GFP_KERNEL, "raydium_%#04x.fw",
+ le32_to_cpu(ts->info.hw_ver));
+ if (!fw_file)
+ return -ENOMEM;
+
+ dev_dbg(&client->dev, "firmware name: %s\n", fw_file);
+
+ error = request_firmware(&fw, fw_file, &client->dev);
+ if (error) {
+ dev_err(&client->dev, "Unable to open firmware %s\n", fw_file);
+ goto out_free_fw_file;
+ }
+
+ disable_irq(client->irq);
+
+ error = raydium_i2c_do_update_firmware(ts, fw);
+ if (error) {
+ dev_err(&client->dev, "firmware update failed: %d\n", error);
+ ts->boot_mode = RAYDIUM_TS_BLDR;
+ goto out_enable_irq;
+ }
+
+ error = raydium_i2c_initialize(ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to initialize device after firmware update: %d\n",
+ error);
+ ts->boot_mode = RAYDIUM_TS_BLDR;
+ goto out_enable_irq;
+ }
+
+ ts->boot_mode = RAYDIUM_TS_MAIN;
+
+out_enable_irq:
+ enable_irq(client->irq);
+ msleep(100);
+
+ release_firmware(fw);
+
+out_free_fw_file:
+ kfree(fw_file);
+
+ return error;
+}
+
+static void raydium_mt_event(struct raydium_data *ts)
+{
+ int i;
+
+ for (i = 0; i < ts->report_size / ts->contact_size; i++) {
+ u8 *contact = &ts->report_data[ts->contact_size * i];
+ bool state = contact[RM_CONTACT_STATE_POS];
+ u8 wx, wy;
+
+ input_mt_slot(ts->input, i);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, state);
+
+ if (!state)
+ continue;
+
+ input_report_abs(ts->input, ABS_MT_POSITION_X,
+ get_unaligned_le16(&contact[RM_CONTACT_X_POS]));
+ input_report_abs(ts->input, ABS_MT_POSITION_Y,
+ get_unaligned_le16(&contact[RM_CONTACT_Y_POS]));
+ input_report_abs(ts->input, ABS_MT_PRESSURE,
+ contact[RM_CONTACT_PRESSURE_POS]);
+
+ wx = contact[RM_CONTACT_WIDTH_X_POS];
+ wy = contact[RM_CONTACT_WIDTH_Y_POS];
+
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, max(wx, wy));
+ input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, min(wx, wy));
+ }
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+}
+
+static irqreturn_t raydium_i2c_irq(int irq, void *_dev)
+{
+ struct raydium_data *ts = _dev;
+ int error;
+ u16 fw_crc;
+ u16 calc_crc;
+
+ if (ts->boot_mode != RAYDIUM_TS_MAIN)
+ goto out;
+
+ error = raydium_i2c_read(ts->client, ts->data_bank_addr,
+ ts->report_data, ts->pkg_size);
+ if (error)
+ goto out;
+
+ fw_crc = get_unaligned_le16(&ts->report_data[ts->report_size]);
+ calc_crc = raydium_calc_chksum(ts->report_data, ts->report_size);
+ if (unlikely(fw_crc != calc_crc)) {
+ dev_warn(&ts->client->dev,
+ "%s: invalid packet crc %#04x vs %#04x\n",
+ __func__, calc_crc, fw_crc);
+ goto out;
+ }
+
+ raydium_mt_event(ts);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static ssize_t raydium_i2c_fw_ver_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d.%d\n", ts->info.main_ver, ts->info.sub_ver);
+}
+
+static ssize_t raydium_i2c_hw_ver_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%#04x\n", le32_to_cpu(ts->info.hw_ver));
+}
+
+static ssize_t raydium_i2c_boot_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%s\n",
+ ts->boot_mode == RAYDIUM_TS_MAIN ?
+ "Normal" : "Recovery");
+}
+
+static ssize_t raydium_i2c_update_fw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+ int error;
+
+ error = mutex_lock_interruptible(&ts->sysfs_mutex);
+ if (error)
+ return error;
+
+ error = raydium_i2c_fw_update(ts);
+
+ mutex_unlock(&ts->sysfs_mutex);
+
+ return error ?: count;
+}
+
+static ssize_t raydium_i2c_calibrate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+ static const u8 cal_cmd[] = { 0x00, 0x01, 0x9E };
+ int error;
+
+ error = mutex_lock_interruptible(&ts->sysfs_mutex);
+ if (error)
+ return error;
+
+ error = raydium_i2c_write_object(client, cal_cmd, sizeof(cal_cmd),
+ RAYDIUM_WAIT_READY);
+ if (error)
+ dev_err(&client->dev, "calibrate command failed: %d\n", error);
+
+ mutex_unlock(&ts->sysfs_mutex);
+ return error ?: count;
+}
+
+static DEVICE_ATTR(fw_version, S_IRUGO, raydium_i2c_fw_ver_show, NULL);
+static DEVICE_ATTR(hw_version, S_IRUGO, raydium_i2c_hw_ver_show, NULL);
+static DEVICE_ATTR(boot_mode, S_IRUGO, raydium_i2c_boot_mode_show, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, raydium_i2c_update_fw_store);
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, raydium_i2c_calibrate_store);
+
+static struct attribute *raydium_i2c_attributes[] = {
+ &dev_attr_update_fw.attr,
+ &dev_attr_boot_mode.attr,
+ &dev_attr_fw_version.attr,
+ &dev_attr_hw_version.attr,
+ &dev_attr_calibrate.attr,
+ NULL
+};
+
+static const struct attribute_group raydium_i2c_attribute_group = {
+ .attrs = raydium_i2c_attributes,
+};
+
+static int raydium_i2c_power_on(struct raydium_data *ts)
+{
+ int error;
+
+ if (!ts->reset_gpio)
+ return 0;
+
+ gpiod_set_value_cansleep(ts->reset_gpio, 1);
+
+ error = regulator_enable(ts->avdd);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "failed to enable avdd regulator: %d\n", error);
+ goto release_reset_gpio;
+ }
+
+ error = regulator_enable(ts->vccio);
+ if (error) {
+ regulator_disable(ts->avdd);
+ dev_err(&ts->client->dev,
+ "failed to enable vccio regulator: %d\n", error);
+ goto release_reset_gpio;
+ }
+
+ udelay(RM_POWERON_DELAY_USEC);
+
+release_reset_gpio:
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+
+ if (error)
+ return error;
+
+ msleep(RM_RESET_DELAY_MSEC);
+
+ return 0;
+}
+
+static void raydium_i2c_power_off(void *_data)
+{
+ struct raydium_data *ts = _data;
+
+ if (ts->reset_gpio) {
+ gpiod_set_value_cansleep(ts->reset_gpio, 1);
+ regulator_disable(ts->vccio);
+ regulator_disable(ts->avdd);
+ }
+}
+
+static int raydium_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ union i2c_smbus_data dummy;
+ struct raydium_data *ts;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev,
+ "i2c check functionality error (need I2C_FUNC_I2C)\n");
+ return -ENXIO;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ mutex_init(&ts->sysfs_mutex);
+
+ ts->client = client;
+ i2c_set_clientdata(client, ts);
+
+ ts->avdd = devm_regulator_get(&client->dev, "avdd");
+ if (IS_ERR(ts->avdd)) {
+ error = PTR_ERR(ts->avdd);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get 'avdd' regulator: %d\n", error);
+ return error;
+ }
+
+ ts->vccio = devm_regulator_get(&client->dev, "vccio");
+ if (IS_ERR(ts->vccio)) {
+ error = PTR_ERR(ts->vccio);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get 'vccio' regulator: %d\n", error);
+ return error;
+ }
+
+ ts->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "failed to get reset gpio: %d\n", error);
+ return error;
+ }
+
+ error = raydium_i2c_power_on(ts);
+ if (error)
+ return error;
+
+ error = devm_add_action_or_reset(&client->dev,
+ raydium_i2c_power_off, ts);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to install power off action: %d\n", error);
+ return error;
+ }
+
+ /* Make sure there is something at this address */
+ if (i2c_smbus_xfer(client->adapter, client->addr, 0,
+ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) {
+ dev_err(&client->dev, "nothing at this address\n");
+ return -ENXIO;
+ }
+
+ error = raydium_i2c_initialize(ts);
+ if (error) {
+ dev_err(&client->dev, "failed to initialize: %d\n", error);
+ return error;
+ }
+
+ ts->report_data = devm_kmalloc(&client->dev,
+ ts->pkg_size, GFP_KERNEL);
+ if (!ts->report_data)
+ return -ENOMEM;
+
+ ts->input = devm_input_allocate_device(&client->dev);
+ if (!ts->input) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->input->name = "Raydium Touchscreen";
+ ts->input->id.bustype = BUS_I2C;
+
+ input_set_abs_params(ts->input, ABS_MT_POSITION_X,
+ 0, le16_to_cpu(ts->info.x_max), 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_POSITION_Y,
+ 0, le16_to_cpu(ts->info.y_max), 0, 0);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->info.x_res);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->info.y_res);
+
+ input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+ error = input_mt_init_slots(ts->input, RM_MAX_TOUCH_NUM,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to initialize MT slots: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(ts->input);
+ if (error) {
+ dev_err(&client->dev,
+ "unable to register input device: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, raydium_i2c_irq,
+ IRQF_ONESHOT, client->name, ts);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev,
+ &raydium_i2c_attribute_group);
+ if (error) {
+ dev_err(&client->dev, "failed to create sysfs attributes: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void __maybe_unused raydium_enter_sleep(struct i2c_client *client)
+{
+ static const u8 sleep_cmd[] = { 0x5A, 0xff, 0x00, 0x0f };
+ int error;
+
+ error = raydium_i2c_send(client, RM_CMD_ENTER_SLEEP,
+ sleep_cmd, sizeof(sleep_cmd));
+ if (error)
+ dev_err(&client->dev,
+ "sleep command failed: %d\n", error);
+}
+
+static int __maybe_unused raydium_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+
+ /* Sleep is not available in BLDR recovery mode */
+ if (ts->boot_mode != RAYDIUM_TS_MAIN)
+ return -EBUSY;
+
+ disable_irq(client->irq);
+
+ if (device_may_wakeup(dev)) {
+ raydium_enter_sleep(client);
+
+ ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+ } else {
+ raydium_i2c_power_off(ts);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused raydium_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct raydium_data *ts = i2c_get_clientdata(client);
+
+ if (device_may_wakeup(dev)) {
+ if (ts->wake_irq_enabled)
+ disable_irq_wake(client->irq);
+ raydium_i2c_sw_reset(client);
+ } else {
+ raydium_i2c_power_on(ts);
+ raydium_i2c_initialize(ts);
+ }
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(raydium_i2c_pm_ops,
+ raydium_i2c_suspend, raydium_i2c_resume);
+
+static const struct i2c_device_id raydium_i2c_id[] = {
+ { "raydium_i2c", 0 },
+ { "rm32380", 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, raydium_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id raydium_acpi_id[] = {
+ { "RAYD0001", 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(acpi, raydium_acpi_id);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id raydium_of_match[] = {
+ { .compatible = "raydium,rm32380", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, raydium_of_match);
+#endif
+
+static struct i2c_driver raydium_i2c_driver = {
+ .probe = raydium_i2c_probe,
+ .id_table = raydium_i2c_id,
+ .driver = {
+ .name = "raydium_ts",
+ .pm = &raydium_i2c_pm_ops,
+ .acpi_match_table = ACPI_PTR(raydium_acpi_id),
+ .of_match_table = of_match_ptr(raydium_of_match),
+ },
+};
+module_i2c_driver(raydium_i2c_driver);
+
+MODULE_AUTHOR("Raydium");
+MODULE_DESCRIPTION("Raydium I2c Touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/resistive-adc-touch.c b/drivers/input/touchscreen/resistive-adc-touch.c
new file mode 100644
index 000000000..6f754a8d3
--- /dev/null
+++ b/drivers/input/touchscreen/resistive-adc-touch.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADC generic resistive touchscreen (GRTS)
+ * This is a generic input driver that connects to an ADC
+ * given the channels in device tree, and reports events to the input
+ * subsystem.
+ *
+ * Copyright (C) 2017,2018 Microchip Technology,
+ * Author: Eugen Hristev <eugen.hristev@microchip.com>
+ *
+ */
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define DRIVER_NAME "resistive-adc-touch"
+#define GRTS_DEFAULT_PRESSURE_MIN 50000
+#define GRTS_DEFAULT_PRESSURE_MAX 65535
+#define GRTS_MAX_POS_MASK GENMASK(11, 0)
+#define GRTS_MAX_CHANNELS 4
+
+enum grts_ch_type {
+ GRTS_CH_X,
+ GRTS_CH_Y,
+ GRTS_CH_PRESSURE,
+ GRTS_CH_Z1,
+ GRTS_CH_Z2,
+ GRTS_CH_MAX = GRTS_CH_Z2 + 1
+};
+
+/**
+ * struct grts_state - generic resistive touch screen information struct
+ * @x_plate_ohms: resistance of the X plate
+ * @pressure_min: number representing the minimum for the pressure
+ * @pressure: are we getting pressure info or not
+ * @iio_chans: list of channels acquired
+ * @iio_cb: iio_callback buffer for the data
+ * @input: the input device structure that we register
+ * @prop: touchscreen properties struct
+ * @ch_map: map of channels that are defined for the touchscreen
+ */
+struct grts_state {
+ u32 x_plate_ohms;
+ u32 pressure_min;
+ bool pressure;
+ struct iio_channel *iio_chans;
+ struct iio_cb_buffer *iio_cb;
+ struct input_dev *input;
+ struct touchscreen_properties prop;
+ u8 ch_map[GRTS_CH_MAX];
+};
+
+static int grts_cb(const void *data, void *private)
+{
+ const u16 *touch_info = data;
+ struct grts_state *st = private;
+ unsigned int x, y, press = 0;
+
+ x = touch_info[st->ch_map[GRTS_CH_X]];
+ y = touch_info[st->ch_map[GRTS_CH_Y]];
+
+ if (st->ch_map[GRTS_CH_PRESSURE] < GRTS_MAX_CHANNELS) {
+ press = touch_info[st->ch_map[GRTS_CH_PRESSURE]];
+ } else if (st->ch_map[GRTS_CH_Z1] < GRTS_MAX_CHANNELS) {
+ unsigned int z1 = touch_info[st->ch_map[GRTS_CH_Z1]];
+ unsigned int z2 = touch_info[st->ch_map[GRTS_CH_Z2]];
+ unsigned int Rt;
+
+ if (likely(x && z1)) {
+ Rt = z2;
+ Rt -= z1;
+ Rt *= st->x_plate_ohms;
+ Rt = DIV_ROUND_CLOSEST(Rt, 16);
+ Rt *= x;
+ Rt /= z1;
+ Rt = DIV_ROUND_CLOSEST(Rt, 256);
+ /*
+ * On increased pressure the resistance (Rt) is
+ * decreasing so, convert values to make it looks as
+ * real pressure.
+ */
+ if (Rt < GRTS_DEFAULT_PRESSURE_MAX)
+ press = GRTS_DEFAULT_PRESSURE_MAX - Rt;
+ }
+ }
+
+ if ((!x && !y) || (st->pressure && (press < st->pressure_min))) {
+ /* report end of touch */
+ input_report_key(st->input, BTN_TOUCH, 0);
+ input_sync(st->input);
+ return 0;
+ }
+
+ /* report proper touch to subsystem*/
+ touchscreen_report_pos(st->input, &st->prop, x, y, false);
+ if (st->pressure)
+ input_report_abs(st->input, ABS_PRESSURE, press);
+ input_report_key(st->input, BTN_TOUCH, 1);
+ input_sync(st->input);
+
+ return 0;
+}
+
+static int grts_open(struct input_dev *dev)
+{
+ int error;
+ struct grts_state *st = input_get_drvdata(dev);
+
+ error = iio_channel_start_all_cb(st->iio_cb);
+ if (error) {
+ dev_err(dev->dev.parent, "failed to start callback buffer.\n");
+ return error;
+ }
+ return 0;
+}
+
+static void grts_close(struct input_dev *dev)
+{
+ struct grts_state *st = input_get_drvdata(dev);
+
+ iio_channel_stop_all_cb(st->iio_cb);
+}
+
+static void grts_disable(void *data)
+{
+ iio_channel_release_all_cb(data);
+}
+
+static int grts_map_channel(struct grts_state *st, struct device *dev,
+ enum grts_ch_type type, const char *name,
+ bool optional)
+{
+ int idx;
+
+ idx = device_property_match_string(dev, "io-channel-names", name);
+ if (idx < 0) {
+ if (!optional)
+ return idx;
+ idx = GRTS_MAX_CHANNELS;
+ } else if (idx >= GRTS_MAX_CHANNELS) {
+ return -EOVERFLOW;
+ }
+
+ st->ch_map[type] = idx;
+ return 0;
+}
+
+static int grts_get_properties(struct grts_state *st, struct device *dev)
+{
+ int error;
+
+ error = grts_map_channel(st, dev, GRTS_CH_X, "x", false);
+ if (error)
+ return error;
+
+ error = grts_map_channel(st, dev, GRTS_CH_Y, "y", false);
+ if (error)
+ return error;
+
+ /* pressure is optional */
+ error = grts_map_channel(st, dev, GRTS_CH_PRESSURE, "pressure", true);
+ if (error)
+ return error;
+
+ if (st->ch_map[GRTS_CH_PRESSURE] < GRTS_MAX_CHANNELS) {
+ st->pressure = true;
+ return 0;
+ }
+
+ /* if no pressure is defined, try optional z1 + z2 */
+ error = grts_map_channel(st, dev, GRTS_CH_Z1, "z1", true);
+ if (error)
+ return error;
+
+ if (st->ch_map[GRTS_CH_Z1] >= GRTS_MAX_CHANNELS)
+ return 0;
+
+ /* if z1 is provided z2 is not optional */
+ error = grts_map_channel(st, dev, GRTS_CH_Z2, "z2", true);
+ if (error)
+ return error;
+
+ error = device_property_read_u32(dev,
+ "touchscreen-x-plate-ohms",
+ &st->x_plate_ohms);
+ if (error) {
+ dev_err(dev, "can't get touchscreen-x-plate-ohms property\n");
+ return error;
+ }
+
+ st->pressure = true;
+ return 0;
+}
+
+static int grts_probe(struct platform_device *pdev)
+{
+ struct grts_state *st;
+ struct input_dev *input;
+ struct device *dev = &pdev->dev;
+ int error;
+
+ st = devm_kzalloc(dev, sizeof(struct grts_state), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ /* get the channels from IIO device */
+ st->iio_chans = devm_iio_channel_get_all(dev);
+ if (IS_ERR(st->iio_chans)) {
+ error = PTR_ERR(st->iio_chans);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "can't get iio channels.\n");
+ return error;
+ }
+
+ if (!device_property_present(dev, "io-channel-names"))
+ return -ENODEV;
+
+ error = grts_get_properties(st, dev);
+ if (error) {
+ dev_err(dev, "Failed to parse properties\n");
+ return error;
+ }
+
+ if (st->pressure) {
+ error = device_property_read_u32(dev,
+ "touchscreen-min-pressure",
+ &st->pressure_min);
+ if (error) {
+ dev_dbg(dev, "can't get touchscreen-min-pressure property.\n");
+ st->pressure_min = GRTS_DEFAULT_PRESSURE_MIN;
+ }
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "failed to allocate input device.\n");
+ return -ENOMEM;
+ }
+
+ input->name = DRIVER_NAME;
+ input->id.bustype = BUS_HOST;
+ input->open = grts_open;
+ input->close = grts_close;
+
+ input_set_abs_params(input, ABS_X, 0, GRTS_MAX_POS_MASK - 1, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, GRTS_MAX_POS_MASK - 1, 0, 0);
+ if (st->pressure)
+ input_set_abs_params(input, ABS_PRESSURE, st->pressure_min,
+ GRTS_DEFAULT_PRESSURE_MAX, 0, 0);
+
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+
+ /* parse optional device tree properties */
+ touchscreen_parse_properties(input, false, &st->prop);
+
+ st->input = input;
+ input_set_drvdata(input, st);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "failed to register input device.");
+ return error;
+ }
+
+ st->iio_cb = iio_channel_get_all_cb(dev, grts_cb, st);
+ if (IS_ERR(st->iio_cb)) {
+ dev_err(dev, "failed to allocate callback buffer.\n");
+ return PTR_ERR(st->iio_cb);
+ }
+
+ error = devm_add_action_or_reset(dev, grts_disable, st->iio_cb);
+ if (error) {
+ dev_err(dev, "failed to add disable action.\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id grts_of_match[] = {
+ {
+ .compatible = "resistive-adc-touch",
+ }, {
+ /* sentinel */
+ },
+};
+
+MODULE_DEVICE_TABLE(of, grts_of_match);
+
+static struct platform_driver grts_driver = {
+ .probe = grts_probe,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = grts_of_match,
+ },
+};
+
+module_platform_driver(grts_driver);
+
+MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>");
+MODULE_DESCRIPTION("Generic ADC Resistive Touch Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/rohm_bu21023.c b/drivers/input/touchscreen/rohm_bu21023.c
new file mode 100644
index 000000000..730596d59
--- /dev/null
+++ b/drivers/input/touchscreen/rohm_bu21023.c
@@ -0,0 +1,1194 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ROHM BU21023/24 Dual touch support resistive touch screen driver
+ * Copyright (C) 2012 ROHM CO.,LTD.
+ */
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define BU21023_NAME "bu21023_ts"
+#define BU21023_FIRMWARE_NAME "bu21023.bin"
+
+#define MAX_CONTACTS 2
+
+#define AXIS_ADJUST 4
+#define AXIS_OFFSET 8
+
+#define FIRMWARE_BLOCK_SIZE 32U
+#define FIRMWARE_RETRY_MAX 4
+
+#define SAMPLING_DELAY 12 /* msec */
+
+#define CALIBRATION_RETRY_MAX 6
+
+#define ROHM_TS_ABS_X_MIN 40
+#define ROHM_TS_ABS_X_MAX 990
+#define ROHM_TS_ABS_Y_MIN 160
+#define ROHM_TS_ABS_Y_MAX 920
+#define ROHM_TS_DISPLACEMENT_MAX 0 /* zero for infinite */
+
+/*
+ * BU21023GUL/BU21023MUV/BU21024FV-M registers map
+ */
+#define VADOUT_YP_H 0x00
+#define VADOUT_YP_L 0x01
+#define VADOUT_XP_H 0x02
+#define VADOUT_XP_L 0x03
+#define VADOUT_YN_H 0x04
+#define VADOUT_YN_L 0x05
+#define VADOUT_XN_H 0x06
+#define VADOUT_XN_L 0x07
+
+#define PRM1_X_H 0x08
+#define PRM1_X_L 0x09
+#define PRM1_Y_H 0x0a
+#define PRM1_Y_L 0x0b
+#define PRM2_X_H 0x0c
+#define PRM2_X_L 0x0d
+#define PRM2_Y_H 0x0e
+#define PRM2_Y_L 0x0f
+
+#define MLT_PRM_MONI_X 0x10
+#define MLT_PRM_MONI_Y 0x11
+
+#define DEBUG_MONI_1 0x12
+#define DEBUG_MONI_2 0x13
+
+#define VADOUT_ZX_H 0x14
+#define VADOUT_ZX_L 0x15
+#define VADOUT_ZY_H 0x16
+#define VADOUT_ZY_L 0x17
+
+#define Z_PARAM_H 0x18
+#define Z_PARAM_L 0x19
+
+/*
+ * Value for VADOUT_*_L
+ */
+#define VADOUT_L_MASK 0x01
+
+/*
+ * Value for PRM*_*_L
+ */
+#define PRM_L_MASK 0x01
+
+#define POS_X1_H 0x20
+#define POS_X1_L 0x21
+#define POS_Y1_H 0x22
+#define POS_Y1_L 0x23
+#define POS_X2_H 0x24
+#define POS_X2_L 0x25
+#define POS_Y2_H 0x26
+#define POS_Y2_L 0x27
+
+/*
+ * Value for POS_*_L
+ */
+#define POS_L_MASK 0x01
+
+#define TOUCH 0x28
+#define TOUCH_DETECT 0x01
+
+#define TOUCH_GESTURE 0x29
+#define SINGLE_TOUCH 0x01
+#define DUAL_TOUCH 0x03
+#define TOUCH_MASK 0x03
+#define CALIBRATION_REQUEST 0x04
+#define CALIBRATION_STATUS 0x08
+#define CALIBRATION_MASK 0x0c
+#define GESTURE_SPREAD 0x10
+#define GESTURE_PINCH 0x20
+#define GESTURE_ROTATE_R 0x40
+#define GESTURE_ROTATE_L 0x80
+
+#define INT_STATUS 0x2a
+#define INT_MASK 0x3d
+#define INT_CLEAR 0x3e
+
+/*
+ * Values for INT_*
+ */
+#define COORD_UPDATE 0x01
+#define CALIBRATION_DONE 0x02
+#define SLEEP_IN 0x04
+#define SLEEP_OUT 0x08
+#define PROGRAM_LOAD_DONE 0x10
+#define ERROR 0x80
+#define INT_ALL 0x9f
+
+#define ERR_STATUS 0x2b
+#define ERR_MASK 0x3f
+
+/*
+ * Values for ERR_*
+ */
+#define ADC_TIMEOUT 0x01
+#define CPU_TIMEOUT 0x02
+#define CALIBRATION_ERR 0x04
+#define PROGRAM_LOAD_ERR 0x10
+
+#define COMMON_SETUP1 0x30
+#define PROGRAM_LOAD_HOST 0x02
+#define PROGRAM_LOAD_EEPROM 0x03
+#define CENSOR_4PORT 0x04
+#define CENSOR_8PORT 0x00 /* Not supported by BU21023 */
+#define CALIBRATION_TYPE_DEFAULT 0x08
+#define CALIBRATION_TYPE_SPECIAL 0x00
+#define INT_ACTIVE_HIGH 0x10
+#define INT_ACTIVE_LOW 0x00
+#define AUTO_CALIBRATION 0x40
+#define MANUAL_CALIBRATION 0x00
+#define COMMON_SETUP1_DEFAULT 0x4e
+
+#define COMMON_SETUP2 0x31
+#define MAF_NONE 0x00
+#define MAF_1SAMPLE 0x01
+#define MAF_3SAMPLES 0x02
+#define MAF_5SAMPLES 0x03
+#define INV_Y 0x04
+#define INV_X 0x08
+#define SWAP_XY 0x10
+
+#define COMMON_SETUP3 0x32
+#define EN_SLEEP 0x01
+#define EN_MULTI 0x02
+#define EN_GESTURE 0x04
+#define EN_INTVL 0x08
+#define SEL_STEP 0x10
+#define SEL_MULTI 0x20
+#define SEL_TBL_DEFAULT 0x40
+
+#define INTERVAL_TIME 0x33
+#define INTERVAL_TIME_DEFAULT 0x10
+
+#define STEP_X 0x34
+#define STEP_X_DEFAULT 0x41
+
+#define STEP_Y 0x35
+#define STEP_Y_DEFAULT 0x8d
+
+#define OFFSET_X 0x38
+#define OFFSET_X_DEFAULT 0x0c
+
+#define OFFSET_Y 0x39
+#define OFFSET_Y_DEFAULT 0x0c
+
+#define THRESHOLD_TOUCH 0x3a
+#define THRESHOLD_TOUCH_DEFAULT 0xa0
+
+#define THRESHOLD_GESTURE 0x3b
+#define THRESHOLD_GESTURE_DEFAULT 0x17
+
+#define SYSTEM 0x40
+#define ANALOG_POWER_ON 0x01
+#define ANALOG_POWER_OFF 0x00
+#define CPU_POWER_ON 0x02
+#define CPU_POWER_OFF 0x00
+
+#define FORCE_CALIBRATION 0x42
+#define FORCE_CALIBRATION_ON 0x01
+#define FORCE_CALIBRATION_OFF 0x00
+
+#define CPU_FREQ 0x50 /* 10 / (reg + 1) MHz */
+#define CPU_FREQ_10MHZ 0x00
+#define CPU_FREQ_5MHZ 0x01
+#define CPU_FREQ_1MHZ 0x09
+
+#define EEPROM_ADDR 0x51
+
+#define CALIBRATION_ADJUST 0x52
+#define CALIBRATION_ADJUST_DEFAULT 0x00
+
+#define THRESHOLD_SLEEP_IN 0x53
+
+#define EVR_XY 0x56
+#define EVR_XY_DEFAULT 0x10
+
+#define PRM_SWOFF_TIME 0x57
+#define PRM_SWOFF_TIME_DEFAULT 0x04
+
+#define PROGRAM_VERSION 0x5f
+
+#define ADC_CTRL 0x60
+#define ADC_DIV_MASK 0x1f /* The minimum value is 4 */
+#define ADC_DIV_DEFAULT 0x08
+
+#define ADC_WAIT 0x61
+#define ADC_WAIT_DEFAULT 0x0a
+
+#define SWCONT 0x62
+#define SWCONT_DEFAULT 0x0f
+
+#define EVR_X 0x63
+#define EVR_X_DEFAULT 0x86
+
+#define EVR_Y 0x64
+#define EVR_Y_DEFAULT 0x64
+
+#define TEST1 0x65
+#define DUALTOUCH_STABILIZE_ON 0x01
+#define DUALTOUCH_STABILIZE_OFF 0x00
+#define DUALTOUCH_REG_ON 0x20
+#define DUALTOUCH_REG_OFF 0x00
+
+#define CALIBRATION_REG1 0x68
+#define CALIBRATION_REG1_DEFAULT 0xd9
+
+#define CALIBRATION_REG2 0x69
+#define CALIBRATION_REG2_DEFAULT 0x36
+
+#define CALIBRATION_REG3 0x6a
+#define CALIBRATION_REG3_DEFAULT 0x32
+
+#define EX_ADDR_H 0x70
+#define EX_ADDR_L 0x71
+#define EX_WDAT 0x72
+#define EX_RDAT 0x73
+#define EX_CHK_SUM1 0x74
+#define EX_CHK_SUM2 0x75
+#define EX_CHK_SUM3 0x76
+
+struct rohm_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+
+ bool initialized;
+
+ unsigned int contact_count[MAX_CONTACTS + 1];
+ int finger_count;
+
+ u8 setup2;
+};
+
+/*
+ * rohm_i2c_burst_read - execute combined I2C message for ROHM BU21023/24
+ * @client: Handle to ROHM BU21023/24
+ * @start: Where to start read address from ROHM BU21023/24
+ * @buf: Where to store read data from ROHM BU21023/24
+ * @len: How many bytes to read
+ *
+ * Returns negative errno, else zero on success.
+ *
+ * Note
+ * In BU21023/24 burst read, stop condition is needed after "address write".
+ * Therefore, transmission is performed in 2 steps.
+ */
+static int rohm_i2c_burst_read(struct i2c_client *client, u8 start, void *buf,
+ size_t len)
+{
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_msg msg[2];
+ int i, ret = 0;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+ msg[0].buf = &start;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = len;
+ msg[1].buf = buf;
+
+ i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
+
+ for (i = 0; i < 2; i++) {
+ if (__i2c_transfer(adap, &msg[i], 1) < 0) {
+ ret = -EIO;
+ break;
+ }
+ }
+
+ i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);
+
+ return ret;
+}
+
+static int rohm_ts_manual_calibration(struct rohm_ts_data *ts)
+{
+ struct i2c_client *client = ts->client;
+ struct device *dev = &client->dev;
+ u8 buf[33]; /* for PRM1_X_H(0x08)-TOUCH(0x28) */
+
+ int retry;
+ bool success = false;
+ bool first_time = true;
+ bool calibration_done;
+
+ u8 reg1, reg2, reg3;
+ s32 reg1_orig, reg2_orig, reg3_orig;
+ s32 val;
+
+ int calib_x = 0, calib_y = 0;
+ int reg_x, reg_y;
+ int err_x, err_y;
+
+ int error, error2;
+ int i;
+
+ reg1_orig = i2c_smbus_read_byte_data(client, CALIBRATION_REG1);
+ if (reg1_orig < 0)
+ return reg1_orig;
+
+ reg2_orig = i2c_smbus_read_byte_data(client, CALIBRATION_REG2);
+ if (reg2_orig < 0)
+ return reg2_orig;
+
+ reg3_orig = i2c_smbus_read_byte_data(client, CALIBRATION_REG3);
+ if (reg3_orig < 0)
+ return reg3_orig;
+
+ error = i2c_smbus_write_byte_data(client, INT_MASK,
+ COORD_UPDATE | SLEEP_IN | SLEEP_OUT |
+ PROGRAM_LOAD_DONE);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client, TEST1,
+ DUALTOUCH_STABILIZE_ON);
+ if (error)
+ goto out;
+
+ for (retry = 0; retry < CALIBRATION_RETRY_MAX; retry++) {
+ /* wait 2 sampling for update */
+ mdelay(2 * SAMPLING_DELAY);
+
+#define READ_CALIB_BUF(reg) buf[((reg) - PRM1_X_H)]
+
+ error = rohm_i2c_burst_read(client, PRM1_X_H, buf, sizeof(buf));
+ if (error)
+ goto out;
+
+ if (READ_CALIB_BUF(TOUCH) & TOUCH_DETECT)
+ continue;
+
+ if (first_time) {
+ /* generate calibration parameter */
+ calib_x = ((int)READ_CALIB_BUF(PRM1_X_H) << 2 |
+ READ_CALIB_BUF(PRM1_X_L)) - AXIS_OFFSET;
+ calib_y = ((int)READ_CALIB_BUF(PRM1_Y_H) << 2 |
+ READ_CALIB_BUF(PRM1_Y_L)) - AXIS_OFFSET;
+
+ error = i2c_smbus_write_byte_data(client, TEST1,
+ DUALTOUCH_STABILIZE_ON | DUALTOUCH_REG_ON);
+ if (error)
+ goto out;
+
+ first_time = false;
+ } else {
+ /* generate adjustment parameter */
+ err_x = (int)READ_CALIB_BUF(PRM1_X_H) << 2 |
+ READ_CALIB_BUF(PRM1_X_L);
+ err_y = (int)READ_CALIB_BUF(PRM1_Y_H) << 2 |
+ READ_CALIB_BUF(PRM1_Y_L);
+
+ /* X axis ajust */
+ if (err_x <= 4)
+ calib_x -= AXIS_ADJUST;
+ else if (err_x >= 60)
+ calib_x += AXIS_ADJUST;
+
+ /* Y axis ajust */
+ if (err_y <= 4)
+ calib_y -= AXIS_ADJUST;
+ else if (err_y >= 60)
+ calib_y += AXIS_ADJUST;
+ }
+
+ /* generate calibration setting value */
+ reg_x = calib_x + ((calib_x & 0x200) << 1);
+ reg_y = calib_y + ((calib_y & 0x200) << 1);
+
+ /* convert for register format */
+ reg1 = reg_x >> 3;
+ reg2 = (reg_y & 0x7) << 4 | (reg_x & 0x7);
+ reg3 = reg_y >> 3;
+
+ error = i2c_smbus_write_byte_data(client,
+ CALIBRATION_REG1, reg1);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client,
+ CALIBRATION_REG2, reg2);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client,
+ CALIBRATION_REG3, reg3);
+ if (error)
+ goto out;
+
+ /*
+ * force calibration sequcence
+ */
+ error = i2c_smbus_write_byte_data(client, FORCE_CALIBRATION,
+ FORCE_CALIBRATION_OFF);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client, FORCE_CALIBRATION,
+ FORCE_CALIBRATION_ON);
+ if (error)
+ goto out;
+
+ /* clear all interrupts */
+ error = i2c_smbus_write_byte_data(client, INT_CLEAR, 0xff);
+ if (error)
+ goto out;
+
+ /*
+ * Wait for the status change of calibration, max 10 sampling
+ */
+ calibration_done = false;
+
+ for (i = 0; i < 10; i++) {
+ mdelay(SAMPLING_DELAY);
+
+ val = i2c_smbus_read_byte_data(client, TOUCH_GESTURE);
+ if (!(val & CALIBRATION_MASK)) {
+ calibration_done = true;
+ break;
+ } else if (val < 0) {
+ error = val;
+ goto out;
+ }
+ }
+
+ if (calibration_done) {
+ val = i2c_smbus_read_byte_data(client, INT_STATUS);
+ if (val == CALIBRATION_DONE) {
+ success = true;
+ break;
+ } else if (val < 0) {
+ error = val;
+ goto out;
+ }
+ } else {
+ dev_warn(dev, "calibration timeout\n");
+ }
+ }
+
+ if (!success) {
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_REG1,
+ reg1_orig);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_REG2,
+ reg2_orig);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_REG3,
+ reg3_orig);
+ if (error)
+ goto out;
+
+ /* calibration data enable */
+ error = i2c_smbus_write_byte_data(client, TEST1,
+ DUALTOUCH_STABILIZE_ON |
+ DUALTOUCH_REG_ON);
+ if (error)
+ goto out;
+
+ /* wait 10 sampling */
+ mdelay(10 * SAMPLING_DELAY);
+
+ error = -EBUSY;
+ }
+
+out:
+ error2 = i2c_smbus_write_byte_data(client, INT_MASK, INT_ALL);
+ if (!error2)
+ /* Clear all interrupts */
+ error2 = i2c_smbus_write_byte_data(client, INT_CLEAR, 0xff);
+
+ return error ? error : error2;
+}
+
+static const unsigned int untouch_threshold[3] = { 0, 1, 5 };
+static const unsigned int single_touch_threshold[3] = { 0, 0, 4 };
+static const unsigned int dual_touch_threshold[3] = { 10, 8, 0 };
+
+static irqreturn_t rohm_ts_soft_irq(int irq, void *dev_id)
+{
+ struct rohm_ts_data *ts = dev_id;
+ struct i2c_client *client = ts->client;
+ struct input_dev *input_dev = ts->input;
+ struct device *dev = &client->dev;
+
+ u8 buf[10]; /* for POS_X1_H(0x20)-TOUCH_GESTURE(0x29) */
+
+ struct input_mt_pos pos[MAX_CONTACTS];
+ int slots[MAX_CONTACTS];
+ u8 touch_flags;
+ unsigned int threshold;
+ int finger_count = -1;
+ int prev_finger_count = ts->finger_count;
+ int count;
+ int error;
+ int i;
+
+ error = i2c_smbus_write_byte_data(client, INT_MASK, INT_ALL);
+ if (error)
+ return IRQ_HANDLED;
+
+ /* Clear all interrupts */
+ error = i2c_smbus_write_byte_data(client, INT_CLEAR, 0xff);
+ if (error)
+ return IRQ_HANDLED;
+
+#define READ_POS_BUF(reg) buf[((reg) - POS_X1_H)]
+
+ error = rohm_i2c_burst_read(client, POS_X1_H, buf, sizeof(buf));
+ if (error)
+ return IRQ_HANDLED;
+
+ touch_flags = READ_POS_BUF(TOUCH_GESTURE) & TOUCH_MASK;
+ if (touch_flags) {
+ /* generate coordinates */
+ pos[0].x = ((s16)READ_POS_BUF(POS_X1_H) << 2) |
+ READ_POS_BUF(POS_X1_L);
+ pos[0].y = ((s16)READ_POS_BUF(POS_Y1_H) << 2) |
+ READ_POS_BUF(POS_Y1_L);
+ pos[1].x = ((s16)READ_POS_BUF(POS_X2_H) << 2) |
+ READ_POS_BUF(POS_X2_L);
+ pos[1].y = ((s16)READ_POS_BUF(POS_Y2_H) << 2) |
+ READ_POS_BUF(POS_Y2_L);
+ }
+
+ switch (touch_flags) {
+ case 0:
+ threshold = untouch_threshold[prev_finger_count];
+ if (++ts->contact_count[0] >= threshold)
+ finger_count = 0;
+ break;
+
+ case SINGLE_TOUCH:
+ threshold = single_touch_threshold[prev_finger_count];
+ if (++ts->contact_count[1] >= threshold)
+ finger_count = 1;
+
+ if (finger_count == 1) {
+ if (pos[1].x != 0 && pos[1].y != 0) {
+ pos[0].x = pos[1].x;
+ pos[0].y = pos[1].y;
+ pos[1].x = 0;
+ pos[1].y = 0;
+ }
+ }
+ break;
+
+ case DUAL_TOUCH:
+ threshold = dual_touch_threshold[prev_finger_count];
+ if (++ts->contact_count[2] >= threshold)
+ finger_count = 2;
+ break;
+
+ default:
+ dev_dbg(dev,
+ "Three or more touches are not supported\n");
+ return IRQ_HANDLED;
+ }
+
+ if (finger_count >= 0) {
+ if (prev_finger_count != finger_count) {
+ count = ts->contact_count[finger_count];
+ memset(ts->contact_count, 0, sizeof(ts->contact_count));
+ ts->contact_count[finger_count] = count;
+ }
+
+ input_mt_assign_slots(input_dev, slots, pos,
+ finger_count, ROHM_TS_DISPLACEMENT_MAX);
+
+ for (i = 0; i < finger_count; i++) {
+ input_mt_slot(input_dev, slots[i]);
+ input_mt_report_slot_state(input_dev,
+ MT_TOOL_FINGER, true);
+ input_report_abs(input_dev,
+ ABS_MT_POSITION_X, pos[i].x);
+ input_report_abs(input_dev,
+ ABS_MT_POSITION_Y, pos[i].y);
+ }
+
+ input_mt_sync_frame(input_dev);
+ input_mt_report_pointer_emulation(input_dev, true);
+ input_sync(input_dev);
+
+ ts->finger_count = finger_count;
+ }
+
+ if (READ_POS_BUF(TOUCH_GESTURE) & CALIBRATION_REQUEST) {
+ error = rohm_ts_manual_calibration(ts);
+ if (error)
+ dev_warn(dev, "manual calibration failed: %d\n",
+ error);
+ }
+
+ i2c_smbus_write_byte_data(client, INT_MASK,
+ CALIBRATION_DONE | SLEEP_OUT | SLEEP_IN |
+ PROGRAM_LOAD_DONE);
+
+ return IRQ_HANDLED;
+}
+
+static int rohm_ts_load_firmware(struct i2c_client *client,
+ const char *firmware_name)
+{
+ struct device *dev = &client->dev;
+ const struct firmware *fw;
+ s32 status;
+ unsigned int offset, len, xfer_len;
+ unsigned int retry = 0;
+ int error, error2;
+
+ error = request_firmware(&fw, firmware_name, dev);
+ if (error) {
+ dev_err(dev, "unable to retrieve firmware %s: %d\n",
+ firmware_name, error);
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, INT_MASK,
+ COORD_UPDATE | CALIBRATION_DONE |
+ SLEEP_IN | SLEEP_OUT);
+ if (error)
+ goto out;
+
+ do {
+ if (retry) {
+ dev_warn(dev, "retrying firmware load\n");
+
+ /* settings for retry */
+ error = i2c_smbus_write_byte_data(client, EX_WDAT, 0);
+ if (error)
+ goto out;
+ }
+
+ error = i2c_smbus_write_byte_data(client, EX_ADDR_H, 0);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client, EX_ADDR_L, 0);
+ if (error)
+ goto out;
+
+ error = i2c_smbus_write_byte_data(client, COMMON_SETUP1,
+ COMMON_SETUP1_DEFAULT);
+ if (error)
+ goto out;
+
+ /* firmware load to the device */
+ offset = 0;
+ len = fw->size;
+
+ while (len) {
+ xfer_len = min(FIRMWARE_BLOCK_SIZE, len);
+
+ error = i2c_smbus_write_i2c_block_data(client, EX_WDAT,
+ xfer_len, &fw->data[offset]);
+ if (error)
+ goto out;
+
+ len -= xfer_len;
+ offset += xfer_len;
+ }
+
+ /* check firmware load result */
+ status = i2c_smbus_read_byte_data(client, INT_STATUS);
+ if (status < 0) {
+ error = status;
+ goto out;
+ }
+
+ /* clear all interrupts */
+ error = i2c_smbus_write_byte_data(client, INT_CLEAR, 0xff);
+ if (error)
+ goto out;
+
+ if (status == PROGRAM_LOAD_DONE)
+ break;
+
+ error = -EIO;
+ } while (++retry <= FIRMWARE_RETRY_MAX);
+
+out:
+ error2 = i2c_smbus_write_byte_data(client, INT_MASK, INT_ALL);
+
+ release_firmware(fw);
+
+ return error ? error : error2;
+}
+
+static ssize_t swap_xy_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rohm_ts_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", !!(ts->setup2 & SWAP_XY));
+}
+
+static ssize_t swap_xy_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rohm_ts_data *ts = i2c_get_clientdata(client);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 0, &val);
+ if (error)
+ return error;
+
+ error = mutex_lock_interruptible(&ts->input->mutex);
+ if (error)
+ return error;
+
+ if (val)
+ ts->setup2 |= SWAP_XY;
+ else
+ ts->setup2 &= ~SWAP_XY;
+
+ if (ts->initialized)
+ error = i2c_smbus_write_byte_data(ts->client, COMMON_SETUP2,
+ ts->setup2);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return error ? error : count;
+}
+
+static ssize_t inv_x_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rohm_ts_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", !!(ts->setup2 & INV_X));
+}
+
+static ssize_t inv_x_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rohm_ts_data *ts = i2c_get_clientdata(client);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 0, &val);
+ if (error)
+ return error;
+
+ error = mutex_lock_interruptible(&ts->input->mutex);
+ if (error)
+ return error;
+
+ if (val)
+ ts->setup2 |= INV_X;
+ else
+ ts->setup2 &= ~INV_X;
+
+ if (ts->initialized)
+ error = i2c_smbus_write_byte_data(ts->client, COMMON_SETUP2,
+ ts->setup2);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return error ? error : count;
+}
+
+static ssize_t inv_y_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rohm_ts_data *ts = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", !!(ts->setup2 & INV_Y));
+}
+
+static ssize_t inv_y_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rohm_ts_data *ts = i2c_get_clientdata(client);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 0, &val);
+ if (error)
+ return error;
+
+ error = mutex_lock_interruptible(&ts->input->mutex);
+ if (error)
+ return error;
+
+ if (val)
+ ts->setup2 |= INV_Y;
+ else
+ ts->setup2 &= ~INV_Y;
+
+ if (ts->initialized)
+ error = i2c_smbus_write_byte_data(client, COMMON_SETUP2,
+ ts->setup2);
+
+ mutex_unlock(&ts->input->mutex);
+
+ return error ? error : count;
+}
+
+static DEVICE_ATTR_RW(swap_xy);
+static DEVICE_ATTR_RW(inv_x);
+static DEVICE_ATTR_RW(inv_y);
+
+static struct attribute *rohm_ts_attrs[] = {
+ &dev_attr_swap_xy.attr,
+ &dev_attr_inv_x.attr,
+ &dev_attr_inv_y.attr,
+ NULL,
+};
+
+static const struct attribute_group rohm_ts_attr_group = {
+ .attrs = rohm_ts_attrs,
+};
+
+static int rohm_ts_device_init(struct i2c_client *client, u8 setup2)
+{
+ struct device *dev = &client->dev;
+ int error;
+
+ disable_irq(client->irq);
+
+ /*
+ * Wait 200usec for reset
+ */
+ udelay(200);
+
+ /* Release analog reset */
+ error = i2c_smbus_write_byte_data(client, SYSTEM,
+ ANALOG_POWER_ON | CPU_POWER_OFF);
+ if (error)
+ return error;
+
+ /* Waiting for the analog warm-up, max. 200usec */
+ udelay(200);
+
+ /* clear all interrupts */
+ error = i2c_smbus_write_byte_data(client, INT_CLEAR, 0xff);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, EX_WDAT, 0);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, COMMON_SETUP1, 0);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, COMMON_SETUP2, setup2);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, COMMON_SETUP3,
+ SEL_TBL_DEFAULT | EN_MULTI);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, THRESHOLD_GESTURE,
+ THRESHOLD_GESTURE_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, INTERVAL_TIME,
+ INTERVAL_TIME_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, CPU_FREQ, CPU_FREQ_10MHZ);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, PRM_SWOFF_TIME,
+ PRM_SWOFF_TIME_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, ADC_CTRL, ADC_DIV_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, ADC_WAIT, ADC_WAIT_DEFAULT);
+ if (error)
+ return error;
+
+ /*
+ * Panel setup, these values change with the panel.
+ */
+ error = i2c_smbus_write_byte_data(client, STEP_X, STEP_X_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, STEP_Y, STEP_Y_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, OFFSET_X, OFFSET_X_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, OFFSET_Y, OFFSET_Y_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, THRESHOLD_TOUCH,
+ THRESHOLD_TOUCH_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, EVR_XY, EVR_XY_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, EVR_X, EVR_X_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, EVR_Y, EVR_Y_DEFAULT);
+ if (error)
+ return error;
+
+ /* Fixed value settings */
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_ADJUST,
+ CALIBRATION_ADJUST_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, SWCONT, SWCONT_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, TEST1,
+ DUALTOUCH_STABILIZE_ON |
+ DUALTOUCH_REG_ON);
+ if (error)
+ return error;
+
+ error = rohm_ts_load_firmware(client, BU21023_FIRMWARE_NAME);
+ if (error) {
+ dev_err(dev, "failed to load firmware: %d\n", error);
+ return error;
+ }
+
+ /*
+ * Manual calibration results are not changed in same environment.
+ * If the force calibration is performed,
+ * the controller will not require calibration request interrupt
+ * when the typical values are set to the calibration registers.
+ */
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_REG1,
+ CALIBRATION_REG1_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_REG2,
+ CALIBRATION_REG2_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, CALIBRATION_REG3,
+ CALIBRATION_REG3_DEFAULT);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, FORCE_CALIBRATION,
+ FORCE_CALIBRATION_OFF);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, FORCE_CALIBRATION,
+ FORCE_CALIBRATION_ON);
+ if (error)
+ return error;
+
+ /* Clear all interrupts */
+ error = i2c_smbus_write_byte_data(client, INT_CLEAR, 0xff);
+ if (error)
+ return error;
+
+ /* Enable coordinates update interrupt */
+ error = i2c_smbus_write_byte_data(client, INT_MASK,
+ CALIBRATION_DONE | SLEEP_OUT |
+ SLEEP_IN | PROGRAM_LOAD_DONE);
+ if (error)
+ return error;
+
+ error = i2c_smbus_write_byte_data(client, ERR_MASK,
+ PROGRAM_LOAD_ERR | CPU_TIMEOUT |
+ ADC_TIMEOUT);
+ if (error)
+ return error;
+
+ /* controller CPU power on */
+ error = i2c_smbus_write_byte_data(client, SYSTEM,
+ ANALOG_POWER_ON | CPU_POWER_ON);
+
+ enable_irq(client->irq);
+
+ return error;
+}
+
+static int rohm_ts_power_off(struct i2c_client *client)
+{
+ int error;
+
+ error = i2c_smbus_write_byte_data(client, SYSTEM,
+ ANALOG_POWER_ON | CPU_POWER_OFF);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to power off device CPU: %d\n", error);
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, SYSTEM,
+ ANALOG_POWER_OFF | CPU_POWER_OFF);
+ if (error)
+ dev_err(&client->dev,
+ "failed to power off the device: %d\n", error);
+
+ return error;
+}
+
+static int rohm_ts_open(struct input_dev *input_dev)
+{
+ struct rohm_ts_data *ts = input_get_drvdata(input_dev);
+ struct i2c_client *client = ts->client;
+ int error;
+
+ if (!ts->initialized) {
+ error = rohm_ts_device_init(client, ts->setup2);
+ if (error) {
+ dev_err(&client->dev,
+ "device initialization failed: %d\n", error);
+ return error;
+ }
+
+ ts->initialized = true;
+ }
+
+ return 0;
+}
+
+static void rohm_ts_close(struct input_dev *input_dev)
+{
+ struct rohm_ts_data *ts = input_get_drvdata(input_dev);
+
+ rohm_ts_power_off(ts->client);
+
+ ts->initialized = false;
+}
+
+static int rohm_bu21023_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct rohm_ts_data *ts;
+ struct input_dev *input;
+ int error;
+
+ if (!client->irq) {
+ dev_err(dev, "IRQ is not assigned\n");
+ return -EINVAL;
+ }
+
+ if (!client->adapter->algo->master_xfer) {
+ dev_err(dev, "I2C level transfers not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* Turn off CPU just in case */
+ error = rohm_ts_power_off(client);
+ if (error)
+ return error;
+
+ ts = devm_kzalloc(dev, sizeof(struct rohm_ts_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+ ts->setup2 = MAF_1SAMPLE;
+ i2c_set_clientdata(client, ts);
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = BU21023_NAME;
+ input->id.bustype = BUS_I2C;
+ input->open = rohm_ts_open;
+ input->close = rohm_ts_close;
+
+ ts->input = input;
+ input_set_drvdata(input, ts);
+
+ input_set_abs_params(input, ABS_MT_POSITION_X,
+ ROHM_TS_ABS_X_MIN, ROHM_TS_ABS_X_MAX, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y,
+ ROHM_TS_ABS_Y_MIN, ROHM_TS_ABS_Y_MAX, 0, 0);
+
+ error = input_mt_init_slots(input, MAX_CONTACTS,
+ INPUT_MT_DIRECT | INPUT_MT_TRACK |
+ INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(dev, "failed to multi touch slots initialization\n");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, rohm_ts_soft_irq,
+ IRQF_ONESHOT, client->name, ts);
+ if (error) {
+ dev_err(dev, "failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "failed to register input device: %d\n", error);
+ return error;
+ }
+
+ error = devm_device_add_group(dev, &rohm_ts_attr_group);
+ if (error) {
+ dev_err(dev, "failed to create sysfs group: %d\n", error);
+ return error;
+ }
+
+ return error;
+}
+
+static const struct i2c_device_id rohm_bu21023_i2c_id[] = {
+ { BU21023_NAME, 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, rohm_bu21023_i2c_id);
+
+static struct i2c_driver rohm_bu21023_i2c_driver = {
+ .driver = {
+ .name = BU21023_NAME,
+ },
+ .probe = rohm_bu21023_i2c_probe,
+ .id_table = rohm_bu21023_i2c_id,
+};
+module_i2c_driver(rohm_bu21023_i2c_driver);
+
+MODULE_DESCRIPTION("ROHM BU21023/24 Touchscreen driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("ROHM Co., Ltd.");
diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c
new file mode 100644
index 000000000..2e70c0b79
--- /dev/null
+++ b/drivers/input/touchscreen/s3c2410_ts.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung S3C24XX touchscreen driver
+ *
+ * Copyright 2004 Arnaud Patard <arnaud.patard@rtp-net.org>
+ * Copyright 2008 Ben Dooks <ben-linux@fluff.org>
+ * Copyright 2009 Simtec Electronics <linux@simtec.co.uk>
+ *
+ * Additional work by Herbert Pötzl <herbert@13thfloor.at> and
+ * Harald Welte <laforge@openmoko.org>
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <linux/soc/samsung/s3c-adc.h>
+#include <linux/platform_data/touchscreen-s3c2410.h>
+
+#define S3C2410_ADCCON (0x00)
+#define S3C2410_ADCTSC (0x04)
+#define S3C2410_ADCDLY (0x08)
+#define S3C2410_ADCDAT0 (0x0C)
+#define S3C2410_ADCDAT1 (0x10)
+#define S3C64XX_ADCUPDN (0x14)
+#define S3C2443_ADCMUX (0x18)
+#define S3C64XX_ADCCLRINT (0x18)
+#define S5P_ADCMUX (0x1C)
+#define S3C64XX_ADCCLRINTPNDNUP (0x20)
+
+/* ADCTSC Register Bits */
+#define S3C2443_ADCTSC_UD_SEN (1 << 8)
+#define S3C2410_ADCTSC_YM_SEN (1<<7)
+#define S3C2410_ADCTSC_YP_SEN (1<<6)
+#define S3C2410_ADCTSC_XM_SEN (1<<5)
+#define S3C2410_ADCTSC_XP_SEN (1<<4)
+#define S3C2410_ADCTSC_PULL_UP_DISABLE (1<<3)
+#define S3C2410_ADCTSC_AUTO_PST (1<<2)
+#define S3C2410_ADCTSC_XY_PST(x) (((x)&0x3)<<0)
+
+/* ADCDAT0 Bits */
+#define S3C2410_ADCDAT0_UPDOWN (1<<15)
+#define S3C2410_ADCDAT0_AUTO_PST (1<<14)
+#define S3C2410_ADCDAT0_XY_PST (0x3<<12)
+#define S3C2410_ADCDAT0_XPDATA_MASK (0x03FF)
+
+/* ADCDAT1 Bits */
+#define S3C2410_ADCDAT1_UPDOWN (1<<15)
+#define S3C2410_ADCDAT1_AUTO_PST (1<<14)
+#define S3C2410_ADCDAT1_XY_PST (0x3<<12)
+#define S3C2410_ADCDAT1_YPDATA_MASK (0x03FF)
+
+
+#define TSC_SLEEP (S3C2410_ADCTSC_PULL_UP_DISABLE | S3C2410_ADCTSC_XY_PST(0))
+
+#define INT_DOWN (0)
+#define INT_UP (1 << 8)
+
+#define WAIT4INT (S3C2410_ADCTSC_YM_SEN | \
+ S3C2410_ADCTSC_YP_SEN | \
+ S3C2410_ADCTSC_XP_SEN | \
+ S3C2410_ADCTSC_XY_PST(3))
+
+#define AUTOPST (S3C2410_ADCTSC_YM_SEN | \
+ S3C2410_ADCTSC_YP_SEN | \
+ S3C2410_ADCTSC_XP_SEN | \
+ S3C2410_ADCTSC_AUTO_PST | \
+ S3C2410_ADCTSC_XY_PST(0))
+
+#define FEAT_PEN_IRQ (1 << 0) /* HAS ADCCLRINTPNDNUP */
+
+/* Per-touchscreen data. */
+
+/**
+ * struct s3c2410ts - driver touchscreen state.
+ * @client: The ADC client we registered with the core driver.
+ * @dev: The device we are bound to.
+ * @input: The input device we registered with the input subsystem.
+ * @clock: The clock for the adc.
+ * @io: Pointer to the IO base.
+ * @xp: The accumulated X position data.
+ * @yp: The accumulated Y position data.
+ * @irq_tc: The interrupt number for pen up/down interrupt
+ * @count: The number of samples collected.
+ * @shift: The log2 of the maximum count to read in one go.
+ * @features: The features supported by the TSADC MOdule.
+ */
+struct s3c2410ts {
+ struct s3c_adc_client *client;
+ struct device *dev;
+ struct input_dev *input;
+ struct clk *clock;
+ void __iomem *io;
+ unsigned long xp;
+ unsigned long yp;
+ int irq_tc;
+ int count;
+ int shift;
+ int features;
+};
+
+static struct s3c2410ts ts;
+
+/**
+ * get_down - return the down state of the pen
+ * @data0: The data read from ADCDAT0 register.
+ * @data1: The data read from ADCDAT1 register.
+ *
+ * Return non-zero if both readings show that the pen is down.
+ */
+static inline bool get_down(unsigned long data0, unsigned long data1)
+{
+ /* returns true if both data values show stylus down */
+ return (!(data0 & S3C2410_ADCDAT0_UPDOWN) &&
+ !(data1 & S3C2410_ADCDAT0_UPDOWN));
+}
+
+static void touch_timer_fire(struct timer_list *unused)
+{
+ unsigned long data0;
+ unsigned long data1;
+ bool down;
+
+ data0 = readl(ts.io + S3C2410_ADCDAT0);
+ data1 = readl(ts.io + S3C2410_ADCDAT1);
+
+ down = get_down(data0, data1);
+
+ if (down) {
+ if (ts.count == (1 << ts.shift)) {
+ ts.xp >>= ts.shift;
+ ts.yp >>= ts.shift;
+
+ dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
+ __func__, ts.xp, ts.yp, ts.count);
+
+ input_report_abs(ts.input, ABS_X, ts.xp);
+ input_report_abs(ts.input, ABS_Y, ts.yp);
+
+ input_report_key(ts.input, BTN_TOUCH, 1);
+ input_sync(ts.input);
+
+ ts.xp = 0;
+ ts.yp = 0;
+ ts.count = 0;
+ }
+
+ s3c_adc_start(ts.client, 0, 1 << ts.shift);
+ } else {
+ ts.xp = 0;
+ ts.yp = 0;
+ ts.count = 0;
+
+ input_report_key(ts.input, BTN_TOUCH, 0);
+ input_sync(ts.input);
+
+ writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
+ }
+}
+
+static DEFINE_TIMER(touch_timer, touch_timer_fire);
+
+/**
+ * stylus_irq - touchscreen stylus event interrupt
+ * @irq: The interrupt number
+ * @dev_id: The device ID.
+ *
+ * Called when the IRQ_TC is fired for a pen up or down event.
+ */
+static irqreturn_t stylus_irq(int irq, void *dev_id)
+{
+ unsigned long data0;
+ unsigned long data1;
+ bool down;
+
+ data0 = readl(ts.io + S3C2410_ADCDAT0);
+ data1 = readl(ts.io + S3C2410_ADCDAT1);
+
+ down = get_down(data0, data1);
+
+ /* TODO we should never get an interrupt with down set while
+ * the timer is running, but maybe we ought to verify that the
+ * timer isn't running anyways. */
+
+ if (down)
+ s3c_adc_start(ts.client, 0, 1 << ts.shift);
+ else
+ dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);
+
+ if (ts.features & FEAT_PEN_IRQ) {
+ /* Clear pen down/up interrupt */
+ writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * s3c24xx_ts_conversion - ADC conversion callback
+ * @client: The client that was registered with the ADC core.
+ * @data0: The reading from ADCDAT0.
+ * @data1: The reading from ADCDAT1.
+ * @left: The number of samples left.
+ *
+ * Called when a conversion has finished.
+ */
+static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
+ unsigned data0, unsigned data1,
+ unsigned *left)
+{
+ dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
+
+ ts.xp += data0;
+ ts.yp += data1;
+
+ ts.count++;
+
+ /* From tests, it seems that it is unlikely to get a pen-up
+ * event during the conversion process which means we can
+ * ignore any pen-up events with less than the requisite
+ * count done.
+ *
+ * In several thousand conversions, no pen-ups where detected
+ * before count completed.
+ */
+}
+
+/**
+ * s3c24xx_ts_select - ADC selection callback.
+ * @client: The client that was registered with the ADC core.
+ * @select: The reason for select.
+ *
+ * Called when the ADC core selects (or deslects) us as a client.
+ */
+static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
+{
+ if (select) {
+ writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
+ ts.io + S3C2410_ADCTSC);
+ } else {
+ mod_timer(&touch_timer, jiffies+1);
+ writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
+ }
+}
+
+/**
+ * s3c2410ts_probe - device core probe entry point
+ * @pdev: The device we are being bound to.
+ *
+ * Initialise, find and allocate any resources we need to run and then
+ * register with the ADC and input systems.
+ */
+static int s3c2410ts_probe(struct platform_device *pdev)
+{
+ struct s3c2410_ts_mach_info *info;
+ struct device *dev = &pdev->dev;
+ struct input_dev *input_dev;
+ struct resource *res;
+ int ret = -EINVAL;
+
+ /* Initialise input stuff */
+ memset(&ts, 0, sizeof(struct s3c2410ts));
+
+ ts.dev = dev;
+
+ info = dev_get_platdata(dev);
+ if (!info) {
+ dev_err(dev, "no platform data, cannot attach\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "initialising touchscreen\n");
+
+ ts.clock = clk_get(dev, "adc");
+ if (IS_ERR(ts.clock)) {
+ dev_err(dev, "cannot get adc clock source\n");
+ return -ENOENT;
+ }
+
+ ret = clk_prepare_enable(ts.clock);
+ if (ret) {
+ dev_err(dev, "Failed! to enabled clocks\n");
+ goto err_clk_get;
+ }
+ dev_dbg(dev, "got and enabled clocks\n");
+
+ ts.irq_tc = ret = platform_get_irq(pdev, 0);
+ if (ret < 0) {
+ dev_err(dev, "no resource for interrupt\n");
+ goto err_clk;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "no resource for registers\n");
+ ret = -ENOENT;
+ goto err_clk;
+ }
+
+ ts.io = ioremap(res->start, resource_size(res));
+ if (ts.io == NULL) {
+ dev_err(dev, "cannot map registers\n");
+ ret = -ENOMEM;
+ goto err_clk;
+ }
+
+ /* inititalise the gpio */
+ if (info->cfg_gpio)
+ info->cfg_gpio(to_platform_device(ts.dev));
+
+ ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
+ s3c24xx_ts_conversion, 1);
+ if (IS_ERR(ts.client)) {
+ dev_err(dev, "failed to register adc client\n");
+ ret = PTR_ERR(ts.client);
+ goto err_iomap;
+ }
+
+ /* Initialise registers */
+ if ((info->delay & 0xffff) > 0)
+ writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);
+
+ writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(dev, "Unable to allocate the input device !!\n");
+ ret = -ENOMEM;
+ goto err_iomap;
+ }
+
+ ts.input = input_dev;
+ ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
+ input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
+
+ ts.input->name = "S3C24XX TouchScreen";
+ ts.input->id.bustype = BUS_HOST;
+ ts.input->id.vendor = 0xDEAD;
+ ts.input->id.product = 0xBEEF;
+ ts.input->id.version = 0x0102;
+
+ ts.shift = info->oversampling_shift;
+ ts.features = platform_get_device_id(pdev)->driver_data;
+
+ ret = request_irq(ts.irq_tc, stylus_irq, 0,
+ "s3c2410_ts_pen", ts.input);
+ if (ret) {
+ dev_err(dev, "cannot get TC interrupt\n");
+ goto err_inputdev;
+ }
+
+ dev_info(dev, "driver attached, registering input device\n");
+
+ /* All went ok, so register to the input system */
+ ret = input_register_device(ts.input);
+ if (ret < 0) {
+ dev_err(dev, "failed to register input device\n");
+ ret = -EIO;
+ goto err_tcirq;
+ }
+
+ return 0;
+
+ err_tcirq:
+ free_irq(ts.irq_tc, ts.input);
+ err_inputdev:
+ input_free_device(ts.input);
+ err_iomap:
+ iounmap(ts.io);
+ err_clk:
+ clk_disable_unprepare(ts.clock);
+ del_timer_sync(&touch_timer);
+ err_clk_get:
+ clk_put(ts.clock);
+ return ret;
+}
+
+/**
+ * s3c2410ts_remove - device core removal entry point
+ * @pdev: The device we are being removed from.
+ *
+ * Free up our state ready to be removed.
+ */
+static int s3c2410ts_remove(struct platform_device *pdev)
+{
+ free_irq(ts.irq_tc, ts.input);
+ del_timer_sync(&touch_timer);
+
+ clk_disable_unprepare(ts.clock);
+ clk_put(ts.clock);
+
+ input_unregister_device(ts.input);
+ iounmap(ts.io);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c2410ts_suspend(struct device *dev)
+{
+ writel(TSC_SLEEP, ts.io + S3C2410_ADCTSC);
+ disable_irq(ts.irq_tc);
+ clk_disable(ts.clock);
+
+ return 0;
+}
+
+static int s3c2410ts_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s3c2410_ts_mach_info *info = dev_get_platdata(&pdev->dev);
+
+ clk_enable(ts.clock);
+ enable_irq(ts.irq_tc);
+
+ /* Initialise registers */
+ if ((info->delay & 0xffff) > 0)
+ writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);
+
+ writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
+
+ return 0;
+}
+
+static const struct dev_pm_ops s3c_ts_pmops = {
+ .suspend = s3c2410ts_suspend,
+ .resume = s3c2410ts_resume,
+};
+#endif
+
+static const struct platform_device_id s3cts_driver_ids[] = {
+ { "s3c2410-ts", 0 },
+ { "s3c2440-ts", 0 },
+ { "s3c64xx-ts", FEAT_PEN_IRQ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, s3cts_driver_ids);
+
+static struct platform_driver s3c_ts_driver = {
+ .driver = {
+ .name = "samsung-ts",
+#ifdef CONFIG_PM
+ .pm = &s3c_ts_pmops,
+#endif
+ },
+ .id_table = s3cts_driver_ids,
+ .probe = s3c2410ts_probe,
+ .remove = s3c2410ts_remove,
+};
+module_platform_driver(s3c_ts_driver);
+
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>, "
+ "Ben Dooks <ben@simtec.co.uk>, "
+ "Simtec Electronics <linux@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C24XX Touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c
new file mode 100644
index 000000000..1a7d00289
--- /dev/null
+++ b/drivers/input/touchscreen/s6sy761.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+// Samsung S6SY761 Touchscreen device driver
+//
+// Copyright (c) 2017 Samsung Electronics Co., Ltd.
+// Copyright (c) 2017 Andi Shyti <andi@etezian.org>
+
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+/* commands */
+#define S6SY761_SENSE_ON 0x10
+#define S6SY761_SENSE_OFF 0x11
+#define S6SY761_TOUCH_FUNCTION 0x30 /* R/W for get/set */
+#define S6SY761_FIRMWARE_INTEGRITY 0x21
+#define S6SY761_PANEL_INFO 0x23
+#define S6SY761_DEVICE_ID 0x52
+#define S6SY761_BOOT_STATUS 0x55
+#define S6SY761_READ_ONE_EVENT 0x60
+#define S6SY761_READ_ALL_EVENT 0x61
+#define S6SY761_CLEAR_EVENT_STACK 0x62
+#define S6SY761_APPLICATION_MODE 0xe4
+
+/* events */
+#define S6SY761_EVENT_INFO 0x02
+#define S6SY761_EVENT_VENDOR_INFO 0x07
+
+/* info */
+#define S6SY761_INFO_BOOT_COMPLETE 0x00
+
+/* firmware status */
+#define S6SY761_FW_OK 0x80
+
+/*
+ * the functionalities are put as a reference
+ * as in the device I am using none of them
+ * works therefore not used in this driver yet.
+ */
+/* touchscreen functionalities */
+#define S6SY761_MASK_TOUCH BIT(0)
+#define S6SY761_MASK_HOVER BIT(1)
+#define S6SY761_MASK_COVER BIT(2)
+#define S6SY761_MASK_GLOVE BIT(3)
+#define S6SY761_MASK_STYLUS BIT(4)
+#define S6SY761_MASK_PALM BIT(5)
+#define S6SY761_MASK_WET BIT(6)
+#define S6SY761_MASK_PROXIMITY BIT(7)
+
+/* boot status (BS) */
+#define S6SY761_BS_BOOT_LOADER 0x10
+#define S6SY761_BS_APPLICATION 0x20
+
+/* event id */
+#define S6SY761_EVENT_ID_COORDINATE 0x00
+#define S6SY761_EVENT_ID_STATUS 0x01
+
+/* event register masks */
+#define S6SY761_MASK_TOUCH_STATE 0xc0 /* byte 0 */
+#define S6SY761_MASK_TID 0x3c
+#define S6SY761_MASK_EID 0x03
+#define S6SY761_MASK_X 0xf0 /* byte 3 */
+#define S6SY761_MASK_Y 0x0f
+#define S6SY761_MASK_Z 0x3f /* byte 6 */
+#define S6SY761_MASK_LEFT_EVENTS 0x3f /* byte 7 */
+#define S6SY761_MASK_TOUCH_TYPE 0xc0 /* MSB in byte 6, LSB in byte 7 */
+
+/* event touch state values */
+#define S6SY761_TS_NONE 0x00
+#define S6SY761_TS_PRESS 0x01
+#define S6SY761_TS_MOVE 0x02
+#define S6SY761_TS_RELEASE 0x03
+
+/* application modes */
+#define S6SY761_APP_NORMAL 0x0
+#define S6SY761_APP_LOW_POWER 0x1
+#define S6SY761_APP_TEST 0x2
+#define S6SY761_APP_FLASH 0x3
+#define S6SY761_APP_SLEEP 0x4
+
+#define S6SY761_EVENT_SIZE 8
+#define S6SY761_EVENT_COUNT 32
+#define S6SY761_DEVID_SIZE 3
+#define S6SY761_PANEL_ID_SIZE 11
+#define S6SY761_TS_STATUS_SIZE 5
+#define S6SY761_MAX_FINGERS 10
+
+#define S6SY761_DEV_NAME "s6sy761"
+
+enum s6sy761_regulators {
+ S6SY761_REGULATOR_VDD,
+ S6SY761_REGULATOR_AVDD,
+};
+
+struct s6sy761_data {
+ struct i2c_client *client;
+ struct regulator_bulk_data regulators[2];
+ struct input_dev *input;
+ struct touchscreen_properties prop;
+
+ u8 data[S6SY761_EVENT_SIZE * S6SY761_EVENT_COUNT];
+
+ u16 devid;
+ u8 tx_channel;
+};
+
+/*
+ * We can't simply use i2c_smbus_read_i2c_block_data because we
+ * need to read more than 255 bytes
+ */
+static int s6sy761_read_events(struct s6sy761_data *sdata, u16 n_events)
+{
+ u8 cmd = S6SY761_READ_ALL_EVENT;
+ struct i2c_msg msgs[2] = {
+ {
+ .addr = sdata->client->addr,
+ .len = 1,
+ .buf = &cmd,
+ },
+ {
+ .addr = sdata->client->addr,
+ .flags = I2C_M_RD,
+ .len = (n_events * S6SY761_EVENT_SIZE),
+ .buf = sdata->data + S6SY761_EVENT_SIZE,
+ },
+ };
+ int ret;
+
+ ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0)
+ return ret;
+
+ return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
+}
+
+static void s6sy761_report_coordinates(struct s6sy761_data *sdata,
+ u8 *event, u8 tid)
+{
+ u8 major = event[4];
+ u8 minor = event[5];
+ u8 z = event[6] & S6SY761_MASK_Z;
+ u16 x = (event[1] << 4) | ((event[3] & S6SY761_MASK_X) >> 4);
+ u16 y = (event[2] << 4) | (event[3] & S6SY761_MASK_Y);
+
+ input_mt_slot(sdata->input, tid);
+
+ input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true);
+ input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
+ input_report_abs(sdata->input, ABS_MT_POSITION_Y, y);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, major);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, minor);
+ input_report_abs(sdata->input, ABS_MT_PRESSURE, z);
+
+ input_sync(sdata->input);
+}
+
+static void s6sy761_report_release(struct s6sy761_data *sdata,
+ u8 *event, u8 tid)
+{
+ input_mt_slot(sdata->input, tid);
+ input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, false);
+
+ input_sync(sdata->input);
+}
+
+static void s6sy761_handle_coordinates(struct s6sy761_data *sdata, u8 *event)
+{
+ u8 tid;
+ u8 touch_state;
+
+ if (unlikely(!(event[0] & S6SY761_MASK_TID)))
+ return;
+
+ tid = ((event[0] & S6SY761_MASK_TID) >> 2) - 1;
+ touch_state = (event[0] & S6SY761_MASK_TOUCH_STATE) >> 6;
+
+ switch (touch_state) {
+
+ case S6SY761_TS_NONE:
+ break;
+ case S6SY761_TS_RELEASE:
+ s6sy761_report_release(sdata, event, tid);
+ break;
+ case S6SY761_TS_PRESS:
+ case S6SY761_TS_MOVE:
+ s6sy761_report_coordinates(sdata, event, tid);
+ break;
+ }
+}
+
+static void s6sy761_handle_events(struct s6sy761_data *sdata, u8 n_events)
+{
+ int i;
+
+ for (i = 0; i < n_events; i++) {
+ u8 *event = &sdata->data[i * S6SY761_EVENT_SIZE];
+ u8 event_id = event[0] & S6SY761_MASK_EID;
+
+ if (!event[0])
+ return;
+
+ switch (event_id) {
+
+ case S6SY761_EVENT_ID_COORDINATE:
+ s6sy761_handle_coordinates(sdata, event);
+ break;
+
+ case S6SY761_EVENT_ID_STATUS:
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static irqreturn_t s6sy761_irq_handler(int irq, void *dev)
+{
+ struct s6sy761_data *sdata = dev;
+ int ret;
+ u8 n_events;
+
+ ret = i2c_smbus_read_i2c_block_data(sdata->client,
+ S6SY761_READ_ONE_EVENT,
+ S6SY761_EVENT_SIZE,
+ sdata->data);
+ if (ret < 0) {
+ dev_err(&sdata->client->dev, "failed to read events\n");
+ return IRQ_HANDLED;
+ }
+
+ if (!sdata->data[0])
+ return IRQ_HANDLED;
+
+ n_events = sdata->data[7] & S6SY761_MASK_LEFT_EVENTS;
+ if (unlikely(n_events > S6SY761_EVENT_COUNT - 1))
+ return IRQ_HANDLED;
+
+ if (n_events) {
+ ret = s6sy761_read_events(sdata, n_events);
+ if (ret < 0) {
+ dev_err(&sdata->client->dev, "failed to read events\n");
+ return IRQ_HANDLED;
+ }
+ }
+
+ s6sy761_handle_events(sdata, n_events + 1);
+
+ return IRQ_HANDLED;
+}
+
+static int s6sy761_input_open(struct input_dev *dev)
+{
+ struct s6sy761_data *sdata = input_get_drvdata(dev);
+
+ return i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON);
+}
+
+static void s6sy761_input_close(struct input_dev *dev)
+{
+ struct s6sy761_data *sdata = input_get_drvdata(dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_OFF);
+ if (ret)
+ dev_err(&sdata->client->dev, "failed to turn off sensing\n");
+}
+
+static ssize_t s6sy761_sysfs_devid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct s6sy761_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%#x\n", sdata->devid);
+}
+
+static DEVICE_ATTR(devid, 0444, s6sy761_sysfs_devid, NULL);
+
+static struct attribute *s6sy761_sysfs_attrs[] = {
+ &dev_attr_devid.attr,
+ NULL
+};
+
+static struct attribute_group s6sy761_attribute_group = {
+ .attrs = s6sy761_sysfs_attrs
+};
+
+static int s6sy761_power_on(struct s6sy761_data *sdata)
+{
+ u8 buffer[S6SY761_EVENT_SIZE];
+ u8 event;
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(sdata->regulators),
+ sdata->regulators);
+ if (ret)
+ return ret;
+
+ msleep(140);
+
+ /* double check whether the touch is functional */
+ ret = i2c_smbus_read_i2c_block_data(sdata->client,
+ S6SY761_READ_ONE_EVENT,
+ S6SY761_EVENT_SIZE,
+ buffer);
+ if (ret < 0)
+ return ret;
+
+ event = (buffer[0] >> 2) & 0xf;
+
+ if ((event != S6SY761_EVENT_INFO &&
+ event != S6SY761_EVENT_VENDOR_INFO) ||
+ buffer[1] != S6SY761_INFO_BOOT_COMPLETE) {
+ return -ENODEV;
+ }
+
+ ret = i2c_smbus_read_byte_data(sdata->client, S6SY761_BOOT_STATUS);
+ if (ret < 0)
+ return ret;
+
+ /* for some reasons the device might be stuck in the bootloader */
+ if (ret != S6SY761_BS_APPLICATION)
+ return -ENODEV;
+
+ /* enable touch functionality */
+ ret = i2c_smbus_write_word_data(sdata->client,
+ S6SY761_TOUCH_FUNCTION,
+ S6SY761_MASK_TOUCH);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int s6sy761_hw_init(struct s6sy761_data *sdata,
+ unsigned int *max_x, unsigned int *max_y)
+{
+ u8 buffer[S6SY761_PANEL_ID_SIZE]; /* larger read size */
+ int ret;
+
+ ret = s6sy761_power_on(sdata);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_read_i2c_block_data(sdata->client,
+ S6SY761_DEVICE_ID,
+ S6SY761_DEVID_SIZE,
+ buffer);
+ if (ret < 0)
+ return ret;
+
+ sdata->devid = get_unaligned_be16(buffer + 1);
+
+ ret = i2c_smbus_read_i2c_block_data(sdata->client,
+ S6SY761_PANEL_INFO,
+ S6SY761_PANEL_ID_SIZE,
+ buffer);
+ if (ret < 0)
+ return ret;
+
+ *max_x = get_unaligned_be16(buffer);
+ *max_y = get_unaligned_be16(buffer + 2);
+
+ /* if no tx channels defined, at least keep one */
+ sdata->tx_channel = max_t(u8, buffer[8], 1);
+
+ ret = i2c_smbus_read_byte_data(sdata->client,
+ S6SY761_FIRMWARE_INTEGRITY);
+ if (ret < 0)
+ return ret;
+ else if (ret != S6SY761_FW_OK)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void s6sy761_power_off(void *data)
+{
+ struct s6sy761_data *sdata = data;
+
+ disable_irq(sdata->client->irq);
+ regulator_bulk_disable(ARRAY_SIZE(sdata->regulators),
+ sdata->regulators);
+}
+
+static int s6sy761_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct s6sy761_data *sdata;
+ unsigned int max_x, max_y;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK))
+ return -ENODEV;
+
+ sdata = devm_kzalloc(&client->dev, sizeof(*sdata), GFP_KERNEL);
+ if (!sdata)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, sdata);
+ sdata->client = client;
+
+ sdata->regulators[S6SY761_REGULATOR_VDD].supply = "vdd";
+ sdata->regulators[S6SY761_REGULATOR_AVDD].supply = "avdd";
+ err = devm_regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(sdata->regulators),
+ sdata->regulators);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(&client->dev, s6sy761_power_off, sdata);
+ if (err)
+ return err;
+
+ err = s6sy761_hw_init(sdata, &max_x, &max_y);
+ if (err)
+ return err;
+
+ sdata->input = devm_input_allocate_device(&client->dev);
+ if (!sdata->input)
+ return -ENOMEM;
+
+ sdata->input->name = S6SY761_DEV_NAME;
+ sdata->input->id.bustype = BUS_I2C;
+ sdata->input->open = s6sy761_input_open;
+ sdata->input->close = s6sy761_input_close;
+
+ input_set_abs_params(sdata->input, ABS_MT_POSITION_X, 0, max_x, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(sdata->input, true, &sdata->prop);
+
+ if (!input_abs_get_max(sdata->input, ABS_X) ||
+ !input_abs_get_max(sdata->input, ABS_Y)) {
+ dev_warn(&client->dev, "the axis have not been set\n");
+ }
+
+ err = input_mt_init_slots(sdata->input, sdata->tx_channel,
+ INPUT_MT_DIRECT);
+ if (err)
+ return err;
+
+ input_set_drvdata(sdata->input, sdata);
+
+ err = input_register_device(sdata->input);
+ if (err)
+ return err;
+
+ err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ s6sy761_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "s6sy761_irq", sdata);
+ if (err)
+ return err;
+
+ err = devm_device_add_group(&client->dev, &s6sy761_attribute_group);
+ if (err)
+ return err;
+
+ pm_runtime_enable(&client->dev);
+
+ return 0;
+}
+
+static void s6sy761_remove(struct i2c_client *client)
+{
+ pm_runtime_disable(&client->dev);
+}
+
+static int __maybe_unused s6sy761_runtime_suspend(struct device *dev)
+{
+ struct s6sy761_data *sdata = dev_get_drvdata(dev);
+
+ return i2c_smbus_write_byte_data(sdata->client,
+ S6SY761_APPLICATION_MODE, S6SY761_APP_SLEEP);
+}
+
+static int __maybe_unused s6sy761_runtime_resume(struct device *dev)
+{
+ struct s6sy761_data *sdata = dev_get_drvdata(dev);
+
+ return i2c_smbus_write_byte_data(sdata->client,
+ S6SY761_APPLICATION_MODE, S6SY761_APP_NORMAL);
+}
+
+static int __maybe_unused s6sy761_suspend(struct device *dev)
+{
+ struct s6sy761_data *sdata = dev_get_drvdata(dev);
+
+ s6sy761_power_off(sdata);
+
+ return 0;
+}
+
+static int __maybe_unused s6sy761_resume(struct device *dev)
+{
+ struct s6sy761_data *sdata = dev_get_drvdata(dev);
+
+ enable_irq(sdata->client->irq);
+
+ return s6sy761_power_on(sdata);
+}
+
+static const struct dev_pm_ops s6sy761_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(s6sy761_suspend, s6sy761_resume)
+ SET_RUNTIME_PM_OPS(s6sy761_runtime_suspend,
+ s6sy761_runtime_resume, NULL)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id s6sy761_of_match[] = {
+ { .compatible = "samsung,s6sy761", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, s6sy761_of_match);
+#endif
+
+static const struct i2c_device_id s6sy761_id[] = {
+ { "s6sy761", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, s6sy761_id);
+
+static struct i2c_driver s6sy761_driver = {
+ .driver = {
+ .name = S6SY761_DEV_NAME,
+ .of_match_table = of_match_ptr(s6sy761_of_match),
+ .pm = &s6sy761_pm_ops,
+ },
+ .probe = s6sy761_probe,
+ .remove = s6sy761_remove,
+ .id_table = s6sy761_id,
+};
+
+module_i2c_driver(s6sy761_driver);
+
+MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>");
+MODULE_DESCRIPTION("Samsung S6SY761 Touch Screen");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/silead.c b/drivers/input/touchscreen/silead.c
new file mode 100644
index 000000000..3eef8c010
--- /dev/null
+++ b/drivers/input/touchscreen/silead.c
@@ -0,0 +1,842 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* -------------------------------------------------------------------------
+ * Copyright (C) 2014-2015, Intel Corporation
+ *
+ * Derived from:
+ * gslX68X.c
+ * Copyright (C) 2010-2015, Shanghai Sileadinc Co.Ltd
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/irq.h>
+#include <linux/regulator/consumer.h>
+
+#include <asm/unaligned.h>
+
+#define SILEAD_TS_NAME "silead_ts"
+
+#define SILEAD_REG_RESET 0xE0
+#define SILEAD_REG_DATA 0x80
+#define SILEAD_REG_TOUCH_NR 0x80
+#define SILEAD_REG_POWER 0xBC
+#define SILEAD_REG_CLOCK 0xE4
+#define SILEAD_REG_STATUS 0xB0
+#define SILEAD_REG_ID 0xFC
+#define SILEAD_REG_MEM_CHECK 0xB0
+
+#define SILEAD_STATUS_OK 0x5A5A5A5A
+#define SILEAD_TS_DATA_LEN 44
+#define SILEAD_CLOCK 0x04
+
+#define SILEAD_CMD_RESET 0x88
+#define SILEAD_CMD_START 0x00
+
+#define SILEAD_POINT_DATA_LEN 0x04
+#define SILEAD_POINT_Y_OFF 0x00
+#define SILEAD_POINT_Y_MSB_OFF 0x01
+#define SILEAD_POINT_X_OFF 0x02
+#define SILEAD_POINT_X_MSB_OFF 0x03
+#define SILEAD_EXTRA_DATA_MASK 0xF0
+
+#define SILEAD_CMD_SLEEP_MIN 10000
+#define SILEAD_CMD_SLEEP_MAX 20000
+#define SILEAD_POWER_SLEEP 20
+#define SILEAD_STARTUP_SLEEP 30
+
+#define SILEAD_MAX_FINGERS 10
+
+enum silead_ts_power {
+ SILEAD_POWER_ON = 1,
+ SILEAD_POWER_OFF = 0
+};
+
+struct silead_ts_data {
+ struct i2c_client *client;
+ struct gpio_desc *gpio_power;
+ struct input_dev *input;
+ struct input_dev *pen_input;
+ struct regulator_bulk_data regulators[2];
+ char fw_name[64];
+ struct touchscreen_properties prop;
+ u32 max_fingers;
+ u32 chip_id;
+ struct input_mt_pos pos[SILEAD_MAX_FINGERS];
+ int slots[SILEAD_MAX_FINGERS];
+ int id[SILEAD_MAX_FINGERS];
+ u32 efi_fw_min_max[4];
+ bool efi_fw_min_max_set;
+ bool pen_supported;
+ bool pen_down;
+ u32 pen_x_res;
+ u32 pen_y_res;
+ int pen_up_count;
+};
+
+struct silead_fw_data {
+ u32 offset;
+ u32 val;
+};
+
+static void silead_apply_efi_fw_min_max(struct silead_ts_data *data)
+{
+ struct input_absinfo *absinfo_x = &data->input->absinfo[ABS_MT_POSITION_X];
+ struct input_absinfo *absinfo_y = &data->input->absinfo[ABS_MT_POSITION_Y];
+
+ if (!data->efi_fw_min_max_set)
+ return;
+
+ absinfo_x->minimum = data->efi_fw_min_max[0];
+ absinfo_x->maximum = data->efi_fw_min_max[1];
+ absinfo_y->minimum = data->efi_fw_min_max[2];
+ absinfo_y->maximum = data->efi_fw_min_max[3];
+
+ if (data->prop.invert_x) {
+ absinfo_x->maximum -= absinfo_x->minimum;
+ absinfo_x->minimum = 0;
+ }
+
+ if (data->prop.invert_y) {
+ absinfo_y->maximum -= absinfo_y->minimum;
+ absinfo_y->minimum = 0;
+ }
+
+ if (data->prop.swap_x_y) {
+ swap(absinfo_x->minimum, absinfo_y->minimum);
+ swap(absinfo_x->maximum, absinfo_y->maximum);
+ }
+}
+
+static int silead_ts_request_input_dev(struct silead_ts_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+
+ data->input = devm_input_allocate_device(dev);
+ if (!data->input) {
+ dev_err(dev,
+ "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_set_abs_params(data->input, ABS_MT_POSITION_X, 0, 4095, 0, 0);
+ input_set_abs_params(data->input, ABS_MT_POSITION_Y, 0, 4095, 0, 0);
+ touchscreen_parse_properties(data->input, true, &data->prop);
+ silead_apply_efi_fw_min_max(data);
+
+ input_mt_init_slots(data->input, data->max_fingers,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED |
+ INPUT_MT_TRACK);
+
+ if (device_property_read_bool(dev, "silead,home-button"))
+ input_set_capability(data->input, EV_KEY, KEY_LEFTMETA);
+
+ data->input->name = SILEAD_TS_NAME;
+ data->input->phys = "input/ts";
+ data->input->id.bustype = BUS_I2C;
+
+ error = input_register_device(data->input);
+ if (error) {
+ dev_err(dev, "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int silead_ts_request_pen_input_dev(struct silead_ts_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+
+ if (!data->pen_supported)
+ return 0;
+
+ data->pen_input = devm_input_allocate_device(dev);
+ if (!data->pen_input)
+ return -ENOMEM;
+
+ input_set_abs_params(data->pen_input, ABS_X, 0, 4095, 0, 0);
+ input_set_abs_params(data->pen_input, ABS_Y, 0, 4095, 0, 0);
+ input_set_capability(data->pen_input, EV_KEY, BTN_TOUCH);
+ input_set_capability(data->pen_input, EV_KEY, BTN_TOOL_PEN);
+ set_bit(INPUT_PROP_DIRECT, data->pen_input->propbit);
+ touchscreen_parse_properties(data->pen_input, false, &data->prop);
+ input_abs_set_res(data->pen_input, ABS_X, data->pen_x_res);
+ input_abs_set_res(data->pen_input, ABS_Y, data->pen_y_res);
+
+ data->pen_input->name = SILEAD_TS_NAME " pen";
+ data->pen_input->phys = "input/pen";
+ data->input->id.bustype = BUS_I2C;
+
+ error = input_register_device(data->pen_input);
+ if (error) {
+ dev_err(dev, "Failed to register pen input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void silead_ts_set_power(struct i2c_client *client,
+ enum silead_ts_power state)
+{
+ struct silead_ts_data *data = i2c_get_clientdata(client);
+
+ if (data->gpio_power) {
+ gpiod_set_value_cansleep(data->gpio_power, state);
+ msleep(SILEAD_POWER_SLEEP);
+ }
+}
+
+static bool silead_ts_handle_pen_data(struct silead_ts_data *data, u8 *buf)
+{
+ u8 *coord = buf + SILEAD_POINT_DATA_LEN;
+ struct input_mt_pos pos;
+
+ if (!data->pen_supported || buf[2] != 0x00 || buf[3] != 0x00)
+ return false;
+
+ if (buf[0] == 0x00 && buf[1] == 0x00 && data->pen_down) {
+ data->pen_up_count++;
+ if (data->pen_up_count == 6) {
+ data->pen_down = false;
+ goto sync;
+ }
+ return true;
+ }
+
+ if (buf[0] == 0x01 && buf[1] == 0x08) {
+ touchscreen_set_mt_pos(&pos, &data->prop,
+ get_unaligned_le16(&coord[SILEAD_POINT_X_OFF]) & 0xfff,
+ get_unaligned_le16(&coord[SILEAD_POINT_Y_OFF]) & 0xfff);
+
+ input_report_abs(data->pen_input, ABS_X, pos.x);
+ input_report_abs(data->pen_input, ABS_Y, pos.y);
+
+ data->pen_up_count = 0;
+ data->pen_down = true;
+ goto sync;
+ }
+
+ return false;
+
+sync:
+ input_report_key(data->pen_input, BTN_TOOL_PEN, data->pen_down);
+ input_report_key(data->pen_input, BTN_TOUCH, data->pen_down);
+ input_sync(data->pen_input);
+ return true;
+}
+
+static void silead_ts_read_data(struct i2c_client *client)
+{
+ struct silead_ts_data *data = i2c_get_clientdata(client);
+ struct input_dev *input = data->input;
+ struct device *dev = &client->dev;
+ u8 *bufp, buf[SILEAD_TS_DATA_LEN];
+ int touch_nr, softbutton, error, i;
+ bool softbutton_pressed = false;
+
+ error = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_DATA,
+ SILEAD_TS_DATA_LEN, buf);
+ if (error < 0) {
+ dev_err(dev, "Data read error %d\n", error);
+ return;
+ }
+
+ if (buf[0] > data->max_fingers) {
+ dev_warn(dev, "More touches reported then supported %d > %d\n",
+ buf[0], data->max_fingers);
+ buf[0] = data->max_fingers;
+ }
+
+ if (silead_ts_handle_pen_data(data, buf))
+ goto sync; /* Pen is down, release all previous touches */
+
+ touch_nr = 0;
+ bufp = buf + SILEAD_POINT_DATA_LEN;
+ for (i = 0; i < buf[0]; i++, bufp += SILEAD_POINT_DATA_LEN) {
+ softbutton = (bufp[SILEAD_POINT_Y_MSB_OFF] &
+ SILEAD_EXTRA_DATA_MASK) >> 4;
+
+ if (softbutton) {
+ /*
+ * For now only respond to softbutton == 0x01, some
+ * tablets *without* a capacative button send 0x04
+ * when crossing the edges of the screen.
+ */
+ if (softbutton == 0x01)
+ softbutton_pressed = true;
+
+ continue;
+ }
+
+ /*
+ * Bits 4-7 are the touch id, note not all models have
+ * hardware touch ids so atm we don't use these.
+ */
+ data->id[touch_nr] = (bufp[SILEAD_POINT_X_MSB_OFF] &
+ SILEAD_EXTRA_DATA_MASK) >> 4;
+ touchscreen_set_mt_pos(&data->pos[touch_nr], &data->prop,
+ get_unaligned_le16(&bufp[SILEAD_POINT_X_OFF]) & 0xfff,
+ get_unaligned_le16(&bufp[SILEAD_POINT_Y_OFF]) & 0xfff);
+ touch_nr++;
+ }
+
+ input_mt_assign_slots(input, data->slots, data->pos, touch_nr, 0);
+
+ for (i = 0; i < touch_nr; i++) {
+ input_mt_slot(input, data->slots[i]);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ input_report_abs(input, ABS_MT_POSITION_X, data->pos[i].x);
+ input_report_abs(input, ABS_MT_POSITION_Y, data->pos[i].y);
+
+ dev_dbg(dev, "x=%d y=%d hw_id=%d sw_id=%d\n", data->pos[i].x,
+ data->pos[i].y, data->id[i], data->slots[i]);
+ }
+
+sync:
+ input_mt_sync_frame(input);
+ input_report_key(input, KEY_LEFTMETA, softbutton_pressed);
+ input_sync(input);
+}
+
+static int silead_ts_init(struct i2c_client *client)
+{
+ struct silead_ts_data *data = i2c_get_clientdata(client);
+ int error;
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET,
+ SILEAD_CMD_RESET);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_TOUCH_NR,
+ data->max_fingers);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_CLOCK,
+ SILEAD_CLOCK);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET,
+ SILEAD_CMD_START);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ return 0;
+
+i2c_write_err:
+ dev_err(&client->dev, "Registers clear error %d\n", error);
+ return error;
+}
+
+static int silead_ts_reset(struct i2c_client *client)
+{
+ int error;
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET,
+ SILEAD_CMD_RESET);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_CLOCK,
+ SILEAD_CLOCK);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_POWER,
+ SILEAD_CMD_START);
+ if (error)
+ goto i2c_write_err;
+ usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX);
+
+ return 0;
+
+i2c_write_err:
+ dev_err(&client->dev, "Chip reset error %d\n", error);
+ return error;
+}
+
+static int silead_ts_startup(struct i2c_client *client)
+{
+ int error;
+
+ error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET, 0x00);
+ if (error) {
+ dev_err(&client->dev, "Startup error %d\n", error);
+ return error;
+ }
+
+ msleep(SILEAD_STARTUP_SLEEP);
+
+ return 0;
+}
+
+static int silead_ts_load_fw(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct silead_ts_data *data = i2c_get_clientdata(client);
+ const struct firmware *fw = NULL;
+ struct silead_fw_data *fw_data;
+ unsigned int fw_size, i;
+ int error;
+
+ dev_dbg(dev, "Firmware file name: %s", data->fw_name);
+
+ /*
+ * Unfortunately, at the time of writing this comment, we have been unable to
+ * get permission from Silead, or from device OEMs, to distribute the necessary
+ * Silead firmware files in linux-firmware.
+ *
+ * On a whole bunch of devices the UEFI BIOS code contains a touchscreen driver,
+ * which contains an embedded copy of the firmware. The fw-loader code has a
+ * "platform" fallback mechanism, which together with info on the firmware
+ * from drivers/platform/x86/touchscreen_dmi.c will use the firmware from the
+ * UEFI driver when the firmware is missing from /lib/firmware. This makes the
+ * touchscreen work OOTB without users needing to manually download the firmware.
+ *
+ * The firmware bundled with the original Windows/Android is usually newer then
+ * the firmware in the UEFI driver and it is better calibrated. This better
+ * calibration can lead to significant differences in the reported min/max
+ * coordinates.
+ *
+ * To deal with this we first try to load the firmware without "platform"
+ * fallback. If that fails we retry with "platform" fallback and if that
+ * succeeds we apply an (optional) set of alternative min/max values from the
+ * "silead,efi-fw-min-max" property.
+ */
+ error = firmware_request_nowarn(&fw, data->fw_name, dev);
+ if (error) {
+ error = firmware_request_platform(&fw, data->fw_name, dev);
+ if (error) {
+ dev_err(dev, "Firmware request error %d\n", error);
+ return error;
+ }
+
+ error = device_property_read_u32_array(dev, "silead,efi-fw-min-max",
+ data->efi_fw_min_max,
+ ARRAY_SIZE(data->efi_fw_min_max));
+ if (!error)
+ data->efi_fw_min_max_set = true;
+
+ /* The EFI (platform) embedded fw does not have pen support */
+ if (data->pen_supported) {
+ dev_warn(dev, "Warning loading '%s' from filesystem failed, using EFI embedded copy.\n",
+ data->fw_name);
+ dev_warn(dev, "Warning pen support is known to be broken in the EFI embedded fw version\n");
+ data->pen_supported = false;
+ }
+ }
+
+ fw_size = fw->size / sizeof(*fw_data);
+ fw_data = (struct silead_fw_data *)fw->data;
+
+ for (i = 0; i < fw_size; i++) {
+ error = i2c_smbus_write_i2c_block_data(client,
+ fw_data[i].offset,
+ 4,
+ (u8 *)&fw_data[i].val);
+ if (error) {
+ dev_err(dev, "Firmware load error %d\n", error);
+ break;
+ }
+ }
+
+ release_firmware(fw);
+ return error ?: 0;
+}
+
+static u32 silead_ts_get_status(struct i2c_client *client)
+{
+ int error;
+ __le32 status;
+
+ error = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_STATUS,
+ sizeof(status), (u8 *)&status);
+ if (error < 0) {
+ dev_err(&client->dev, "Status read error %d\n", error);
+ return error;
+ }
+
+ return le32_to_cpu(status);
+}
+
+static int silead_ts_get_id(struct i2c_client *client)
+{
+ struct silead_ts_data *data = i2c_get_clientdata(client);
+ __le32 chip_id;
+ int error;
+
+ error = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_ID,
+ sizeof(chip_id), (u8 *)&chip_id);
+ if (error < 0)
+ return error;
+
+ data->chip_id = le32_to_cpu(chip_id);
+ dev_info(&client->dev, "Silead chip ID: 0x%8X", data->chip_id);
+
+ return 0;
+}
+
+static int silead_ts_setup(struct i2c_client *client)
+{
+ int error;
+ u32 status;
+
+ /*
+ * Some buggy BIOS-es bring up the chip in a stuck state where it
+ * blocks the I2C bus. The following steps are necessary to
+ * unstuck the chip / bus:
+ * 1. Turn off the Silead chip.
+ * 2. Try to do an I2C transfer with the chip, this will fail in
+ * response to which the I2C-bus-driver will call:
+ * i2c_recover_bus() which will unstuck the I2C-bus. Note the
+ * unstuck-ing of the I2C bus only works if we first drop the
+ * chip off the bus by turning it off.
+ * 3. Turn the chip back on.
+ *
+ * On the x86/ACPI systems were this problem is seen, step 1. and
+ * 3. require making ACPI calls and dealing with ACPI Power
+ * Resources. The workaround below runtime-suspends the chip to
+ * turn it off, leaving it up to the ACPI subsystem to deal with
+ * this.
+ */
+
+ if (device_property_read_bool(&client->dev,
+ "silead,stuck-controller-bug")) {
+ pm_runtime_set_active(&client->dev);
+ pm_runtime_enable(&client->dev);
+ pm_runtime_allow(&client->dev);
+
+ pm_runtime_suspend(&client->dev);
+
+ dev_warn(&client->dev, FW_BUG "Stuck I2C bus: please ignore the next 'controller timed out' error\n");
+ silead_ts_get_id(client);
+
+ /* The forbid will also resume the device */
+ pm_runtime_forbid(&client->dev);
+ pm_runtime_disable(&client->dev);
+ }
+
+ silead_ts_set_power(client, SILEAD_POWER_OFF);
+ silead_ts_set_power(client, SILEAD_POWER_ON);
+
+ error = silead_ts_get_id(client);
+ if (error) {
+ dev_err(&client->dev, "Chip ID read error %d\n", error);
+ return error;
+ }
+
+ error = silead_ts_init(client);
+ if (error)
+ return error;
+
+ error = silead_ts_reset(client);
+ if (error)
+ return error;
+
+ error = silead_ts_load_fw(client);
+ if (error)
+ return error;
+
+ error = silead_ts_startup(client);
+ if (error)
+ return error;
+
+ status = silead_ts_get_status(client);
+ if (status != SILEAD_STATUS_OK) {
+ dev_err(&client->dev,
+ "Initialization error, status: 0x%X\n", status);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static irqreturn_t silead_ts_threaded_irq_handler(int irq, void *id)
+{
+ struct silead_ts_data *data = id;
+ struct i2c_client *client = data->client;
+
+ silead_ts_read_data(client);
+
+ return IRQ_HANDLED;
+}
+
+static void silead_ts_read_props(struct i2c_client *client)
+{
+ struct silead_ts_data *data = i2c_get_clientdata(client);
+ struct device *dev = &client->dev;
+ const char *str;
+ int error;
+
+ error = device_property_read_u32(dev, "silead,max-fingers",
+ &data->max_fingers);
+ if (error) {
+ dev_dbg(dev, "Max fingers read error %d\n", error);
+ data->max_fingers = 5; /* Most devices handle up-to 5 fingers */
+ }
+
+ error = device_property_read_string(dev, "firmware-name", &str);
+ if (!error)
+ snprintf(data->fw_name, sizeof(data->fw_name),
+ "silead/%s", str);
+ else
+ dev_dbg(dev, "Firmware file name read error. Using default.");
+
+ data->pen_supported = device_property_read_bool(dev, "silead,pen-supported");
+ device_property_read_u32(dev, "silead,pen-resolution-x", &data->pen_x_res);
+ device_property_read_u32(dev, "silead,pen-resolution-y", &data->pen_y_res);
+}
+
+#ifdef CONFIG_ACPI
+static int silead_ts_set_default_fw_name(struct silead_ts_data *data,
+ const struct i2c_device_id *id)
+{
+ const struct acpi_device_id *acpi_id;
+ struct device *dev = &data->client->dev;
+ int i;
+
+ if (ACPI_HANDLE(dev)) {
+ acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!acpi_id)
+ return -ENODEV;
+
+ snprintf(data->fw_name, sizeof(data->fw_name),
+ "silead/%s.fw", acpi_id->id);
+
+ for (i = 0; i < strlen(data->fw_name); i++)
+ data->fw_name[i] = tolower(data->fw_name[i]);
+ } else {
+ snprintf(data->fw_name, sizeof(data->fw_name),
+ "silead/%s.fw", id->name);
+ }
+
+ return 0;
+}
+#else
+static int silead_ts_set_default_fw_name(struct silead_ts_data *data,
+ const struct i2c_device_id *id)
+{
+ snprintf(data->fw_name, sizeof(data->fw_name),
+ "silead/%s.fw", id->name);
+ return 0;
+}
+#endif
+
+static void silead_disable_regulator(void *arg)
+{
+ struct silead_ts_data *data = arg;
+
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators);
+}
+
+static int silead_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct silead_ts_data *data;
+ struct device *dev = &client->dev;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK |
+ I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+ dev_err(dev, "I2C functionality check failed\n");
+ return -ENXIO;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, data);
+ data->client = client;
+
+ error = silead_ts_set_default_fw_name(data, id);
+ if (error)
+ return error;
+
+ silead_ts_read_props(client);
+
+ /* We must have the IRQ provided by DT or ACPI subsystem */
+ if (client->irq <= 0)
+ return -ENODEV;
+
+ data->regulators[0].supply = "vddio";
+ data->regulators[1].supply = "avdd";
+ error = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (error)
+ return error;
+
+ /*
+ * Enable regulators at probe and disable them at remove, we need
+ * to keep the chip powered otherwise it forgets its firmware.
+ */
+ error = regulator_bulk_enable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (error)
+ return error;
+
+ error = devm_add_action_or_reset(dev, silead_disable_regulator, data);
+ if (error)
+ return error;
+
+ /* Power GPIO pin */
+ data->gpio_power = devm_gpiod_get_optional(dev, "power", GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpio_power)) {
+ if (PTR_ERR(data->gpio_power) != -EPROBE_DEFER)
+ dev_err(dev, "Shutdown GPIO request failed\n");
+ return PTR_ERR(data->gpio_power);
+ }
+
+ error = silead_ts_setup(client);
+ if (error)
+ return error;
+
+ error = silead_ts_request_input_dev(data);
+ if (error)
+ return error;
+
+ error = silead_ts_request_pen_input_dev(data);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, silead_ts_threaded_irq_handler,
+ IRQF_ONESHOT, client->name, data);
+ if (error) {
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "IRQ request failed %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused silead_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ disable_irq(client->irq);
+ silead_ts_set_power(client, SILEAD_POWER_OFF);
+ return 0;
+}
+
+static int __maybe_unused silead_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ bool second_try = false;
+ int error, status;
+
+ silead_ts_set_power(client, SILEAD_POWER_ON);
+
+ retry:
+ error = silead_ts_reset(client);
+ if (error)
+ return error;
+
+ if (second_try) {
+ error = silead_ts_load_fw(client);
+ if (error)
+ return error;
+ }
+
+ error = silead_ts_startup(client);
+ if (error)
+ return error;
+
+ status = silead_ts_get_status(client);
+ if (status != SILEAD_STATUS_OK) {
+ if (!second_try) {
+ second_try = true;
+ dev_dbg(dev, "Reloading firmware after unsuccessful resume\n");
+ goto retry;
+ }
+ dev_err(dev, "Resume error, status: 0x%02x\n", status);
+ return -ENODEV;
+ }
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(silead_ts_pm, silead_ts_suspend, silead_ts_resume);
+
+static const struct i2c_device_id silead_ts_id[] = {
+ { "gsl1680", 0 },
+ { "gsl1688", 0 },
+ { "gsl3670", 0 },
+ { "gsl3675", 0 },
+ { "gsl3692", 0 },
+ { "mssl1680", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, silead_ts_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id silead_ts_acpi_match[] = {
+ { "GSL1680", 0 },
+ { "GSL1688", 0 },
+ { "GSL3670", 0 },
+ { "GSL3675", 0 },
+ { "GSL3692", 0 },
+ { "MSSL1680", 0 },
+ { "MSSL0001", 0 },
+ { "MSSL0002", 0 },
+ { "MSSL0017", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, silead_ts_acpi_match);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id silead_ts_of_match[] = {
+ { .compatible = "silead,gsl1680" },
+ { .compatible = "silead,gsl1688" },
+ { .compatible = "silead,gsl3670" },
+ { .compatible = "silead,gsl3675" },
+ { .compatible = "silead,gsl3692" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, silead_ts_of_match);
+#endif
+
+static struct i2c_driver silead_ts_driver = {
+ .probe = silead_ts_probe,
+ .id_table = silead_ts_id,
+ .driver = {
+ .name = SILEAD_TS_NAME,
+ .acpi_match_table = ACPI_PTR(silead_ts_acpi_match),
+ .of_match_table = of_match_ptr(silead_ts_of_match),
+ .pm = &silead_ts_pm,
+ },
+};
+module_i2c_driver(silead_ts_driver);
+
+MODULE_AUTHOR("Robert Dolca <robert.dolca@intel.com>");
+MODULE_DESCRIPTION("Silead I2C touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c
new file mode 100644
index 000000000..6274555f1
--- /dev/null
+++ b/drivers/input/touchscreen/sis_i2c.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Touch Screen driver for SiS 9200 family I2C Touch panels
+ *
+ * Copyright (C) 2015 SiS, Inc.
+ * Copyright (C) 2016 Nextfour Group
+ */
+
+#include <linux/crc-itu-t.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#define SIS_I2C_NAME "sis_i2c_ts"
+
+/*
+ * The I2C packet format:
+ * le16 byte count
+ * u8 Report ID
+ * <contact data - variable length>
+ * u8 Number of contacts
+ * le16 Scan Time (optional)
+ * le16 CRC
+ *
+ * One touch point information consists of 6+ bytes, the order is:
+ * u8 contact state
+ * u8 finger id
+ * le16 x axis
+ * le16 y axis
+ * u8 contact width (optional)
+ * u8 contact height (optional)
+ * u8 pressure (optional)
+ *
+ * Maximum amount of data transmitted in one shot is 64 bytes, if controller
+ * needs to report more contacts than fit in one packet it will send true
+ * number of contacts in first packet and 0 as number of contacts in second
+ * packet.
+ */
+
+#define SIS_MAX_PACKET_SIZE 64
+
+#define SIS_PKT_LEN_OFFSET 0
+#define SIS_PKT_REPORT_OFFSET 2 /* Report ID/type */
+#define SIS_PKT_CONTACT_OFFSET 3 /* First contact */
+
+#define SIS_SCAN_TIME_LEN 2
+
+/* Supported report types */
+#define SIS_ALL_IN_ONE_PACKAGE 0x10
+#define SIS_PKT_IS_TOUCH(x) (((x) & 0x0f) == 0x01)
+#define SIS_PKT_IS_HIDI2C(x) (((x) & 0x0f) == 0x06)
+
+/* Contact properties within report */
+#define SIS_PKT_HAS_AREA(x) ((x) & BIT(4))
+#define SIS_PKT_HAS_PRESSURE(x) ((x) & BIT(5))
+#define SIS_PKT_HAS_SCANTIME(x) ((x) & BIT(6))
+
+/* Contact size */
+#define SIS_BASE_LEN_PER_CONTACT 6
+#define SIS_AREA_LEN_PER_CONTACT 2
+#define SIS_PRESSURE_LEN_PER_CONTACT 1
+
+/* Offsets within contact data */
+#define SIS_CONTACT_STATUS_OFFSET 0
+#define SIS_CONTACT_ID_OFFSET 1 /* Contact ID */
+#define SIS_CONTACT_X_OFFSET 2
+#define SIS_CONTACT_Y_OFFSET 4
+#define SIS_CONTACT_WIDTH_OFFSET 6
+#define SIS_CONTACT_HEIGHT_OFFSET 7
+#define SIS_CONTACT_PRESSURE_OFFSET(id) (SIS_PKT_HAS_AREA(id) ? 8 : 6)
+
+/* Individual contact state */
+#define SIS_STATUS_UP 0x0
+#define SIS_STATUS_DOWN 0x3
+
+/* Touchscreen parameters */
+#define SIS_MAX_FINGERS 10
+#define SIS_MAX_X 4095
+#define SIS_MAX_Y 4095
+#define SIS_MAX_PRESSURE 255
+
+/* Resolution diagonal */
+#define SIS_AREA_LENGTH_LONGER 5792
+/*((SIS_MAX_X^2) + (SIS_MAX_Y^2))^0.5*/
+#define SIS_AREA_LENGTH_SHORT 5792
+#define SIS_AREA_UNIT (5792 / 32)
+
+struct sis_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+
+ struct gpio_desc *attn_gpio;
+ struct gpio_desc *reset_gpio;
+
+ u8 packet[SIS_MAX_PACKET_SIZE];
+};
+
+static int sis_read_packet(struct i2c_client *client, u8 *buf,
+ unsigned int *num_contacts,
+ unsigned int *contact_size)
+{
+ int count_idx;
+ int ret;
+ u16 len;
+ u16 crc, pkg_crc;
+ u8 report_id;
+
+ ret = i2c_master_recv(client, buf, SIS_MAX_PACKET_SIZE);
+ if (ret <= 0)
+ return -EIO;
+
+ len = get_unaligned_le16(&buf[SIS_PKT_LEN_OFFSET]);
+ if (len > SIS_MAX_PACKET_SIZE) {
+ dev_err(&client->dev,
+ "%s: invalid packet length (%d vs %d)\n",
+ __func__, len, SIS_MAX_PACKET_SIZE);
+ return -E2BIG;
+ }
+
+ if (len < 10)
+ return -EINVAL;
+
+ report_id = buf[SIS_PKT_REPORT_OFFSET];
+ count_idx = len - 1;
+ *contact_size = SIS_BASE_LEN_PER_CONTACT;
+
+ if (report_id != SIS_ALL_IN_ONE_PACKAGE) {
+ if (SIS_PKT_IS_TOUCH(report_id)) {
+ /*
+ * Calculate CRC ignoring packet length
+ * in the beginning and CRC transmitted
+ * at the end of the packet.
+ */
+ crc = crc_itu_t(0, buf + 2, len - 2 - 2);
+ pkg_crc = get_unaligned_le16(&buf[len - 2]);
+
+ if (crc != pkg_crc) {
+ dev_err(&client->dev,
+ "%s: CRC Error (%d vs %d)\n",
+ __func__, crc, pkg_crc);
+ return -EINVAL;
+ }
+
+ count_idx -= 2;
+
+ } else if (!SIS_PKT_IS_HIDI2C(report_id)) {
+ dev_err(&client->dev,
+ "%s: invalid packet ID %#02x\n",
+ __func__, report_id);
+ return -EINVAL;
+ }
+
+ if (SIS_PKT_HAS_SCANTIME(report_id))
+ count_idx -= SIS_SCAN_TIME_LEN;
+
+ if (SIS_PKT_HAS_AREA(report_id))
+ *contact_size += SIS_AREA_LEN_PER_CONTACT;
+ if (SIS_PKT_HAS_PRESSURE(report_id))
+ *contact_size += SIS_PRESSURE_LEN_PER_CONTACT;
+ }
+
+ *num_contacts = buf[count_idx];
+ return 0;
+}
+
+static int sis_ts_report_contact(struct sis_ts_data *ts, const u8 *data, u8 id)
+{
+ struct input_dev *input = ts->input;
+ int slot;
+ u8 status = data[SIS_CONTACT_STATUS_OFFSET];
+ u8 pressure;
+ u8 height, width;
+ u16 x, y;
+
+ if (status != SIS_STATUS_DOWN && status != SIS_STATUS_UP) {
+ dev_err(&ts->client->dev, "Unexpected touch status: %#02x\n",
+ data[SIS_CONTACT_STATUS_OFFSET]);
+ return -EINVAL;
+ }
+
+ slot = input_mt_get_slot_by_key(input, data[SIS_CONTACT_ID_OFFSET]);
+ if (slot < 0)
+ return -ENOENT;
+
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER,
+ status == SIS_STATUS_DOWN);
+
+ if (status == SIS_STATUS_DOWN) {
+ pressure = height = width = 1;
+ if (id != SIS_ALL_IN_ONE_PACKAGE) {
+ if (SIS_PKT_HAS_AREA(id)) {
+ width = data[SIS_CONTACT_WIDTH_OFFSET];
+ height = data[SIS_CONTACT_HEIGHT_OFFSET];
+ }
+
+ if (SIS_PKT_HAS_PRESSURE(id))
+ pressure =
+ data[SIS_CONTACT_PRESSURE_OFFSET(id)];
+ }
+
+ x = get_unaligned_le16(&data[SIS_CONTACT_X_OFFSET]);
+ y = get_unaligned_le16(&data[SIS_CONTACT_Y_OFFSET]);
+
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ width * SIS_AREA_UNIT);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ height * SIS_AREA_UNIT);
+ input_report_abs(input, ABS_MT_PRESSURE, pressure);
+ input_report_abs(input, ABS_MT_POSITION_X, x);
+ input_report_abs(input, ABS_MT_POSITION_Y, y);
+ }
+
+ return 0;
+}
+
+static void sis_ts_handle_packet(struct sis_ts_data *ts)
+{
+ const u8 *contact;
+ unsigned int num_to_report = 0;
+ unsigned int num_contacts;
+ unsigned int num_reported;
+ unsigned int contact_size;
+ int error;
+ u8 report_id;
+
+ do {
+ error = sis_read_packet(ts->client, ts->packet,
+ &num_contacts, &contact_size);
+ if (error)
+ break;
+
+ if (num_to_report == 0) {
+ num_to_report = num_contacts;
+ } else if (num_contacts != 0) {
+ dev_err(&ts->client->dev,
+ "%s: nonzero (%d) point count in tail packet\n",
+ __func__, num_contacts);
+ break;
+ }
+
+ report_id = ts->packet[SIS_PKT_REPORT_OFFSET];
+ contact = &ts->packet[SIS_PKT_CONTACT_OFFSET];
+ num_reported = 0;
+
+ while (num_to_report > 0) {
+ error = sis_ts_report_contact(ts, contact, report_id);
+ if (error)
+ break;
+
+ contact += contact_size;
+ num_to_report--;
+ num_reported++;
+
+ if (report_id != SIS_ALL_IN_ONE_PACKAGE &&
+ num_reported >= 5) {
+ /*
+ * The remainder of contacts is sent
+ * in the 2nd packet.
+ */
+ break;
+ }
+ }
+ } while (num_to_report > 0);
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+}
+
+static irqreturn_t sis_ts_irq_handler(int irq, void *dev_id)
+{
+ struct sis_ts_data *ts = dev_id;
+
+ do {
+ sis_ts_handle_packet(ts);
+ } while (ts->attn_gpio && gpiod_get_value_cansleep(ts->attn_gpio));
+
+ return IRQ_HANDLED;
+}
+
+static void sis_ts_reset(struct sis_ts_data *ts)
+{
+ if (ts->reset_gpio) {
+ /* Get out of reset */
+ usleep_range(1000, 2000);
+ gpiod_set_value(ts->reset_gpio, 1);
+ usleep_range(1000, 2000);
+ gpiod_set_value(ts->reset_gpio, 0);
+ msleep(100);
+ }
+}
+
+static int sis_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sis_ts_data *ts;
+ struct input_dev *input;
+ int error;
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+
+ ts->attn_gpio = devm_gpiod_get_optional(&client->dev,
+ "attn", GPIOD_IN);
+ if (IS_ERR(ts->attn_gpio)) {
+ error = PTR_ERR(ts->attn_gpio);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get attention GPIO: %d\n", error);
+ return error;
+ }
+
+ ts->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to get reset GPIO: %d\n", error);
+ return error;
+ }
+
+ sis_ts_reset(ts);
+
+ ts->input = input = devm_input_allocate_device(&client->dev);
+ if (!input) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input->name = "SiS Touchscreen";
+ input->id.bustype = BUS_I2C;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, SIS_MAX_X, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, SIS_MAX_Y, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, SIS_MAX_PRESSURE, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR,
+ 0, SIS_AREA_LENGTH_LONGER, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR,
+ 0, SIS_AREA_LENGTH_SHORT, 0, 0);
+
+ error = input_mt_init_slots(input, SIS_MAX_FINGERS, INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to initialize MT slots: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, sis_ts_irq_handler,
+ IRQF_ONESHOT,
+ client->name, ts);
+ if (error) {
+ dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(ts->input);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sis_ts_dt_ids[] = {
+ { .compatible = "sis,9200-ts" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sis_ts_dt_ids);
+#endif
+
+static const struct i2c_device_id sis_ts_id[] = {
+ { SIS_I2C_NAME, 0 },
+ { "9200-ts", 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, sis_ts_id);
+
+static struct i2c_driver sis_ts_driver = {
+ .driver = {
+ .name = SIS_I2C_NAME,
+ .of_match_table = of_match_ptr(sis_ts_dt_ids),
+ },
+ .probe = sis_ts_probe,
+ .id_table = sis_ts_id,
+};
+module_i2c_driver(sis_ts_driver);
+
+MODULE_DESCRIPTION("SiS 9200 Family Touchscreen Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Mika Penttilä <mika.penttila@nextfour.com>");
diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c
new file mode 100644
index 000000000..e38ba3e4f
--- /dev/null
+++ b/drivers/input/touchscreen/st1232.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ST1232 Touchscreen Controller Driver
+ *
+ * Copyright (C) 2010 Renesas Solutions Corp.
+ * Tony SIM <chinyeow.sim.xt@renesas.com>
+ *
+ * Using code from:
+ * - android.git.kernel.org: projects/kernel/common.git: synaptics_i2c_rmi.c
+ * Copyright (C) 2007 Google, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_qos.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define ST1232_TS_NAME "st1232-ts"
+#define ST1633_TS_NAME "st1633-ts"
+
+#define REG_STATUS 0x01 /* Device Status | Error Code */
+
+#define STATUS_NORMAL 0x00
+#define STATUS_INIT 0x01
+#define STATUS_ERROR 0x02
+#define STATUS_AUTO_TUNING 0x03
+#define STATUS_IDLE 0x04
+#define STATUS_POWER_DOWN 0x05
+
+#define ERROR_NONE 0x00
+#define ERROR_INVALID_ADDRESS 0x10
+#define ERROR_INVALID_VALUE 0x20
+#define ERROR_INVALID_PLATFORM 0x30
+
+#define REG_XY_RESOLUTION 0x04
+#define REG_XY_COORDINATES 0x12
+#define ST_TS_MAX_FINGERS 10
+
+struct st_chip_info {
+ bool have_z;
+ u16 max_area;
+ u16 max_fingers;
+};
+
+struct st1232_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct touchscreen_properties prop;
+ struct dev_pm_qos_request low_latency_req;
+ struct gpio_desc *reset_gpio;
+ const struct st_chip_info *chip_info;
+ int read_buf_len;
+ u8 *read_buf;
+};
+
+static int st1232_ts_read_data(struct st1232_ts_data *ts, u8 reg,
+ unsigned int n)
+{
+ struct i2c_client *client = ts->client;
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .len = sizeof(reg),
+ .buf = &reg,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD | I2C_M_DMA_SAFE,
+ .len = n,
+ .buf = ts->read_buf,
+ }
+ };
+ int ret;
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg))
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int st1232_ts_wait_ready(struct st1232_ts_data *ts)
+{
+ unsigned int retries;
+ int error;
+
+ for (retries = 100; retries; retries--) {
+ error = st1232_ts_read_data(ts, REG_STATUS, 1);
+ if (!error) {
+ switch (ts->read_buf[0]) {
+ case STATUS_NORMAL | ERROR_NONE:
+ case STATUS_IDLE | ERROR_NONE:
+ return 0;
+ }
+ }
+
+ usleep_range(1000, 2000);
+ }
+
+ return -ENXIO;
+}
+
+static int st1232_ts_read_resolution(struct st1232_ts_data *ts, u16 *max_x,
+ u16 *max_y)
+{
+ u8 *buf;
+ int error;
+
+ /* select resolution register */
+ error = st1232_ts_read_data(ts, REG_XY_RESOLUTION, 3);
+ if (error)
+ return error;
+
+ buf = ts->read_buf;
+
+ *max_x = (((buf[0] & 0x0070) << 4) | buf[1]) - 1;
+ *max_y = (((buf[0] & 0x0007) << 8) | buf[2]) - 1;
+
+ return 0;
+}
+
+static int st1232_ts_parse_and_report(struct st1232_ts_data *ts)
+{
+ struct input_dev *input = ts->input_dev;
+ struct input_mt_pos pos[ST_TS_MAX_FINGERS];
+ u8 z[ST_TS_MAX_FINGERS];
+ int slots[ST_TS_MAX_FINGERS];
+ int n_contacts = 0;
+ int i;
+
+ for (i = 0; i < ts->chip_info->max_fingers; i++) {
+ u8 *buf = &ts->read_buf[i * 4];
+
+ if (buf[0] & BIT(7)) {
+ unsigned int x = ((buf[0] & 0x70) << 4) | buf[1];
+ unsigned int y = ((buf[0] & 0x07) << 8) | buf[2];
+
+ touchscreen_set_mt_pos(&pos[n_contacts],
+ &ts->prop, x, y);
+
+ /* st1232 includes a z-axis / touch strength */
+ if (ts->chip_info->have_z)
+ z[n_contacts] = ts->read_buf[i + 6];
+
+ n_contacts++;
+ }
+ }
+
+ input_mt_assign_slots(input, slots, pos, n_contacts, 0);
+ for (i = 0; i < n_contacts; i++) {
+ input_mt_slot(input, slots[i]);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ input_report_abs(input, ABS_MT_POSITION_X, pos[i].x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos[i].y);
+ if (ts->chip_info->have_z)
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, z[i]);
+ }
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+
+ return n_contacts;
+}
+
+static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
+{
+ struct st1232_ts_data *ts = dev_id;
+ int count;
+ int error;
+
+ error = st1232_ts_read_data(ts, REG_XY_COORDINATES, ts->read_buf_len);
+ if (error)
+ goto out;
+
+ count = st1232_ts_parse_and_report(ts);
+ if (!count) {
+ if (ts->low_latency_req.dev) {
+ dev_pm_qos_remove_request(&ts->low_latency_req);
+ ts->low_latency_req.dev = NULL;
+ }
+ } else if (!ts->low_latency_req.dev) {
+ /* First contact, request 100 us latency. */
+ dev_pm_qos_add_ancestor_request(&ts->client->dev,
+ &ts->low_latency_req,
+ DEV_PM_QOS_RESUME_LATENCY, 100);
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static void st1232_ts_power(struct st1232_ts_data *ts, bool poweron)
+{
+ if (ts->reset_gpio)
+ gpiod_set_value_cansleep(ts->reset_gpio, !poweron);
+}
+
+static void st1232_ts_power_off(void *data)
+{
+ st1232_ts_power(data, false);
+}
+
+static const struct st_chip_info st1232_chip_info = {
+ .have_z = true,
+ .max_area = 0xff,
+ .max_fingers = 2,
+};
+
+static const struct st_chip_info st1633_chip_info = {
+ .have_z = false,
+ .max_area = 0x00,
+ .max_fingers = 5,
+};
+
+static int st1232_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct st_chip_info *match;
+ struct st1232_ts_data *ts;
+ struct input_dev *input_dev;
+ u16 max_x, max_y;
+ int error;
+
+ match = device_get_match_data(&client->dev);
+ if (!match && id)
+ match = (const void *)id->driver_data;
+ if (!match) {
+ dev_err(&client->dev, "unknown device model\n");
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "need I2C_FUNC_I2C\n");
+ return -EIO;
+ }
+
+ if (!client->irq) {
+ dev_err(&client->dev, "no IRQ?\n");
+ return -EINVAL;
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->chip_info = match;
+
+ /* allocate a buffer according to the number of registers to read */
+ ts->read_buf_len = ts->chip_info->max_fingers * 4;
+ ts->read_buf = devm_kzalloc(&client->dev, ts->read_buf_len, GFP_KERNEL);
+ if (!ts->read_buf)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ ts->client = client;
+ ts->input_dev = input_dev;
+
+ ts->reset_gpio = devm_gpiod_get_optional(&client->dev, NULL,
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+ dev_err(&client->dev, "Unable to request GPIO pin: %d.\n",
+ error);
+ return error;
+ }
+
+ st1232_ts_power(ts, true);
+
+ error = devm_add_action_or_reset(&client->dev, st1232_ts_power_off, ts);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to install power off action: %d\n", error);
+ return error;
+ }
+
+ input_dev->name = "st1232-touchscreen";
+ input_dev->id.bustype = BUS_I2C;
+
+ /* Wait until device is ready */
+ error = st1232_ts_wait_ready(ts);
+ if (error)
+ return error;
+
+ /* Read resolution from the chip */
+ error = st1232_ts_read_resolution(ts, &max_x, &max_y);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read resolution: %d\n", error);
+ return error;
+ }
+
+ if (ts->chip_info->have_z)
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0,
+ ts->chip_info->max_area, 0, 0);
+
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+ 0, max_x, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+ 0, max_y, 0, 0);
+
+ touchscreen_parse_properties(input_dev, true, &ts->prop);
+
+ error = input_mt_init_slots(input_dev, ts->chip_info->max_fingers,
+ INPUT_MT_DIRECT | INPUT_MT_TRACK |
+ INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&client->dev, "failed to initialize MT slots\n");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, st1232_ts_irq_handler,
+ IRQF_ONESHOT,
+ client->name, ts);
+ if (error) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ return error;
+ }
+
+ error = input_register_device(ts->input_dev);
+ if (error) {
+ dev_err(&client->dev, "Unable to register %s input device\n",
+ input_dev->name);
+ return error;
+ }
+
+ i2c_set_clientdata(client, ts);
+
+ return 0;
+}
+
+static int __maybe_unused st1232_ts_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct st1232_ts_data *ts = i2c_get_clientdata(client);
+
+ disable_irq(client->irq);
+
+ if (!device_may_wakeup(&client->dev))
+ st1232_ts_power(ts, false);
+
+ return 0;
+}
+
+static int __maybe_unused st1232_ts_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct st1232_ts_data *ts = i2c_get_clientdata(client);
+
+ if (!device_may_wakeup(&client->dev))
+ st1232_ts_power(ts, true);
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(st1232_ts_pm_ops,
+ st1232_ts_suspend, st1232_ts_resume);
+
+static const struct i2c_device_id st1232_ts_id[] = {
+ { ST1232_TS_NAME, (unsigned long)&st1232_chip_info },
+ { ST1633_TS_NAME, (unsigned long)&st1633_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, st1232_ts_id);
+
+static const struct of_device_id st1232_ts_dt_ids[] = {
+ { .compatible = "sitronix,st1232", .data = &st1232_chip_info },
+ { .compatible = "sitronix,st1633", .data = &st1633_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, st1232_ts_dt_ids);
+
+static struct i2c_driver st1232_ts_driver = {
+ .probe = st1232_ts_probe,
+ .id_table = st1232_ts_id,
+ .driver = {
+ .name = ST1232_TS_NAME,
+ .of_match_table = st1232_ts_dt_ids,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .pm = &st1232_ts_pm_ops,
+ },
+};
+
+module_i2c_driver(st1232_ts_driver);
+
+MODULE_AUTHOR("Tony SIM <chinyeow.sim.xt@renesas.com>");
+MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@ginzinger.com>");
+MODULE_DESCRIPTION("SITRONIX ST1232 Touchscreen Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
new file mode 100644
index 000000000..d5bd17080
--- /dev/null
+++ b/drivers/input/touchscreen/stmfts.c
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: GPL-2.0
+// STMicroelectronics FTS Touchscreen device driver
+//
+// Copyright (c) 2017 Samsung Electronics Co., Ltd.
+// Copyright (c) 2017 Andi Shyti <andi@etezian.org>
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+/* I2C commands */
+#define STMFTS_READ_INFO 0x80
+#define STMFTS_READ_STATUS 0x84
+#define STMFTS_READ_ONE_EVENT 0x85
+#define STMFTS_READ_ALL_EVENT 0x86
+#define STMFTS_LATEST_EVENT 0x87
+#define STMFTS_SLEEP_IN 0x90
+#define STMFTS_SLEEP_OUT 0x91
+#define STMFTS_MS_MT_SENSE_OFF 0x92
+#define STMFTS_MS_MT_SENSE_ON 0x93
+#define STMFTS_SS_HOVER_SENSE_OFF 0x94
+#define STMFTS_SS_HOVER_SENSE_ON 0x95
+#define STMFTS_MS_KEY_SENSE_OFF 0x9a
+#define STMFTS_MS_KEY_SENSE_ON 0x9b
+#define STMFTS_SYSTEM_RESET 0xa0
+#define STMFTS_CLEAR_EVENT_STACK 0xa1
+#define STMFTS_FULL_FORCE_CALIBRATION 0xa2
+#define STMFTS_MS_CX_TUNING 0xa3
+#define STMFTS_SS_CX_TUNING 0xa4
+
+/* events */
+#define STMFTS_EV_NO_EVENT 0x00
+#define STMFTS_EV_MULTI_TOUCH_DETECTED 0x02
+#define STMFTS_EV_MULTI_TOUCH_ENTER 0x03
+#define STMFTS_EV_MULTI_TOUCH_LEAVE 0x04
+#define STMFTS_EV_MULTI_TOUCH_MOTION 0x05
+#define STMFTS_EV_HOVER_ENTER 0x07
+#define STMFTS_EV_HOVER_LEAVE 0x08
+#define STMFTS_EV_HOVER_MOTION 0x09
+#define STMFTS_EV_KEY_STATUS 0x0e
+#define STMFTS_EV_ERROR 0x0f
+#define STMFTS_EV_CONTROLLER_READY 0x10
+#define STMFTS_EV_SLEEP_OUT_CONTROLLER_READY 0x11
+#define STMFTS_EV_STATUS 0x16
+#define STMFTS_EV_DEBUG 0xdb
+
+/* multi touch related event masks */
+#define STMFTS_MASK_EVENT_ID 0x0f
+#define STMFTS_MASK_TOUCH_ID 0xf0
+#define STMFTS_MASK_LEFT_EVENT 0x0f
+#define STMFTS_MASK_X_MSB 0x0f
+#define STMFTS_MASK_Y_LSB 0xf0
+
+/* key related event masks */
+#define STMFTS_MASK_KEY_NO_TOUCH 0x00
+#define STMFTS_MASK_KEY_MENU 0x01
+#define STMFTS_MASK_KEY_BACK 0x02
+
+#define STMFTS_EVENT_SIZE 8
+#define STMFTS_STACK_DEPTH 32
+#define STMFTS_DATA_MAX_SIZE (STMFTS_EVENT_SIZE * STMFTS_STACK_DEPTH)
+#define STMFTS_MAX_FINGERS 10
+#define STMFTS_DEV_NAME "stmfts"
+
+enum stmfts_regulators {
+ STMFTS_REGULATOR_VDD,
+ STMFTS_REGULATOR_AVDD,
+};
+
+struct stmfts_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct led_classdev led_cdev;
+ struct mutex mutex;
+
+ struct touchscreen_properties prop;
+
+ struct regulator_bulk_data regulators[2];
+
+ /*
+ * Presence of ledvdd will be used also to check
+ * whether the LED is supported.
+ */
+ struct regulator *ledvdd;
+
+ u16 chip_id;
+ u8 chip_ver;
+ u16 fw_ver;
+ u8 config_id;
+ u8 config_ver;
+
+ u8 data[STMFTS_DATA_MAX_SIZE];
+
+ struct completion cmd_done;
+
+ bool use_key;
+ bool led_status;
+ bool hover_enabled;
+ bool running;
+};
+
+static int stmfts_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct stmfts_data *sdata = container_of(led_cdev,
+ struct stmfts_data, led_cdev);
+ int err;
+
+ if (value != sdata->led_status && sdata->ledvdd) {
+ if (!value) {
+ regulator_disable(sdata->ledvdd);
+ } else {
+ err = regulator_enable(sdata->ledvdd);
+ if (err) {
+ dev_warn(&sdata->client->dev,
+ "failed to disable ledvdd regulator: %d\n",
+ err);
+ return err;
+ }
+ }
+ sdata->led_status = value;
+ }
+
+ return 0;
+}
+
+static enum led_brightness stmfts_brightness_get(struct led_classdev *led_cdev)
+{
+ struct stmfts_data *sdata = container_of(led_cdev,
+ struct stmfts_data, led_cdev);
+
+ return !!regulator_is_enabled(sdata->ledvdd);
+}
+
+/*
+ * We can't simply use i2c_smbus_read_i2c_block_data because we
+ * need to read more than 255 bytes (
+ */
+static int stmfts_read_events(struct stmfts_data *sdata)
+{
+ u8 cmd = STMFTS_READ_ALL_EVENT;
+ struct i2c_msg msgs[2] = {
+ {
+ .addr = sdata->client->addr,
+ .len = 1,
+ .buf = &cmd,
+ },
+ {
+ .addr = sdata->client->addr,
+ .flags = I2C_M_RD,
+ .len = STMFTS_DATA_MAX_SIZE,
+ .buf = sdata->data,
+ },
+ };
+ int ret;
+
+ ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0)
+ return ret;
+
+ return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
+}
+
+static void stmfts_report_contact_event(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4;
+ u16 x = event[1] | ((event[2] & STMFTS_MASK_X_MSB) << 8);
+ u16 y = (event[2] >> 4) | (event[3] << 4);
+ u8 maj = event[4];
+ u8 min = event[5];
+ u8 orientation = event[6];
+ u8 area = event[7];
+
+ input_mt_slot(sdata->input, slot_id);
+
+ input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true);
+ input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
+ input_report_abs(sdata->input, ABS_MT_POSITION_Y, y);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, maj);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, min);
+ input_report_abs(sdata->input, ABS_MT_PRESSURE, area);
+ input_report_abs(sdata->input, ABS_MT_ORIENTATION, orientation);
+
+ input_sync(sdata->input);
+}
+
+static void stmfts_report_contact_release(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4;
+
+ input_mt_slot(sdata->input, slot_id);
+ input_mt_report_slot_inactive(sdata->input);
+
+ input_sync(sdata->input);
+}
+
+static void stmfts_report_hover_event(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ u16 x = (event[2] << 4) | (event[4] >> 4);
+ u16 y = (event[3] << 4) | (event[4] & STMFTS_MASK_Y_LSB);
+ u8 z = event[5];
+
+ input_report_abs(sdata->input, ABS_X, x);
+ input_report_abs(sdata->input, ABS_Y, y);
+ input_report_abs(sdata->input, ABS_DISTANCE, z);
+
+ input_sync(sdata->input);
+}
+
+static void stmfts_report_key_event(struct stmfts_data *sdata, const u8 event[])
+{
+ switch (event[2]) {
+ case 0:
+ input_report_key(sdata->input, KEY_BACK, 0);
+ input_report_key(sdata->input, KEY_MENU, 0);
+ break;
+
+ case STMFTS_MASK_KEY_BACK:
+ input_report_key(sdata->input, KEY_BACK, 1);
+ break;
+
+ case STMFTS_MASK_KEY_MENU:
+ input_report_key(sdata->input, KEY_MENU, 1);
+ break;
+
+ default:
+ dev_warn(&sdata->client->dev,
+ "unknown key event: %#02x\n", event[2]);
+ break;
+ }
+
+ input_sync(sdata->input);
+}
+
+static void stmfts_parse_events(struct stmfts_data *sdata)
+{
+ int i;
+
+ for (i = 0; i < STMFTS_STACK_DEPTH; i++) {
+ u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
+
+ switch (event[0]) {
+
+ case STMFTS_EV_CONTROLLER_READY:
+ case STMFTS_EV_SLEEP_OUT_CONTROLLER_READY:
+ case STMFTS_EV_STATUS:
+ complete(&sdata->cmd_done);
+ fallthrough;
+
+ case STMFTS_EV_NO_EVENT:
+ case STMFTS_EV_DEBUG:
+ return;
+ }
+
+ switch (event[0] & STMFTS_MASK_EVENT_ID) {
+
+ case STMFTS_EV_MULTI_TOUCH_ENTER:
+ case STMFTS_EV_MULTI_TOUCH_MOTION:
+ stmfts_report_contact_event(sdata, event);
+ break;
+
+ case STMFTS_EV_MULTI_TOUCH_LEAVE:
+ stmfts_report_contact_release(sdata, event);
+ break;
+
+ case STMFTS_EV_HOVER_ENTER:
+ case STMFTS_EV_HOVER_LEAVE:
+ case STMFTS_EV_HOVER_MOTION:
+ stmfts_report_hover_event(sdata, event);
+ break;
+
+ case STMFTS_EV_KEY_STATUS:
+ stmfts_report_key_event(sdata, event);
+ break;
+
+ case STMFTS_EV_ERROR:
+ dev_warn(&sdata->client->dev,
+ "error code: 0x%x%x%x%x%x%x",
+ event[6], event[5], event[4],
+ event[3], event[2], event[1]);
+ break;
+
+ default:
+ dev_err(&sdata->client->dev,
+ "unknown event %#02x\n", event[0]);
+ }
+ }
+}
+
+static irqreturn_t stmfts_irq_handler(int irq, void *dev)
+{
+ struct stmfts_data *sdata = dev;
+ int err;
+
+ mutex_lock(&sdata->mutex);
+
+ err = stmfts_read_events(sdata);
+ if (unlikely(err))
+ dev_err(&sdata->client->dev,
+ "failed to read events: %d\n", err);
+ else
+ stmfts_parse_events(sdata);
+
+ mutex_unlock(&sdata->mutex);
+ return IRQ_HANDLED;
+}
+
+static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
+{
+ int err;
+
+ reinit_completion(&sdata->cmd_done);
+
+ err = i2c_smbus_write_byte(sdata->client, cmd);
+ if (err)
+ return err;
+
+ if (!wait_for_completion_timeout(&sdata->cmd_done,
+ msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int stmfts_input_open(struct input_dev *dev)
+{
+ struct stmfts_data *sdata = input_get_drvdata(dev);
+ int err;
+
+ err = pm_runtime_resume_and_get(&sdata->client->dev);
+ if (err)
+ return err;
+
+ err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_ON);
+ if (err) {
+ pm_runtime_put_sync(&sdata->client->dev);
+ return err;
+ }
+
+ mutex_lock(&sdata->mutex);
+ sdata->running = true;
+
+ if (sdata->hover_enabled) {
+ err = i2c_smbus_write_byte(sdata->client,
+ STMFTS_SS_HOVER_SENSE_ON);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to enable hover\n");
+ }
+ mutex_unlock(&sdata->mutex);
+
+ if (sdata->use_key) {
+ err = i2c_smbus_write_byte(sdata->client,
+ STMFTS_MS_KEY_SENSE_ON);
+ if (err)
+ /* I can still use only the touch screen */
+ dev_warn(&sdata->client->dev,
+ "failed to enable touchkey\n");
+ }
+
+ return 0;
+}
+
+static void stmfts_input_close(struct input_dev *dev)
+{
+ struct stmfts_data *sdata = input_get_drvdata(dev);
+ int err;
+
+ err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_OFF);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to disable touchscreen: %d\n", err);
+
+ mutex_lock(&sdata->mutex);
+
+ sdata->running = false;
+
+ if (sdata->hover_enabled) {
+ err = i2c_smbus_write_byte(sdata->client,
+ STMFTS_SS_HOVER_SENSE_OFF);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to disable hover: %d\n", err);
+ }
+ mutex_unlock(&sdata->mutex);
+
+ if (sdata->use_key) {
+ err = i2c_smbus_write_byte(sdata->client,
+ STMFTS_MS_KEY_SENSE_OFF);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to disable touchkey: %d\n", err);
+ }
+
+ pm_runtime_put_sync(&sdata->client->dev);
+}
+
+static ssize_t stmfts_sysfs_chip_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%#x\n", sdata->chip_id);
+}
+
+static ssize_t stmfts_sysfs_chip_version(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sdata->chip_ver);
+}
+
+static ssize_t stmfts_sysfs_fw_ver(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sdata->fw_ver);
+}
+
+static ssize_t stmfts_sysfs_config_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%#x\n", sdata->config_id);
+}
+
+static ssize_t stmfts_sysfs_config_version(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sdata->config_ver);
+}
+
+static ssize_t stmfts_sysfs_read_status(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+ u8 status[4];
+ int err;
+
+ err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_STATUS,
+ sizeof(status), status);
+ if (err)
+ return err;
+
+ return sprintf(buf, "%#02x\n", status[0]);
+}
+
+static ssize_t stmfts_sysfs_hover_enable_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sdata->hover_enabled);
+}
+
+static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+ unsigned long value;
+ int err = 0;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ mutex_lock(&sdata->mutex);
+
+ if (value && sdata->hover_enabled)
+ goto out;
+
+ if (sdata->running)
+ err = i2c_smbus_write_byte(sdata->client,
+ value ? STMFTS_SS_HOVER_SENSE_ON :
+ STMFTS_SS_HOVER_SENSE_OFF);
+
+ if (!err)
+ sdata->hover_enabled = !!value;
+
+out:
+ mutex_unlock(&sdata->mutex);
+
+ return len;
+}
+
+static DEVICE_ATTR(chip_id, 0444, stmfts_sysfs_chip_id, NULL);
+static DEVICE_ATTR(chip_version, 0444, stmfts_sysfs_chip_version, NULL);
+static DEVICE_ATTR(fw_ver, 0444, stmfts_sysfs_fw_ver, NULL);
+static DEVICE_ATTR(config_id, 0444, stmfts_sysfs_config_id, NULL);
+static DEVICE_ATTR(config_version, 0444, stmfts_sysfs_config_version, NULL);
+static DEVICE_ATTR(status, 0444, stmfts_sysfs_read_status, NULL);
+static DEVICE_ATTR(hover_enable, 0644, stmfts_sysfs_hover_enable_read,
+ stmfts_sysfs_hover_enable_write);
+
+static struct attribute *stmfts_sysfs_attrs[] = {
+ &dev_attr_chip_id.attr,
+ &dev_attr_chip_version.attr,
+ &dev_attr_fw_ver.attr,
+ &dev_attr_config_id.attr,
+ &dev_attr_config_version.attr,
+ &dev_attr_status.attr,
+ &dev_attr_hover_enable.attr,
+ NULL
+};
+
+static struct attribute_group stmfts_attribute_group = {
+ .attrs = stmfts_sysfs_attrs
+};
+
+static int stmfts_power_on(struct stmfts_data *sdata)
+{
+ int err;
+ u8 reg[8];
+
+ err = regulator_bulk_enable(ARRAY_SIZE(sdata->regulators),
+ sdata->regulators);
+ if (err)
+ return err;
+
+ /*
+ * The datasheet does not specify the power on time, but considering
+ * that the reset time is < 10ms, I sleep 20ms to be sure
+ */
+ msleep(20);
+
+ err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_INFO,
+ sizeof(reg), reg);
+ if (err < 0)
+ return err;
+ if (err != sizeof(reg))
+ return -EIO;
+
+ sdata->chip_id = be16_to_cpup((__be16 *)&reg[6]);
+ sdata->chip_ver = reg[0];
+ sdata->fw_ver = be16_to_cpup((__be16 *)&reg[2]);
+ sdata->config_id = reg[4];
+ sdata->config_ver = reg[5];
+
+ enable_irq(sdata->client->irq);
+
+ msleep(50);
+
+ err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
+ if (err)
+ return err;
+
+ err = stmfts_command(sdata, STMFTS_SLEEP_OUT);
+ if (err)
+ return err;
+
+ /* optional tuning */
+ err = stmfts_command(sdata, STMFTS_MS_CX_TUNING);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to perform mutual auto tune: %d\n", err);
+
+ /* optional tuning */
+ err = stmfts_command(sdata, STMFTS_SS_CX_TUNING);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to perform self auto tune: %d\n", err);
+
+ err = stmfts_command(sdata, STMFTS_FULL_FORCE_CALIBRATION);
+ if (err)
+ return err;
+
+ /*
+ * At this point no one is using the touchscreen
+ * and I don't really care about the return value
+ */
+ (void) i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
+
+ return 0;
+}
+
+static void stmfts_power_off(void *data)
+{
+ struct stmfts_data *sdata = data;
+
+ disable_irq(sdata->client->irq);
+ regulator_bulk_disable(ARRAY_SIZE(sdata->regulators),
+ sdata->regulators);
+}
+
+/* This function is void because I don't want to prevent using the touch key
+ * only because the LEDs don't get registered
+ */
+static int stmfts_enable_led(struct stmfts_data *sdata)
+{
+ int err;
+
+ /* get the regulator for powering the leds on */
+ sdata->ledvdd = devm_regulator_get(&sdata->client->dev, "ledvdd");
+ if (IS_ERR(sdata->ledvdd))
+ return PTR_ERR(sdata->ledvdd);
+
+ sdata->led_cdev.name = STMFTS_DEV_NAME;
+ sdata->led_cdev.max_brightness = LED_ON;
+ sdata->led_cdev.brightness = LED_OFF;
+ sdata->led_cdev.brightness_set_blocking = stmfts_brightness_set;
+ sdata->led_cdev.brightness_get = stmfts_brightness_get;
+
+ err = devm_led_classdev_register(&sdata->client->dev, &sdata->led_cdev);
+ if (err) {
+ devm_regulator_put(sdata->ledvdd);
+ return err;
+ }
+
+ return 0;
+}
+
+static int stmfts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err;
+ struct stmfts_data *sdata;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK))
+ return -ENODEV;
+
+ sdata = devm_kzalloc(&client->dev, sizeof(*sdata), GFP_KERNEL);
+ if (!sdata)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, sdata);
+
+ sdata->client = client;
+ mutex_init(&sdata->mutex);
+ init_completion(&sdata->cmd_done);
+
+ sdata->regulators[STMFTS_REGULATOR_VDD].supply = "vdd";
+ sdata->regulators[STMFTS_REGULATOR_AVDD].supply = "avdd";
+ err = devm_regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(sdata->regulators),
+ sdata->regulators);
+ if (err)
+ return err;
+
+ sdata->input = devm_input_allocate_device(&client->dev);
+ if (!sdata->input)
+ return -ENOMEM;
+
+ sdata->input->name = STMFTS_DEV_NAME;
+ sdata->input->id.bustype = BUS_I2C;
+ sdata->input->open = stmfts_input_open;
+ sdata->input->close = stmfts_input_close;
+
+ input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_Y);
+ touchscreen_parse_properties(sdata->input, true, &sdata->prop);
+
+ input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
+
+ sdata->use_key = device_property_read_bool(&client->dev,
+ "touch-key-connected");
+ if (sdata->use_key) {
+ input_set_capability(sdata->input, EV_KEY, KEY_MENU);
+ input_set_capability(sdata->input, EV_KEY, KEY_BACK);
+ }
+
+ err = input_mt_init_slots(sdata->input,
+ STMFTS_MAX_FINGERS, INPUT_MT_DIRECT);
+ if (err)
+ return err;
+
+ input_set_drvdata(sdata->input, sdata);
+
+ /*
+ * stmfts_power_on expects interrupt to be disabled, but
+ * at this point the device is still off and I do not trust
+ * the status of the irq line that can generate some spurious
+ * interrupts. To be on the safe side it's better to not enable
+ * the interrupts during their request.
+ */
+ err = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, stmfts_irq_handler,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "stmfts_irq", sdata);
+ if (err)
+ return err;
+
+ dev_dbg(&client->dev, "initializing ST-Microelectronics FTS...\n");
+
+ err = stmfts_power_on(sdata);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(&client->dev, stmfts_power_off, sdata);
+ if (err)
+ return err;
+
+ err = input_register_device(sdata->input);
+ if (err)
+ return err;
+
+ if (sdata->use_key) {
+ err = stmfts_enable_led(sdata);
+ if (err) {
+ /*
+ * Even if the LEDs have failed to be initialized and
+ * used in the driver, I can still use the device even
+ * without LEDs. The ledvdd regulator pointer will be
+ * used as a flag.
+ */
+ dev_warn(&client->dev, "unable to use touchkey leds\n");
+ sdata->ledvdd = NULL;
+ }
+ }
+
+ err = devm_device_add_group(&client->dev, &stmfts_attribute_group);
+ if (err)
+ return err;
+
+ pm_runtime_enable(&client->dev);
+ device_enable_async_suspend(&client->dev);
+
+ return 0;
+}
+
+static void stmfts_remove(struct i2c_client *client)
+{
+ pm_runtime_disable(&client->dev);
+}
+
+static int __maybe_unused stmfts_runtime_suspend(struct device *dev)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
+ if (ret)
+ dev_warn(dev, "failed to suspend device: %d\n", ret);
+
+ return ret;
+}
+
+static int __maybe_unused stmfts_runtime_resume(struct device *dev)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_OUT);
+ if (ret)
+ dev_err(dev, "failed to resume device: %d\n", ret);
+
+ return ret;
+}
+
+static int __maybe_unused stmfts_suspend(struct device *dev)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ stmfts_power_off(sdata);
+
+ return 0;
+}
+
+static int __maybe_unused stmfts_resume(struct device *dev)
+{
+ struct stmfts_data *sdata = dev_get_drvdata(dev);
+
+ return stmfts_power_on(sdata);
+}
+
+static const struct dev_pm_ops stmfts_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(stmfts_suspend, stmfts_resume)
+ SET_RUNTIME_PM_OPS(stmfts_runtime_suspend, stmfts_runtime_resume, NULL)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id stmfts_of_match[] = {
+ { .compatible = "st,stmfts", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, stmfts_of_match);
+#endif
+
+static const struct i2c_device_id stmfts_id[] = {
+ { "stmfts", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, stmfts_id);
+
+static struct i2c_driver stmfts_driver = {
+ .driver = {
+ .name = STMFTS_DEV_NAME,
+ .of_match_table = of_match_ptr(stmfts_of_match),
+ .pm = &stmfts_pm_ops,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = stmfts_probe,
+ .remove = stmfts_remove,
+ .id_table = stmfts_id,
+};
+
+module_i2c_driver(stmfts_driver);
+
+MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>");
+MODULE_DESCRIPTION("STMicroelectronics FTS Touch Screen");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 000000000..25c45c3a3
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA 0x0B
+#define STMPE_REG_TSC_CTRL 0x40
+#define STMPE_REG_TSC_CFG 0x41
+#define STMPE_REG_FIFO_TH 0x4A
+#define STMPE_REG_FIFO_STA 0x4B
+#define STMPE_REG_FIFO_SIZE 0x4C
+#define STMPE_REG_TSC_DATA_XYZ 0x52
+#define STMPE_REG_TSC_FRACTION_Z 0x56
+#define STMPE_REG_TSC_I_DRIVE 0x58
+
+#define OP_MOD_XYZ 0
+
+#define STMPE_TSC_CTRL_TSC_EN (1<<0)
+
+#define STMPE_FIFO_STA_RESET (1<<0)
+
+#define STMPE_IRQ_TOUCH_DET 0
+
+#define STMPE_TS_NAME "stmpe-ts"
+#define XY_MASK 0xfff
+
+/**
+ * struct stmpe_touch - stmpe811 touch screen controller state
+ * @stmpe: pointer back to STMPE MFD container
+ * @idev: registered input device
+ * @work: a work item used to scan the device
+ * @dev: a pointer back to the MFD cell struct device*
+ * @prop: Touchscreen properties
+ * @ave_ctrl: Sample average control
+ * (0 -> 1 sample, 1 -> 2 samples, 2 -> 4 samples, 3 -> 8 samples)
+ * @touch_det_delay: Touch detect interrupt delay
+ * (0 -> 10 us, 1 -> 50 us, 2 -> 100 us, 3 -> 500 us,
+ * 4-> 1 ms, 5 -> 5 ms, 6 -> 10 ms, 7 -> 50 ms)
+ * recommended is 3
+ * @settling: Panel driver settling time
+ * (0 -> 10 us, 1 -> 100 us, 2 -> 500 us, 3 -> 1 ms,
+ * 4 -> 5 ms, 5 -> 10 ms, 6 for 50 ms, 7 -> 100 ms)
+ * recommended is 2
+ * @fraction_z: Length of the fractional part in z
+ * (fraction_z ([0..7]) = Count of the fractional part)
+ * recommended is 7
+ * @i_drive: current limit value of the touchscreen drivers
+ * (0 -> 20 mA typical 35 mA max, 1 -> 50 mA typical 80 mA max)
+ */
+struct stmpe_touch {
+ struct stmpe *stmpe;
+ struct input_dev *idev;
+ struct delayed_work work;
+ struct device *dev;
+ struct touchscreen_properties prop;
+ u8 ave_ctrl;
+ u8 touch_det_delay;
+ u8 settling;
+ u8 fraction_z;
+ u8 i_drive;
+};
+
+static int __stmpe_reset_fifo(struct stmpe *stmpe)
+{
+ int ret;
+
+ ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+ STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+ if (ret)
+ return ret;
+
+ return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+ STMPE_FIFO_STA_RESET, 0);
+}
+
+static void stmpe_work(struct work_struct *work)
+{
+ int int_sta;
+ u32 timeout = 40;
+
+ struct stmpe_touch *ts =
+ container_of(work, struct stmpe_touch, work.work);
+
+ int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+ /*
+ * touch_det sometimes get desasserted or just get stuck. This appears
+ * to be a silicon bug, We still have to clearify this with the
+ * manufacture. As a workaround We release the key anyway if the
+ * touch_det keeps coming in after 4ms, while the FIFO contains no value
+ * during the whole time.
+ */
+ while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+ timeout--;
+ int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+ udelay(100);
+ }
+
+ /* reset the FIFO before we report release event */
+ __stmpe_reset_fifo(ts->stmpe);
+
+ input_report_abs(ts->idev, ABS_PRESSURE, 0);
+ input_report_key(ts->idev, BTN_TOUCH, 0);
+ input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+ u8 data_set[4];
+ int x, y, z;
+ struct stmpe_touch *ts = data;
+
+ /*
+ * Cancel scheduled polling for release if we have new value
+ * available. Wait if the polling is already running.
+ */
+ cancel_delayed_work_sync(&ts->work);
+
+ /*
+ * The FIFO sometimes just crashes and stops generating interrupts. This
+ * appears to be a silicon bug. We still have to clearify this with
+ * the manufacture. As a workaround we disable the TSC while we are
+ * collecting data and flush the FIFO after reading
+ */
+ stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+ STMPE_TSC_CTRL_TSC_EN, 0);
+
+ stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+ x = (data_set[0] << 4) | (data_set[1] >> 4);
+ y = ((data_set[1] & 0xf) << 8) | data_set[2];
+ z = data_set[3];
+
+ touchscreen_report_pos(ts->idev, &ts->prop, x, y, false);
+ input_report_abs(ts->idev, ABS_PRESSURE, z);
+ input_report_key(ts->idev, BTN_TOUCH, 1);
+ input_sync(ts->idev);
+
+ /* flush the FIFO after we have read out our values. */
+ __stmpe_reset_fifo(ts->stmpe);
+
+ /* reenable the tsc */
+ stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+ STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+ /* start polling for touch_det to detect release */
+ schedule_delayed_work(&ts->work, msecs_to_jiffies(50));
+
+ return IRQ_HANDLED;
+}
+
+static int stmpe_init_hw(struct stmpe_touch *ts)
+{
+ int ret;
+ u8 tsc_cfg, tsc_cfg_mask;
+ struct stmpe *stmpe = ts->stmpe;
+ struct device *dev = ts->dev;
+
+ ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+ if (ret) {
+ dev_err(dev, "Could not enable clock for ADC and TS\n");
+ return ret;
+ }
+
+ ret = stmpe811_adc_common_init(stmpe);
+ if (ret) {
+ stmpe_disable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+ return ret;
+ }
+
+ tsc_cfg = STMPE_AVE_CTRL(ts->ave_ctrl) |
+ STMPE_DET_DELAY(ts->touch_det_delay) |
+ STMPE_SETTLING(ts->settling);
+ tsc_cfg_mask = STMPE_AVE_CTRL(0xff) | STMPE_DET_DELAY(0xff) |
+ STMPE_SETTLING(0xff);
+
+ ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
+ if (ret) {
+ dev_err(dev, "Could not config touch\n");
+ return ret;
+ }
+
+ ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+ STMPE_FRACTION_Z(0xff), STMPE_FRACTION_Z(ts->fraction_z));
+ if (ret) {
+ dev_err(dev, "Could not config touch\n");
+ return ret;
+ }
+
+ ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+ STMPE_I_DRIVE(0xff), STMPE_I_DRIVE(ts->i_drive));
+ if (ret) {
+ dev_err(dev, "Could not config touch\n");
+ return ret;
+ }
+
+ /* set FIFO to 1 for single point reading */
+ ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+ if (ret) {
+ dev_err(dev, "Could not set FIFO\n");
+ return ret;
+ }
+
+ ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+ STMPE_OP_MODE(0xff), STMPE_OP_MODE(OP_MOD_XYZ));
+ if (ret) {
+ dev_err(dev, "Could not set mode\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+ struct stmpe_touch *ts = input_get_drvdata(dev);
+ int ret = 0;
+
+ ret = __stmpe_reset_fifo(ts->stmpe);
+ if (ret)
+ return ret;
+
+ return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+ STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+ struct stmpe_touch *ts = input_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&ts->work);
+
+ stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+ STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static void stmpe_ts_get_platform_info(struct platform_device *pdev,
+ struct stmpe_touch *ts)
+{
+ struct device_node *np = pdev->dev.of_node;
+ u32 val;
+
+ if (np) {
+ if (!of_property_read_u32(np, "st,sample-time", &val))
+ ts->stmpe->sample_time = val;
+ if (!of_property_read_u32(np, "st,mod-12b", &val))
+ ts->stmpe->mod_12b = val;
+ if (!of_property_read_u32(np, "st,ref-sel", &val))
+ ts->stmpe->ref_sel = val;
+ if (!of_property_read_u32(np, "st,adc-freq", &val))
+ ts->stmpe->adc_freq = val;
+ if (!of_property_read_u32(np, "st,ave-ctrl", &val))
+ ts->ave_ctrl = val;
+ if (!of_property_read_u32(np, "st,touch-det-delay", &val))
+ ts->touch_det_delay = val;
+ if (!of_property_read_u32(np, "st,settling", &val))
+ ts->settling = val;
+ if (!of_property_read_u32(np, "st,fraction-z", &val))
+ ts->fraction_z = val;
+ if (!of_property_read_u32(np, "st,i-drive", &val))
+ ts->i_drive = val;
+ }
+}
+
+static int stmpe_input_probe(struct platform_device *pdev)
+{
+ struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+ struct stmpe_touch *ts;
+ struct input_dev *idev;
+ int error;
+ int ts_irq;
+
+ ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+ if (ts_irq < 0)
+ return ts_irq;
+
+ ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ idev = devm_input_allocate_device(&pdev->dev);
+ if (!idev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ts);
+ ts->stmpe = stmpe;
+ ts->idev = idev;
+ ts->dev = &pdev->dev;
+
+ stmpe_ts_get_platform_info(pdev, ts);
+
+ INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+ error = devm_request_threaded_irq(&pdev->dev, ts_irq,
+ NULL, stmpe_ts_handler,
+ IRQF_ONESHOT, STMPE_TS_NAME, ts);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+ return error;
+ }
+
+ error = stmpe_init_hw(ts);
+ if (error)
+ return error;
+
+ idev->name = STMPE_TS_NAME;
+ idev->phys = STMPE_TS_NAME"/input0";
+ idev->id.bustype = BUS_I2C;
+
+ idev->open = stmpe_ts_open;
+ idev->close = stmpe_ts_close;
+
+ input_set_drvdata(idev, ts);
+
+ input_set_capability(idev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+ touchscreen_parse_properties(idev, false, &ts->prop);
+
+ error = input_register_device(idev);
+ if (error) {
+ dev_err(&pdev->dev, "Could not register input device\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int stmpe_ts_remove(struct platform_device *pdev)
+{
+ struct stmpe_touch *ts = platform_get_drvdata(pdev);
+
+ stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+ return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+ .driver = {
+ .name = STMPE_TS_NAME,
+ },
+ .probe = stmpe_input_probe,
+ .remove = stmpe_ts_remove,
+};
+module_platform_driver(stmpe_ts_driver);
+
+static const struct of_device_id stmpe_ts_ids[] = {
+ { .compatible = "st,stmpe-ts", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, stmpe_ts_ids);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
new file mode 100644
index 000000000..73eb8f80b
--- /dev/null
+++ b/drivers/input/touchscreen/sun4i-ts.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Allwinner sunxi resistive touchscreen controller driver
+ *
+ * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede@redhat.com>
+ *
+ * The hwmon parts are based on work by Corentin LABBE which is:
+ * Copyright (C) 2013 Corentin LABBE <clabbe.montjoie@gmail.com>
+ */
+
+/*
+ * The sun4i-ts controller is capable of detecting a second touch, but when a
+ * second touch is present then the accuracy becomes so bad the reported touch
+ * location is not useable.
+ *
+ * The original android driver contains some complicated heuristics using the
+ * aprox. distance between the 2 touches to see if the user is making a pinch
+ * open / close movement, and then reports emulated multi-touch events around
+ * the last touch coordinate (as the dual-touch coordinates are worthless).
+ *
+ * These kinds of heuristics are just asking for trouble (and don't belong
+ * in the kernel). So this driver offers straight forward, reliable single
+ * touch functionality only.
+ *
+ * s.a. A20 User Manual "1.15 TP" (Documentation/arm/sunxi.rst)
+ * (looks like the description in the A20 User Manual v1.3 is better
+ * than the one in the A10 User Manual v.1.5)
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/thermal.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define TP_CTRL0 0x00
+#define TP_CTRL1 0x04
+#define TP_CTRL2 0x08
+#define TP_CTRL3 0x0c
+#define TP_INT_FIFOC 0x10
+#define TP_INT_FIFOS 0x14
+#define TP_TPR 0x18
+#define TP_CDAT 0x1c
+#define TEMP_DATA 0x20
+#define TP_DATA 0x24
+
+/* TP_CTRL0 bits */
+#define ADC_FIRST_DLY(x) ((x) << 24) /* 8 bits */
+#define ADC_FIRST_DLY_MODE(x) ((x) << 23)
+#define ADC_CLK_SEL(x) ((x) << 22)
+#define ADC_CLK_DIV(x) ((x) << 20) /* 3 bits */
+#define FS_DIV(x) ((x) << 16) /* 4 bits */
+#define T_ACQ(x) ((x) << 0) /* 16 bits */
+
+/* TP_CTRL1 bits */
+#define STYLUS_UP_DEBOUN(x) ((x) << 12) /* 8 bits */
+#define STYLUS_UP_DEBOUN_EN(x) ((x) << 9)
+#define TOUCH_PAN_CALI_EN(x) ((x) << 6)
+#define TP_DUAL_EN(x) ((x) << 5)
+#define TP_MODE_EN(x) ((x) << 4)
+#define TP_ADC_SELECT(x) ((x) << 3)
+#define ADC_CHAN_SELECT(x) ((x) << 0) /* 3 bits */
+
+/* on sun6i, bits 3~6 are left shifted by 1 to 4~7 */
+#define SUN6I_TP_MODE_EN(x) ((x) << 5)
+
+/* TP_CTRL2 bits */
+#define TP_SENSITIVE_ADJUST(x) ((x) << 28) /* 4 bits */
+#define TP_MODE_SELECT(x) ((x) << 26) /* 2 bits */
+#define PRE_MEA_EN(x) ((x) << 24)
+#define PRE_MEA_THRE_CNT(x) ((x) << 0) /* 24 bits */
+
+/* TP_CTRL3 bits */
+#define FILTER_EN(x) ((x) << 2)
+#define FILTER_TYPE(x) ((x) << 0) /* 2 bits */
+
+/* TP_INT_FIFOC irq and fifo mask / control bits */
+#define TEMP_IRQ_EN(x) ((x) << 18)
+#define OVERRUN_IRQ_EN(x) ((x) << 17)
+#define DATA_IRQ_EN(x) ((x) << 16)
+#define TP_DATA_XY_CHANGE(x) ((x) << 13)
+#define FIFO_TRIG(x) ((x) << 8) /* 5 bits */
+#define DATA_DRQ_EN(x) ((x) << 7)
+#define FIFO_FLUSH(x) ((x) << 4)
+#define TP_UP_IRQ_EN(x) ((x) << 1)
+#define TP_DOWN_IRQ_EN(x) ((x) << 0)
+
+/* TP_INT_FIFOS irq and fifo status bits */
+#define TEMP_DATA_PENDING BIT(18)
+#define FIFO_OVERRUN_PENDING BIT(17)
+#define FIFO_DATA_PENDING BIT(16)
+#define TP_IDLE_FLG BIT(2)
+#define TP_UP_PENDING BIT(1)
+#define TP_DOWN_PENDING BIT(0)
+
+/* TP_TPR bits */
+#define TEMP_ENABLE(x) ((x) << 16)
+#define TEMP_PERIOD(x) ((x) << 0) /* t = x * 256 * 16 / clkin */
+
+struct sun4i_ts_data {
+ struct device *dev;
+ struct input_dev *input;
+ void __iomem *base;
+ unsigned int irq;
+ bool ignore_fifo_data;
+ int temp_data;
+ int temp_offset;
+ int temp_step;
+};
+
+static void sun4i_ts_irq_handle_input(struct sun4i_ts_data *ts, u32 reg_val)
+{
+ u32 x, y;
+
+ if (reg_val & FIFO_DATA_PENDING) {
+ x = readl(ts->base + TP_DATA);
+ y = readl(ts->base + TP_DATA);
+ /* The 1st location reported after an up event is unreliable */
+ if (!ts->ignore_fifo_data) {
+ input_report_abs(ts->input, ABS_X, x);
+ input_report_abs(ts->input, ABS_Y, y);
+ /*
+ * The hardware has a separate down status bit, but
+ * that gets set before we get the first location,
+ * resulting in reporting a click on the old location.
+ */
+ input_report_key(ts->input, BTN_TOUCH, 1);
+ input_sync(ts->input);
+ } else {
+ ts->ignore_fifo_data = false;
+ }
+ }
+
+ if (reg_val & TP_UP_PENDING) {
+ ts->ignore_fifo_data = true;
+ input_report_key(ts->input, BTN_TOUCH, 0);
+ input_sync(ts->input);
+ }
+}
+
+static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
+{
+ struct sun4i_ts_data *ts = dev_id;
+ u32 reg_val;
+
+ reg_val = readl(ts->base + TP_INT_FIFOS);
+
+ if (reg_val & TEMP_DATA_PENDING)
+ ts->temp_data = readl(ts->base + TEMP_DATA);
+
+ if (ts->input)
+ sun4i_ts_irq_handle_input(ts, reg_val);
+
+ writel(reg_val, ts->base + TP_INT_FIFOS);
+
+ return IRQ_HANDLED;
+}
+
+static int sun4i_ts_open(struct input_dev *dev)
+{
+ struct sun4i_ts_data *ts = input_get_drvdata(dev);
+
+ /* Flush, set trig level to 1, enable temp, data and up irqs */
+ writel(TEMP_IRQ_EN(1) | DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) |
+ TP_UP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
+
+ return 0;
+}
+
+static void sun4i_ts_close(struct input_dev *dev)
+{
+ struct sun4i_ts_data *ts = input_get_drvdata(dev);
+
+ /* Deactivate all input IRQs */
+ writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
+}
+
+static int sun4i_get_temp(const struct sun4i_ts_data *ts, int *temp)
+{
+ /* No temp_data until the first irq */
+ if (ts->temp_data == -1)
+ return -EAGAIN;
+
+ *temp = ts->temp_data * ts->temp_step - ts->temp_offset;
+
+ return 0;
+}
+
+static int sun4i_get_tz_temp(struct thermal_zone_device *tz, int *temp)
+{
+ return sun4i_get_temp(tz->devdata, temp);
+}
+
+static const struct thermal_zone_device_ops sun4i_ts_tz_ops = {
+ .get_temp = sun4i_get_tz_temp,
+};
+
+static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct sun4i_ts_data *ts = dev_get_drvdata(dev);
+ int temp;
+ int error;
+
+ error = sun4i_get_temp(ts, &temp);
+ if (error)
+ return error;
+
+ return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t show_temp_label(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ return sprintf(buf, "SoC temperature\n");
+}
+
+static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL);
+static DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL);
+
+static struct attribute *sun4i_ts_attrs[] = {
+ &dev_attr_temp1_input.attr,
+ &dev_attr_temp1_label.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(sun4i_ts);
+
+static int sun4i_ts_probe(struct platform_device *pdev)
+{
+ struct sun4i_ts_data *ts;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device *hwmon;
+ struct thermal_zone_device *thermal;
+ int error;
+ u32 reg;
+ bool ts_attached;
+ u32 tp_sensitive_adjust = 15;
+ u32 filter_type = 1;
+
+ ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->dev = dev;
+ ts->ignore_fifo_data = true;
+ ts->temp_data = -1;
+ if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts")) {
+ /* Allwinner SDK has temperature (C) = (value / 6) - 271 */
+ ts->temp_offset = 271000;
+ ts->temp_step = 167;
+ } else if (of_device_is_compatible(np, "allwinner,sun4i-a10-ts")) {
+ /*
+ * The A10 temperature sensor has quite a wide spread, these
+ * parameters are based on the averaging of the calibration
+ * results of 4 completely different boards, with a spread of
+ * temp_step from 0.096 - 0.170 and temp_offset from 176 - 331.
+ */
+ ts->temp_offset = 257000;
+ ts->temp_step = 133;
+ } else {
+ /*
+ * The user manuals do not contain the formula for calculating
+ * the temperature. The formula used here is from the AXP209,
+ * which is designed by X-Powers, an affiliate of Allwinner:
+ *
+ * temperature (C) = (value * 0.1) - 144.7
+ *
+ * Allwinner does not have any documentation whatsoever for
+ * this hardware. Moreover, it is claimed that the sensor
+ * is inaccurate and cannot work properly.
+ */
+ ts->temp_offset = 144700;
+ ts->temp_step = 100;
+ }
+
+ ts_attached = of_property_read_bool(np, "allwinner,ts-attached");
+ if (ts_attached) {
+ ts->input = devm_input_allocate_device(dev);
+ if (!ts->input)
+ return -ENOMEM;
+
+ ts->input->name = pdev->name;
+ ts->input->phys = "sun4i_ts/input0";
+ ts->input->open = sun4i_ts_open;
+ ts->input->close = sun4i_ts_close;
+ ts->input->id.bustype = BUS_HOST;
+ ts->input->id.vendor = 0x0001;
+ ts->input->id.product = 0x0001;
+ ts->input->id.version = 0x0100;
+ ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
+ __set_bit(BTN_TOUCH, ts->input->keybit);
+ input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
+ input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
+ input_set_drvdata(ts->input, ts);
+ }
+
+ ts->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->base))
+ return PTR_ERR(ts->base);
+
+ ts->irq = platform_get_irq(pdev, 0);
+ error = devm_request_irq(dev, ts->irq, sun4i_ts_irq, 0, "sun4i-ts", ts);
+ if (error)
+ return error;
+
+ /*
+ * Select HOSC clk, clkin = clk / 6, adc samplefreq = clkin / 8192,
+ * t_acq = clkin / (16 * 64)
+ */
+ writel(ADC_CLK_SEL(0) | ADC_CLK_DIV(2) | FS_DIV(7) | T_ACQ(63),
+ ts->base + TP_CTRL0);
+
+ /*
+ * tp_sensitive_adjust is an optional property
+ * tp_mode = 0 : only x and y coordinates, as we don't use dual touch
+ */
+ of_property_read_u32(np, "allwinner,tp-sensitive-adjust",
+ &tp_sensitive_adjust);
+ writel(TP_SENSITIVE_ADJUST(tp_sensitive_adjust) | TP_MODE_SELECT(0),
+ ts->base + TP_CTRL2);
+
+ /*
+ * Enable median and averaging filter, optional property for
+ * filter type.
+ */
+ of_property_read_u32(np, "allwinner,filter-type", &filter_type);
+ writel(FILTER_EN(1) | FILTER_TYPE(filter_type), ts->base + TP_CTRL3);
+
+ /* Enable temperature measurement, period 1953 (2 seconds) */
+ writel(TEMP_ENABLE(1) | TEMP_PERIOD(1953), ts->base + TP_TPR);
+
+ /*
+ * Set stylus up debounce to aprox 10 ms, enable debounce, and
+ * finally enable tp mode.
+ */
+ reg = STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1);
+ if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts"))
+ reg |= SUN6I_TP_MODE_EN(1);
+ else
+ reg |= TP_MODE_EN(1);
+ writel(reg, ts->base + TP_CTRL1);
+
+ /*
+ * The thermal core does not register hwmon devices for DT-based
+ * thermal zone sensors, such as this one.
+ */
+ hwmon = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
+ ts, sun4i_ts_groups);
+ if (IS_ERR(hwmon))
+ return PTR_ERR(hwmon);
+
+ thermal = devm_thermal_of_zone_register(ts->dev, 0, ts,
+ &sun4i_ts_tz_ops);
+ if (IS_ERR(thermal))
+ return PTR_ERR(thermal);
+
+ writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
+
+ if (ts_attached) {
+ error = input_register_device(ts->input);
+ if (error) {
+ writel(0, ts->base + TP_INT_FIFOC);
+ return error;
+ }
+ }
+
+ platform_set_drvdata(pdev, ts);
+ return 0;
+}
+
+static int sun4i_ts_remove(struct platform_device *pdev)
+{
+ struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
+
+ /* Explicit unregister to avoid open/close changing the imask later */
+ if (ts->input)
+ input_unregister_device(ts->input);
+
+ /* Deactivate all IRQs */
+ writel(0, ts->base + TP_INT_FIFOC);
+
+ return 0;
+}
+
+static const struct of_device_id sun4i_ts_of_match[] = {
+ { .compatible = "allwinner,sun4i-a10-ts", },
+ { .compatible = "allwinner,sun5i-a13-ts", },
+ { .compatible = "allwinner,sun6i-a31-ts", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_ts_of_match);
+
+static struct platform_driver sun4i_ts_driver = {
+ .driver = {
+ .name = "sun4i-ts",
+ .of_match_table = of_match_ptr(sun4i_ts_of_match),
+ },
+ .probe = sun4i_ts_probe,
+ .remove = sun4i_ts_remove,
+};
+
+module_platform_driver(sun4i_ts_driver);
+
+MODULE_DESCRIPTION("Allwinner sun4i resistive touchscreen controller driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c
new file mode 100644
index 000000000..8ddb3f7d3
--- /dev/null
+++ b/drivers/input/touchscreen/sur40.c
@@ -0,0 +1,1190 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Surface2.0/SUR40/PixelSense input driver
+ *
+ * Copyright (c) 2014 by Florian 'floe' Echtler <floe@butterbrot.org>
+ *
+ * Derived from the USB Skeleton driver 1.1,
+ * Copyright (c) 2003 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * and from the Apple USB BCM5974 multitouch driver,
+ * Copyright (c) 2008 Henrik Rydberg (rydberg@euromail.se)
+ *
+ * and from the generic hid-multitouch driver,
+ * Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *
+ * and from the v4l2-pci-skeleton driver,
+ * Copyright (c) Copyright 2014 Cisco Systems, Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/printk.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/usb/input.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-sg.h>
+
+/* read 512 bytes from endpoint 0x86 -> get header + blobs */
+struct sur40_header {
+
+ __le16 type; /* always 0x0001 */
+ __le16 count; /* count of blobs (if 0: continue prev. packet) */
+
+ __le32 packet_id; /* unique ID for all packets in one frame */
+
+ __le32 timestamp; /* milliseconds (inc. by 16 or 17 each frame) */
+ __le32 unknown; /* "epoch?" always 02/03 00 00 00 */
+
+} __packed;
+
+struct sur40_blob {
+
+ __le16 blob_id;
+
+ u8 action; /* 0x02 = enter/exit, 0x03 = update (?) */
+ u8 type; /* bitmask (0x01 blob, 0x02 touch, 0x04 tag) */
+
+ __le16 bb_pos_x; /* upper left corner of bounding box */
+ __le16 bb_pos_y;
+
+ __le16 bb_size_x; /* size of bounding box */
+ __le16 bb_size_y;
+
+ __le16 pos_x; /* finger tip position */
+ __le16 pos_y;
+
+ __le16 ctr_x; /* centroid position */
+ __le16 ctr_y;
+
+ __le16 axis_x; /* somehow related to major/minor axis, mostly: */
+ __le16 axis_y; /* axis_x == bb_size_y && axis_y == bb_size_x */
+
+ __le32 angle; /* orientation in radians relative to x axis -
+ actually an IEEE754 float, don't use in kernel */
+
+ __le32 area; /* size in pixels/pressure (?) */
+
+ u8 padding[24];
+
+ __le32 tag_id; /* valid when type == 0x04 (SUR40_TAG) */
+ __le32 unknown;
+
+} __packed;
+
+/* combined header/blob data */
+struct sur40_data {
+ struct sur40_header header;
+ struct sur40_blob blobs[];
+} __packed;
+
+/* read 512 bytes from endpoint 0x82 -> get header below
+ * continue reading 16k blocks until header.size bytes read */
+struct sur40_image_header {
+ __le32 magic; /* "SUBF" */
+ __le32 packet_id;
+ __le32 size; /* always 0x0007e900 = 960x540 */
+ __le32 timestamp; /* milliseconds (increases by 16 or 17 each frame) */
+ __le32 unknown; /* "epoch?" always 02/03 00 00 00 */
+} __packed;
+
+/* version information */
+#define DRIVER_SHORT "sur40"
+#define DRIVER_LONG "Samsung SUR40"
+#define DRIVER_AUTHOR "Florian 'floe' Echtler <floe@butterbrot.org>"
+#define DRIVER_DESC "Surface2.0/SUR40/PixelSense input driver"
+
+/* vendor and device IDs */
+#define ID_MICROSOFT 0x045e
+#define ID_SUR40 0x0775
+
+/* sensor resolution */
+#define SENSOR_RES_X 1920
+#define SENSOR_RES_Y 1080
+
+/* touch data endpoint */
+#define TOUCH_ENDPOINT 0x86
+
+/* video data endpoint */
+#define VIDEO_ENDPOINT 0x82
+
+/* video header fields */
+#define VIDEO_HEADER_MAGIC 0x46425553
+#define VIDEO_PACKET_SIZE 16384
+
+/* polling interval (ms) */
+#define POLL_INTERVAL 1
+
+/* maximum number of contacts FIXME: this is a guess? */
+#define MAX_CONTACTS 64
+
+/* control commands */
+#define SUR40_GET_VERSION 0xb0 /* 12 bytes string */
+#define SUR40_ACCEL_CAPS 0xb3 /* 5 bytes */
+#define SUR40_SENSOR_CAPS 0xc1 /* 24 bytes */
+
+#define SUR40_POKE 0xc5 /* poke register byte */
+#define SUR40_PEEK 0xc4 /* 48 bytes registers */
+
+#define SUR40_GET_STATE 0xc5 /* 4 bytes state (?) */
+#define SUR40_GET_SENSORS 0xb1 /* 8 bytes sensors */
+
+#define SUR40_BLOB 0x01
+#define SUR40_TOUCH 0x02
+#define SUR40_TAG 0x04
+
+/* video controls */
+#define SUR40_BRIGHTNESS_MAX 0xff
+#define SUR40_BRIGHTNESS_MIN 0x00
+#define SUR40_BRIGHTNESS_DEF 0xff
+
+#define SUR40_CONTRAST_MAX 0x0f
+#define SUR40_CONTRAST_MIN 0x00
+#define SUR40_CONTRAST_DEF 0x0a
+
+#define SUR40_GAIN_MAX 0x09
+#define SUR40_GAIN_MIN 0x00
+#define SUR40_GAIN_DEF 0x08
+
+#define SUR40_BACKLIGHT_MAX 0x01
+#define SUR40_BACKLIGHT_MIN 0x00
+#define SUR40_BACKLIGHT_DEF 0x01
+
+#define sur40_str(s) #s
+#define SUR40_PARAM_RANGE(lo, hi) " (range " sur40_str(lo) "-" sur40_str(hi) ")"
+
+/* module parameters */
+static uint brightness = SUR40_BRIGHTNESS_DEF;
+module_param(brightness, uint, 0644);
+MODULE_PARM_DESC(brightness, "set initial brightness"
+ SUR40_PARAM_RANGE(SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX));
+static uint contrast = SUR40_CONTRAST_DEF;
+module_param(contrast, uint, 0644);
+MODULE_PARM_DESC(contrast, "set initial contrast"
+ SUR40_PARAM_RANGE(SUR40_CONTRAST_MIN, SUR40_CONTRAST_MAX));
+static uint gain = SUR40_GAIN_DEF;
+module_param(gain, uint, 0644);
+MODULE_PARM_DESC(gain, "set initial gain"
+ SUR40_PARAM_RANGE(SUR40_GAIN_MIN, SUR40_GAIN_MAX));
+
+static const struct v4l2_pix_format sur40_pix_format[] = {
+ {
+ .pixelformat = V4L2_TCH_FMT_TU08,
+ .width = SENSOR_RES_X / 2,
+ .height = SENSOR_RES_Y / 2,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_RAW,
+ .bytesperline = SENSOR_RES_X / 2,
+ .sizeimage = (SENSOR_RES_X/2) * (SENSOR_RES_Y/2),
+ },
+ {
+ .pixelformat = V4L2_PIX_FMT_GREY,
+ .width = SENSOR_RES_X / 2,
+ .height = SENSOR_RES_Y / 2,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_RAW,
+ .bytesperline = SENSOR_RES_X / 2,
+ .sizeimage = (SENSOR_RES_X/2) * (SENSOR_RES_Y/2),
+ }
+};
+
+/* master device state */
+struct sur40_state {
+
+ struct usb_device *usbdev;
+ struct device *dev;
+ struct input_dev *input;
+
+ struct v4l2_device v4l2;
+ struct video_device vdev;
+ struct mutex lock;
+ struct v4l2_pix_format pix_fmt;
+ struct v4l2_ctrl_handler hdl;
+
+ struct vb2_queue queue;
+ struct list_head buf_list;
+ spinlock_t qlock;
+ int sequence;
+
+ struct sur40_data *bulk_in_buffer;
+ size_t bulk_in_size;
+ u8 bulk_in_epaddr;
+ u8 vsvideo;
+
+ char phys[64];
+};
+
+struct sur40_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+/* forward declarations */
+static const struct video_device sur40_video_device;
+static const struct vb2_queue sur40_queue;
+static void sur40_process_video(struct sur40_state *sur40);
+static int sur40_s_ctrl(struct v4l2_ctrl *ctrl);
+
+static const struct v4l2_ctrl_ops sur40_ctrl_ops = {
+ .s_ctrl = sur40_s_ctrl,
+};
+
+/*
+ * Note: an earlier, non-public version of this driver used USB_RECIP_ENDPOINT
+ * here by mistake which is very likely to have corrupted the firmware EEPROM
+ * on two separate SUR40 devices. Thanks to Alan Stern who spotted this bug.
+ * Should you ever run into a similar problem, the background story to this
+ * incident and instructions on how to fix the corrupted EEPROM are available
+ * at https://floe.butterbrot.org/matrix/hacking/surface/brick.html
+*/
+
+/* command wrapper */
+static int sur40_command(struct sur40_state *dev,
+ u8 command, u16 index, void *buffer, u16 size)
+{
+ return usb_control_msg(dev->usbdev, usb_rcvctrlpipe(dev->usbdev, 0),
+ command,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x00, index, buffer, size, 1000);
+}
+
+/* poke a byte in the panel register space */
+static int sur40_poke(struct sur40_state *dev, u8 offset, u8 value)
+{
+ int result;
+ u8 index = 0x96; // 0xae for permanent write
+
+ result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
+ SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x32, index, NULL, 0, 1000);
+ if (result < 0)
+ goto error;
+ msleep(5);
+
+ result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
+ SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x72, offset, NULL, 0, 1000);
+ if (result < 0)
+ goto error;
+ msleep(5);
+
+ result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
+ SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0xb2, value, NULL, 0, 1000);
+ if (result < 0)
+ goto error;
+ msleep(5);
+
+error:
+ return result;
+}
+
+static int sur40_set_preprocessor(struct sur40_state *dev, u8 value)
+{
+ u8 setting_07[2] = { 0x01, 0x00 };
+ u8 setting_17[2] = { 0x85, 0x80 };
+ int result;
+
+ if (value > 1)
+ return -ERANGE;
+
+ result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
+ SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x07, setting_07[value], NULL, 0, 1000);
+ if (result < 0)
+ goto error;
+ msleep(5);
+
+ result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
+ SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x17, setting_17[value], NULL, 0, 1000);
+ if (result < 0)
+ goto error;
+ msleep(5);
+
+error:
+ return result;
+}
+
+static void sur40_set_vsvideo(struct sur40_state *handle, u8 value)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ sur40_poke(handle, 0x1c+i, value);
+ handle->vsvideo = value;
+}
+
+static void sur40_set_irlevel(struct sur40_state *handle, u8 value)
+{
+ int i;
+
+ for (i = 0; i < 8; i++)
+ sur40_poke(handle, 0x08+(2*i), value);
+}
+
+/* Initialization routine, called from sur40_open */
+static int sur40_init(struct sur40_state *dev)
+{
+ int result;
+ u8 *buffer;
+
+ buffer = kmalloc(24, GFP_KERNEL);
+ if (!buffer) {
+ result = -ENOMEM;
+ goto error;
+ }
+
+ /* stupidly replay the original MS driver init sequence */
+ result = sur40_command(dev, SUR40_GET_VERSION, 0x00, buffer, 12);
+ if (result < 0)
+ goto error;
+
+ result = sur40_command(dev, SUR40_GET_VERSION, 0x01, buffer, 12);
+ if (result < 0)
+ goto error;
+
+ result = sur40_command(dev, SUR40_GET_VERSION, 0x02, buffer, 12);
+ if (result < 0)
+ goto error;
+
+ result = sur40_command(dev, SUR40_SENSOR_CAPS, 0x00, buffer, 24);
+ if (result < 0)
+ goto error;
+
+ result = sur40_command(dev, SUR40_ACCEL_CAPS, 0x00, buffer, 5);
+ if (result < 0)
+ goto error;
+
+ result = sur40_command(dev, SUR40_GET_VERSION, 0x03, buffer, 12);
+ if (result < 0)
+ goto error;
+
+ result = 0;
+
+ /*
+ * Discard the result buffer - no known data inside except
+ * some version strings, maybe extract these sometime...
+ */
+error:
+ kfree(buffer);
+ return result;
+}
+
+/*
+ * Callback routines from input_dev
+ */
+
+/* Enable the device, polling will now start. */
+static int sur40_open(struct input_dev *input)
+{
+ struct sur40_state *sur40 = input_get_drvdata(input);
+
+ dev_dbg(sur40->dev, "open\n");
+ return sur40_init(sur40);
+}
+
+/* Disable device, polling has stopped. */
+static void sur40_close(struct input_dev *input)
+{
+ struct sur40_state *sur40 = input_get_drvdata(input);
+
+ dev_dbg(sur40->dev, "close\n");
+ /*
+ * There is no known way to stop the device, so we simply
+ * stop polling.
+ */
+}
+
+/*
+ * This function is called when a whole contact has been processed,
+ * so that it can assign it to a slot and store the data there.
+ */
+static void sur40_report_blob(struct sur40_blob *blob, struct input_dev *input)
+{
+ int wide, major, minor;
+ int bb_size_x, bb_size_y, pos_x, pos_y, ctr_x, ctr_y, slotnum;
+
+ if (blob->type != SUR40_TOUCH)
+ return;
+
+ slotnum = input_mt_get_slot_by_key(input, blob->blob_id);
+ if (slotnum < 0 || slotnum >= MAX_CONTACTS)
+ return;
+
+ bb_size_x = le16_to_cpu(blob->bb_size_x);
+ bb_size_y = le16_to_cpu(blob->bb_size_y);
+
+ pos_x = le16_to_cpu(blob->pos_x);
+ pos_y = le16_to_cpu(blob->pos_y);
+
+ ctr_x = le16_to_cpu(blob->ctr_x);
+ ctr_y = le16_to_cpu(blob->ctr_y);
+
+ input_mt_slot(input, slotnum);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
+ wide = (bb_size_x > bb_size_y);
+ major = max(bb_size_x, bb_size_y);
+ minor = min(bb_size_x, bb_size_y);
+
+ input_report_abs(input, ABS_MT_POSITION_X, pos_x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos_y);
+ input_report_abs(input, ABS_MT_TOOL_X, ctr_x);
+ input_report_abs(input, ABS_MT_TOOL_Y, ctr_y);
+
+ /* TODO: use a better orientation measure */
+ input_report_abs(input, ABS_MT_ORIENTATION, wide);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, major);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR, minor);
+}
+
+/* core function: poll for new input data */
+static void sur40_poll(struct input_dev *input)
+{
+ struct sur40_state *sur40 = input_get_drvdata(input);
+ int result, bulk_read, need_blobs, packet_blobs, i;
+ struct sur40_header *header = &sur40->bulk_in_buffer->header;
+ struct sur40_blob *inblob = &sur40->bulk_in_buffer->blobs[0];
+
+ dev_dbg(sur40->dev, "poll\n");
+
+ need_blobs = -1;
+
+ do {
+
+ /* perform a blocking bulk read to get data from the device */
+ result = usb_bulk_msg(sur40->usbdev,
+ usb_rcvbulkpipe(sur40->usbdev, sur40->bulk_in_epaddr),
+ sur40->bulk_in_buffer, sur40->bulk_in_size,
+ &bulk_read, 1000);
+
+ dev_dbg(sur40->dev, "received %d bytes\n", bulk_read);
+
+ if (result < 0) {
+ dev_err(sur40->dev, "error in usb_bulk_read\n");
+ return;
+ }
+
+ result = bulk_read - sizeof(struct sur40_header);
+
+ if (result % sizeof(struct sur40_blob) != 0) {
+ dev_err(sur40->dev, "transfer size mismatch\n");
+ return;
+ }
+
+ /* first packet? */
+ if (need_blobs == -1) {
+ need_blobs = le16_to_cpu(header->count);
+ dev_dbg(sur40->dev, "need %d blobs\n", need_blobs);
+ /* packet_id = le32_to_cpu(header->packet_id); */
+ }
+
+ /*
+ * Sanity check. when video data is also being retrieved, the
+ * packet ID will usually increase in the middle of a series
+ * instead of at the end. However, the data is still consistent,
+ * so the packet ID is probably just valid for the first packet
+ * in a series.
+
+ if (packet_id != le32_to_cpu(header->packet_id))
+ dev_dbg(sur40->dev, "packet ID mismatch\n");
+ */
+
+ packet_blobs = result / sizeof(struct sur40_blob);
+ dev_dbg(sur40->dev, "received %d blobs\n", packet_blobs);
+
+ /* packets always contain at least 4 blobs, even if empty */
+ if (packet_blobs > need_blobs)
+ packet_blobs = need_blobs;
+
+ for (i = 0; i < packet_blobs; i++) {
+ need_blobs--;
+ dev_dbg(sur40->dev, "processing blob\n");
+ sur40_report_blob(&(inblob[i]), input);
+ }
+
+ } while (need_blobs > 0);
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+
+ sur40_process_video(sur40);
+}
+
+/* deal with video data */
+static void sur40_process_video(struct sur40_state *sur40)
+{
+
+ struct sur40_image_header *img = (void *)(sur40->bulk_in_buffer);
+ struct sur40_buffer *new_buf;
+ struct usb_sg_request sgr;
+ struct sg_table *sgt;
+ int result, bulk_read;
+
+ if (!vb2_start_streaming_called(&sur40->queue))
+ return;
+
+ /* get a new buffer from the list */
+ spin_lock(&sur40->qlock);
+ if (list_empty(&sur40->buf_list)) {
+ dev_dbg(sur40->dev, "buffer queue empty\n");
+ spin_unlock(&sur40->qlock);
+ return;
+ }
+ new_buf = list_entry(sur40->buf_list.next, struct sur40_buffer, list);
+ list_del(&new_buf->list);
+ spin_unlock(&sur40->qlock);
+
+ dev_dbg(sur40->dev, "buffer acquired\n");
+
+ /* retrieve data via bulk read */
+ result = usb_bulk_msg(sur40->usbdev,
+ usb_rcvbulkpipe(sur40->usbdev, VIDEO_ENDPOINT),
+ sur40->bulk_in_buffer, sur40->bulk_in_size,
+ &bulk_read, 1000);
+
+ if (result < 0) {
+ dev_err(sur40->dev, "error in usb_bulk_read\n");
+ goto err_poll;
+ }
+
+ if (bulk_read != sizeof(struct sur40_image_header)) {
+ dev_err(sur40->dev, "received %d bytes (%zd expected)\n",
+ bulk_read, sizeof(struct sur40_image_header));
+ goto err_poll;
+ }
+
+ if (le32_to_cpu(img->magic) != VIDEO_HEADER_MAGIC) {
+ dev_err(sur40->dev, "image magic mismatch\n");
+ goto err_poll;
+ }
+
+ if (le32_to_cpu(img->size) != sur40->pix_fmt.sizeimage) {
+ dev_err(sur40->dev, "image size mismatch\n");
+ goto err_poll;
+ }
+
+ dev_dbg(sur40->dev, "header acquired\n");
+
+ sgt = vb2_dma_sg_plane_desc(&new_buf->vb.vb2_buf, 0);
+
+ result = usb_sg_init(&sgr, sur40->usbdev,
+ usb_rcvbulkpipe(sur40->usbdev, VIDEO_ENDPOINT), 0,
+ sgt->sgl, sgt->nents, sur40->pix_fmt.sizeimage, 0);
+ if (result < 0) {
+ dev_err(sur40->dev, "error %d in usb_sg_init\n", result);
+ goto err_poll;
+ }
+
+ usb_sg_wait(&sgr);
+ if (sgr.status < 0) {
+ dev_err(sur40->dev, "error %d in usb_sg_wait\n", sgr.status);
+ goto err_poll;
+ }
+
+ dev_dbg(sur40->dev, "image acquired\n");
+
+ /* return error if streaming was stopped in the meantime */
+ if (sur40->sequence == -1)
+ return;
+
+ /* mark as finished */
+ new_buf->vb.vb2_buf.timestamp = ktime_get_ns();
+ new_buf->vb.sequence = sur40->sequence++;
+ new_buf->vb.field = V4L2_FIELD_NONE;
+ vb2_buffer_done(&new_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+ dev_dbg(sur40->dev, "buffer marked done\n");
+ return;
+
+err_poll:
+ vb2_buffer_done(&new_buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+}
+
+/* Initialize input device parameters. */
+static int sur40_input_setup_events(struct input_dev *input_dev)
+{
+ int error;
+
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+ 0, SENSOR_RES_X, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+ 0, SENSOR_RES_Y, 0, 0);
+
+ input_set_abs_params(input_dev, ABS_MT_TOOL_X,
+ 0, SENSOR_RES_X, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOOL_Y,
+ 0, SENSOR_RES_Y, 0, 0);
+
+ /* max value unknown, but major/minor axis
+ * can never be larger than screen */
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
+ 0, SENSOR_RES_X, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR,
+ 0, SENSOR_RES_Y, 0, 0);
+
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+
+ error = input_mt_init_slots(input_dev, MAX_CONTACTS,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(input_dev->dev.parent, "failed to set up slots\n");
+ return error;
+ }
+
+ return 0;
+}
+
+/* Check candidate USB interface. */
+static int sur40_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+ struct sur40_state *sur40;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ struct input_dev *input;
+ int error;
+
+ /* Check if we really have the right interface. */
+ iface_desc = interface->cur_altsetting;
+ if (iface_desc->desc.bInterfaceClass != 0xFF)
+ return -ENODEV;
+
+ if (iface_desc->desc.bNumEndpoints < 5)
+ return -ENODEV;
+
+ /* Use endpoint #4 (0x86). */
+ endpoint = &iface_desc->endpoint[4].desc;
+ if (endpoint->bEndpointAddress != TOUCH_ENDPOINT)
+ return -ENODEV;
+
+ /* Allocate memory for our device state and initialize it. */
+ sur40 = kzalloc(sizeof(struct sur40_state), GFP_KERNEL);
+ if (!sur40)
+ return -ENOMEM;
+
+ input = input_allocate_device();
+ if (!input) {
+ error = -ENOMEM;
+ goto err_free_dev;
+ }
+
+ /* initialize locks/lists */
+ INIT_LIST_HEAD(&sur40->buf_list);
+ spin_lock_init(&sur40->qlock);
+ mutex_init(&sur40->lock);
+
+ /* Set up regular input device structure */
+ input->name = DRIVER_LONG;
+ usb_to_input_id(usbdev, &input->id);
+ usb_make_path(usbdev, sur40->phys, sizeof(sur40->phys));
+ strlcat(sur40->phys, "/input0", sizeof(sur40->phys));
+ input->phys = sur40->phys;
+ input->dev.parent = &interface->dev;
+
+ input->open = sur40_open;
+ input->close = sur40_close;
+
+ error = sur40_input_setup_events(input);
+ if (error)
+ goto err_free_input;
+
+ input_set_drvdata(input, sur40);
+ error = input_setup_polling(input, sur40_poll);
+ if (error) {
+ dev_err(&interface->dev, "failed to set up polling");
+ goto err_free_input;
+ }
+
+ input_set_poll_interval(input, POLL_INTERVAL);
+
+ sur40->usbdev = usbdev;
+ sur40->dev = &interface->dev;
+ sur40->input = input;
+
+ /* use the bulk-in endpoint tested above */
+ sur40->bulk_in_size = usb_endpoint_maxp(endpoint);
+ sur40->bulk_in_epaddr = endpoint->bEndpointAddress;
+ sur40->bulk_in_buffer = kmalloc(sur40->bulk_in_size, GFP_KERNEL);
+ if (!sur40->bulk_in_buffer) {
+ dev_err(&interface->dev, "Unable to allocate input buffer.");
+ error = -ENOMEM;
+ goto err_free_input;
+ }
+
+ /* register the polled input device */
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&interface->dev,
+ "Unable to register polled input device.");
+ goto err_free_buffer;
+ }
+
+ /* register the video master device */
+ snprintf(sur40->v4l2.name, sizeof(sur40->v4l2.name), "%s", DRIVER_LONG);
+ error = v4l2_device_register(sur40->dev, &sur40->v4l2);
+ if (error) {
+ dev_err(&interface->dev,
+ "Unable to register video master device.");
+ goto err_unreg_v4l2;
+ }
+
+ /* initialize the lock and subdevice */
+ sur40->queue = sur40_queue;
+ sur40->queue.drv_priv = sur40;
+ sur40->queue.lock = &sur40->lock;
+ sur40->queue.dev = sur40->dev;
+
+ /* initialize the queue */
+ error = vb2_queue_init(&sur40->queue);
+ if (error)
+ goto err_unreg_v4l2;
+
+ sur40->pix_fmt = sur40_pix_format[0];
+ sur40->vdev = sur40_video_device;
+ sur40->vdev.v4l2_dev = &sur40->v4l2;
+ sur40->vdev.lock = &sur40->lock;
+ sur40->vdev.queue = &sur40->queue;
+ video_set_drvdata(&sur40->vdev, sur40);
+
+ /* initialize the control handler for 4 controls */
+ v4l2_ctrl_handler_init(&sur40->hdl, 4);
+ sur40->v4l2.ctrl_handler = &sur40->hdl;
+ sur40->vsvideo = (SUR40_CONTRAST_DEF << 4) | SUR40_GAIN_DEF;
+
+ v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_BRIGHTNESS,
+ SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX, 1, clamp(brightness,
+ (uint)SUR40_BRIGHTNESS_MIN, (uint)SUR40_BRIGHTNESS_MAX));
+
+ v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_CONTRAST,
+ SUR40_CONTRAST_MIN, SUR40_CONTRAST_MAX, 1, clamp(contrast,
+ (uint)SUR40_CONTRAST_MIN, (uint)SUR40_CONTRAST_MAX));
+
+ v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_GAIN,
+ SUR40_GAIN_MIN, SUR40_GAIN_MAX, 1, clamp(gain,
+ (uint)SUR40_GAIN_MIN, (uint)SUR40_GAIN_MAX));
+
+ v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops,
+ V4L2_CID_BACKLIGHT_COMPENSATION, SUR40_BACKLIGHT_MIN,
+ SUR40_BACKLIGHT_MAX, 1, SUR40_BACKLIGHT_DEF);
+
+ v4l2_ctrl_handler_setup(&sur40->hdl);
+
+ if (sur40->hdl.error) {
+ dev_err(&interface->dev,
+ "Unable to register video controls.");
+ v4l2_ctrl_handler_free(&sur40->hdl);
+ error = sur40->hdl.error;
+ goto err_unreg_v4l2;
+ }
+
+ error = video_register_device(&sur40->vdev, VFL_TYPE_TOUCH, -1);
+ if (error) {
+ dev_err(&interface->dev,
+ "Unable to register video subdevice.");
+ goto err_unreg_video;
+ }
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(interface, sur40);
+ dev_dbg(&interface->dev, "%s is now attached\n", DRIVER_DESC);
+
+ return 0;
+
+err_unreg_video:
+ video_unregister_device(&sur40->vdev);
+err_unreg_v4l2:
+ v4l2_device_unregister(&sur40->v4l2);
+err_free_buffer:
+ kfree(sur40->bulk_in_buffer);
+err_free_input:
+ input_free_device(input);
+err_free_dev:
+ kfree(sur40);
+
+ return error;
+}
+
+/* Unregister device & clean up. */
+static void sur40_disconnect(struct usb_interface *interface)
+{
+ struct sur40_state *sur40 = usb_get_intfdata(interface);
+
+ v4l2_ctrl_handler_free(&sur40->hdl);
+ video_unregister_device(&sur40->vdev);
+ v4l2_device_unregister(&sur40->v4l2);
+
+ input_unregister_device(sur40->input);
+ kfree(sur40->bulk_in_buffer);
+ kfree(sur40);
+
+ usb_set_intfdata(interface, NULL);
+ dev_dbg(&interface->dev, "%s is now disconnected\n", DRIVER_DESC);
+}
+
+/*
+ * Setup the constraints of the queue: besides setting the number of planes
+ * per buffer and the size and allocation context of each plane, it also
+ * checks if sufficient buffers have been allocated. Usually 3 is a good
+ * minimum number: many DMA engines need a minimum of 2 buffers in the
+ * queue and you need to have another available for userspace processing.
+ */
+static int sur40_queue_setup(struct vb2_queue *q,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct sur40_state *sur40 = vb2_get_drv_priv(q);
+
+ if (q->num_buffers + *nbuffers < 3)
+ *nbuffers = 3 - q->num_buffers;
+
+ if (*nplanes)
+ return sizes[0] < sur40->pix_fmt.sizeimage ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = sur40->pix_fmt.sizeimage;
+
+ return 0;
+}
+
+/*
+ * Prepare the buffer for queueing to the DMA engine: check and set the
+ * payload size.
+ */
+static int sur40_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct sur40_state *sur40 = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = sur40->pix_fmt.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ dev_err(&sur40->usbdev->dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+ return 0;
+}
+
+/*
+ * Queue this buffer to the DMA engine.
+ */
+static void sur40_buffer_queue(struct vb2_buffer *vb)
+{
+ struct sur40_state *sur40 = vb2_get_drv_priv(vb->vb2_queue);
+ struct sur40_buffer *buf = (struct sur40_buffer *)vb;
+
+ spin_lock(&sur40->qlock);
+ list_add_tail(&buf->list, &sur40->buf_list);
+ spin_unlock(&sur40->qlock);
+}
+
+static void return_all_buffers(struct sur40_state *sur40,
+ enum vb2_buffer_state state)
+{
+ struct sur40_buffer *buf, *node;
+
+ spin_lock(&sur40->qlock);
+ list_for_each_entry_safe(buf, node, &sur40->buf_list, list) {
+ vb2_buffer_done(&buf->vb.vb2_buf, state);
+ list_del(&buf->list);
+ }
+ spin_unlock(&sur40->qlock);
+}
+
+/*
+ * Start streaming. First check if the minimum number of buffers have been
+ * queued. If not, then return -ENOBUFS and the vb2 framework will call
+ * this function again the next time a buffer has been queued until enough
+ * buffers are available to actually start the DMA engine.
+ */
+static int sur40_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sur40_state *sur40 = vb2_get_drv_priv(vq);
+
+ sur40->sequence = 0;
+ return 0;
+}
+
+/*
+ * Stop the DMA engine. Any remaining buffers in the DMA queue are dequeued
+ * and passed on to the vb2 framework marked as STATE_ERROR.
+ */
+static void sur40_stop_streaming(struct vb2_queue *vq)
+{
+ struct sur40_state *sur40 = vb2_get_drv_priv(vq);
+ vb2_wait_for_all_buffers(vq);
+ sur40->sequence = -1;
+
+ /* Release all active buffers */
+ return_all_buffers(sur40, VB2_BUF_STATE_ERROR);
+}
+
+/* V4L ioctl */
+static int sur40_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sur40_state *sur40 = video_drvdata(file);
+
+ strscpy(cap->driver, DRIVER_SHORT, sizeof(cap->driver));
+ strscpy(cap->card, DRIVER_LONG, sizeof(cap->card));
+ usb_make_path(sur40->usbdev, cap->bus_info, sizeof(cap->bus_info));
+ return 0;
+}
+
+static int sur40_vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ if (i->index != 0)
+ return -EINVAL;
+ i->type = V4L2_INPUT_TYPE_TOUCH;
+ i->std = V4L2_STD_UNKNOWN;
+ strscpy(i->name, "In-Cell Sensor", sizeof(i->name));
+ i->capabilities = 0;
+ return 0;
+}
+
+static int sur40_vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ return (i == 0) ? 0 : -EINVAL;
+}
+
+static int sur40_vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int sur40_vidioc_try_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ switch (f->fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_GREY:
+ f->fmt.pix = sur40_pix_format[1];
+ break;
+
+ default:
+ f->fmt.pix = sur40_pix_format[0];
+ break;
+ }
+
+ return 0;
+}
+
+static int sur40_vidioc_s_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sur40_state *sur40 = video_drvdata(file);
+
+ switch (f->fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_GREY:
+ sur40->pix_fmt = sur40_pix_format[1];
+ break;
+
+ default:
+ sur40->pix_fmt = sur40_pix_format[0];
+ break;
+ }
+
+ f->fmt.pix = sur40->pix_fmt;
+ return 0;
+}
+
+static int sur40_vidioc_g_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sur40_state *sur40 = video_drvdata(file);
+
+ f->fmt.pix = sur40->pix_fmt;
+ return 0;
+}
+
+static int sur40_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct sur40_state *sur40 = container_of(ctrl->handler,
+ struct sur40_state, hdl);
+ u8 value = sur40->vsvideo;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ sur40_set_irlevel(sur40, ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ value = (value & 0x0f) | (ctrl->val << 4);
+ sur40_set_vsvideo(sur40, value);
+ break;
+ case V4L2_CID_GAIN:
+ value = (value & 0xf0) | (ctrl->val);
+ sur40_set_vsvideo(sur40, value);
+ break;
+ case V4L2_CID_BACKLIGHT_COMPENSATION:
+ sur40_set_preprocessor(sur40, ctrl->val);
+ break;
+ }
+ return 0;
+}
+
+static int sur40_ioctl_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *p)
+{
+ if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ p->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ p->parm.capture.timeperframe.numerator = 1;
+ p->parm.capture.timeperframe.denominator = 60;
+ p->parm.capture.readbuffers = 3;
+ return 0;
+}
+
+static int sur40_vidioc_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index >= ARRAY_SIZE(sur40_pix_format))
+ return -EINVAL;
+
+ f->pixelformat = sur40_pix_format[f->index].pixelformat;
+ f->flags = 0;
+ return 0;
+}
+
+static int sur40_vidioc_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *f)
+{
+ struct sur40_state *sur40 = video_drvdata(file);
+
+ if ((f->index != 0) || ((f->pixel_format != V4L2_TCH_FMT_TU08)
+ && (f->pixel_format != V4L2_PIX_FMT_GREY)))
+ return -EINVAL;
+
+ f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ f->discrete.width = sur40->pix_fmt.width;
+ f->discrete.height = sur40->pix_fmt.height;
+ return 0;
+}
+
+static int sur40_vidioc_enum_frameintervals(struct file *file, void *priv,
+ struct v4l2_frmivalenum *f)
+{
+ struct sur40_state *sur40 = video_drvdata(file);
+
+ if ((f->index > 0) || ((f->pixel_format != V4L2_TCH_FMT_TU08)
+ && (f->pixel_format != V4L2_PIX_FMT_GREY))
+ || (f->width != sur40->pix_fmt.width)
+ || (f->height != sur40->pix_fmt.height))
+ return -EINVAL;
+
+ f->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ f->discrete.denominator = 60;
+ f->discrete.numerator = 1;
+ return 0;
+}
+
+
+static const struct usb_device_id sur40_table[] = {
+ { USB_DEVICE(ID_MICROSOFT, ID_SUR40) }, /* Samsung SUR40 */
+ { } /* terminating null entry */
+};
+MODULE_DEVICE_TABLE(usb, sur40_table);
+
+/* V4L2 structures */
+static const struct vb2_ops sur40_queue_ops = {
+ .queue_setup = sur40_queue_setup,
+ .buf_prepare = sur40_buffer_prepare,
+ .buf_queue = sur40_buffer_queue,
+ .start_streaming = sur40_start_streaming,
+ .stop_streaming = sur40_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static const struct vb2_queue sur40_queue = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ /*
+ * VB2_USERPTR in currently not enabled: passing a user pointer to
+ * dma-sg will result in segment sizes that are not a multiple of
+ * 512 bytes, which is required by the host controller.
+ */
+ .io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF,
+ .buf_struct_size = sizeof(struct sur40_buffer),
+ .ops = &sur40_queue_ops,
+ .mem_ops = &vb2_dma_sg_memops,
+ .timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC,
+ .min_buffers_needed = 3,
+};
+
+static const struct v4l2_file_operations sur40_video_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .unlocked_ioctl = video_ioctl2,
+ .read = vb2_fop_read,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll,
+};
+
+static const struct v4l2_ioctl_ops sur40_video_ioctl_ops = {
+
+ .vidioc_querycap = sur40_vidioc_querycap,
+
+ .vidioc_enum_fmt_vid_cap = sur40_vidioc_enum_fmt,
+ .vidioc_try_fmt_vid_cap = sur40_vidioc_try_fmt,
+ .vidioc_s_fmt_vid_cap = sur40_vidioc_s_fmt,
+ .vidioc_g_fmt_vid_cap = sur40_vidioc_g_fmt,
+
+ .vidioc_enum_framesizes = sur40_vidioc_enum_framesizes,
+ .vidioc_enum_frameintervals = sur40_vidioc_enum_frameintervals,
+
+ .vidioc_g_parm = sur40_ioctl_parm,
+ .vidioc_s_parm = sur40_ioctl_parm,
+
+ .vidioc_enum_input = sur40_vidioc_enum_input,
+ .vidioc_g_input = sur40_vidioc_g_input,
+ .vidioc_s_input = sur40_vidioc_s_input,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+static const struct video_device sur40_video_device = {
+ .name = DRIVER_LONG,
+ .fops = &sur40_video_fops,
+ .ioctl_ops = &sur40_video_ioctl_ops,
+ .release = video_device_release_empty,
+ .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH |
+ V4L2_CAP_READWRITE | V4L2_CAP_STREAMING,
+};
+
+/* USB-specific object needed to register this driver with the USB subsystem. */
+static struct usb_driver sur40_driver = {
+ .name = DRIVER_SHORT,
+ .probe = sur40_probe,
+ .disconnect = sur40_disconnect,
+ .id_table = sur40_table,
+};
+
+module_usb_driver(sur40_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c
new file mode 100644
index 000000000..1da23e558
--- /dev/null
+++ b/drivers/input/touchscreen/surface3_spi.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Ntrig/Microsoft Touchscreens over SPI
+ *
+ * Copyright (c) 2016 Red Hat Inc.
+ */
+
+
+#include <linux/kernel.h>
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/acpi.h>
+
+#include <asm/unaligned.h>
+
+#define SURFACE3_PACKET_SIZE 264
+
+#define SURFACE3_REPORT_TOUCH 0xd2
+#define SURFACE3_REPORT_PEN 0x16
+
+struct surface3_ts_data {
+ struct spi_device *spi;
+ struct gpio_desc *gpiod_rst[2];
+ struct input_dev *input_dev;
+ struct input_dev *pen_input_dev;
+ int pen_tool;
+
+ u8 rd_buf[SURFACE3_PACKET_SIZE] ____cacheline_aligned;
+};
+
+struct surface3_ts_data_finger {
+ u8 status;
+ __le16 tracking_id;
+ __le16 x;
+ __le16 cx;
+ __le16 y;
+ __le16 cy;
+ __le16 width;
+ __le16 height;
+ u32 padding;
+} __packed;
+
+struct surface3_ts_data_pen {
+ u8 status;
+ __le16 x;
+ __le16 y;
+ __le16 pressure;
+ u8 padding;
+} __packed;
+
+static int surface3_spi_read(struct surface3_ts_data *ts_data)
+{
+ struct spi_device *spi = ts_data->spi;
+
+ memset(ts_data->rd_buf, 0, sizeof(ts_data->rd_buf));
+ return spi_read(spi, ts_data->rd_buf, sizeof(ts_data->rd_buf));
+}
+
+static void surface3_spi_report_touch(struct surface3_ts_data *ts_data,
+ struct surface3_ts_data_finger *finger)
+{
+ int st = finger->status & 0x01;
+ int slot;
+
+ slot = input_mt_get_slot_by_key(ts_data->input_dev,
+ get_unaligned_le16(&finger->tracking_id));
+ if (slot < 0)
+ return;
+
+ input_mt_slot(ts_data->input_dev, slot);
+ input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, st);
+ if (st) {
+ input_report_abs(ts_data->input_dev,
+ ABS_MT_POSITION_X,
+ get_unaligned_le16(&finger->x));
+ input_report_abs(ts_data->input_dev,
+ ABS_MT_POSITION_Y,
+ get_unaligned_le16(&finger->y));
+ input_report_abs(ts_data->input_dev,
+ ABS_MT_WIDTH_MAJOR,
+ get_unaligned_le16(&finger->width));
+ input_report_abs(ts_data->input_dev,
+ ABS_MT_WIDTH_MINOR,
+ get_unaligned_le16(&finger->height));
+ }
+}
+
+static void surface3_spi_process_touch(struct surface3_ts_data *ts_data, u8 *data)
+{
+ unsigned int i;
+
+ for (i = 0; i < 13; i++) {
+ struct surface3_ts_data_finger *finger;
+
+ finger = (struct surface3_ts_data_finger *)&data[17 +
+ i * sizeof(struct surface3_ts_data_finger)];
+
+ /*
+ * When bit 5 of status is 1, it marks the end of the report:
+ * - touch present: 0xe7
+ * - touch released: 0xe4
+ * - nothing valuable: 0xff
+ */
+ if (finger->status & 0x10)
+ break;
+
+ surface3_spi_report_touch(ts_data, finger);
+ }
+
+ input_mt_sync_frame(ts_data->input_dev);
+ input_sync(ts_data->input_dev);
+}
+
+static void surface3_spi_report_pen(struct surface3_ts_data *ts_data,
+ struct surface3_ts_data_pen *pen)
+{
+ struct input_dev *dev = ts_data->pen_input_dev;
+ int st = pen->status;
+ int prox = st & 0x01;
+ int rubber = st & 0x18;
+ int tool = (prox && rubber) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+
+ /* fake proximity out to switch tools */
+ if (ts_data->pen_tool != tool) {
+ input_report_key(dev, ts_data->pen_tool, 0);
+ input_sync(dev);
+ ts_data->pen_tool = tool;
+ }
+
+ input_report_key(dev, BTN_TOUCH, st & 0x12);
+
+ input_report_key(dev, ts_data->pen_tool, prox);
+
+ if (st) {
+ input_report_key(dev,
+ BTN_STYLUS,
+ st & 0x04);
+
+ input_report_abs(dev,
+ ABS_X,
+ get_unaligned_le16(&pen->x));
+ input_report_abs(dev,
+ ABS_Y,
+ get_unaligned_le16(&pen->y));
+ input_report_abs(dev,
+ ABS_PRESSURE,
+ get_unaligned_le16(&pen->pressure));
+ }
+}
+
+static void surface3_spi_process_pen(struct surface3_ts_data *ts_data, u8 *data)
+{
+ struct surface3_ts_data_pen *pen;
+
+ pen = (struct surface3_ts_data_pen *)&data[15];
+
+ surface3_spi_report_pen(ts_data, pen);
+ input_sync(ts_data->pen_input_dev);
+}
+
+static void surface3_spi_process(struct surface3_ts_data *ts_data)
+{
+ static const char header[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xa5, 0x5a, 0xe7, 0x7e, 0x01
+ };
+ u8 *data = ts_data->rd_buf;
+
+ if (memcmp(header, data, sizeof(header)))
+ dev_err(&ts_data->spi->dev,
+ "%s header error: %*ph, ignoring...\n",
+ __func__, (int)sizeof(header), data);
+
+ switch (data[9]) {
+ case SURFACE3_REPORT_TOUCH:
+ surface3_spi_process_touch(ts_data, data);
+ break;
+ case SURFACE3_REPORT_PEN:
+ surface3_spi_process_pen(ts_data, data);
+ break;
+ default:
+ dev_err(&ts_data->spi->dev,
+ "%s unknown packet type: %x, ignoring...\n",
+ __func__, data[9]);
+ break;
+ }
+}
+
+static irqreturn_t surface3_spi_irq_handler(int irq, void *dev_id)
+{
+ struct surface3_ts_data *data = dev_id;
+
+ if (surface3_spi_read(data))
+ return IRQ_HANDLED;
+
+ dev_dbg(&data->spi->dev, "%s received -> %*ph\n",
+ __func__, SURFACE3_PACKET_SIZE, data->rd_buf);
+ surface3_spi_process(data);
+
+ return IRQ_HANDLED;
+}
+
+static void surface3_spi_power(struct surface3_ts_data *data, bool on)
+{
+ gpiod_set_value(data->gpiod_rst[0], on);
+ gpiod_set_value(data->gpiod_rst[1], on);
+ /* let the device settle a little */
+ msleep(20);
+}
+
+/**
+ * surface3_spi_get_gpio_config - Get GPIO config from ACPI/DT
+ *
+ * @data: surface3_spi_ts_data pointer
+ */
+static int surface3_spi_get_gpio_config(struct surface3_ts_data *data)
+{
+ int error;
+ struct device *dev;
+ struct gpio_desc *gpiod;
+ int i;
+
+ dev = &data->spi->dev;
+
+ /* Get the reset lines GPIO pin number */
+ for (i = 0; i < 2; i++) {
+ gpiod = devm_gpiod_get_index(dev, NULL, i, GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod)) {
+ error = PTR_ERR(gpiod);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev,
+ "Failed to get power GPIO %d: %d\n",
+ i,
+ error);
+ return error;
+ }
+
+ data->gpiod_rst[i] = gpiod;
+ }
+
+ return 0;
+}
+
+static int surface3_spi_create_touch_input(struct surface3_ts_data *data)
+{
+ struct input_dev *input;
+ int error;
+
+ input = devm_input_allocate_device(&data->spi->dev);
+ if (!input)
+ return -ENOMEM;
+
+ data->input_dev = input;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, 9600, 0, 0);
+ input_abs_set_res(input, ABS_MT_POSITION_X, 40);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 7200, 0, 0);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, 48);
+ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 1024, 0, 0);
+ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 1024, 0, 0);
+ input_mt_init_slots(input, 10, INPUT_MT_DIRECT);
+
+ input->name = "Surface3 SPI Capacitive TouchScreen";
+ input->phys = "input/ts";
+ input->id.bustype = BUS_SPI;
+ input->id.vendor = 0x045e; /* Microsoft */
+ input->id.product = 0x0001;
+ input->id.version = 0x0000;
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&data->spi->dev,
+ "Failed to register input device: %d", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int surface3_spi_create_pen_input(struct surface3_ts_data *data)
+{
+ struct input_dev *input;
+ int error;
+
+ input = devm_input_allocate_device(&data->spi->dev);
+ if (!input)
+ return -ENOMEM;
+
+ data->pen_input_dev = input;
+ data->pen_tool = BTN_TOOL_PEN;
+
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+ __set_bit(INPUT_PROP_POINTER, input->propbit);
+ input_set_abs_params(input, ABS_X, 0, 9600, 0, 0);
+ input_abs_set_res(input, ABS_X, 40);
+ input_set_abs_params(input, ABS_Y, 0, 7200, 0, 0);
+ input_abs_set_res(input, ABS_Y, 48);
+ input_set_abs_params(input, ABS_PRESSURE, 0, 1024, 0, 0);
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+ input_set_capability(input, EV_KEY, BTN_STYLUS);
+ input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
+ input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
+
+ input->name = "Surface3 SPI Pen Input";
+ input->phys = "input/ts";
+ input->id.bustype = BUS_SPI;
+ input->id.vendor = 0x045e; /* Microsoft */
+ input->id.product = 0x0002;
+ input->id.version = 0x0000;
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&data->spi->dev,
+ "Failed to register input device: %d", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int surface3_spi_probe(struct spi_device *spi)
+{
+ struct surface3_ts_data *data;
+ int error;
+
+ /* Set up SPI*/
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_0;
+ error = spi_setup(spi);
+ if (error)
+ return error;
+
+ data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->spi = spi;
+ spi_set_drvdata(spi, data);
+
+ error = surface3_spi_get_gpio_config(data);
+ if (error)
+ return error;
+
+ surface3_spi_power(data, true);
+ surface3_spi_power(data, false);
+ surface3_spi_power(data, true);
+
+ error = surface3_spi_create_touch_input(data);
+ if (error)
+ return error;
+
+ error = surface3_spi_create_pen_input(data);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&spi->dev, spi->irq,
+ NULL, surface3_spi_irq_handler,
+ IRQF_ONESHOT,
+ "Surface3-irq", data);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int __maybe_unused surface3_spi_suspend(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct surface3_ts_data *data = spi_get_drvdata(spi);
+
+ disable_irq(data->spi->irq);
+
+ surface3_spi_power(data, false);
+
+ return 0;
+}
+
+static int __maybe_unused surface3_spi_resume(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct surface3_ts_data *data = spi_get_drvdata(spi);
+
+ surface3_spi_power(data, true);
+
+ enable_irq(data->spi->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(surface3_spi_pm_ops,
+ surface3_spi_suspend,
+ surface3_spi_resume);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id surface3_spi_acpi_match[] = {
+ { "MSHW0037", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, surface3_spi_acpi_match);
+#endif
+
+static struct spi_driver surface3_spi_driver = {
+ .driver = {
+ .name = "Surface3-spi",
+ .acpi_match_table = ACPI_PTR(surface3_spi_acpi_match),
+ .pm = &surface3_spi_pm_ops,
+ },
+ .probe = surface3_spi_probe,
+};
+
+module_spi_driver(surface3_spi_driver);
+
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_DESCRIPTION("Surface 3 SPI touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/sx8654.c b/drivers/input/touchscreen/sx8654.c
new file mode 100644
index 000000000..de85e57b2
--- /dev/null
+++ b/drivers/input/touchscreen/sx8654.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Semtech SX8654 I2C touchscreen controller.
+ *
+ * Copyright (c) 2015 Armadeus Systems
+ * Sébastien Szymanski <sebastien.szymanski@armadeus.com>
+ *
+ * Using code from:
+ * - sx865x.c
+ * Copyright (c) 2013 U-MoBo Srl
+ * Pierluigi Passaro <p.passaro@u-mobo.com>
+ * - sx8650.c
+ * Copyright (c) 2009 Wayne Roberts
+ * - tsc2007.c
+ * Copyright (c) 2008 Kwangwoo Lee
+ * - ads7846.c
+ * Copyright (c) 2005 David Brownell
+ * Copyright (c) 2006 Nokia Corporation
+ * - corgi_ts.c
+ * Copyright (C) 2004-2005 Richard Purdie
+ * - omap_ts.[hc], ads7846.h, ts_osk.c
+ * Copyright (C) 2002 MontaVista Software
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2005 Dirk Behme
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+/* register addresses */
+#define I2C_REG_TOUCH0 0x00
+#define I2C_REG_TOUCH1 0x01
+#define I2C_REG_CHANMASK 0x04
+#define I2C_REG_IRQMASK 0x22
+#define I2C_REG_IRQSRC 0x23
+#define I2C_REG_SOFTRESET 0x3f
+
+#define I2C_REG_SX8650_STAT 0x05
+#define SX8650_STAT_CONVIRQ BIT(7)
+
+/* commands */
+#define CMD_READ_REGISTER 0x40
+#define CMD_PENTRG 0xe0
+
+/* value for I2C_REG_SOFTRESET */
+#define SOFTRESET_VALUE 0xde
+
+/* bits for I2C_REG_IRQSRC */
+#define IRQ_PENTOUCH_TOUCHCONVDONE BIT(3)
+#define IRQ_PENRELEASE BIT(2)
+
+/* bits for RegTouch1 */
+#define CONDIRQ 0x20
+#define RPDNT_100K 0x00
+#define FILT_7SA 0x03
+
+/* bits for I2C_REG_CHANMASK */
+#define CONV_X BIT(7)
+#define CONV_Y BIT(6)
+
+/* coordinates rate: higher nibble of CTRL0 register */
+#define RATE_MANUAL 0x00
+#define RATE_5000CPS 0xf0
+
+/* power delay: lower nibble of CTRL0 register */
+#define POWDLY_1_1MS 0x0b
+
+/* for sx8650, as we have no pen release IRQ there: timeout in ns following the
+ * last PENIRQ after which we assume the pen is lifted.
+ */
+#define SX8650_PENIRQ_TIMEOUT msecs_to_jiffies(10)
+
+#define MAX_12BIT ((1 << 12) - 1)
+#define MAX_I2C_READ_LEN 10 /* see datasheet section 5.1.5 */
+
+/* channel definition */
+#define CH_X 0x00
+#define CH_Y 0x01
+
+struct sx865x_data {
+ u8 cmd_manual;
+ u8 chan_mask;
+ bool has_irq_penrelease;
+ bool has_reg_irqmask;
+ irq_handler_t irqh;
+};
+
+struct sx8654 {
+ struct input_dev *input;
+ struct i2c_client *client;
+ struct gpio_desc *gpio_reset;
+
+ spinlock_t lock; /* for input reporting from irq/timer */
+ struct timer_list timer;
+
+ struct touchscreen_properties props;
+
+ const struct sx865x_data *data;
+};
+
+static inline void sx865x_penrelease(struct sx8654 *ts)
+{
+ struct input_dev *input_dev = ts->input;
+
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_sync(input_dev);
+}
+
+static void sx865x_penrelease_timer_handler(struct timer_list *t)
+{
+ struct sx8654 *ts = from_timer(ts, t, timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ts->lock, flags);
+ sx865x_penrelease(ts);
+ spin_unlock_irqrestore(&ts->lock, flags);
+ dev_dbg(&ts->client->dev, "penrelease by timer\n");
+}
+
+static irqreturn_t sx8650_irq(int irq, void *handle)
+{
+ struct sx8654 *ts = handle;
+ struct device *dev = &ts->client->dev;
+ int len, i;
+ unsigned long flags;
+ u8 stat;
+ u16 x, y;
+ u16 ch;
+ u16 chdata;
+ __be16 data[MAX_I2C_READ_LEN / sizeof(__be16)];
+ u8 nchan = hweight32(ts->data->chan_mask);
+ u8 readlen = nchan * sizeof(*data);
+
+ stat = i2c_smbus_read_byte_data(ts->client, CMD_READ_REGISTER
+ | I2C_REG_SX8650_STAT);
+
+ if (!(stat & SX8650_STAT_CONVIRQ)) {
+ dev_dbg(dev, "%s ignore stat [0x%02x]", __func__, stat);
+ return IRQ_HANDLED;
+ }
+
+ len = i2c_master_recv(ts->client, (u8 *)data, readlen);
+ if (len != readlen) {
+ dev_dbg(dev, "ignore short recv (%d)\n", len);
+ return IRQ_HANDLED;
+ }
+
+ spin_lock_irqsave(&ts->lock, flags);
+
+ x = 0;
+ y = 0;
+ for (i = 0; i < nchan; i++) {
+ chdata = be16_to_cpu(data[i]);
+
+ if (unlikely(chdata == 0xFFFF)) {
+ dev_dbg(dev, "invalid qualified data @ %d\n", i);
+ continue;
+ } else if (unlikely(chdata & 0x8000)) {
+ dev_warn(dev, "hibit @ %d [0x%04x]\n", i, chdata);
+ continue;
+ }
+
+ ch = chdata >> 12;
+ if (ch == CH_X)
+ x = chdata & MAX_12BIT;
+ else if (ch == CH_Y)
+ y = chdata & MAX_12BIT;
+ else
+ dev_warn(dev, "unknown channel %d [0x%04x]\n", ch,
+ chdata);
+ }
+
+ touchscreen_report_pos(ts->input, &ts->props, x, y, false);
+ input_report_key(ts->input, BTN_TOUCH, 1);
+ input_sync(ts->input);
+ dev_dbg(dev, "point(%4d,%4d)\n", x, y);
+
+ mod_timer(&ts->timer, jiffies + SX8650_PENIRQ_TIMEOUT);
+ spin_unlock_irqrestore(&ts->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sx8654_irq(int irq, void *handle)
+{
+ struct sx8654 *sx8654 = handle;
+ int irqsrc;
+ u8 data[4];
+ unsigned int x, y;
+ int retval;
+
+ irqsrc = i2c_smbus_read_byte_data(sx8654->client,
+ CMD_READ_REGISTER | I2C_REG_IRQSRC);
+ dev_dbg(&sx8654->client->dev, "irqsrc = 0x%x", irqsrc);
+
+ if (irqsrc < 0)
+ goto out;
+
+ if (irqsrc & IRQ_PENRELEASE) {
+ dev_dbg(&sx8654->client->dev, "pen release interrupt");
+
+ input_report_key(sx8654->input, BTN_TOUCH, 0);
+ input_sync(sx8654->input);
+ }
+
+ if (irqsrc & IRQ_PENTOUCH_TOUCHCONVDONE) {
+ dev_dbg(&sx8654->client->dev, "pen touch interrupt");
+
+ retval = i2c_master_recv(sx8654->client, data, sizeof(data));
+ if (retval != sizeof(data))
+ goto out;
+
+ /* invalid data */
+ if (unlikely(data[0] & 0x80 || data[2] & 0x80))
+ goto out;
+
+ x = ((data[0] & 0xf) << 8) | (data[1]);
+ y = ((data[2] & 0xf) << 8) | (data[3]);
+
+ touchscreen_report_pos(sx8654->input, &sx8654->props, x, y,
+ false);
+ input_report_key(sx8654->input, BTN_TOUCH, 1);
+ input_sync(sx8654->input);
+
+ dev_dbg(&sx8654->client->dev, "point(%4d,%4d)\n", x, y);
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int sx8654_reset(struct sx8654 *ts)
+{
+ int err;
+
+ if (ts->gpio_reset) {
+ gpiod_set_value_cansleep(ts->gpio_reset, 1);
+ udelay(2); /* Tpulse > 1µs */
+ gpiod_set_value_cansleep(ts->gpio_reset, 0);
+ } else {
+ dev_dbg(&ts->client->dev, "NRST unavailable, try softreset\n");
+ err = i2c_smbus_write_byte_data(ts->client, I2C_REG_SOFTRESET,
+ SOFTRESET_VALUE);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int sx8654_open(struct input_dev *dev)
+{
+ struct sx8654 *sx8654 = input_get_drvdata(dev);
+ struct i2c_client *client = sx8654->client;
+ int error;
+
+ /* enable pen trigger mode */
+ error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH0,
+ RATE_5000CPS | POWDLY_1_1MS);
+ if (error) {
+ dev_err(&client->dev, "writing to I2C_REG_TOUCH0 failed");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte(client, CMD_PENTRG);
+ if (error) {
+ dev_err(&client->dev, "writing command CMD_PENTRG failed");
+ return error;
+ }
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static void sx8654_close(struct input_dev *dev)
+{
+ struct sx8654 *sx8654 = input_get_drvdata(dev);
+ struct i2c_client *client = sx8654->client;
+ int error;
+
+ disable_irq(client->irq);
+
+ if (!sx8654->data->has_irq_penrelease)
+ del_timer_sync(&sx8654->timer);
+
+ /* enable manual mode mode */
+ error = i2c_smbus_write_byte(client, sx8654->data->cmd_manual);
+ if (error) {
+ dev_err(&client->dev, "writing command CMD_MANUAL failed");
+ return;
+ }
+
+ error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH0, RATE_MANUAL);
+ if (error) {
+ dev_err(&client->dev, "writing to I2C_REG_TOUCH0 failed");
+ return;
+ }
+}
+
+static int sx8654_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sx8654 *sx8654;
+ struct input_dev *input;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ return -ENXIO;
+
+ sx8654 = devm_kzalloc(&client->dev, sizeof(*sx8654), GFP_KERNEL);
+ if (!sx8654)
+ return -ENOMEM;
+
+ sx8654->gpio_reset = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sx8654->gpio_reset)) {
+ error = PTR_ERR(sx8654->gpio_reset);
+ if (error != -EPROBE_DEFER)
+ dev_err(&client->dev, "unable to get reset-gpio: %d\n",
+ error);
+ return error;
+ }
+ dev_dbg(&client->dev, "got GPIO reset pin\n");
+
+ sx8654->data = device_get_match_data(&client->dev);
+ if (!sx8654->data)
+ sx8654->data = (const struct sx865x_data *)id->driver_data;
+ if (!sx8654->data) {
+ dev_err(&client->dev, "invalid or missing device data\n");
+ return -EINVAL;
+ }
+
+ if (!sx8654->data->has_irq_penrelease) {
+ dev_dbg(&client->dev, "use timer for penrelease\n");
+ timer_setup(&sx8654->timer, sx865x_penrelease_timer_handler, 0);
+ spin_lock_init(&sx8654->lock);
+ }
+
+ input = devm_input_allocate_device(&client->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "SX8654 I2C Touchscreen";
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &client->dev;
+ input->open = sx8654_open;
+ input->close = sx8654_close;
+
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+ input_set_capability(input, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input, ABS_X, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, MAX_12BIT, 0, 0);
+
+ touchscreen_parse_properties(input, false, &sx8654->props);
+
+ sx8654->client = client;
+ sx8654->input = input;
+
+ input_set_drvdata(sx8654->input, sx8654);
+
+ error = sx8654_reset(sx8654);
+ if (error) {
+ dev_err(&client->dev, "reset failed");
+ return error;
+ }
+
+ error = i2c_smbus_write_byte_data(client, I2C_REG_CHANMASK,
+ sx8654->data->chan_mask);
+ if (error) {
+ dev_err(&client->dev, "writing to I2C_REG_CHANMASK failed");
+ return error;
+ }
+
+ if (sx8654->data->has_reg_irqmask) {
+ error = i2c_smbus_write_byte_data(client, I2C_REG_IRQMASK,
+ IRQ_PENTOUCH_TOUCHCONVDONE |
+ IRQ_PENRELEASE);
+ if (error) {
+ dev_err(&client->dev, "writing I2C_REG_IRQMASK failed");
+ return error;
+ }
+ }
+
+ error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH1,
+ CONDIRQ | RPDNT_100K | FILT_7SA);
+ if (error) {
+ dev_err(&client->dev, "writing to I2C_REG_TOUCH1 failed");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, sx8654->data->irqh,
+ IRQF_ONESHOT,
+ client->name, sx8654);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to enable IRQ %d, error: %d\n",
+ client->irq, error);
+ return error;
+ }
+
+ /* Disable the IRQ, we'll enable it in sx8654_open() */
+ disable_irq(client->irq);
+
+ error = input_register_device(sx8654->input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct sx865x_data sx8650_data = {
+ .cmd_manual = 0xb0,
+ .has_irq_penrelease = false,
+ .has_reg_irqmask = false,
+ .chan_mask = (CONV_X | CONV_Y),
+ .irqh = sx8650_irq,
+};
+
+static const struct sx865x_data sx8654_data = {
+ .cmd_manual = 0xc0,
+ .has_irq_penrelease = true,
+ .has_reg_irqmask = true,
+ .chan_mask = (CONV_X | CONV_Y),
+ .irqh = sx8654_irq,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id sx8654_of_match[] = {
+ {
+ .compatible = "semtech,sx8650",
+ .data = &sx8650_data,
+ }, {
+ .compatible = "semtech,sx8654",
+ .data = &sx8654_data,
+ }, {
+ .compatible = "semtech,sx8655",
+ .data = &sx8654_data,
+ }, {
+ .compatible = "semtech,sx8656",
+ .data = &sx8654_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sx8654_of_match);
+#endif
+
+static const struct i2c_device_id sx8654_id_table[] = {
+ { .name = "semtech_sx8650", .driver_data = (long)&sx8650_data },
+ { .name = "semtech_sx8654", .driver_data = (long)&sx8654_data },
+ { .name = "semtech_sx8655", .driver_data = (long)&sx8654_data },
+ { .name = "semtech_sx8656", .driver_data = (long)&sx8654_data },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sx8654_id_table);
+
+static struct i2c_driver sx8654_driver = {
+ .driver = {
+ .name = "sx8654",
+ .of_match_table = of_match_ptr(sx8654_of_match),
+ },
+ .id_table = sx8654_id_table,
+ .probe = sx8654_probe,
+};
+module_i2c_driver(sx8654_driver);
+
+MODULE_AUTHOR("Sébastien Szymanski <sebastien.szymanski@armadeus.com>");
+MODULE_DESCRIPTION("Semtech SX8654 I2C Touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c
new file mode 100644
index 000000000..f2fb6a9a1
--- /dev/null
+++ b/drivers/input/touchscreen/ti_am335x_tsc.c
@@ -0,0 +1,567 @@
+/*
+ * TI Touch Screen driver
+ *
+ * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/sort.h>
+#include <linux/pm_wakeirq.h>
+
+#include <linux/mfd/ti_am335x_tscadc.h>
+
+#define ADCFSM_STEPID 0x10
+#define SEQ_SETTLE 275
+#define MAX_12BIT ((1 << 12) - 1)
+
+#define TSC_IRQENB_MASK (IRQENB_FIFO0THRES | IRQENB_EOS | IRQENB_HW_PEN)
+
+static const int config_pins[] = {
+ STEPCONFIG_XPP,
+ STEPCONFIG_XNN,
+ STEPCONFIG_YPP,
+ STEPCONFIG_YNN,
+};
+
+struct titsc {
+ struct input_dev *input;
+ struct ti_tscadc_dev *mfd_tscadc;
+ struct device *dev;
+ unsigned int irq;
+ unsigned int wires;
+ unsigned int x_plate_resistance;
+ bool pen_down;
+ int coordinate_readouts;
+ u32 config_inp[4];
+ u32 bit_xp, bit_xn, bit_yp, bit_yn;
+ u32 inp_xp, inp_xn, inp_yp, inp_yn;
+ u32 step_mask;
+ u32 charge_delay;
+};
+
+static unsigned int titsc_readl(struct titsc *ts, unsigned int reg)
+{
+ return readl(ts->mfd_tscadc->tscadc_base + reg);
+}
+
+static void titsc_writel(struct titsc *tsc, unsigned int reg,
+ unsigned int val)
+{
+ writel(val, tsc->mfd_tscadc->tscadc_base + reg);
+}
+
+static int titsc_config_wires(struct titsc *ts_dev)
+{
+ u32 analog_line[4];
+ u32 wire_order[4];
+ int i, bit_cfg;
+
+ for (i = 0; i < 4; i++) {
+ /*
+ * Get the order in which TSC wires are attached
+ * w.r.t. each of the analog input lines on the EVM.
+ */
+ analog_line[i] = (ts_dev->config_inp[i] & 0xF0) >> 4;
+ wire_order[i] = ts_dev->config_inp[i] & 0x0F;
+ if (WARN_ON(analog_line[i] > 7))
+ return -EINVAL;
+ if (WARN_ON(wire_order[i] > ARRAY_SIZE(config_pins)))
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 4; i++) {
+ int an_line;
+ int wi_order;
+
+ an_line = analog_line[i];
+ wi_order = wire_order[i];
+ bit_cfg = config_pins[wi_order];
+ if (bit_cfg == 0)
+ return -EINVAL;
+ switch (wi_order) {
+ case 0:
+ ts_dev->bit_xp = bit_cfg;
+ ts_dev->inp_xp = an_line;
+ break;
+
+ case 1:
+ ts_dev->bit_xn = bit_cfg;
+ ts_dev->inp_xn = an_line;
+ break;
+
+ case 2:
+ ts_dev->bit_yp = bit_cfg;
+ ts_dev->inp_yp = an_line;
+ break;
+ case 3:
+ ts_dev->bit_yn = bit_cfg;
+ ts_dev->inp_yn = an_line;
+ break;
+ }
+ }
+ return 0;
+}
+
+static void titsc_step_config(struct titsc *ts_dev)
+{
+ unsigned int config;
+ int i, n;
+ int end_step, first_step, tsc_steps;
+ u32 stepenable;
+
+ config = STEPCONFIG_MODE_HWSYNC |
+ STEPCONFIG_AVG_16 | ts_dev->bit_xp |
+ STEPCONFIG_INM_ADCREFM;
+ switch (ts_dev->wires) {
+ case 4:
+ config |= STEPCONFIG_INP(ts_dev->inp_yp) | ts_dev->bit_xn;
+ break;
+ case 5:
+ config |= ts_dev->bit_yn |
+ STEPCONFIG_INP_AN4 | ts_dev->bit_xn |
+ ts_dev->bit_yp;
+ break;
+ case 8:
+ config |= STEPCONFIG_INP(ts_dev->inp_yp) | ts_dev->bit_xn;
+ break;
+ }
+
+ tsc_steps = ts_dev->coordinate_readouts * 2 + 2;
+ first_step = TOTAL_STEPS - tsc_steps;
+ /* Steps 16 to 16-coordinate_readouts is for X */
+ end_step = first_step + tsc_steps;
+ n = 0;
+ for (i = end_step - ts_dev->coordinate_readouts; i < end_step; i++) {
+ titsc_writel(ts_dev, REG_STEPCONFIG(i), config);
+ titsc_writel(ts_dev, REG_STEPDELAY(i),
+ n++ == 0 ? STEPCONFIG_OPENDLY : 0);
+ }
+
+ config = 0;
+ config = STEPCONFIG_MODE_HWSYNC |
+ STEPCONFIG_AVG_16 | ts_dev->bit_yn |
+ STEPCONFIG_INM_ADCREFM;
+ switch (ts_dev->wires) {
+ case 4:
+ config |= ts_dev->bit_yp | STEPCONFIG_INP(ts_dev->inp_xp);
+ break;
+ case 5:
+ config |= ts_dev->bit_xp | STEPCONFIG_INP_AN4 |
+ STEPCONFIG_XNP | STEPCONFIG_YPN;
+ break;
+ case 8:
+ config |= ts_dev->bit_yp | STEPCONFIG_INP(ts_dev->inp_xp);
+ break;
+ }
+
+ /* 1 ... coordinate_readouts is for Y */
+ end_step = first_step + ts_dev->coordinate_readouts;
+ n = 0;
+ for (i = first_step; i < end_step; i++) {
+ titsc_writel(ts_dev, REG_STEPCONFIG(i), config);
+ titsc_writel(ts_dev, REG_STEPDELAY(i),
+ n++ == 0 ? STEPCONFIG_OPENDLY : 0);
+ }
+
+ /* Make CHARGECONFIG same as IDLECONFIG */
+
+ config = titsc_readl(ts_dev, REG_IDLECONFIG);
+ titsc_writel(ts_dev, REG_CHARGECONFIG, config);
+ titsc_writel(ts_dev, REG_CHARGEDELAY, ts_dev->charge_delay);
+
+ /* coordinate_readouts + 1 ... coordinate_readouts + 2 is for Z */
+ config = STEPCONFIG_MODE_HWSYNC |
+ STEPCONFIG_AVG_16 | ts_dev->bit_yp |
+ ts_dev->bit_xn | STEPCONFIG_INM_ADCREFM |
+ STEPCONFIG_INP(ts_dev->inp_xp);
+ titsc_writel(ts_dev, REG_STEPCONFIG(end_step), config);
+ titsc_writel(ts_dev, REG_STEPDELAY(end_step),
+ STEPCONFIG_OPENDLY);
+
+ end_step++;
+ config = STEPCONFIG_MODE_HWSYNC |
+ STEPCONFIG_AVG_16 | ts_dev->bit_yp |
+ ts_dev->bit_xn | STEPCONFIG_INM_ADCREFM |
+ STEPCONFIG_INP(ts_dev->inp_yn);
+ titsc_writel(ts_dev, REG_STEPCONFIG(end_step), config);
+ titsc_writel(ts_dev, REG_STEPDELAY(end_step),
+ STEPCONFIG_OPENDLY);
+
+ /* The steps end ... end - readouts * 2 + 2 and bit 0 for TS_Charge */
+ stepenable = 1;
+ for (i = 0; i < tsc_steps; i++)
+ stepenable |= 1 << (first_step + i + 1);
+
+ ts_dev->step_mask = stepenable;
+ am335x_tsc_se_set_cache(ts_dev->mfd_tscadc, ts_dev->step_mask);
+}
+
+static int titsc_cmp_coord(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+static void titsc_read_coordinates(struct titsc *ts_dev,
+ u32 *x, u32 *y, u32 *z1, u32 *z2)
+{
+ unsigned int yvals[7], xvals[7];
+ unsigned int i, xsum = 0, ysum = 0;
+ unsigned int creads = ts_dev->coordinate_readouts;
+
+ for (i = 0; i < creads; i++) {
+ yvals[i] = titsc_readl(ts_dev, REG_FIFO0);
+ yvals[i] &= 0xfff;
+ }
+
+ *z1 = titsc_readl(ts_dev, REG_FIFO0);
+ *z1 &= 0xfff;
+ *z2 = titsc_readl(ts_dev, REG_FIFO0);
+ *z2 &= 0xfff;
+
+ for (i = 0; i < creads; i++) {
+ xvals[i] = titsc_readl(ts_dev, REG_FIFO0);
+ xvals[i] &= 0xfff;
+ }
+
+ /*
+ * If co-ordinates readouts is less than 4 then
+ * report the average. In case of 4 or more
+ * readouts, sort the co-ordinate samples, drop
+ * min and max values and report the average of
+ * remaining values.
+ */
+ if (creads <= 3) {
+ for (i = 0; i < creads; i++) {
+ ysum += yvals[i];
+ xsum += xvals[i];
+ }
+ ysum /= creads;
+ xsum /= creads;
+ } else {
+ sort(yvals, creads, sizeof(unsigned int),
+ titsc_cmp_coord, NULL);
+ sort(xvals, creads, sizeof(unsigned int),
+ titsc_cmp_coord, NULL);
+ for (i = 1; i < creads - 1; i++) {
+ ysum += yvals[i];
+ xsum += xvals[i];
+ }
+ ysum /= creads - 2;
+ xsum /= creads - 2;
+ }
+ *y = ysum;
+ *x = xsum;
+}
+
+static irqreturn_t titsc_irq(int irq, void *dev)
+{
+ struct titsc *ts_dev = dev;
+ struct input_dev *input_dev = ts_dev->input;
+ unsigned int fsm, status, irqclr = 0;
+ unsigned int x = 0, y = 0;
+ unsigned int z1, z2, z;
+
+ status = titsc_readl(ts_dev, REG_RAWIRQSTATUS);
+ if (status & IRQENB_HW_PEN) {
+ ts_dev->pen_down = true;
+ irqclr |= IRQENB_HW_PEN;
+ pm_stay_awake(ts_dev->dev);
+ }
+
+ if (status & IRQENB_PENUP) {
+ fsm = titsc_readl(ts_dev, REG_ADCFSM);
+ if (fsm == ADCFSM_STEPID) {
+ ts_dev->pen_down = false;
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_report_abs(input_dev, ABS_PRESSURE, 0);
+ input_sync(input_dev);
+ pm_relax(ts_dev->dev);
+ } else {
+ ts_dev->pen_down = true;
+ }
+ irqclr |= IRQENB_PENUP;
+ }
+
+ if (status & IRQENB_EOS)
+ irqclr |= IRQENB_EOS;
+
+ /*
+ * ADC and touchscreen share the IRQ line.
+ * FIFO1 interrupts are used by ADC. Handle FIFO0 IRQs here only
+ */
+ if (status & IRQENB_FIFO0THRES) {
+
+ titsc_read_coordinates(ts_dev, &x, &y, &z1, &z2);
+
+ if (ts_dev->pen_down && z1 != 0 && z2 != 0) {
+ /*
+ * Calculate pressure using formula
+ * Resistance(touch) = x plate resistance *
+ * x position/4096 * ((z2 / z1) - 1)
+ */
+ z = z1 - z2;
+ z *= x;
+ z *= ts_dev->x_plate_resistance;
+ z /= z2;
+ z = (z + 2047) >> 12;
+
+ if (z <= MAX_12BIT) {
+ input_report_abs(input_dev, ABS_X, x);
+ input_report_abs(input_dev, ABS_Y, y);
+ input_report_abs(input_dev, ABS_PRESSURE, z);
+ input_report_key(input_dev, BTN_TOUCH, 1);
+ input_sync(input_dev);
+ }
+ }
+ irqclr |= IRQENB_FIFO0THRES;
+ }
+ if (irqclr) {
+ titsc_writel(ts_dev, REG_IRQSTATUS, irqclr);
+ if (status & IRQENB_EOS)
+ am335x_tsc_se_set_cache(ts_dev->mfd_tscadc,
+ ts_dev->step_mask);
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int titsc_parse_dt(struct platform_device *pdev,
+ struct titsc *ts_dev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ int err;
+
+ if (!node)
+ return -EINVAL;
+
+ err = of_property_read_u32(node, "ti,wires", &ts_dev->wires);
+ if (err < 0)
+ return err;
+ switch (ts_dev->wires) {
+ case 4:
+ case 5:
+ case 8:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32(node, "ti,x-plate-resistance",
+ &ts_dev->x_plate_resistance);
+ if (err < 0)
+ return err;
+
+ /*
+ * Try with the new binding first. If it fails, try again with
+ * bogus, miss-spelled version.
+ */
+ err = of_property_read_u32(node, "ti,coordinate-readouts",
+ &ts_dev->coordinate_readouts);
+ if (err < 0) {
+ dev_warn(&pdev->dev, "please use 'ti,coordinate-readouts' instead\n");
+ err = of_property_read_u32(node, "ti,coordiante-readouts",
+ &ts_dev->coordinate_readouts);
+ }
+
+ if (err < 0)
+ return err;
+
+ if (ts_dev->coordinate_readouts <= 0) {
+ dev_warn(&pdev->dev,
+ "invalid co-ordinate readouts, resetting it to 5\n");
+ ts_dev->coordinate_readouts = 5;
+ }
+
+ err = of_property_read_u32(node, "ti,charge-delay",
+ &ts_dev->charge_delay);
+ /*
+ * If ti,charge-delay value is not specified, then use
+ * CHARGEDLY_OPENDLY as the default value.
+ */
+ if (err < 0) {
+ ts_dev->charge_delay = CHARGEDLY_OPENDLY;
+ dev_warn(&pdev->dev, "ti,charge-delay not specified\n");
+ }
+
+ return of_property_read_u32_array(node, "ti,wire-config",
+ ts_dev->config_inp, ARRAY_SIZE(ts_dev->config_inp));
+}
+
+/*
+ * The functions for inserting/removing driver as a module.
+ */
+
+static int titsc_probe(struct platform_device *pdev)
+{
+ struct titsc *ts_dev;
+ struct input_dev *input_dev;
+ struct ti_tscadc_dev *tscadc_dev = ti_tscadc_dev_get(pdev);
+ int err;
+
+ /* Allocate memory for device */
+ ts_dev = kzalloc(sizeof(*ts_dev), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!ts_dev || !input_dev) {
+ dev_err(&pdev->dev, "failed to allocate memory.\n");
+ err = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ tscadc_dev->tsc = ts_dev;
+ ts_dev->mfd_tscadc = tscadc_dev;
+ ts_dev->input = input_dev;
+ ts_dev->irq = tscadc_dev->irq;
+ ts_dev->dev = &pdev->dev;
+
+ err = titsc_parse_dt(pdev, ts_dev);
+ if (err) {
+ dev_err(&pdev->dev, "Could not find valid DT data.\n");
+ goto err_free_mem;
+ }
+
+ err = request_irq(ts_dev->irq, titsc_irq,
+ IRQF_SHARED, pdev->dev.driver->name, ts_dev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to allocate irq.\n");
+ goto err_free_mem;
+ }
+
+ device_init_wakeup(&pdev->dev, true);
+ err = dev_pm_set_wake_irq(&pdev->dev, ts_dev->irq);
+ if (err)
+ dev_err(&pdev->dev, "irq wake enable failed.\n");
+
+ titsc_writel(ts_dev, REG_IRQSTATUS, TSC_IRQENB_MASK);
+ titsc_writel(ts_dev, REG_IRQENABLE, IRQENB_FIFO0THRES);
+ titsc_writel(ts_dev, REG_IRQENABLE, IRQENB_EOS);
+ err = titsc_config_wires(ts_dev);
+ if (err) {
+ dev_err(&pdev->dev, "wrong i/p wire configuration\n");
+ goto err_free_irq;
+ }
+ titsc_step_config(ts_dev);
+ titsc_writel(ts_dev, REG_FIFO0THR,
+ ts_dev->coordinate_readouts * 2 + 2 - 1);
+
+ input_dev->name = "ti-tsc";
+ input_dev->dev.parent = &pdev->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0);
+
+ /* register to the input system */
+ err = input_register_device(input_dev);
+ if (err)
+ goto err_free_irq;
+
+ platform_set_drvdata(pdev, ts_dev);
+ return 0;
+
+err_free_irq:
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+ free_irq(ts_dev->irq, ts_dev);
+err_free_mem:
+ input_free_device(input_dev);
+ kfree(ts_dev);
+ return err;
+}
+
+static int titsc_remove(struct platform_device *pdev)
+{
+ struct titsc *ts_dev = platform_get_drvdata(pdev);
+ u32 steps;
+
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+ free_irq(ts_dev->irq, ts_dev);
+
+ /* total steps followed by the enable mask */
+ steps = 2 * ts_dev->coordinate_readouts + 2;
+ steps = (1 << steps) - 1;
+ am335x_tsc_se_clr(ts_dev->mfd_tscadc, steps);
+
+ input_unregister_device(ts_dev->input);
+
+ kfree(ts_dev);
+ return 0;
+}
+
+static int __maybe_unused titsc_suspend(struct device *dev)
+{
+ struct titsc *ts_dev = dev_get_drvdata(dev);
+ unsigned int idle;
+
+ if (device_may_wakeup(dev)) {
+ titsc_writel(ts_dev, REG_IRQSTATUS, TSC_IRQENB_MASK);
+ idle = titsc_readl(ts_dev, REG_IRQENABLE);
+ titsc_writel(ts_dev, REG_IRQENABLE,
+ (idle | IRQENB_HW_PEN));
+ titsc_writel(ts_dev, REG_IRQWAKEUP, IRQWKUP_ENB);
+ }
+ return 0;
+}
+
+static int __maybe_unused titsc_resume(struct device *dev)
+{
+ struct titsc *ts_dev = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev)) {
+ titsc_writel(ts_dev, REG_IRQWAKEUP,
+ 0x00);
+ titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN);
+ pm_relax(dev);
+ }
+ titsc_step_config(ts_dev);
+ titsc_writel(ts_dev, REG_FIFO0THR,
+ ts_dev->coordinate_readouts * 2 + 2 - 1);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(titsc_pm_ops, titsc_suspend, titsc_resume);
+
+static const struct of_device_id ti_tsc_dt_ids[] = {
+ { .compatible = "ti,am3359-tsc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ti_tsc_dt_ids);
+
+static struct platform_driver ti_tsc_driver = {
+ .probe = titsc_probe,
+ .remove = titsc_remove,
+ .driver = {
+ .name = "TI-am335x-tsc",
+ .pm = &titsc_pm_ops,
+ .of_match_table = ti_tsc_dt_ids,
+ },
+};
+module_platform_driver(ti_tsc_driver);
+
+MODULE_DESCRIPTION("TI touchscreen controller driver");
+MODULE_AUTHOR("Rachna Patil <rachna@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/touchit213.c b/drivers/input/touchscreen/touchit213.c
new file mode 100644
index 000000000..fb49687da
--- /dev/null
+++ b/drivers/input/touchscreen/touchit213.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sahara TouchIT-213 serial touchscreen driver
+ *
+ * Copyright (c) 2007-2008 Claudio Nieder <private@claudio.ch>
+ *
+ * Based on Touchright driver (drivers/input/touchscreen/touchright.c)
+ * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com>
+ * Copyright (c) 2004 Vojtech Pavlik
+ * and Dan Streetman <ddstreet@ieee.org>
+ */
+
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Sahara TouchIT-213 serial touchscreen driver"
+
+MODULE_AUTHOR("Claudio Nieder <private@claudio.ch>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+/*
+ * Data is received through COM1 at 9600bit/s,8bit,no parity in packets
+ * of 5 byte each.
+ *
+ * +--------+ +--------+ +--------+ +--------+ +--------+
+ * |1000000p| |0xxxxxxx| |0xxxxxxx| |0yyyyyyy| |0yyyyyyy|
+ * +--------+ +--------+ +--------+ +--------+ +--------+
+ * MSB LSB MSB LSB
+ *
+ * The value of p is 1 as long as the screen is touched and 0 when
+ * reporting the location where touching stopped, e.g. where the pen was
+ * lifted from the screen.
+ *
+ * When holding the screen in landscape mode as the BIOS text output is
+ * presented, x is the horizontal axis with values growing from left to
+ * right and y is the vertical axis with values growing from top to
+ * bottom.
+ *
+ * When holding the screen in portrait mode with the Sahara logo in its
+ * correct position, x ist the vertical axis with values growing from
+ * top to bottom and y is the horizontal axis with values growing from
+ * right to left.
+ */
+
+#define T213_FORMAT_TOUCH_BIT 0x01
+#define T213_FORMAT_STATUS_BYTE 0x80
+#define T213_FORMAT_STATUS_MASK ~T213_FORMAT_TOUCH_BIT
+
+/*
+ * On my Sahara Touch-IT 213 I have observed x values from 0 to 0x7f0
+ * and y values from 0x1d to 0x7e9, so the actual measurement is
+ * probably done with an 11 bit precision.
+ */
+#define T213_MIN_XC 0
+#define T213_MAX_XC 0x07ff
+#define T213_MIN_YC 0
+#define T213_MAX_YC 0x07ff
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct touchit213 {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char csum;
+ unsigned char data[5];
+ char phys[32];
+};
+
+static irqreturn_t touchit213_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct touchit213 *touchit213 = serio_get_drvdata(serio);
+ struct input_dev *dev = touchit213->dev;
+
+ touchit213->data[touchit213->idx] = data;
+
+ switch (touchit213->idx++) {
+ case 0:
+ if ((touchit213->data[0] & T213_FORMAT_STATUS_MASK) !=
+ T213_FORMAT_STATUS_BYTE) {
+ pr_debug("unsynchronized data: 0x%02x\n", data);
+ touchit213->idx = 0;
+ }
+ break;
+
+ case 4:
+ touchit213->idx = 0;
+ input_report_abs(dev, ABS_X,
+ (touchit213->data[1] << 7) | touchit213->data[2]);
+ input_report_abs(dev, ABS_Y,
+ (touchit213->data[3] << 7) | touchit213->data[4]);
+ input_report_key(dev, BTN_TOUCH,
+ touchit213->data[0] & T213_FORMAT_TOUCH_BIT);
+ input_sync(dev);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * touchit213_disconnect() is the opposite of touchit213_connect()
+ */
+
+static void touchit213_disconnect(struct serio *serio)
+{
+ struct touchit213 *touchit213 = serio_get_drvdata(serio);
+
+ input_get_device(touchit213->dev);
+ input_unregister_device(touchit213->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(touchit213->dev);
+ kfree(touchit213);
+}
+
+/*
+ * touchit213_connect() is the routine that is called when someone adds a
+ * new serio device that supports the Touchright protocol and registers it as
+ * an input device.
+ */
+
+static int touchit213_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct touchit213 *touchit213;
+ struct input_dev *input_dev;
+ int err;
+
+ touchit213 = kzalloc(sizeof(struct touchit213), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!touchit213 || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ touchit213->serio = serio;
+ touchit213->dev = input_dev;
+ snprintf(touchit213->phys, sizeof(touchit213->phys),
+ "%s/input0", serio->phys);
+
+ input_dev->name = "Sahara Touch-iT213 Serial TouchScreen";
+ input_dev->phys = touchit213->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_TOUCHIT213;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(touchit213->dev, ABS_X,
+ T213_MIN_XC, T213_MAX_XC, 0, 0);
+ input_set_abs_params(touchit213->dev, ABS_Y,
+ T213_MIN_YC, T213_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, touchit213);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(touchit213->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(touchit213);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id touchit213_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_TOUCHIT213,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, touchit213_serio_ids);
+
+static struct serio_driver touchit213_drv = {
+ .driver = {
+ .name = "touchit213",
+ },
+ .description = DRIVER_DESC,
+ .id_table = touchit213_serio_ids,
+ .interrupt = touchit213_interrupt,
+ .connect = touchit213_connect,
+ .disconnect = touchit213_disconnect,
+};
+
+module_serio_driver(touchit213_drv);
diff --git a/drivers/input/touchscreen/touchright.c b/drivers/input/touchscreen/touchright.c
new file mode 100644
index 000000000..3cd58a13e
--- /dev/null
+++ b/drivers/input/touchscreen/touchright.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Touchright serial touchscreen driver
+ *
+ * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com>
+ *
+ * Based on MicroTouch driver (drivers/input/touchscreen/mtouch.c)
+ * Copyright (c) 2004 Vojtech Pavlik
+ * and Dan Streetman <ddstreet@ieee.org>
+ */
+
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Touchright serial touchscreen driver"
+
+MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define TR_FORMAT_TOUCH_BIT 0x01
+#define TR_FORMAT_STATUS_BYTE 0x40
+#define TR_FORMAT_STATUS_MASK ~TR_FORMAT_TOUCH_BIT
+
+#define TR_LENGTH 5
+
+#define TR_MIN_XC 0
+#define TR_MAX_XC 0x1ff
+#define TR_MIN_YC 0
+#define TR_MAX_YC 0x1ff
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct tr {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ unsigned char data[TR_LENGTH];
+ char phys[32];
+};
+
+static irqreturn_t tr_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct tr *tr = serio_get_drvdata(serio);
+ struct input_dev *dev = tr->dev;
+
+ tr->data[tr->idx] = data;
+
+ if ((tr->data[0] & TR_FORMAT_STATUS_MASK) == TR_FORMAT_STATUS_BYTE) {
+ if (++tr->idx == TR_LENGTH) {
+ input_report_abs(dev, ABS_X,
+ (tr->data[1] << 5) | (tr->data[2] >> 1));
+ input_report_abs(dev, ABS_Y,
+ (tr->data[3] << 5) | (tr->data[4] >> 1));
+ input_report_key(dev, BTN_TOUCH,
+ tr->data[0] & TR_FORMAT_TOUCH_BIT);
+ input_sync(dev);
+ tr->idx = 0;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * tr_disconnect() is the opposite of tr_connect()
+ */
+
+static void tr_disconnect(struct serio *serio)
+{
+ struct tr *tr = serio_get_drvdata(serio);
+
+ input_get_device(tr->dev);
+ input_unregister_device(tr->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(tr->dev);
+ kfree(tr);
+}
+
+/*
+ * tr_connect() is the routine that is called when someone adds a
+ * new serio device that supports the Touchright protocol and registers it as
+ * an input device.
+ */
+
+static int tr_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct tr *tr;
+ struct input_dev *input_dev;
+ int err;
+
+ tr = kzalloc(sizeof(struct tr), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!tr || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ tr->serio = serio;
+ tr->dev = input_dev;
+ snprintf(tr->phys, sizeof(tr->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "Touchright Serial TouchScreen";
+ input_dev->phys = tr->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_TOUCHRIGHT;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(tr->dev, ABS_X, TR_MIN_XC, TR_MAX_XC, 0, 0);
+ input_set_abs_params(tr->dev, ABS_Y, TR_MIN_YC, TR_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, tr);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(tr->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(tr);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id tr_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_TOUCHRIGHT,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, tr_serio_ids);
+
+static struct serio_driver tr_drv = {
+ .driver = {
+ .name = "touchright",
+ },
+ .description = DRIVER_DESC,
+ .id_table = tr_serio_ids,
+ .interrupt = tr_interrupt,
+ .connect = tr_connect,
+ .disconnect = tr_disconnect,
+};
+
+module_serio_driver(tr_drv);
diff --git a/drivers/input/touchscreen/touchwin.c b/drivers/input/touchscreen/touchwin.c
new file mode 100644
index 000000000..bde3c6ee3
--- /dev/null
+++ b/drivers/input/touchscreen/touchwin.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Touchwindow serial touchscreen driver
+ *
+ * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com>
+ *
+ * Based on MicroTouch driver (drivers/input/touchscreen/mtouch.c)
+ * Copyright (c) 2004 Vojtech Pavlik
+ * and Dan Streetman <ddstreet@ieee.org>
+ */
+
+
+/*
+ * 2005/02/19 Rick Koch:
+ * The Touchwindow I used is made by Edmark Corp. and
+ * constantly outputs a stream of 0's unless it is touched.
+ * It then outputs 3 bytes: X, Y, and a copy of Y.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define DRIVER_DESC "Touchwindow serial touchscreen driver"
+
+MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Definitions & global arrays.
+ */
+
+#define TW_LENGTH 3
+
+#define TW_MIN_XC 0
+#define TW_MAX_XC 0xff
+#define TW_MIN_YC 0
+#define TW_MAX_YC 0xff
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct tw {
+ struct input_dev *dev;
+ struct serio *serio;
+ int idx;
+ int touched;
+ unsigned char data[TW_LENGTH];
+ char phys[32];
+};
+
+static irqreturn_t tw_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct tw *tw = serio_get_drvdata(serio);
+ struct input_dev *dev = tw->dev;
+
+ if (data) { /* touch */
+ tw->touched = 1;
+ tw->data[tw->idx++] = data;
+ /* verify length and that the two Y's are the same */
+ if (tw->idx == TW_LENGTH && tw->data[1] == tw->data[2]) {
+ input_report_abs(dev, ABS_X, tw->data[0]);
+ input_report_abs(dev, ABS_Y, tw->data[1]);
+ input_report_key(dev, BTN_TOUCH, 1);
+ input_sync(dev);
+ tw->idx = 0;
+ }
+ } else if (tw->touched) { /* untouch */
+ input_report_key(dev, BTN_TOUCH, 0);
+ input_sync(dev);
+ tw->idx = 0;
+ tw->touched = 0;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * tw_disconnect() is the opposite of tw_connect()
+ */
+
+static void tw_disconnect(struct serio *serio)
+{
+ struct tw *tw = serio_get_drvdata(serio);
+
+ input_get_device(tw->dev);
+ input_unregister_device(tw->dev);
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_put_device(tw->dev);
+ kfree(tw);
+}
+
+/*
+ * tw_connect() is the routine that is called when someone adds a
+ * new serio device that supports the Touchwin protocol and registers it as
+ * an input device.
+ */
+
+static int tw_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct tw *tw;
+ struct input_dev *input_dev;
+ int err;
+
+ tw = kzalloc(sizeof(struct tw), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!tw || !input_dev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ tw->serio = serio;
+ tw->dev = input_dev;
+ snprintf(tw->phys, sizeof(tw->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "Touchwindow Serial TouchScreen";
+ input_dev->phys = tw->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_TOUCHWIN;
+ input_dev->id.product = 0;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(tw->dev, ABS_X, TW_MIN_XC, TW_MAX_XC, 0, 0);
+ input_set_abs_params(tw->dev, ABS_Y, TW_MIN_YC, TW_MAX_YC, 0, 0);
+
+ serio_set_drvdata(serio, tw);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(tw->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(tw);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static const struct serio_device_id tw_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_TOUCHWIN,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, tw_serio_ids);
+
+static struct serio_driver tw_drv = {
+ .driver = {
+ .name = "touchwin",
+ },
+ .description = DRIVER_DESC,
+ .id_table = tw_serio_ids,
+ .interrupt = tw_interrupt,
+ .connect = tw_connect,
+ .disconnect = tw_disconnect,
+};
+
+module_serio_driver(tw_drv);
diff --git a/drivers/input/touchscreen/tps6507x-ts.c b/drivers/input/touchscreen/tps6507x-ts.c
new file mode 100644
index 000000000..357a3108f
--- /dev/null
+++ b/drivers/input/touchscreen/tps6507x-ts.c
@@ -0,0 +1,294 @@
+/*
+ * Touchscreen driver for the tps6507x chip.
+ *
+ * Copyright (c) 2009 RidgeRun (todd.fischer@ridgerun.com)
+ *
+ * Credits:
+ *
+ * Using code from tsc2007, MtekVision Co., Ltd.
+ *
+ * For licencing details see kernel-base/COPYING
+ *
+ * TPS65070, TPS65073, TPS650731, and TPS650732 support
+ * 10 bit touch screen interface.
+ */
+
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/tps6507x.h>
+#include <linux/input/tps6507x-ts.h>
+#include <linux/delay.h>
+
+#define TSC_DEFAULT_POLL_PERIOD 30 /* ms */
+#define TPS_DEFAULT_MIN_PRESSURE 0x30
+#define MAX_10BIT ((1 << 10) - 1)
+
+#define TPS6507X_ADCONFIG_CONVERT_TS (TPS6507X_ADCONFIG_AD_ENABLE | \
+ TPS6507X_ADCONFIG_START_CONVERSION | \
+ TPS6507X_ADCONFIG_INPUT_REAL_TSC)
+#define TPS6507X_ADCONFIG_POWER_DOWN_TS (TPS6507X_ADCONFIG_INPUT_REAL_TSC)
+
+struct ts_event {
+ u16 x;
+ u16 y;
+ u16 pressure;
+};
+
+struct tps6507x_ts {
+ struct device *dev;
+ struct input_dev *input;
+ struct tps6507x_dev *mfd;
+ char phys[32];
+ struct ts_event tc;
+ u16 min_pressure;
+ bool pendown;
+};
+
+static int tps6507x_read_u8(struct tps6507x_ts *tsc, u8 reg, u8 *data)
+{
+ return tsc->mfd->read_dev(tsc->mfd, reg, 1, data);
+}
+
+static int tps6507x_write_u8(struct tps6507x_ts *tsc, u8 reg, u8 data)
+{
+ return tsc->mfd->write_dev(tsc->mfd, reg, 1, &data);
+}
+
+static s32 tps6507x_adc_conversion(struct tps6507x_ts *tsc,
+ u8 tsc_mode, u16 *value)
+{
+ s32 ret;
+ u8 adc_status;
+ u8 result;
+
+ /* Route input signal to A/D converter */
+
+ ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE, tsc_mode);
+ if (ret) {
+ dev_err(tsc->dev, "TSC mode read failed\n");
+ goto err;
+ }
+
+ /* Start A/D conversion */
+
+ ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG,
+ TPS6507X_ADCONFIG_CONVERT_TS);
+ if (ret) {
+ dev_err(tsc->dev, "ADC config write failed\n");
+ return ret;
+ }
+
+ do {
+ ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADCONFIG,
+ &adc_status);
+ if (ret) {
+ dev_err(tsc->dev, "ADC config read failed\n");
+ goto err;
+ }
+ } while (adc_status & TPS6507X_ADCONFIG_START_CONVERSION);
+
+ ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_2, &result);
+ if (ret) {
+ dev_err(tsc->dev, "ADC result 2 read failed\n");
+ goto err;
+ }
+
+ *value = (result & TPS6507X_REG_ADRESULT_2_MASK) << 8;
+
+ ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_1, &result);
+ if (ret) {
+ dev_err(tsc->dev, "ADC result 1 read failed\n");
+ goto err;
+ }
+
+ *value |= result;
+
+ dev_dbg(tsc->dev, "TSC channel %d = 0x%X\n", tsc_mode, *value);
+
+err:
+ return ret;
+}
+
+/* Need to call tps6507x_adc_standby() after using A/D converter for the
+ * touch screen interrupt to work properly.
+ */
+
+static s32 tps6507x_adc_standby(struct tps6507x_ts *tsc)
+{
+ s32 ret;
+ s32 loops = 0;
+ u8 val;
+
+ ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG,
+ TPS6507X_ADCONFIG_INPUT_TSC);
+ if (ret)
+ return ret;
+
+ ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE,
+ TPS6507X_TSCMODE_STANDBY);
+ if (ret)
+ return ret;
+
+ ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val);
+ if (ret)
+ return ret;
+
+ while (val & TPS6507X_REG_TSC_INT) {
+ mdelay(10);
+ ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val);
+ if (ret)
+ return ret;
+ loops++;
+ }
+
+ return ret;
+}
+
+static void tps6507x_ts_poll(struct input_dev *input_dev)
+{
+ struct tps6507x_ts *tsc = input_get_drvdata(input_dev);
+ bool pendown;
+ s32 ret;
+
+ ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_PRESSURE,
+ &tsc->tc.pressure);
+ if (ret)
+ goto done;
+
+ pendown = tsc->tc.pressure > tsc->min_pressure;
+
+ if (unlikely(!pendown && tsc->pendown)) {
+ dev_dbg(tsc->dev, "UP\n");
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_report_abs(input_dev, ABS_PRESSURE, 0);
+ input_sync(input_dev);
+ tsc->pendown = false;
+ }
+
+ if (pendown) {
+
+ if (!tsc->pendown) {
+ dev_dbg(tsc->dev, "DOWN\n");
+ input_report_key(input_dev, BTN_TOUCH, 1);
+ } else
+ dev_dbg(tsc->dev, "still down\n");
+
+ ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_X_POSITION,
+ &tsc->tc.x);
+ if (ret)
+ goto done;
+
+ ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_Y_POSITION,
+ &tsc->tc.y);
+ if (ret)
+ goto done;
+
+ input_report_abs(input_dev, ABS_X, tsc->tc.x);
+ input_report_abs(input_dev, ABS_Y, tsc->tc.y);
+ input_report_abs(input_dev, ABS_PRESSURE, tsc->tc.pressure);
+ input_sync(input_dev);
+ tsc->pendown = true;
+ }
+
+done:
+ tps6507x_adc_standby(tsc);
+}
+
+static int tps6507x_ts_probe(struct platform_device *pdev)
+{
+ struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent);
+ const struct tps6507x_board *tps_board;
+ const struct touchscreen_init_data *init_data;
+ struct tps6507x_ts *tsc;
+ struct input_dev *input_dev;
+ int error;
+
+ /*
+ * tps_board points to pmic related constants
+ * coming from the board-evm file.
+ */
+ tps_board = dev_get_platdata(tps6507x_dev->dev);
+ if (!tps_board) {
+ dev_err(tps6507x_dev->dev,
+ "Could not find tps6507x platform data\n");
+ return -ENODEV;
+ }
+
+ /*
+ * init_data points to array of regulator_init structures
+ * coming from the board-evm file.
+ */
+ init_data = tps_board->tps6507x_ts_init_data;
+
+ tsc = devm_kzalloc(&pdev->dev, sizeof(struct tps6507x_ts), GFP_KERNEL);
+ if (!tsc) {
+ dev_err(tps6507x_dev->dev, "failed to allocate driver data\n");
+ return -ENOMEM;
+ }
+
+ tsc->mfd = tps6507x_dev;
+ tsc->dev = tps6507x_dev->dev;
+ tsc->min_pressure = init_data ?
+ init_data->min_pressure : TPS_DEFAULT_MIN_PRESSURE;
+
+ snprintf(tsc->phys, sizeof(tsc->phys),
+ "%s/input0", dev_name(tsc->dev));
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev) {
+ dev_err(tsc->dev, "Failed to allocate polled input device.\n");
+ return -ENOMEM;
+ }
+
+ tsc->input = input_dev;
+ input_set_drvdata(input_dev, tsc);
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, 0, MAX_10BIT, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MAX_10BIT, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_10BIT, 0, 0);
+
+ input_dev->name = "TPS6507x Touchscreen";
+ input_dev->phys = tsc->phys;
+ input_dev->dev.parent = tsc->dev;
+ input_dev->id.bustype = BUS_I2C;
+ if (init_data) {
+ input_dev->id.vendor = init_data->vendor;
+ input_dev->id.product = init_data->product;
+ input_dev->id.version = init_data->version;
+ }
+
+ error = tps6507x_adc_standby(tsc);
+ if (error)
+ return error;
+
+ error = input_setup_polling(input_dev, tps6507x_ts_poll);
+ if (error)
+ return error;
+
+ input_set_poll_interval(input_dev,
+ init_data ? init_data->poll_period :
+ TSC_DEFAULT_POLL_PERIOD);
+
+ error = input_register_device(input_dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static struct platform_driver tps6507x_ts_driver = {
+ .driver = {
+ .name = "tps6507x-ts",
+ },
+ .probe = tps6507x_ts_probe,
+};
+module_platform_driver(tps6507x_ts_driver);
+
+MODULE_AUTHOR("Todd Fischer <todd.fischer@ridgerun.com>");
+MODULE_DESCRIPTION("TPS6507x - TouchScreen driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:tps6507x-ts");
diff --git a/drivers/input/touchscreen/ts4800-ts.c b/drivers/input/touchscreen/ts4800-ts.c
new file mode 100644
index 000000000..6cf66aadc
--- /dev/null
+++ b/drivers/input/touchscreen/ts4800-ts.c
@@ -0,0 +1,223 @@
+/*
+ * Touchscreen driver for the TS-4800 board
+ *
+ * Copyright (c) 2015 - Savoir-faire Linux
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/bitops.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* polling interval in ms */
+#define POLL_INTERVAL 3
+
+#define DEBOUNCE_COUNT 1
+
+/* sensor values are 12-bit wide */
+#define MAX_12BIT ((1 << 12) - 1)
+
+#define PENDOWN_MASK 0x1
+
+#define X_OFFSET 0x0
+#define Y_OFFSET 0x2
+
+struct ts4800_ts {
+ struct input_dev *input;
+ struct device *dev;
+ char phys[32];
+
+ void __iomem *base;
+ struct regmap *regmap;
+ unsigned int reg;
+ unsigned int bit;
+
+ bool pendown;
+ int debounce;
+};
+
+static int ts4800_ts_open(struct input_dev *input_dev)
+{
+ struct ts4800_ts *ts = input_get_drvdata(input_dev);
+ int error;
+
+ ts->pendown = false;
+ ts->debounce = DEBOUNCE_COUNT;
+
+ error = regmap_update_bits(ts->regmap, ts->reg, ts->bit, ts->bit);
+ if (error) {
+ dev_warn(ts->dev, "Failed to enable touchscreen: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void ts4800_ts_close(struct input_dev *input_dev)
+{
+ struct ts4800_ts *ts = input_get_drvdata(input_dev);
+ int ret;
+
+ ret = regmap_update_bits(ts->regmap, ts->reg, ts->bit, 0);
+ if (ret)
+ dev_warn(ts->dev, "Failed to disable touchscreen\n");
+
+}
+
+static void ts4800_ts_poll(struct input_dev *input_dev)
+{
+ struct ts4800_ts *ts = input_get_drvdata(input_dev);
+ u16 last_x = readw(ts->base + X_OFFSET);
+ u16 last_y = readw(ts->base + Y_OFFSET);
+ bool pendown = last_x & PENDOWN_MASK;
+
+ if (pendown) {
+ if (ts->debounce) {
+ ts->debounce--;
+ return;
+ }
+
+ if (!ts->pendown) {
+ input_report_key(input_dev, BTN_TOUCH, 1);
+ ts->pendown = true;
+ }
+
+ last_x = ((~last_x) >> 4) & MAX_12BIT;
+ last_y = ((~last_y) >> 4) & MAX_12BIT;
+
+ input_report_abs(input_dev, ABS_X, last_x);
+ input_report_abs(input_dev, ABS_Y, last_y);
+ input_sync(input_dev);
+ } else if (ts->pendown) {
+ ts->pendown = false;
+ ts->debounce = DEBOUNCE_COUNT;
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_sync(input_dev);
+ }
+}
+
+static int ts4800_parse_dt(struct platform_device *pdev,
+ struct ts4800_ts *ts)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *syscon_np;
+ u32 reg, bit;
+ int error;
+
+ syscon_np = of_parse_phandle(np, "syscon", 0);
+ if (!syscon_np) {
+ dev_err(dev, "no syscon property\n");
+ return -ENODEV;
+ }
+
+ ts->regmap = syscon_node_to_regmap(syscon_np);
+ of_node_put(syscon_np);
+ if (IS_ERR(ts->regmap)) {
+ dev_err(dev, "cannot get parent's regmap\n");
+ return PTR_ERR(ts->regmap);
+ }
+
+ error = of_property_read_u32_index(np, "syscon", 1, &reg);
+ if (error < 0) {
+ dev_err(dev, "no offset in syscon\n");
+ return error;
+ }
+
+ ts->reg = reg;
+
+ error = of_property_read_u32_index(np, "syscon", 2, &bit);
+ if (error < 0) {
+ dev_err(dev, "no bit in syscon\n");
+ return error;
+ }
+
+ ts->bit = BIT(bit);
+
+ return 0;
+}
+
+static int ts4800_ts_probe(struct platform_device *pdev)
+{
+ struct input_dev *input_dev;
+ struct ts4800_ts *ts;
+ int error;
+
+ ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ error = ts4800_parse_dt(pdev, ts);
+ if (error)
+ return error;
+
+ ts->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->base))
+ return PTR_ERR(ts->base);
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&pdev->dev));
+ ts->input = input_dev;
+ ts->dev = &pdev->dev;
+
+ input_set_drvdata(input_dev, ts);
+
+ input_dev->name = "TS-4800 Touchscreen";
+ input_dev->phys = ts->phys;
+
+ input_dev->open = ts4800_ts_open;
+ input_dev->close = ts4800_ts_close;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0);
+
+ error = input_setup_polling(input_dev, ts4800_ts_poll);
+ if (error) {
+ dev_err(&pdev->dev, "Unable to set up polling: %d\n", error);
+ return error;
+ }
+
+ input_set_poll_interval(input_dev, POLL_INTERVAL);
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&pdev->dev,
+ "Unable to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id ts4800_ts_of_match[] = {
+ { .compatible = "technologic,ts4800-ts", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ts4800_ts_of_match);
+
+static struct platform_driver ts4800_ts_driver = {
+ .driver = {
+ .name = "ts4800-ts",
+ .of_match_table = ts4800_ts_of_match,
+ },
+ .probe = ts4800_ts_probe,
+};
+module_platform_driver(ts4800_ts_driver);
+
+MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");
+MODULE_DESCRIPTION("TS-4800 Touchscreen Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ts4800_ts");
diff --git a/drivers/input/touchscreen/tsc2004.c b/drivers/input/touchscreen/tsc2004.c
new file mode 100644
index 000000000..a9565353e
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2004.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * TSC2004 touchscreen driver
+ *
+ * Copyright (C) 2015 QWERTY Embedded Design
+ * Copyright (C) 2015 EMAC Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include "tsc200x-core.h"
+
+static const struct input_id tsc2004_input_id = {
+ .bustype = BUS_I2C,
+ .product = 2004,
+};
+
+static int tsc2004_cmd(struct device *dev, u8 cmd)
+{
+ u8 tx = TSC200X_CMD | TSC200X_CMD_12BIT | cmd;
+ s32 data;
+ struct i2c_client *i2c = to_i2c_client(dev);
+
+ data = i2c_smbus_write_byte(i2c, tx);
+ if (data < 0) {
+ dev_err(dev, "%s: failed, command: %x i2c error: %d\n",
+ __func__, cmd, data);
+ return data;
+ }
+
+ return 0;
+}
+
+static int tsc2004_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+
+{
+ return tsc200x_probe(&i2c->dev, i2c->irq, &tsc2004_input_id,
+ devm_regmap_init_i2c(i2c, &tsc200x_regmap_config),
+ tsc2004_cmd);
+}
+
+static void tsc2004_remove(struct i2c_client *i2c)
+{
+ tsc200x_remove(&i2c->dev);
+}
+
+static const struct i2c_device_id tsc2004_idtable[] = {
+ { "tsc2004", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tsc2004_idtable);
+
+#ifdef CONFIG_OF
+static const struct of_device_id tsc2004_of_match[] = {
+ { .compatible = "ti,tsc2004" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tsc2004_of_match);
+#endif
+
+static struct i2c_driver tsc2004_driver = {
+ .driver = {
+ .name = "tsc2004",
+ .of_match_table = of_match_ptr(tsc2004_of_match),
+ .pm = &tsc200x_pm_ops,
+ },
+ .id_table = tsc2004_idtable,
+ .probe = tsc2004_probe,
+ .remove = tsc2004_remove,
+};
+module_i2c_driver(tsc2004_driver);
+
+MODULE_AUTHOR("Michael Welling <mwelling@ieee.org>");
+MODULE_DESCRIPTION("TSC2004 Touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
new file mode 100644
index 000000000..555dfe98b
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * TSC2005 touchscreen driver
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2015 QWERTY Embedded Design
+ * Copyright (C) 2015 EMAC Inc.
+ *
+ * Based on original tsc2005.c by Lauri Leukkunen <lauri.leukkunen@nokia.com>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include "tsc200x-core.h"
+
+static const struct input_id tsc2005_input_id = {
+ .bustype = BUS_SPI,
+ .product = 2005,
+};
+
+static int tsc2005_cmd(struct device *dev, u8 cmd)
+{
+ u8 tx = TSC200X_CMD | TSC200X_CMD_12BIT | cmd;
+ struct spi_transfer xfer = {
+ .tx_buf = &tx,
+ .len = 1,
+ .bits_per_word = 8,
+ };
+ struct spi_message msg;
+ struct spi_device *spi = to_spi_device(dev);
+ int error;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ error = spi_sync(spi, &msg);
+ if (error) {
+ dev_err(dev, "%s: failed, command: %x, spi error: %d\n",
+ __func__, cmd, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int tsc2005_probe(struct spi_device *spi)
+{
+ int error;
+
+ spi->mode = SPI_MODE_0;
+ spi->bits_per_word = 8;
+ if (!spi->max_speed_hz)
+ spi->max_speed_hz = TSC2005_SPI_MAX_SPEED_HZ;
+
+ error = spi_setup(spi);
+ if (error)
+ return error;
+
+ return tsc200x_probe(&spi->dev, spi->irq, &tsc2005_input_id,
+ devm_regmap_init_spi(spi, &tsc200x_regmap_config),
+ tsc2005_cmd);
+}
+
+static void tsc2005_remove(struct spi_device *spi)
+{
+ tsc200x_remove(&spi->dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id tsc2005_of_match[] = {
+ { .compatible = "ti,tsc2005" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tsc2005_of_match);
+#endif
+
+static struct spi_driver tsc2005_driver = {
+ .driver = {
+ .name = "tsc2005",
+ .of_match_table = of_match_ptr(tsc2005_of_match),
+ .pm = &tsc200x_pm_ops,
+ },
+ .probe = tsc2005_probe,
+ .remove = tsc2005_remove,
+};
+module_spi_driver(tsc2005_driver);
+
+MODULE_AUTHOR("Michael Welling <mwelling@ieee.org>");
+MODULE_DESCRIPTION("TSC2005 Touchscreen Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:tsc2005");
diff --git a/drivers/input/touchscreen/tsc2007.h b/drivers/input/touchscreen/tsc2007.h
new file mode 100644
index 000000000..69b08dd6c
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2007.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/*
+ * Copyright (c) 2008 MtekVision Co., Ltd.
+ * Kwangwoo Lee <kwlee@mtekvision.com>
+ *
+ * Using code from:
+ * - ads7846.c
+ * Copyright (c) 2005 David Brownell
+ * Copyright (c) 2006 Nokia Corporation
+ * - corgi_ts.c
+ * Copyright (C) 2004-2005 Richard Purdie
+ * - omap_ts.[hc], ads7846.h, ts_osk.c
+ * Copyright (C) 2002 MontaVista Software
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2005 Dirk Behme
+ */
+
+#ifndef _TSC2007_H
+#define _TSC2007_H
+
+struct gpio_desc;
+
+#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
+#define TSC2007_MEASURE_AUX (0x2 << 4)
+#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
+#define TSC2007_ACTIVATE_XN (0x8 << 4)
+#define TSC2007_ACTIVATE_YN (0x9 << 4)
+#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
+#define TSC2007_SETUP (0xb << 4)
+#define TSC2007_MEASURE_X (0xc << 4)
+#define TSC2007_MEASURE_Y (0xd << 4)
+#define TSC2007_MEASURE_Z1 (0xe << 4)
+#define TSC2007_MEASURE_Z2 (0xf << 4)
+
+#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
+#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
+#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
+#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
+
+#define TSC2007_12BIT (0x0 << 1)
+#define TSC2007_8BIT (0x1 << 1)
+
+#define MAX_12BIT ((1 << 12) - 1)
+
+#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
+
+#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
+#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
+#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
+#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
+#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
+
+struct ts_event {
+ u16 x;
+ u16 y;
+ u16 z1, z2;
+};
+
+struct tsc2007 {
+ struct input_dev *input;
+ char phys[32];
+
+ struct i2c_client *client;
+
+ u16 model;
+ u16 x_plate_ohms;
+ u16 max_rt;
+ unsigned long poll_period; /* in jiffies */
+ int fuzzx;
+ int fuzzy;
+ int fuzzz;
+
+ struct gpio_desc *gpiod;
+ int irq;
+
+ wait_queue_head_t wait;
+ bool stopped;
+
+ int (*get_pendown_state)(struct device *);
+ void (*clear_penirq)(void);
+
+ struct mutex mlock;
+};
+
+int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd);
+u32 tsc2007_calculate_resistance(struct tsc2007 *tsc, struct ts_event *tc);
+bool tsc2007_is_pen_down(struct tsc2007 *ts);
+
+#if IS_ENABLED(CONFIG_TOUCHSCREEN_TSC2007_IIO)
+/* defined in tsc2007_iio.c */
+int tsc2007_iio_configure(struct tsc2007 *ts);
+#else
+static inline int tsc2007_iio_configure(struct tsc2007 *ts)
+{
+ return 0;
+}
+#endif /* CONFIG_TOUCHSCREEN_TSC2007_IIO */
+
+#endif /* _TSC2007_H */
diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c
new file mode 100644
index 000000000..3e871d182
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2007_core.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/input/touchscreen/tsc2007.c
+ *
+ * Copyright (c) 2008 MtekVision Co., Ltd.
+ * Kwangwoo Lee <kwlee@mtekvision.com>
+ *
+ * Using code from:
+ * - ads7846.c
+ * Copyright (c) 2005 David Brownell
+ * Copyright (c) 2006 Nokia Corporation
+ * - corgi_ts.c
+ * Copyright (C) 2004-2005 Richard Purdie
+ * - omap_ts.[hc], ads7846.h, ts_osk.c
+ * Copyright (C) 2002 MontaVista Software
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2005 Dirk Behme
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/platform_data/tsc2007.h>
+#include "tsc2007.h"
+
+int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
+{
+ s32 data;
+ u16 val;
+
+ data = i2c_smbus_read_word_data(tsc->client, cmd);
+ if (data < 0) {
+ dev_err(&tsc->client->dev, "i2c io error: %d\n", data);
+ return data;
+ }
+
+ /* The protocol and raw data format from i2c interface:
+ * S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P
+ * Where DataLow has [D11-D4], DataHigh has [D3-D0 << 4 | Dummy 4bit].
+ */
+ val = swab16(data) >> 4;
+
+ dev_dbg(&tsc->client->dev, "data: 0x%x, val: 0x%x\n", data, val);
+
+ return val;
+}
+
+static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
+{
+ /* y- still on; turn on only y+ (and ADC) */
+ tc->y = tsc2007_xfer(tsc, READ_Y);
+
+ /* turn y- off, x+ on, then leave in lowpower */
+ tc->x = tsc2007_xfer(tsc, READ_X);
+
+ /* turn y+ off, x- on; we'll use formula #1 */
+ tc->z1 = tsc2007_xfer(tsc, READ_Z1);
+ tc->z2 = tsc2007_xfer(tsc, READ_Z2);
+
+ /* Prepare for next touch reading - power down ADC, enable PENIRQ */
+ tsc2007_xfer(tsc, PWRDOWN);
+}
+
+u32 tsc2007_calculate_resistance(struct tsc2007 *tsc, struct ts_event *tc)
+{
+ u32 rt = 0;
+
+ /* range filtering */
+ if (tc->x == MAX_12BIT)
+ tc->x = 0;
+
+ if (likely(tc->x && tc->z1)) {
+ /* compute touch resistance using equation #1 */
+ rt = tc->z2 - tc->z1;
+ rt *= tc->x;
+ rt *= tsc->x_plate_ohms;
+ rt /= tc->z1;
+ rt = (rt + 2047) >> 12;
+ }
+
+ return rt;
+}
+
+bool tsc2007_is_pen_down(struct tsc2007 *ts)
+{
+ /*
+ * NOTE: We can't rely on the pressure to determine the pen down
+ * state, even though this controller has a pressure sensor.
+ * The pressure value can fluctuate for quite a while after
+ * lifting the pen and in some cases may not even settle at the
+ * expected value.
+ *
+ * The only safe way to check for the pen up condition is in the
+ * work function by reading the pen signal state (it's a GPIO
+ * and IRQ). Unfortunately such callback is not always available,
+ * in that case we assume that the pen is down and expect caller
+ * to fall back on the pressure reading.
+ */
+
+ if (!ts->get_pendown_state)
+ return true;
+
+ return ts->get_pendown_state(&ts->client->dev);
+}
+
+static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
+{
+ struct tsc2007 *ts = handle;
+ struct input_dev *input = ts->input;
+ struct ts_event tc;
+ u32 rt;
+
+ while (!ts->stopped && tsc2007_is_pen_down(ts)) {
+
+ /* pen is down, continue with the measurement */
+
+ mutex_lock(&ts->mlock);
+ tsc2007_read_values(ts, &tc);
+ mutex_unlock(&ts->mlock);
+
+ rt = tsc2007_calculate_resistance(ts, &tc);
+
+ if (!rt && !ts->get_pendown_state) {
+ /*
+ * If pressure reported is 0 and we don't have
+ * callback to check pendown state, we have to
+ * assume that pen was lifted up.
+ */
+ break;
+ }
+
+ if (rt <= ts->max_rt) {
+ dev_dbg(&ts->client->dev,
+ "DOWN point(%4d,%4d), resistance (%4u)\n",
+ tc.x, tc.y, rt);
+
+ rt = ts->max_rt - rt;
+
+ input_report_key(input, BTN_TOUCH, 1);
+ input_report_abs(input, ABS_X, tc.x);
+ input_report_abs(input, ABS_Y, tc.y);
+ input_report_abs(input, ABS_PRESSURE, rt);
+
+ input_sync(input);
+
+ } else {
+ /*
+ * Sample found inconsistent by debouncing or pressure is
+ * beyond the maximum. Don't report it to user space,
+ * repeat at least once more the measurement.
+ */
+ dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
+ }
+
+ wait_event_timeout(ts->wait, ts->stopped, ts->poll_period);
+ }
+
+ dev_dbg(&ts->client->dev, "UP\n");
+
+ input_report_key(input, BTN_TOUCH, 0);
+ input_report_abs(input, ABS_PRESSURE, 0);
+ input_sync(input);
+
+ if (ts->clear_penirq)
+ ts->clear_penirq();
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t tsc2007_hard_irq(int irq, void *handle)
+{
+ struct tsc2007 *ts = handle;
+
+ if (tsc2007_is_pen_down(ts))
+ return IRQ_WAKE_THREAD;
+
+ if (ts->clear_penirq)
+ ts->clear_penirq();
+
+ return IRQ_HANDLED;
+}
+
+static void tsc2007_stop(struct tsc2007 *ts)
+{
+ ts->stopped = true;
+ mb();
+ wake_up(&ts->wait);
+
+ disable_irq(ts->irq);
+}
+
+static int tsc2007_open(struct input_dev *input_dev)
+{
+ struct tsc2007 *ts = input_get_drvdata(input_dev);
+ int err;
+
+ ts->stopped = false;
+ mb();
+
+ enable_irq(ts->irq);
+
+ /* Prepare for touch readings - power down ADC and enable PENIRQ */
+ err = tsc2007_xfer(ts, PWRDOWN);
+ if (err < 0) {
+ tsc2007_stop(ts);
+ return err;
+ }
+
+ return 0;
+}
+
+static void tsc2007_close(struct input_dev *input_dev)
+{
+ struct tsc2007 *ts = input_get_drvdata(input_dev);
+
+ tsc2007_stop(ts);
+}
+
+static int tsc2007_get_pendown_state_gpio(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct tsc2007 *ts = i2c_get_clientdata(client);
+
+ return gpiod_get_value(ts->gpiod);
+}
+
+static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts)
+{
+ u32 val32;
+ u64 val64;
+
+ if (!device_property_read_u32(dev, "ti,max-rt", &val32))
+ ts->max_rt = val32;
+ else
+ ts->max_rt = MAX_12BIT;
+
+ if (!device_property_read_u32(dev, "ti,fuzzx", &val32))
+ ts->fuzzx = val32;
+
+ if (!device_property_read_u32(dev, "ti,fuzzy", &val32))
+ ts->fuzzy = val32;
+
+ if (!device_property_read_u32(dev, "ti,fuzzz", &val32))
+ ts->fuzzz = val32;
+
+ if (!device_property_read_u64(dev, "ti,poll-period", &val64))
+ ts->poll_period = msecs_to_jiffies(val64);
+ else
+ ts->poll_period = msecs_to_jiffies(1);
+
+ if (!device_property_read_u32(dev, "ti,x-plate-ohms", &val32)) {
+ ts->x_plate_ohms = val32;
+ } else {
+ dev_err(dev, "Missing ti,x-plate-ohms device property\n");
+ return -EINVAL;
+ }
+
+ ts->gpiod = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
+ if (IS_ERR(ts->gpiod))
+ return PTR_ERR(ts->gpiod);
+
+ if (ts->gpiod)
+ ts->get_pendown_state = tsc2007_get_pendown_state_gpio;
+ else
+ dev_warn(dev, "Pen down GPIO is not specified in properties\n");
+
+ return 0;
+}
+
+static int tsc2007_probe_pdev(struct device *dev, struct tsc2007 *ts,
+ const struct tsc2007_platform_data *pdata,
+ const struct i2c_device_id *id)
+{
+ ts->model = pdata->model;
+ ts->x_plate_ohms = pdata->x_plate_ohms;
+ ts->max_rt = pdata->max_rt ? : MAX_12BIT;
+ ts->poll_period = msecs_to_jiffies(pdata->poll_period ? : 1);
+ ts->get_pendown_state = pdata->get_pendown_state;
+ ts->clear_penirq = pdata->clear_penirq;
+ ts->fuzzx = pdata->fuzzx;
+ ts->fuzzy = pdata->fuzzy;
+ ts->fuzzz = pdata->fuzzz;
+
+ if (pdata->x_plate_ohms == 0) {
+ dev_err(dev, "x_plate_ohms is not set up in platform data\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void tsc2007_call_exit_platform_hw(void *data)
+{
+ struct device *dev = data;
+ const struct tsc2007_platform_data *pdata = dev_get_platdata(dev);
+
+ pdata->exit_platform_hw();
+}
+
+static int tsc2007_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct tsc2007_platform_data *pdata =
+ dev_get_platdata(&client->dev);
+ struct tsc2007 *ts;
+ struct input_dev *input_dev;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ return -EIO;
+
+ ts = devm_kzalloc(&client->dev, sizeof(struct tsc2007), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ if (pdata)
+ err = tsc2007_probe_pdev(&client->dev, ts, pdata, id);
+ else
+ err = tsc2007_probe_properties(&client->dev, ts);
+ if (err)
+ return err;
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, ts);
+
+ ts->client = client;
+ ts->irq = client->irq;
+ ts->input = input_dev;
+
+ init_waitqueue_head(&ts->wait);
+ mutex_init(&ts->mlock);
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input0", dev_name(&client->dev));
+
+ input_dev->name = "TSC2007 Touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->id.bustype = BUS_I2C;
+
+ input_dev->open = tsc2007_open;
+ input_dev->close = tsc2007_close;
+
+ input_set_drvdata(input_dev, ts);
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+
+ input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT,
+ ts->fuzzz, 0);
+
+ if (pdata) {
+ if (pdata->exit_platform_hw) {
+ err = devm_add_action(&client->dev,
+ tsc2007_call_exit_platform_hw,
+ &client->dev);
+ if (err) {
+ dev_err(&client->dev,
+ "Failed to register exit_platform_hw action, %d\n",
+ err);
+ return err;
+ }
+ }
+
+ if (pdata->init_platform_hw)
+ pdata->init_platform_hw();
+ }
+
+ err = devm_request_threaded_irq(&client->dev, ts->irq,
+ tsc2007_hard_irq, tsc2007_soft_irq,
+ IRQF_ONESHOT,
+ client->dev.driver->name, ts);
+ if (err) {
+ dev_err(&client->dev, "Failed to request irq %d: %d\n",
+ ts->irq, err);
+ return err;
+ }
+
+ tsc2007_stop(ts);
+
+ /* power down the chip (TSC2007_SETUP does not ACK on I2C) */
+ err = tsc2007_xfer(ts, PWRDOWN);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "Failed to setup chip: %d\n", err);
+ return err; /* chip does not respond */
+ }
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&client->dev,
+ "Failed to register input device: %d\n", err);
+ return err;
+ }
+
+ err = tsc2007_iio_configure(ts);
+ if (err) {
+ dev_err(&client->dev,
+ "Failed to register with IIO: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id tsc2007_idtable[] = {
+ { "tsc2007", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, tsc2007_idtable);
+
+static const struct of_device_id tsc2007_of_match[] = {
+ { .compatible = "ti,tsc2007" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tsc2007_of_match);
+
+static struct i2c_driver tsc2007_driver = {
+ .driver = {
+ .name = "tsc2007",
+ .of_match_table = tsc2007_of_match,
+ },
+ .id_table = tsc2007_idtable,
+ .probe = tsc2007_probe,
+};
+
+module_i2c_driver(tsc2007_driver);
+
+MODULE_AUTHOR("Kwangwoo Lee <kwlee@mtekvision.com>");
+MODULE_DESCRIPTION("TSC2007 TouchScreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/tsc2007_iio.c b/drivers/input/touchscreen/tsc2007_iio.c
new file mode 100644
index 000000000..752eb7fe5
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2007_iio.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016 Golden Delicious Comp. GmbH&Co. KG
+ * Nikolaus Schaller <hns@goldelico.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include "tsc2007.h"
+
+struct tsc2007_iio {
+ struct tsc2007 *ts;
+};
+
+#define TSC2007_CHAN_IIO(_chan, _name, _type, _chan_info) \
+{ \
+ .datasheet_name = _name, \
+ .type = _type, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(_chan_info), \
+ .indexed = 1, \
+ .channel = _chan, \
+}
+
+static const struct iio_chan_spec tsc2007_iio_channel[] = {
+ TSC2007_CHAN_IIO(0, "x", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(1, "y", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(2, "z1", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(3, "z2", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(4, "adc", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(5, "rt", IIO_VOLTAGE, IIO_CHAN_INFO_RAW), /* Ohms? */
+ TSC2007_CHAN_IIO(6, "pen", IIO_PRESSURE, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(7, "temp0", IIO_TEMP, IIO_CHAN_INFO_RAW),
+ TSC2007_CHAN_IIO(8, "temp1", IIO_TEMP, IIO_CHAN_INFO_RAW),
+};
+
+static int tsc2007_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct tsc2007_iio *iio = iio_priv(indio_dev);
+ struct tsc2007 *tsc = iio->ts;
+ int adc_chan = chan->channel;
+ int ret = 0;
+
+ if (adc_chan >= ARRAY_SIZE(tsc2007_iio_channel))
+ return -EINVAL;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ mutex_lock(&tsc->mlock);
+
+ switch (chan->channel) {
+ case 0:
+ *val = tsc2007_xfer(tsc, READ_X);
+ break;
+ case 1:
+ *val = tsc2007_xfer(tsc, READ_Y);
+ break;
+ case 2:
+ *val = tsc2007_xfer(tsc, READ_Z1);
+ break;
+ case 3:
+ *val = tsc2007_xfer(tsc, READ_Z2);
+ break;
+ case 4:
+ *val = tsc2007_xfer(tsc, (ADC_ON_12BIT | TSC2007_MEASURE_AUX));
+ break;
+ case 5: {
+ struct ts_event tc;
+
+ tc.x = tsc2007_xfer(tsc, READ_X);
+ tc.z1 = tsc2007_xfer(tsc, READ_Z1);
+ tc.z2 = tsc2007_xfer(tsc, READ_Z2);
+ *val = tsc2007_calculate_resistance(tsc, &tc);
+ break;
+ }
+ case 6:
+ *val = tsc2007_is_pen_down(tsc);
+ break;
+ case 7:
+ *val = tsc2007_xfer(tsc,
+ (ADC_ON_12BIT | TSC2007_MEASURE_TEMP0));
+ break;
+ case 8:
+ *val = tsc2007_xfer(tsc,
+ (ADC_ON_12BIT | TSC2007_MEASURE_TEMP1));
+ break;
+ }
+
+ /* Prepare for next touch reading - power down ADC, enable PENIRQ */
+ tsc2007_xfer(tsc, PWRDOWN);
+
+ mutex_unlock(&tsc->mlock);
+
+ ret = IIO_VAL_INT;
+
+ return ret;
+}
+
+static const struct iio_info tsc2007_iio_info = {
+ .read_raw = tsc2007_read_raw,
+};
+
+int tsc2007_iio_configure(struct tsc2007 *ts)
+{
+ struct iio_dev *indio_dev;
+ struct tsc2007_iio *iio;
+ int error;
+
+ indio_dev = devm_iio_device_alloc(&ts->client->dev, sizeof(*iio));
+ if (!indio_dev) {
+ dev_err(&ts->client->dev, "iio_device_alloc failed\n");
+ return -ENOMEM;
+ }
+
+ iio = iio_priv(indio_dev);
+ iio->ts = ts;
+
+ indio_dev->name = "tsc2007";
+ indio_dev->info = &tsc2007_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = tsc2007_iio_channel;
+ indio_dev->num_channels = ARRAY_SIZE(tsc2007_iio_channel);
+
+ error = devm_iio_device_register(&ts->client->dev, indio_dev);
+ if (error) {
+ dev_err(&ts->client->dev,
+ "iio_device_register() failed: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
diff --git a/drivers/input/touchscreen/tsc200x-core.c b/drivers/input/touchscreen/tsc200x-core.c
new file mode 100644
index 000000000..72c7258b9
--- /dev/null
+++ b/drivers/input/touchscreen/tsc200x-core.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * TSC2004/TSC2005 touchscreen driver core
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2015 QWERTY Embedded Design
+ * Copyright (C) 2015 EMAC Inc.
+ *
+ * Author: Lauri Leukkunen <lauri.leukkunen@nokia.com>
+ * based on TSC2301 driver by Klaus K. Pedersen <klaus.k.pedersen@nokia.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+#include <linux/gpio/consumer.h>
+#include "tsc200x-core.h"
+
+/*
+ * The touchscreen interface operates as follows:
+ *
+ * 1) Pen is pressed against the touchscreen.
+ * 2) TSC200X performs AD conversion.
+ * 3) After the conversion is done TSC200X drives DAV line down.
+ * 4) GPIO IRQ is received and tsc200x_irq_thread() is scheduled.
+ * 5) tsc200x_irq_thread() queues up a transfer to fetch the x, y, z1, z2
+ * values.
+ * 6) tsc200x_irq_thread() reports coordinates to input layer and sets up
+ * tsc200x_penup_timer() to be called after TSC200X_PENUP_TIME_MS (40ms).
+ * 7) When the penup timer expires, there have not been touch or DAV interrupts
+ * during the last 40ms which means the pen has been lifted.
+ *
+ * ESD recovery via a hardware reset is done if the TSC200X doesn't respond
+ * after a configurable period (in ms) of activity. If esd_timeout is 0, the
+ * watchdog is disabled.
+ */
+
+static const struct regmap_range tsc200x_writable_ranges[] = {
+ regmap_reg_range(TSC200X_REG_AUX_HIGH, TSC200X_REG_CFR2),
+};
+
+static const struct regmap_access_table tsc200x_writable_table = {
+ .yes_ranges = tsc200x_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(tsc200x_writable_ranges),
+};
+
+const struct regmap_config tsc200x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .reg_stride = 0x08,
+ .max_register = 0x78,
+ .read_flag_mask = TSC200X_REG_READ,
+ .write_flag_mask = TSC200X_REG_PND0,
+ .wr_table = &tsc200x_writable_table,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+EXPORT_SYMBOL_GPL(tsc200x_regmap_config);
+
+struct tsc200x_data {
+ u16 x;
+ u16 y;
+ u16 z1;
+ u16 z2;
+} __packed;
+#define TSC200X_DATA_REGS 4
+
+struct tsc200x {
+ struct device *dev;
+ struct regmap *regmap;
+ __u16 bustype;
+
+ struct input_dev *idev;
+ char phys[32];
+
+ struct mutex mutex;
+
+ /* raw copy of previous x,y,z */
+ int in_x;
+ int in_y;
+ int in_z1;
+ int in_z2;
+
+ struct touchscreen_properties prop;
+
+ spinlock_t lock;
+ struct timer_list penup_timer;
+
+ unsigned int esd_timeout;
+ struct delayed_work esd_work;
+ unsigned long last_valid_interrupt;
+
+ unsigned int x_plate_ohm;
+
+ bool opened;
+ bool suspended;
+
+ bool pen_down;
+
+ struct regulator *vio;
+
+ struct gpio_desc *reset_gpio;
+ int (*tsc200x_cmd)(struct device *dev, u8 cmd);
+ int irq;
+};
+
+static void tsc200x_update_pen_state(struct tsc200x *ts,
+ int x, int y, int pressure)
+{
+ if (pressure) {
+ touchscreen_report_pos(ts->idev, &ts->prop, x, y, false);
+ input_report_abs(ts->idev, ABS_PRESSURE, pressure);
+ if (!ts->pen_down) {
+ input_report_key(ts->idev, BTN_TOUCH, !!pressure);
+ ts->pen_down = true;
+ }
+ } else {
+ input_report_abs(ts->idev, ABS_PRESSURE, 0);
+ if (ts->pen_down) {
+ input_report_key(ts->idev, BTN_TOUCH, 0);
+ ts->pen_down = false;
+ }
+ }
+ input_sync(ts->idev);
+ dev_dbg(ts->dev, "point(%4d,%4d), pressure (%4d)\n", x, y,
+ pressure);
+}
+
+static irqreturn_t tsc200x_irq_thread(int irq, void *_ts)
+{
+ struct tsc200x *ts = _ts;
+ unsigned long flags;
+ unsigned int pressure;
+ struct tsc200x_data tsdata;
+ int error;
+
+ /* read the coordinates */
+ error = regmap_bulk_read(ts->regmap, TSC200X_REG_X, &tsdata,
+ TSC200X_DATA_REGS);
+ if (unlikely(error))
+ goto out;
+
+ /* validate position */
+ if (unlikely(tsdata.x > MAX_12BIT || tsdata.y > MAX_12BIT))
+ goto out;
+
+ /* Skip reading if the pressure components are out of range */
+ if (unlikely(tsdata.z1 == 0 || tsdata.z2 > MAX_12BIT))
+ goto out;
+ if (unlikely(tsdata.z1 >= tsdata.z2))
+ goto out;
+
+ /*
+ * Skip point if this is a pen down with the exact same values as
+ * the value before pen-up - that implies SPI fed us stale data
+ */
+ if (!ts->pen_down &&
+ ts->in_x == tsdata.x && ts->in_y == tsdata.y &&
+ ts->in_z1 == tsdata.z1 && ts->in_z2 == tsdata.z2) {
+ goto out;
+ }
+
+ /*
+ * At this point we are happy we have a valid and useful reading.
+ * Remember it for later comparisons. We may now begin downsampling.
+ */
+ ts->in_x = tsdata.x;
+ ts->in_y = tsdata.y;
+ ts->in_z1 = tsdata.z1;
+ ts->in_z2 = tsdata.z2;
+
+ /* Compute touch pressure resistance using equation #1 */
+ pressure = tsdata.x * (tsdata.z2 - tsdata.z1) / tsdata.z1;
+ pressure = pressure * ts->x_plate_ohm / 4096;
+ if (unlikely(pressure > MAX_12BIT))
+ goto out;
+
+ spin_lock_irqsave(&ts->lock, flags);
+
+ tsc200x_update_pen_state(ts, tsdata.x, tsdata.y, pressure);
+ mod_timer(&ts->penup_timer,
+ jiffies + msecs_to_jiffies(TSC200X_PENUP_TIME_MS));
+
+ spin_unlock_irqrestore(&ts->lock, flags);
+
+ ts->last_valid_interrupt = jiffies;
+out:
+ return IRQ_HANDLED;
+}
+
+static void tsc200x_penup_timer(struct timer_list *t)
+{
+ struct tsc200x *ts = from_timer(ts, t, penup_timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ts->lock, flags);
+ tsc200x_update_pen_state(ts, 0, 0, 0);
+ spin_unlock_irqrestore(&ts->lock, flags);
+}
+
+static void tsc200x_start_scan(struct tsc200x *ts)
+{
+ regmap_write(ts->regmap, TSC200X_REG_CFR0, TSC200X_CFR0_INITVALUE);
+ regmap_write(ts->regmap, TSC200X_REG_CFR1, TSC200X_CFR1_INITVALUE);
+ regmap_write(ts->regmap, TSC200X_REG_CFR2, TSC200X_CFR2_INITVALUE);
+ ts->tsc200x_cmd(ts->dev, TSC200X_CMD_NORMAL);
+}
+
+static void tsc200x_stop_scan(struct tsc200x *ts)
+{
+ ts->tsc200x_cmd(ts->dev, TSC200X_CMD_STOP);
+}
+
+static void tsc200x_reset(struct tsc200x *ts)
+{
+ if (ts->reset_gpio) {
+ gpiod_set_value_cansleep(ts->reset_gpio, 1);
+ usleep_range(100, 500); /* only 10us required */
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+ }
+}
+
+/* must be called with ts->mutex held */
+static void __tsc200x_disable(struct tsc200x *ts)
+{
+ tsc200x_stop_scan(ts);
+
+ disable_irq(ts->irq);
+ del_timer_sync(&ts->penup_timer);
+
+ cancel_delayed_work_sync(&ts->esd_work);
+
+ enable_irq(ts->irq);
+}
+
+/* must be called with ts->mutex held */
+static void __tsc200x_enable(struct tsc200x *ts)
+{
+ tsc200x_start_scan(ts);
+
+ if (ts->esd_timeout && ts->reset_gpio) {
+ ts->last_valid_interrupt = jiffies;
+ schedule_delayed_work(&ts->esd_work,
+ round_jiffies_relative(
+ msecs_to_jiffies(ts->esd_timeout)));
+ }
+}
+
+static ssize_t tsc200x_selftest_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct tsc200x *ts = dev_get_drvdata(dev);
+ unsigned int temp_high;
+ unsigned int temp_high_orig;
+ unsigned int temp_high_test;
+ bool success = true;
+ int error;
+
+ mutex_lock(&ts->mutex);
+
+ /*
+ * Test TSC200X communications via temp high register.
+ */
+ __tsc200x_disable(ts);
+
+ error = regmap_read(ts->regmap, TSC200X_REG_TEMP_HIGH, &temp_high_orig);
+ if (error) {
+ dev_warn(dev, "selftest failed: read error %d\n", error);
+ success = false;
+ goto out;
+ }
+
+ temp_high_test = (temp_high_orig - 1) & MAX_12BIT;
+
+ error = regmap_write(ts->regmap, TSC200X_REG_TEMP_HIGH, temp_high_test);
+ if (error) {
+ dev_warn(dev, "selftest failed: write error %d\n", error);
+ success = false;
+ goto out;
+ }
+
+ error = regmap_read(ts->regmap, TSC200X_REG_TEMP_HIGH, &temp_high);
+ if (error) {
+ dev_warn(dev, "selftest failed: read error %d after write\n",
+ error);
+ success = false;
+ goto out;
+ }
+
+ if (temp_high != temp_high_test) {
+ dev_warn(dev, "selftest failed: %d != %d\n",
+ temp_high, temp_high_test);
+ success = false;
+ }
+
+ /* hardware reset */
+ tsc200x_reset(ts);
+
+ if (!success)
+ goto out;
+
+ /* test that the reset really happened */
+ error = regmap_read(ts->regmap, TSC200X_REG_TEMP_HIGH, &temp_high);
+ if (error) {
+ dev_warn(dev, "selftest failed: read error %d after reset\n",
+ error);
+ success = false;
+ goto out;
+ }
+
+ if (temp_high != temp_high_orig) {
+ dev_warn(dev, "selftest failed after reset: %d != %d\n",
+ temp_high, temp_high_orig);
+ success = false;
+ }
+
+out:
+ __tsc200x_enable(ts);
+ mutex_unlock(&ts->mutex);
+
+ return sprintf(buf, "%d\n", success);
+}
+
+static DEVICE_ATTR(selftest, S_IRUGO, tsc200x_selftest_show, NULL);
+
+static struct attribute *tsc200x_attrs[] = {
+ &dev_attr_selftest.attr,
+ NULL
+};
+
+static umode_t tsc200x_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct tsc200x *ts = dev_get_drvdata(dev);
+ umode_t mode = attr->mode;
+
+ if (attr == &dev_attr_selftest.attr) {
+ if (!ts->reset_gpio)
+ mode = 0;
+ }
+
+ return mode;
+}
+
+static const struct attribute_group tsc200x_attr_group = {
+ .is_visible = tsc200x_attr_is_visible,
+ .attrs = tsc200x_attrs,
+};
+
+static void tsc200x_esd_work(struct work_struct *work)
+{
+ struct tsc200x *ts = container_of(work, struct tsc200x, esd_work.work);
+ int error;
+ unsigned int r;
+
+ if (!mutex_trylock(&ts->mutex)) {
+ /*
+ * If the mutex is taken, it means that disable or enable is in
+ * progress. In that case just reschedule the work. If the work
+ * is not needed, it will be canceled by disable.
+ */
+ goto reschedule;
+ }
+
+ if (time_is_after_jiffies(ts->last_valid_interrupt +
+ msecs_to_jiffies(ts->esd_timeout)))
+ goto out;
+
+ /* We should be able to read register without disabling interrupts. */
+ error = regmap_read(ts->regmap, TSC200X_REG_CFR0, &r);
+ if (!error &&
+ !((r ^ TSC200X_CFR0_INITVALUE) & TSC200X_CFR0_RW_MASK)) {
+ goto out;
+ }
+
+ /*
+ * If we could not read our known value from configuration register 0
+ * then we should reset the controller as if from power-up and start
+ * scanning again.
+ */
+ dev_info(ts->dev, "TSC200X not responding - resetting\n");
+
+ disable_irq(ts->irq);
+ del_timer_sync(&ts->penup_timer);
+
+ tsc200x_update_pen_state(ts, 0, 0, 0);
+
+ tsc200x_reset(ts);
+
+ enable_irq(ts->irq);
+ tsc200x_start_scan(ts);
+
+out:
+ mutex_unlock(&ts->mutex);
+reschedule:
+ /* re-arm the watchdog */
+ schedule_delayed_work(&ts->esd_work,
+ round_jiffies_relative(
+ msecs_to_jiffies(ts->esd_timeout)));
+}
+
+static int tsc200x_open(struct input_dev *input)
+{
+ struct tsc200x *ts = input_get_drvdata(input);
+
+ mutex_lock(&ts->mutex);
+
+ if (!ts->suspended)
+ __tsc200x_enable(ts);
+
+ ts->opened = true;
+
+ mutex_unlock(&ts->mutex);
+
+ return 0;
+}
+
+static void tsc200x_close(struct input_dev *input)
+{
+ struct tsc200x *ts = input_get_drvdata(input);
+
+ mutex_lock(&ts->mutex);
+
+ if (!ts->suspended)
+ __tsc200x_disable(ts);
+
+ ts->opened = false;
+
+ mutex_unlock(&ts->mutex);
+}
+
+int tsc200x_probe(struct device *dev, int irq, const struct input_id *tsc_id,
+ struct regmap *regmap,
+ int (*tsc200x_cmd)(struct device *dev, u8 cmd))
+{
+ struct tsc200x *ts;
+ struct input_dev *input_dev;
+ u32 x_plate_ohm;
+ u32 esd_timeout;
+ int error;
+
+ if (irq <= 0) {
+ dev_err(dev, "no irq\n");
+ return -ENODEV;
+ }
+
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ if (!tsc200x_cmd) {
+ dev_err(dev, "no cmd function\n");
+ return -ENODEV;
+ }
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ ts->irq = irq;
+ ts->dev = dev;
+ ts->idev = input_dev;
+ ts->regmap = regmap;
+ ts->tsc200x_cmd = tsc200x_cmd;
+
+ error = device_property_read_u32(dev, "ti,x-plate-ohms", &x_plate_ohm);
+ ts->x_plate_ohm = error ? TSC200X_DEF_RESISTOR : x_plate_ohm;
+
+ error = device_property_read_u32(dev, "ti,esd-recovery-timeout-ms",
+ &esd_timeout);
+ ts->esd_timeout = error ? 0 : esd_timeout;
+
+ ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->reset_gpio)) {
+ error = PTR_ERR(ts->reset_gpio);
+ dev_err(dev, "error acquiring reset gpio: %d\n", error);
+ return error;
+ }
+
+ ts->vio = devm_regulator_get(dev, "vio");
+ if (IS_ERR(ts->vio)) {
+ error = PTR_ERR(ts->vio);
+ dev_err(dev, "error acquiring vio regulator: %d", error);
+ return error;
+ }
+
+ mutex_init(&ts->mutex);
+
+ spin_lock_init(&ts->lock);
+ timer_setup(&ts->penup_timer, tsc200x_penup_timer, 0);
+
+ INIT_DELAYED_WORK(&ts->esd_work, tsc200x_esd_work);
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input-ts", dev_name(dev));
+
+ if (tsc_id->product == 2004) {
+ input_dev->name = "TSC200X touchscreen";
+ } else {
+ input_dev->name = devm_kasprintf(dev, GFP_KERNEL,
+ "TSC%04d touchscreen",
+ tsc_id->product);
+ if (!input_dev->name)
+ return -ENOMEM;
+ }
+
+ input_dev->phys = ts->phys;
+ input_dev->id = *tsc_id;
+
+ input_dev->open = tsc200x_open;
+ input_dev->close = tsc200x_close;
+
+ input_set_drvdata(input_dev, ts);
+
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+
+ input_set_abs_params(input_dev, ABS_X,
+ 0, MAX_12BIT, TSC200X_DEF_X_FUZZ, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ 0, MAX_12BIT, TSC200X_DEF_Y_FUZZ, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE,
+ 0, MAX_12BIT, TSC200X_DEF_P_FUZZ, 0);
+
+ touchscreen_parse_properties(input_dev, false, &ts->prop);
+
+ /* Ensure the touchscreen is off */
+ tsc200x_stop_scan(ts);
+
+ error = devm_request_threaded_irq(dev, irq, NULL,
+ tsc200x_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "tsc200x", ts);
+ if (error) {
+ dev_err(dev, "Failed to request irq, err: %d\n", error);
+ return error;
+ }
+
+ error = regulator_enable(ts->vio);
+ if (error)
+ return error;
+
+ dev_set_drvdata(dev, ts);
+ error = sysfs_create_group(&dev->kobj, &tsc200x_attr_group);
+ if (error) {
+ dev_err(dev,
+ "Failed to create sysfs attributes, err: %d\n", error);
+ goto disable_regulator;
+ }
+
+ error = input_register_device(ts->idev);
+ if (error) {
+ dev_err(dev,
+ "Failed to register input device, err: %d\n", error);
+ goto err_remove_sysfs;
+ }
+
+ irq_set_irq_wake(irq, 1);
+ return 0;
+
+err_remove_sysfs:
+ sysfs_remove_group(&dev->kobj, &tsc200x_attr_group);
+disable_regulator:
+ regulator_disable(ts->vio);
+ return error;
+}
+EXPORT_SYMBOL_GPL(tsc200x_probe);
+
+void tsc200x_remove(struct device *dev)
+{
+ struct tsc200x *ts = dev_get_drvdata(dev);
+
+ sysfs_remove_group(&dev->kobj, &tsc200x_attr_group);
+
+ regulator_disable(ts->vio);
+}
+EXPORT_SYMBOL_GPL(tsc200x_remove);
+
+static int __maybe_unused tsc200x_suspend(struct device *dev)
+{
+ struct tsc200x *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->mutex);
+
+ if (!ts->suspended && ts->opened)
+ __tsc200x_disable(ts);
+
+ ts->suspended = true;
+
+ mutex_unlock(&ts->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused tsc200x_resume(struct device *dev)
+{
+ struct tsc200x *ts = dev_get_drvdata(dev);
+
+ mutex_lock(&ts->mutex);
+
+ if (ts->suspended && ts->opened)
+ __tsc200x_enable(ts);
+
+ ts->suspended = false;
+
+ mutex_unlock(&ts->mutex);
+
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(tsc200x_pm_ops, tsc200x_suspend, tsc200x_resume);
+EXPORT_SYMBOL_GPL(tsc200x_pm_ops);
+
+MODULE_AUTHOR("Lauri Leukkunen <lauri.leukkunen@nokia.com>");
+MODULE_DESCRIPTION("TSC200x Touchscreen Driver Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/tsc200x-core.h b/drivers/input/touchscreen/tsc200x-core.h
new file mode 100644
index 000000000..4ded34425
--- /dev/null
+++ b/drivers/input/touchscreen/tsc200x-core.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TSC200X_CORE_H
+#define _TSC200X_CORE_H
+
+/* control byte 1 */
+#define TSC200X_CMD 0x80
+#define TSC200X_CMD_NORMAL 0x00
+#define TSC200X_CMD_STOP 0x01
+#define TSC200X_CMD_12BIT 0x04
+
+/* control byte 0 */
+#define TSC200X_REG_READ 0x01 /* R/W access */
+#define TSC200X_REG_PND0 0x02 /* Power Not Down Control */
+#define TSC200X_REG_X (0x0 << 3)
+#define TSC200X_REG_Y (0x1 << 3)
+#define TSC200X_REG_Z1 (0x2 << 3)
+#define TSC200X_REG_Z2 (0x3 << 3)
+#define TSC200X_REG_AUX (0x4 << 3)
+#define TSC200X_REG_TEMP1 (0x5 << 3)
+#define TSC200X_REG_TEMP2 (0x6 << 3)
+#define TSC200X_REG_STATUS (0x7 << 3)
+#define TSC200X_REG_AUX_HIGH (0x8 << 3)
+#define TSC200X_REG_AUX_LOW (0x9 << 3)
+#define TSC200X_REG_TEMP_HIGH (0xA << 3)
+#define TSC200X_REG_TEMP_LOW (0xB << 3)
+#define TSC200X_REG_CFR0 (0xC << 3)
+#define TSC200X_REG_CFR1 (0xD << 3)
+#define TSC200X_REG_CFR2 (0xE << 3)
+#define TSC200X_REG_CONV_FUNC (0xF << 3)
+
+/* configuration register 0 */
+#define TSC200X_CFR0_PRECHARGE_276US 0x0040
+#define TSC200X_CFR0_STABTIME_1MS 0x0300
+#define TSC200X_CFR0_CLOCK_1MHZ 0x1000
+#define TSC200X_CFR0_RESOLUTION12 0x2000
+#define TSC200X_CFR0_PENMODE 0x8000
+#define TSC200X_CFR0_INITVALUE (TSC200X_CFR0_STABTIME_1MS | \
+ TSC200X_CFR0_CLOCK_1MHZ | \
+ TSC200X_CFR0_RESOLUTION12 | \
+ TSC200X_CFR0_PRECHARGE_276US | \
+ TSC200X_CFR0_PENMODE)
+
+/* bits common to both read and write of configuration register 0 */
+#define TSC200X_CFR0_RW_MASK 0x3fff
+
+/* configuration register 1 */
+#define TSC200X_CFR1_BATCHDELAY_4MS 0x0003
+#define TSC200X_CFR1_INITVALUE TSC200X_CFR1_BATCHDELAY_4MS
+
+/* configuration register 2 */
+#define TSC200X_CFR2_MAVE_Z 0x0004
+#define TSC200X_CFR2_MAVE_Y 0x0008
+#define TSC200X_CFR2_MAVE_X 0x0010
+#define TSC200X_CFR2_AVG_7 0x0800
+#define TSC200X_CFR2_MEDIUM_15 0x3000
+#define TSC200X_CFR2_INITVALUE (TSC200X_CFR2_MAVE_X | \
+ TSC200X_CFR2_MAVE_Y | \
+ TSC200X_CFR2_MAVE_Z | \
+ TSC200X_CFR2_MEDIUM_15 | \
+ TSC200X_CFR2_AVG_7)
+
+#define MAX_12BIT 0xfff
+#define TSC200X_DEF_X_FUZZ 4
+#define TSC200X_DEF_Y_FUZZ 8
+#define TSC200X_DEF_P_FUZZ 2
+#define TSC200X_DEF_RESISTOR 280
+
+#define TSC2005_SPI_MAX_SPEED_HZ 10000000
+#define TSC200X_PENUP_TIME_MS 40
+
+extern const struct regmap_config tsc200x_regmap_config;
+extern const struct dev_pm_ops tsc200x_pm_ops;
+
+int tsc200x_probe(struct device *dev, int irq, const struct input_id *tsc_id,
+ struct regmap *regmap,
+ int (*tsc200x_cmd)(struct device *dev, u8 cmd));
+void tsc200x_remove(struct device *dev);
+
+#endif
diff --git a/drivers/input/touchscreen/tsc40.c b/drivers/input/touchscreen/tsc40.c
new file mode 100644
index 000000000..139577021
--- /dev/null
+++ b/drivers/input/touchscreen/tsc40.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TSC-40 serial touchscreen driver. It should be compatible with
+ * TSC-10 and 25.
+ *
+ * Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+
+#define PACKET_LENGTH 5
+struct tsc_ser {
+ struct input_dev *dev;
+ struct serio *serio;
+ u32 idx;
+ unsigned char data[PACKET_LENGTH];
+ char phys[32];
+};
+
+static void tsc_process_data(struct tsc_ser *ptsc)
+{
+ struct input_dev *dev = ptsc->dev;
+ u8 *data = ptsc->data;
+ u32 x;
+ u32 y;
+
+ x = ((data[1] & 0x03) << 8) | data[2];
+ y = ((data[3] & 0x03) << 8) | data[4];
+
+ input_report_abs(dev, ABS_X, x);
+ input_report_abs(dev, ABS_Y, y);
+ input_report_key(dev, BTN_TOUCH, 1);
+
+ input_sync(dev);
+}
+
+static irqreturn_t tsc_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct tsc_ser *ptsc = serio_get_drvdata(serio);
+ struct input_dev *dev = ptsc->dev;
+
+ ptsc->data[ptsc->idx] = data;
+ switch (ptsc->idx++) {
+ case 0:
+ if (unlikely((data & 0x3e) != 0x10)) {
+ dev_dbg(&serio->dev,
+ "unsynchronized packet start (0x%02x)\n", data);
+ ptsc->idx = 0;
+ } else if (!(data & 0x01)) {
+ input_report_key(dev, BTN_TOUCH, 0);
+ input_sync(dev);
+ ptsc->idx = 0;
+ }
+ break;
+
+ case 1:
+ case 3:
+ if (unlikely(data & 0xfc)) {
+ dev_dbg(&serio->dev,
+ "unsynchronized data 0x%02x at offset %d\n",
+ data, ptsc->idx - 1);
+ ptsc->idx = 0;
+ }
+ break;
+
+ case 4:
+ tsc_process_data(ptsc);
+ ptsc->idx = 0;
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int tsc_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct tsc_ser *ptsc;
+ struct input_dev *input_dev;
+ int error;
+
+ ptsc = kzalloc(sizeof(struct tsc_ser), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!ptsc || !input_dev) {
+ error = -ENOMEM;
+ goto fail1;
+ }
+
+ ptsc->serio = serio;
+ ptsc->dev = input_dev;
+ snprintf(ptsc->phys, sizeof(ptsc->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "TSC-10/25/40 Serial TouchScreen";
+ input_dev->phys = ptsc->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_TSC40;
+ input_dev->id.product = 40;
+ input_dev->id.version = 0x0001;
+ input_dev->dev.parent = &serio->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+ input_set_abs_params(ptsc->dev, ABS_X, 0, 0x3ff, 0, 0);
+ input_set_abs_params(ptsc->dev, ABS_Y, 0, 0x3ff, 0, 0);
+
+ serio_set_drvdata(serio, ptsc);
+
+ error = serio_open(serio, drv);
+ if (error)
+ goto fail2;
+
+ error = input_register_device(ptsc->dev);
+ if (error)
+ goto fail3;
+
+ return 0;
+
+fail3:
+ serio_close(serio);
+fail2:
+ serio_set_drvdata(serio, NULL);
+fail1:
+ input_free_device(input_dev);
+ kfree(ptsc);
+ return error;
+}
+
+static void tsc_disconnect(struct serio *serio)
+{
+ struct tsc_ser *ptsc = serio_get_drvdata(serio);
+
+ serio_close(serio);
+
+ input_unregister_device(ptsc->dev);
+ kfree(ptsc);
+
+ serio_set_drvdata(serio, NULL);
+}
+
+static const struct serio_device_id tsc_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_TSC40,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(serio, tsc_serio_ids);
+
+#define DRIVER_DESC "TSC-10/25/40 serial touchscreen driver"
+
+static struct serio_driver tsc_drv = {
+ .driver = {
+ .name = "tsc40",
+ },
+ .description = DRIVER_DESC,
+ .id_table = tsc_serio_ids,
+ .interrupt = tsc_interrupt,
+ .connect = tsc_connect,
+ .disconnect = tsc_disconnect,
+};
+
+module_serio_driver(tsc_drv);
+
+MODULE_AUTHOR("Sebastian Andrzej Siewior <bigeasy@linutronix.de>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c
new file mode 100644
index 000000000..dfd3b3559
--- /dev/null
+++ b/drivers/input/touchscreen/ucb1400_ts.c
@@ -0,0 +1,458 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Philips UCB1400 touchscreen driver
+ *
+ * Author: Nicolas Pitre
+ * Created: September 25, 2006
+ * Copyright: MontaVista Software, Inc.
+ *
+ * Spliting done by: Marek Vasut <marek.vasut@gmail.com>
+ * If something doesn't work and it worked before spliting, e-mail me,
+ * dont bother Nicolas please ;-)
+ *
+ * This code is heavily based on ucb1x00-*.c copyrighted by Russell King
+ * covering the UCB1100, UCB1200 and UCB1300.. Support for the UCB1400 has
+ * been made separate from ucb1x00-core/ucb1x00-ts on Russell's request.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/input.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/ucb1400.h>
+
+#define UCB1400_TS_POLL_PERIOD 10 /* ms */
+
+static bool adcsync;
+static int ts_delay = 55; /* us */
+static int ts_delay_pressure; /* us */
+
+/* Switch to interrupt mode. */
+static void ucb1400_ts_mode_int(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW |
+ UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND |
+ UCB_TS_CR_MODE_INT);
+}
+
+/*
+ * Switch to pressure mode, and read pressure. We don't need to wait
+ * here, since both plates are being driven.
+ */
+static unsigned int ucb1400_ts_read_pressure(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW |
+ UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+
+ udelay(ts_delay_pressure);
+
+ return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync);
+}
+
+/*
+ * Switch to X position mode and measure Y plate. We switch the plate
+ * configuration in pressure mode, then switch to position mode. This
+ * gives a faster response time. Even so, we need to wait about 55us
+ * for things to stabilise.
+ */
+static unsigned int ucb1400_ts_read_xpos(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
+ UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);
+
+ udelay(ts_delay);
+
+ return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync);
+}
+
+/*
+ * Switch to Y position mode and measure X plate. We switch the plate
+ * configuration in pressure mode, then switch to position mode. This
+ * gives a faster response time. Even so, we need to wait about 55us
+ * for things to stabilise.
+ */
+static int ucb1400_ts_read_ypos(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
+ UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA);
+
+ udelay(ts_delay);
+
+ return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPX, adcsync);
+}
+
+/*
+ * Switch to X plate resistance mode. Set MX to ground, PX to
+ * supply. Measure current.
+ */
+static unsigned int ucb1400_ts_read_xres(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+ return ucb1400_adc_read(ucb->ac97, 0, adcsync);
+}
+
+/*
+ * Switch to Y plate resistance mode. Set MY to ground, PY to
+ * supply. Measure current.
+ */
+static unsigned int ucb1400_ts_read_yres(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR,
+ UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
+ UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA);
+ return ucb1400_adc_read(ucb->ac97, 0, adcsync);
+}
+
+static int ucb1400_ts_pen_up(struct ucb1400_ts *ucb)
+{
+ unsigned short val = ucb1400_reg_read(ucb->ac97, UCB_TS_CR);
+
+ return val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW);
+}
+
+static void ucb1400_ts_irq_enable(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, UCB_IE_TSPX);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, UCB_IE_TSPX);
+}
+
+static void ucb1400_ts_irq_disable(struct ucb1400_ts *ucb)
+{
+ ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, 0);
+}
+
+static void ucb1400_ts_report_event(struct input_dev *idev, u16 pressure, u16 x, u16 y)
+{
+ input_report_abs(idev, ABS_X, x);
+ input_report_abs(idev, ABS_Y, y);
+ input_report_abs(idev, ABS_PRESSURE, pressure);
+ input_report_key(idev, BTN_TOUCH, 1);
+ input_sync(idev);
+}
+
+static void ucb1400_ts_event_release(struct input_dev *idev)
+{
+ input_report_abs(idev, ABS_PRESSURE, 0);
+ input_report_key(idev, BTN_TOUCH, 0);
+ input_sync(idev);
+}
+
+static void ucb1400_clear_pending_irq(struct ucb1400_ts *ucb)
+{
+ unsigned int isr;
+
+ isr = ucb1400_reg_read(ucb->ac97, UCB_IE_STATUS);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, isr);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0);
+
+ if (isr & UCB_IE_TSPX)
+ ucb1400_ts_irq_disable(ucb);
+ else
+ dev_dbg(&ucb->ts_idev->dev,
+ "ucb1400: unexpected IE_STATUS = %#x\n", isr);
+}
+
+/*
+ * A restriction with interrupts exists when using the ucb1400, as
+ * the codec read/write routines may sleep while waiting for codec
+ * access completion and uses semaphores for access control to the
+ * AC97 bus. Therefore the driver is forced to use threaded interrupt
+ * handler.
+ */
+static irqreturn_t ucb1400_irq(int irqnr, void *devid)
+{
+ struct ucb1400_ts *ucb = devid;
+ unsigned int x, y, p;
+
+ if (unlikely(irqnr != ucb->irq))
+ return IRQ_NONE;
+
+ ucb1400_clear_pending_irq(ucb);
+
+ /* Start with a small delay before checking pendown state */
+ msleep(UCB1400_TS_POLL_PERIOD);
+
+ while (!ucb->stopped && !ucb1400_ts_pen_up(ucb)) {
+ ucb1400_adc_enable(ucb->ac97);
+ x = ucb1400_ts_read_xpos(ucb);
+ y = ucb1400_ts_read_ypos(ucb);
+ p = ucb1400_ts_read_pressure(ucb);
+ ucb1400_adc_disable(ucb->ac97);
+
+ ucb1400_ts_report_event(ucb->ts_idev, p, x, y);
+
+ wait_event_timeout(ucb->ts_wait, ucb->stopped,
+ msecs_to_jiffies(UCB1400_TS_POLL_PERIOD));
+ }
+
+ ucb1400_ts_event_release(ucb->ts_idev);
+
+ if (!ucb->stopped) {
+ /* Switch back to interrupt mode. */
+ ucb1400_ts_mode_int(ucb);
+ ucb1400_ts_irq_enable(ucb);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void ucb1400_ts_stop(struct ucb1400_ts *ucb)
+{
+ /* Signal IRQ thread to stop polling and disable the handler. */
+ ucb->stopped = true;
+ mb();
+ wake_up(&ucb->ts_wait);
+ disable_irq(ucb->irq);
+
+ ucb1400_ts_irq_disable(ucb);
+ ucb1400_reg_write(ucb->ac97, UCB_TS_CR, 0);
+}
+
+/* Must be called with ts->lock held */
+static void ucb1400_ts_start(struct ucb1400_ts *ucb)
+{
+ /* Tell IRQ thread that it may poll the device. */
+ ucb->stopped = false;
+ mb();
+
+ ucb1400_ts_mode_int(ucb);
+ ucb1400_ts_irq_enable(ucb);
+
+ enable_irq(ucb->irq);
+}
+
+static int ucb1400_ts_open(struct input_dev *idev)
+{
+ struct ucb1400_ts *ucb = input_get_drvdata(idev);
+
+ ucb1400_ts_start(ucb);
+
+ return 0;
+}
+
+static void ucb1400_ts_close(struct input_dev *idev)
+{
+ struct ucb1400_ts *ucb = input_get_drvdata(idev);
+
+ ucb1400_ts_stop(ucb);
+}
+
+#ifndef NO_IRQ
+#define NO_IRQ 0
+#endif
+
+/*
+ * Try to probe our interrupt, rather than relying on lots of
+ * hard-coded machine dependencies.
+ */
+static int ucb1400_ts_detect_irq(struct ucb1400_ts *ucb,
+ struct platform_device *pdev)
+{
+ unsigned long mask, timeout;
+
+ mask = probe_irq_on();
+
+ /* Enable the ADC interrupt. */
+ ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, UCB_IE_ADC);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, UCB_IE_ADC);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0);
+
+ /* Cause an ADC interrupt. */
+ ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA);
+ ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START);
+
+ /* Wait for the conversion to complete. */
+ timeout = jiffies + HZ/2;
+ while (!(ucb1400_reg_read(ucb->ac97, UCB_ADC_DATA) &
+ UCB_ADC_DAT_VALID)) {
+ cpu_relax();
+ if (time_after(jiffies, timeout)) {
+ dev_err(&pdev->dev, "timed out in IRQ probe\n");
+ probe_irq_off(mask);
+ return -ENODEV;
+ }
+ }
+ ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, 0);
+
+ /* Disable and clear interrupt. */
+ ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, 0);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, 0);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff);
+ ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0);
+
+ /* Read triggered interrupt. */
+ ucb->irq = probe_irq_off(mask);
+ if (ucb->irq < 0 || ucb->irq == NO_IRQ)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int ucb1400_ts_probe(struct platform_device *pdev)
+{
+ struct ucb1400_ts *ucb = dev_get_platdata(&pdev->dev);
+ int error, x_res, y_res;
+ u16 fcsr;
+
+ ucb->ts_idev = input_allocate_device();
+ if (!ucb->ts_idev) {
+ error = -ENOMEM;
+ goto err;
+ }
+
+ /* Only in case the IRQ line wasn't supplied, try detecting it */
+ if (ucb->irq < 0) {
+ error = ucb1400_ts_detect_irq(ucb, pdev);
+ if (error) {
+ dev_err(&pdev->dev, "IRQ probe failed\n");
+ goto err_free_devs;
+ }
+ }
+ dev_dbg(&pdev->dev, "found IRQ %d\n", ucb->irq);
+
+ init_waitqueue_head(&ucb->ts_wait);
+
+ input_set_drvdata(ucb->ts_idev, ucb);
+
+ ucb->ts_idev->dev.parent = &pdev->dev;
+ ucb->ts_idev->name = "UCB1400 touchscreen interface";
+ ucb->ts_idev->id.vendor = ucb1400_reg_read(ucb->ac97,
+ AC97_VENDOR_ID1);
+ ucb->ts_idev->id.product = ucb->id;
+ ucb->ts_idev->open = ucb1400_ts_open;
+ ucb->ts_idev->close = ucb1400_ts_close;
+ ucb->ts_idev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY);
+ ucb->ts_idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ /*
+ * Enable ADC filter to prevent horrible jitter on Colibri.
+ * This also further reduces jitter on boards where ADCSYNC
+ * pin is connected.
+ */
+ fcsr = ucb1400_reg_read(ucb->ac97, UCB_FCSR);
+ ucb1400_reg_write(ucb->ac97, UCB_FCSR, fcsr | UCB_FCSR_AVE);
+
+ ucb1400_adc_enable(ucb->ac97);
+ x_res = ucb1400_ts_read_xres(ucb);
+ y_res = ucb1400_ts_read_yres(ucb);
+ ucb1400_adc_disable(ucb->ac97);
+ dev_dbg(&pdev->dev, "x/y = %d/%d\n", x_res, y_res);
+
+ input_set_abs_params(ucb->ts_idev, ABS_X, 0, x_res, 0, 0);
+ input_set_abs_params(ucb->ts_idev, ABS_Y, 0, y_res, 0, 0);
+ input_set_abs_params(ucb->ts_idev, ABS_PRESSURE, 0, 0, 0, 0);
+
+ ucb1400_ts_stop(ucb);
+
+ error = request_threaded_irq(ucb->irq, NULL, ucb1400_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "UCB1400", ucb);
+ if (error) {
+ dev_err(&pdev->dev,
+ "unable to grab irq%d: %d\n", ucb->irq, error);
+ goto err_free_devs;
+ }
+
+ error = input_register_device(ucb->ts_idev);
+ if (error)
+ goto err_free_irq;
+
+ return 0;
+
+err_free_irq:
+ free_irq(ucb->irq, ucb);
+err_free_devs:
+ input_free_device(ucb->ts_idev);
+err:
+ return error;
+}
+
+static int ucb1400_ts_remove(struct platform_device *pdev)
+{
+ struct ucb1400_ts *ucb = dev_get_platdata(&pdev->dev);
+
+ free_irq(ucb->irq, ucb);
+ input_unregister_device(ucb->ts_idev);
+
+ return 0;
+}
+
+static int __maybe_unused ucb1400_ts_suspend(struct device *dev)
+{
+ struct ucb1400_ts *ucb = dev_get_platdata(dev);
+ struct input_dev *idev = ucb->ts_idev;
+
+ mutex_lock(&idev->mutex);
+
+ if (input_device_enabled(idev))
+ ucb1400_ts_stop(ucb);
+
+ mutex_unlock(&idev->mutex);
+ return 0;
+}
+
+static int __maybe_unused ucb1400_ts_resume(struct device *dev)
+{
+ struct ucb1400_ts *ucb = dev_get_platdata(dev);
+ struct input_dev *idev = ucb->ts_idev;
+
+ mutex_lock(&idev->mutex);
+
+ if (input_device_enabled(idev))
+ ucb1400_ts_start(ucb);
+
+ mutex_unlock(&idev->mutex);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ucb1400_ts_pm_ops,
+ ucb1400_ts_suspend, ucb1400_ts_resume);
+
+static struct platform_driver ucb1400_ts_driver = {
+ .probe = ucb1400_ts_probe,
+ .remove = ucb1400_ts_remove,
+ .driver = {
+ .name = "ucb1400_ts",
+ .pm = &ucb1400_ts_pm_ops,
+ },
+};
+module_platform_driver(ucb1400_ts_driver);
+
+module_param(adcsync, bool, 0444);
+MODULE_PARM_DESC(adcsync, "Synchronize touch readings with ADCSYNC pin.");
+
+module_param(ts_delay, int, 0444);
+MODULE_PARM_DESC(ts_delay, "Delay between panel setup and"
+ " position read. Default = 55us.");
+
+module_param(ts_delay_pressure, int, 0444);
+MODULE_PARM_DESC(ts_delay_pressure,
+ "delay between panel setup and pressure read."
+ " Default = 0us.");
+
+MODULE_DESCRIPTION("Philips UCB1400 touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c
new file mode 100644
index 000000000..d6d04b9f0
--- /dev/null
+++ b/drivers/input/touchscreen/usbtouchscreen.c
@@ -0,0 +1,1865 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/******************************************************************************
+ * usbtouchscreen.c
+ * Driver for USB Touchscreens, supporting those devices:
+ * - eGalax Touchkit
+ * includes eTurboTouch CT-410/510/700
+ * - 3M/Microtouch EX II series
+ * - ITM
+ * - PanJit TouchSet
+ * - eTurboTouch
+ * - Gunze AHL61
+ * - DMC TSC-10/25
+ * - IRTOUCHSYSTEMS/UNITOP
+ * - IdealTEK URTC1000
+ * - General Touch
+ * - GoTop Super_Q2/GogoPen/PenPower tablets
+ * - JASTEC USB touch controller/DigiTech DTR-02U
+ * - Zytronic capacitive touchscreen
+ * - NEXIO/iNexio
+ * - Elo TouchSystems 2700 IntelliTouch
+ * - EasyTouch USB Dual/Multi touch controller from Data Modul
+ *
+ * Copyright (C) 2004-2007 by Daniel Ritz <daniel.ritz@gmx.ch>
+ * Copyright (C) by Todd E. Johnson (mtouchusb.c)
+ *
+ * Driver is based on touchkitusb.c
+ * - ITM parts are from itmtouch.c
+ * - 3M parts are from mtouchusb.c
+ * - PanJit parts are from an unmerged driver by Lanslott Gish
+ * - DMC TSC 10/25 are from Holger Schurig, with ideas from an unmerged
+ * driver from Marius Vollmer
+ *
+ *****************************************************************************/
+
+//#define DEBUG
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+#include <linux/hid.h>
+#include <linux/mutex.h>
+
+static bool swap_xy;
+module_param(swap_xy, bool, 0644);
+MODULE_PARM_DESC(swap_xy, "If set X and Y axes are swapped.");
+
+static bool hwcalib_xy;
+module_param(hwcalib_xy, bool, 0644);
+MODULE_PARM_DESC(hwcalib_xy, "If set hw-calibrated X/Y are used if available");
+
+/* device specifc data/functions */
+struct usbtouch_usb;
+struct usbtouch_device_info {
+ int min_xc, max_xc;
+ int min_yc, max_yc;
+ int min_press, max_press;
+ int rept_size;
+
+ /*
+ * Always service the USB devices irq not just when the input device is
+ * open. This is useful when devices have a watchdog which prevents us
+ * from periodically polling the device. Leave this unset unless your
+ * touchscreen device requires it, as it does consume more of the USB
+ * bandwidth.
+ */
+ bool irq_always;
+
+ void (*process_pkt) (struct usbtouch_usb *usbtouch, unsigned char *pkt, int len);
+
+ /*
+ * used to get the packet len. possible return values:
+ * > 0: packet len
+ * = 0: skip one byte
+ * < 0: -return value more bytes needed
+ */
+ int (*get_pkt_len) (unsigned char *pkt, int len);
+
+ int (*read_data) (struct usbtouch_usb *usbtouch, unsigned char *pkt);
+ int (*alloc) (struct usbtouch_usb *usbtouch);
+ int (*init) (struct usbtouch_usb *usbtouch);
+ void (*exit) (struct usbtouch_usb *usbtouch);
+};
+
+/* a usbtouch device */
+struct usbtouch_usb {
+ unsigned char *data;
+ dma_addr_t data_dma;
+ int data_size;
+ unsigned char *buffer;
+ int buf_len;
+ struct urb *irq;
+ struct usb_interface *interface;
+ struct input_dev *input;
+ struct usbtouch_device_info *type;
+ struct mutex pm_mutex; /* serialize access to open/suspend */
+ bool is_open;
+ char name[128];
+ char phys[64];
+ void *priv;
+
+ int x, y;
+ int touch, press;
+};
+
+
+/* device types */
+enum {
+ DEVTYPE_IGNORE = -1,
+ DEVTYPE_EGALAX,
+ DEVTYPE_PANJIT,
+ DEVTYPE_3M,
+ DEVTYPE_ITM,
+ DEVTYPE_ETURBO,
+ DEVTYPE_GUNZE,
+ DEVTYPE_DMC_TSC10,
+ DEVTYPE_IRTOUCH,
+ DEVTYPE_IRTOUCH_HIRES,
+ DEVTYPE_IDEALTEK,
+ DEVTYPE_GENERAL_TOUCH,
+ DEVTYPE_GOTOP,
+ DEVTYPE_JASTEC,
+ DEVTYPE_E2I,
+ DEVTYPE_ZYTRONIC,
+ DEVTYPE_TC45USB,
+ DEVTYPE_NEXIO,
+ DEVTYPE_ELO,
+ DEVTYPE_ETOUCH,
+};
+
+#define USB_DEVICE_HID_CLASS(vend, prod) \
+ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS \
+ | USB_DEVICE_ID_MATCH_DEVICE, \
+ .idVendor = (vend), \
+ .idProduct = (prod), \
+ .bInterfaceClass = USB_INTERFACE_CLASS_HID
+
+static const struct usb_device_id usbtouch_devices[] = {
+#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
+ /* ignore the HID capable devices, handled by usbhid */
+ {USB_DEVICE_HID_CLASS(0x0eef, 0x0001), .driver_info = DEVTYPE_IGNORE},
+ {USB_DEVICE_HID_CLASS(0x0eef, 0x0002), .driver_info = DEVTYPE_IGNORE},
+
+ /* normal device IDs */
+ {USB_DEVICE(0x3823, 0x0001), .driver_info = DEVTYPE_EGALAX},
+ {USB_DEVICE(0x3823, 0x0002), .driver_info = DEVTYPE_EGALAX},
+ {USB_DEVICE(0x0123, 0x0001), .driver_info = DEVTYPE_EGALAX},
+ {USB_DEVICE(0x0eef, 0x0001), .driver_info = DEVTYPE_EGALAX},
+ {USB_DEVICE(0x0eef, 0x0002), .driver_info = DEVTYPE_EGALAX},
+ {USB_DEVICE(0x1234, 0x0001), .driver_info = DEVTYPE_EGALAX},
+ {USB_DEVICE(0x1234, 0x0002), .driver_info = DEVTYPE_EGALAX},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT
+ {USB_DEVICE(0x134c, 0x0001), .driver_info = DEVTYPE_PANJIT},
+ {USB_DEVICE(0x134c, 0x0002), .driver_info = DEVTYPE_PANJIT},
+ {USB_DEVICE(0x134c, 0x0003), .driver_info = DEVTYPE_PANJIT},
+ {USB_DEVICE(0x134c, 0x0004), .driver_info = DEVTYPE_PANJIT},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_3M
+ {USB_DEVICE(0x0596, 0x0001), .driver_info = DEVTYPE_3M},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ITM
+ {USB_DEVICE(0x0403, 0xf9e9), .driver_info = DEVTYPE_ITM},
+ {USB_DEVICE(0x16e3, 0xf9e9), .driver_info = DEVTYPE_ITM},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO
+ {USB_DEVICE(0x1234, 0x5678), .driver_info = DEVTYPE_ETURBO},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE
+ {USB_DEVICE(0x0637, 0x0001), .driver_info = DEVTYPE_GUNZE},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10
+ {USB_DEVICE(0x0afa, 0x03e8), .driver_info = DEVTYPE_DMC_TSC10},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH
+ {USB_DEVICE(0x255e, 0x0001), .driver_info = DEVTYPE_IRTOUCH},
+ {USB_DEVICE(0x595a, 0x0001), .driver_info = DEVTYPE_IRTOUCH},
+ {USB_DEVICE(0x6615, 0x0001), .driver_info = DEVTYPE_IRTOUCH},
+ {USB_DEVICE(0x6615, 0x0012), .driver_info = DEVTYPE_IRTOUCH_HIRES},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK
+ {USB_DEVICE(0x1391, 0x1000), .driver_info = DEVTYPE_IDEALTEK},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH
+ {USB_DEVICE(0x0dfc, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP
+ {USB_DEVICE(0x08f2, 0x007f), .driver_info = DEVTYPE_GOTOP},
+ {USB_DEVICE(0x08f2, 0x00ce), .driver_info = DEVTYPE_GOTOP},
+ {USB_DEVICE(0x08f2, 0x00f4), .driver_info = DEVTYPE_GOTOP},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_JASTEC
+ {USB_DEVICE(0x0f92, 0x0001), .driver_info = DEVTYPE_JASTEC},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_E2I
+ {USB_DEVICE(0x1ac7, 0x0001), .driver_info = DEVTYPE_E2I},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ZYTRONIC
+ {USB_DEVICE(0x14c8, 0x0003), .driver_info = DEVTYPE_ZYTRONIC},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ETT_TC45USB
+ /* TC5UH */
+ {USB_DEVICE(0x0664, 0x0309), .driver_info = DEVTYPE_TC45USB},
+ /* TC4UM */
+ {USB_DEVICE(0x0664, 0x0306), .driver_info = DEVTYPE_TC45USB},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_NEXIO
+ /* data interface only */
+ {USB_DEVICE_AND_INTERFACE_INFO(0x10f0, 0x2002, 0x0a, 0x00, 0x00),
+ .driver_info = DEVTYPE_NEXIO},
+ {USB_DEVICE_AND_INTERFACE_INFO(0x1870, 0x0001, 0x0a, 0x00, 0x00),
+ .driver_info = DEVTYPE_NEXIO},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ELO
+ {USB_DEVICE(0x04e7, 0x0020), .driver_info = DEVTYPE_ELO},
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_EASYTOUCH
+ {USB_DEVICE(0x7374, 0x0001), .driver_info = DEVTYPE_ETOUCH},
+#endif
+
+ {}
+};
+
+
+/*****************************************************************************
+ * e2i Part
+ */
+
+#ifdef CONFIG_TOUCHSCREEN_USB_E2I
+static int e2i_init(struct usbtouch_usb *usbtouch)
+{
+ int ret;
+ struct usb_device *udev = interface_to_usbdev(usbtouch->interface);
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ 0x01, 0x02, 0x0000, 0x0081,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+
+ dev_dbg(&usbtouch->interface->dev,
+ "%s - usb_control_msg - E2I_RESET - bytes|err: %d\n",
+ __func__, ret);
+ return ret;
+}
+
+static int e2i_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ int tmp = (pkt[0] << 8) | pkt[1];
+ dev->x = (pkt[2] << 8) | pkt[3];
+ dev->y = (pkt[4] << 8) | pkt[5];
+
+ tmp = tmp - 0xA000;
+ dev->touch = (tmp > 0);
+ dev->press = (tmp > 0 ? tmp : 0);
+
+ return 1;
+}
+#endif
+
+
+/*****************************************************************************
+ * eGalax part
+ */
+
+#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
+
+#ifndef MULTI_PACKET
+#define MULTI_PACKET
+#endif
+
+#define EGALAX_PKT_TYPE_MASK 0xFE
+#define EGALAX_PKT_TYPE_REPT 0x80
+#define EGALAX_PKT_TYPE_DIAG 0x0A
+
+static int egalax_init(struct usbtouch_usb *usbtouch)
+{
+ int ret, i;
+ unsigned char *buf;
+ struct usb_device *udev = interface_to_usbdev(usbtouch->interface);
+
+ /*
+ * An eGalax diagnostic packet kicks the device into using the right
+ * protocol. We send a "check active" packet. The response will be
+ * read later and ignored.
+ */
+
+ buf = kmalloc(3, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = EGALAX_PKT_TYPE_DIAG;
+ buf[1] = 1; /* length */
+ buf[2] = 'A'; /* command - check active */
+
+ for (i = 0; i < 3; i++) {
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ 0,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, 0, buf, 3,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret >= 0) {
+ ret = 0;
+ break;
+ }
+ if (ret != -EPIPE)
+ break;
+ }
+
+ kfree(buf);
+
+ return ret;
+}
+
+static int egalax_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ if ((pkt[0] & EGALAX_PKT_TYPE_MASK) != EGALAX_PKT_TYPE_REPT)
+ return 0;
+
+ dev->x = ((pkt[3] & 0x0F) << 7) | (pkt[4] & 0x7F);
+ dev->y = ((pkt[1] & 0x0F) << 7) | (pkt[2] & 0x7F);
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+
+static int egalax_get_pkt_len(unsigned char *buf, int len)
+{
+ switch (buf[0] & EGALAX_PKT_TYPE_MASK) {
+ case EGALAX_PKT_TYPE_REPT:
+ return 5;
+
+ case EGALAX_PKT_TYPE_DIAG:
+ if (len < 2)
+ return -1;
+
+ return buf[1] + 2;
+ }
+
+ return 0;
+}
+#endif
+
+/*****************************************************************************
+ * EasyTouch part
+ */
+
+#ifdef CONFIG_TOUCHSCREEN_USB_EASYTOUCH
+
+#ifndef MULTI_PACKET
+#define MULTI_PACKET
+#endif
+
+#define ETOUCH_PKT_TYPE_MASK 0xFE
+#define ETOUCH_PKT_TYPE_REPT 0x80
+#define ETOUCH_PKT_TYPE_REPT2 0xB0
+#define ETOUCH_PKT_TYPE_DIAG 0x0A
+
+static int etouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ if ((pkt[0] & ETOUCH_PKT_TYPE_MASK) != ETOUCH_PKT_TYPE_REPT &&
+ (pkt[0] & ETOUCH_PKT_TYPE_MASK) != ETOUCH_PKT_TYPE_REPT2)
+ return 0;
+
+ dev->x = ((pkt[1] & 0x1F) << 7) | (pkt[2] & 0x7F);
+ dev->y = ((pkt[3] & 0x1F) << 7) | (pkt[4] & 0x7F);
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+
+static int etouch_get_pkt_len(unsigned char *buf, int len)
+{
+ switch (buf[0] & ETOUCH_PKT_TYPE_MASK) {
+ case ETOUCH_PKT_TYPE_REPT:
+ case ETOUCH_PKT_TYPE_REPT2:
+ return 5;
+
+ case ETOUCH_PKT_TYPE_DIAG:
+ if (len < 2)
+ return -1;
+
+ return buf[1] + 2;
+ }
+
+ return 0;
+}
+#endif
+
+/*****************************************************************************
+ * PanJit Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT
+static int panjit_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1];
+ dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3];
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+#endif
+
+
+/*****************************************************************************
+ * 3M/Microtouch Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_3M
+
+#define MTOUCHUSB_ASYNC_REPORT 1
+#define MTOUCHUSB_RESET 7
+#define MTOUCHUSB_REQ_CTRLLR_ID 10
+
+#define MTOUCHUSB_REQ_CTRLLR_ID_LEN 16
+
+static int mtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ if (hwcalib_xy) {
+ dev->x = (pkt[4] << 8) | pkt[3];
+ dev->y = 0xffff - ((pkt[6] << 8) | pkt[5]);
+ } else {
+ dev->x = (pkt[8] << 8) | pkt[7];
+ dev->y = (pkt[10] << 8) | pkt[9];
+ }
+ dev->touch = (pkt[2] & 0x40) ? 1 : 0;
+
+ return 1;
+}
+
+struct mtouch_priv {
+ u8 fw_rev_major;
+ u8 fw_rev_minor;
+};
+
+static ssize_t mtouch_firmware_rev_show(struct device *dev,
+ struct device_attribute *attr, char *output)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
+ struct mtouch_priv *priv = usbtouch->priv;
+
+ return scnprintf(output, PAGE_SIZE, "%1x.%1x\n",
+ priv->fw_rev_major, priv->fw_rev_minor);
+}
+static DEVICE_ATTR(firmware_rev, 0444, mtouch_firmware_rev_show, NULL);
+
+static struct attribute *mtouch_attrs[] = {
+ &dev_attr_firmware_rev.attr,
+ NULL
+};
+
+static const struct attribute_group mtouch_attr_group = {
+ .attrs = mtouch_attrs,
+};
+
+static int mtouch_get_fw_revision(struct usbtouch_usb *usbtouch)
+{
+ struct usb_device *udev = interface_to_usbdev(usbtouch->interface);
+ struct mtouch_priv *priv = usbtouch->priv;
+ u8 *buf;
+ int ret;
+
+ buf = kzalloc(MTOUCHUSB_REQ_CTRLLR_ID_LEN, GFP_NOIO);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ MTOUCHUSB_REQ_CTRLLR_ID,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, 0, buf, MTOUCHUSB_REQ_CTRLLR_ID_LEN,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret != MTOUCHUSB_REQ_CTRLLR_ID_LEN) {
+ dev_warn(&usbtouch->interface->dev,
+ "Failed to read FW rev: %d\n", ret);
+ ret = ret < 0 ? ret : -EIO;
+ goto free;
+ }
+
+ priv->fw_rev_major = buf[3];
+ priv->fw_rev_minor = buf[4];
+
+ ret = 0;
+
+free:
+ kfree(buf);
+ return ret;
+}
+
+static int mtouch_alloc(struct usbtouch_usb *usbtouch)
+{
+ int ret;
+
+ usbtouch->priv = kmalloc(sizeof(struct mtouch_priv), GFP_KERNEL);
+ if (!usbtouch->priv)
+ return -ENOMEM;
+
+ ret = sysfs_create_group(&usbtouch->interface->dev.kobj,
+ &mtouch_attr_group);
+ if (ret) {
+ kfree(usbtouch->priv);
+ usbtouch->priv = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtouch_init(struct usbtouch_usb *usbtouch)
+{
+ int ret, i;
+ struct usb_device *udev = interface_to_usbdev(usbtouch->interface);
+
+ ret = mtouch_get_fw_revision(usbtouch);
+ if (ret)
+ return ret;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ MTOUCHUSB_RESET,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 1, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ dev_dbg(&usbtouch->interface->dev,
+ "%s - usb_control_msg - MTOUCHUSB_RESET - bytes|err: %d\n",
+ __func__, ret);
+ if (ret < 0)
+ return ret;
+ msleep(150);
+
+ for (i = 0; i < 3; i++) {
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ MTOUCHUSB_ASYNC_REPORT,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 1, 1, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ dev_dbg(&usbtouch->interface->dev,
+ "%s - usb_control_msg - MTOUCHUSB_ASYNC_REPORT - bytes|err: %d\n",
+ __func__, ret);
+ if (ret >= 0)
+ break;
+ if (ret != -EPIPE)
+ return ret;
+ }
+
+ /* Default min/max xy are the raw values, override if using hw-calib */
+ if (hwcalib_xy) {
+ input_set_abs_params(usbtouch->input, ABS_X, 0, 0xffff, 0, 0);
+ input_set_abs_params(usbtouch->input, ABS_Y, 0, 0xffff, 0, 0);
+ }
+
+ return 0;
+}
+
+static void mtouch_exit(struct usbtouch_usb *usbtouch)
+{
+ struct mtouch_priv *priv = usbtouch->priv;
+
+ sysfs_remove_group(&usbtouch->interface->dev.kobj, &mtouch_attr_group);
+ kfree(priv);
+}
+#endif
+
+
+/*****************************************************************************
+ * ITM Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_ITM
+static int itm_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ int touch;
+ /*
+ * ITM devices report invalid x/y data if not touched.
+ * if the screen was touched before but is not touched any more
+ * report touch as 0 with the last valid x/y data once. then stop
+ * reporting data until touched again.
+ */
+ dev->press = ((pkt[2] & 0x01) << 7) | (pkt[5] & 0x7F);
+
+ touch = ~pkt[7] & 0x20;
+ if (!touch) {
+ if (dev->touch) {
+ dev->touch = 0;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ dev->x = ((pkt[0] & 0x1F) << 7) | (pkt[3] & 0x7F);
+ dev->y = ((pkt[1] & 0x1F) << 7) | (pkt[4] & 0x7F);
+ dev->touch = touch;
+
+ return 1;
+}
+#endif
+
+
+/*****************************************************************************
+ * eTurboTouch part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO
+#ifndef MULTI_PACKET
+#define MULTI_PACKET
+#endif
+static int eturbo_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ unsigned int shift;
+
+ /* packets should start with sync */
+ if (!(pkt[0] & 0x80))
+ return 0;
+
+ shift = (6 - (pkt[0] & 0x03));
+ dev->x = ((pkt[3] << 7) | pkt[4]) >> shift;
+ dev->y = ((pkt[1] << 7) | pkt[2]) >> shift;
+ dev->touch = (pkt[0] & 0x10) ? 1 : 0;
+
+ return 1;
+}
+
+static int eturbo_get_pkt_len(unsigned char *buf, int len)
+{
+ if (buf[0] & 0x80)
+ return 5;
+ if (buf[0] == 0x01)
+ return 3;
+ return 0;
+}
+#endif
+
+
+/*****************************************************************************
+ * Gunze part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE
+static int gunze_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ if (!(pkt[0] & 0x80) || ((pkt[1] | pkt[2] | pkt[3]) & 0x80))
+ return 0;
+
+ dev->x = ((pkt[0] & 0x1F) << 7) | (pkt[2] & 0x7F);
+ dev->y = ((pkt[1] & 0x1F) << 7) | (pkt[3] & 0x7F);
+ dev->touch = pkt[0] & 0x20;
+
+ return 1;
+}
+#endif
+
+/*****************************************************************************
+ * DMC TSC-10/25 Part
+ *
+ * Documentation about the controller and it's protocol can be found at
+ * http://www.dmccoltd.com/files/controler/tsc10usb_pi_e.pdf
+ * http://www.dmccoltd.com/files/controler/tsc25_usb_e.pdf
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10
+
+/* supported data rates. currently using 130 */
+#define TSC10_RATE_POINT 0x50
+#define TSC10_RATE_30 0x40
+#define TSC10_RATE_50 0x41
+#define TSC10_RATE_80 0x42
+#define TSC10_RATE_100 0x43
+#define TSC10_RATE_130 0x44
+#define TSC10_RATE_150 0x45
+
+/* commands */
+#define TSC10_CMD_RESET 0x55
+#define TSC10_CMD_RATE 0x05
+#define TSC10_CMD_DATA1 0x01
+
+static int dmc_tsc10_init(struct usbtouch_usb *usbtouch)
+{
+ struct usb_device *dev = interface_to_usbdev(usbtouch->interface);
+ int ret = -ENOMEM;
+ unsigned char *buf;
+
+ buf = kmalloc(2, GFP_NOIO);
+ if (!buf)
+ goto err_nobuf;
+ /* reset */
+ buf[0] = buf[1] = 0xFF;
+ ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0),
+ TSC10_CMD_RESET,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, 0, buf, 2, USB_CTRL_SET_TIMEOUT);
+ if (ret < 0)
+ goto err_out;
+ if (buf[0] != 0x06) {
+ ret = -ENODEV;
+ goto err_out;
+ }
+
+ /* TSC-25 data sheet specifies a delay after the RESET command */
+ msleep(150);
+
+ /* set coordinate output rate */
+ buf[0] = buf[1] = 0xFF;
+ ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0),
+ TSC10_CMD_RATE,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ TSC10_RATE_150, 0, buf, 2, USB_CTRL_SET_TIMEOUT);
+ if (ret < 0)
+ goto err_out;
+ if ((buf[0] != 0x06) && (buf[0] != 0x15 || buf[1] != 0x01)) {
+ ret = -ENODEV;
+ goto err_out;
+ }
+
+ /* start sending data */
+ ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ TSC10_CMD_DATA1,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+err_out:
+ kfree(buf);
+err_nobuf:
+ return ret;
+}
+
+
+static int dmc_tsc10_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = ((pkt[2] & 0x03) << 8) | pkt[1];
+ dev->y = ((pkt[4] & 0x03) << 8) | pkt[3];
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+#endif
+
+
+/*****************************************************************************
+ * IRTOUCH Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH
+static int irtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = (pkt[3] << 8) | pkt[2];
+ dev->y = (pkt[5] << 8) | pkt[4];
+ dev->touch = (pkt[1] & 0x03) ? 1 : 0;
+
+ return 1;
+}
+#endif
+
+/*****************************************************************************
+ * ET&T TC5UH/TC4UM part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_ETT_TC45USB
+static int tc45usb_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1];
+ dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3];
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+#endif
+
+/*****************************************************************************
+ * IdealTEK URTC1000 Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK
+#ifndef MULTI_PACKET
+#define MULTI_PACKET
+#endif
+static int idealtek_get_pkt_len(unsigned char *buf, int len)
+{
+ if (buf[0] & 0x80)
+ return 5;
+ if (buf[0] == 0x01)
+ return len;
+ return 0;
+}
+
+static int idealtek_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ switch (pkt[0] & 0x98) {
+ case 0x88:
+ /* touch data in IdealTEK mode */
+ dev->x = (pkt[1] << 5) | (pkt[2] >> 2);
+ dev->y = (pkt[3] << 5) | (pkt[4] >> 2);
+ dev->touch = (pkt[0] & 0x40) ? 1 : 0;
+ return 1;
+
+ case 0x98:
+ /* touch data in MT emulation mode */
+ dev->x = (pkt[2] << 5) | (pkt[1] >> 2);
+ dev->y = (pkt[4] << 5) | (pkt[3] >> 2);
+ dev->touch = (pkt[0] & 0x40) ? 1 : 0;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+#endif
+
+/*****************************************************************************
+ * General Touch Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH
+static int general_touch_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = (pkt[2] << 8) | pkt[1];
+ dev->y = (pkt[4] << 8) | pkt[3];
+ dev->press = pkt[5] & 0xff;
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+#endif
+
+/*****************************************************************************
+ * GoTop Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP
+static int gotop_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = ((pkt[1] & 0x38) << 4) | pkt[2];
+ dev->y = ((pkt[1] & 0x07) << 7) | pkt[3];
+ dev->touch = pkt[0] & 0x01;
+
+ return 1;
+}
+#endif
+
+/*****************************************************************************
+ * JASTEC Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_JASTEC
+static int jastec_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = ((pkt[0] & 0x3f) << 6) | (pkt[2] & 0x3f);
+ dev->y = ((pkt[1] & 0x3f) << 6) | (pkt[3] & 0x3f);
+ dev->touch = (pkt[0] & 0x40) >> 6;
+
+ return 1;
+}
+#endif
+
+/*****************************************************************************
+ * Zytronic Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_ZYTRONIC
+static int zytronic_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ struct usb_interface *intf = dev->interface;
+
+ switch (pkt[0]) {
+ case 0x3A: /* command response */
+ dev_dbg(&intf->dev, "%s: Command response %d\n", __func__, pkt[1]);
+ break;
+
+ case 0xC0: /* down */
+ dev->x = (pkt[1] & 0x7f) | ((pkt[2] & 0x07) << 7);
+ dev->y = (pkt[3] & 0x7f) | ((pkt[4] & 0x07) << 7);
+ dev->touch = 1;
+ dev_dbg(&intf->dev, "%s: down %d,%d\n", __func__, dev->x, dev->y);
+ return 1;
+
+ case 0x80: /* up */
+ dev->x = (pkt[1] & 0x7f) | ((pkt[2] & 0x07) << 7);
+ dev->y = (pkt[3] & 0x7f) | ((pkt[4] & 0x07) << 7);
+ dev->touch = 0;
+ dev_dbg(&intf->dev, "%s: up %d,%d\n", __func__, dev->x, dev->y);
+ return 1;
+
+ default:
+ dev_dbg(&intf->dev, "%s: Unknown return %d\n", __func__, pkt[0]);
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+/*****************************************************************************
+ * NEXIO Part
+ */
+#ifdef CONFIG_TOUCHSCREEN_USB_NEXIO
+
+#define NEXIO_TIMEOUT 5000
+#define NEXIO_BUFSIZE 1024
+#define NEXIO_THRESHOLD 50
+
+struct nexio_priv {
+ struct urb *ack;
+ unsigned char *ack_buf;
+};
+
+struct nexio_touch_packet {
+ u8 flags; /* 0xe1 = touch, 0xe1 = release */
+ __be16 data_len; /* total bytes of touch data */
+ __be16 x_len; /* bytes for X axis */
+ __be16 y_len; /* bytes for Y axis */
+ u8 data[];
+} __attribute__ ((packed));
+
+static unsigned char nexio_ack_pkt[2] = { 0xaa, 0x02 };
+static unsigned char nexio_init_pkt[4] = { 0x82, 0x04, 0x0a, 0x0f };
+
+static void nexio_ack_complete(struct urb *urb)
+{
+}
+
+static int nexio_alloc(struct usbtouch_usb *usbtouch)
+{
+ struct nexio_priv *priv;
+ int ret = -ENOMEM;
+
+ usbtouch->priv = kmalloc(sizeof(struct nexio_priv), GFP_KERNEL);
+ if (!usbtouch->priv)
+ goto out_buf;
+
+ priv = usbtouch->priv;
+
+ priv->ack_buf = kmemdup(nexio_ack_pkt, sizeof(nexio_ack_pkt),
+ GFP_KERNEL);
+ if (!priv->ack_buf)
+ goto err_priv;
+
+ priv->ack = usb_alloc_urb(0, GFP_KERNEL);
+ if (!priv->ack) {
+ dev_dbg(&usbtouch->interface->dev,
+ "%s - usb_alloc_urb failed: usbtouch->ack\n", __func__);
+ goto err_ack_buf;
+ }
+
+ return 0;
+
+err_ack_buf:
+ kfree(priv->ack_buf);
+err_priv:
+ kfree(priv);
+out_buf:
+ return ret;
+}
+
+static int nexio_init(struct usbtouch_usb *usbtouch)
+{
+ struct usb_device *dev = interface_to_usbdev(usbtouch->interface);
+ struct usb_host_interface *interface = usbtouch->interface->cur_altsetting;
+ struct nexio_priv *priv = usbtouch->priv;
+ int ret = -ENOMEM;
+ int actual_len, i;
+ unsigned char *buf;
+ char *firmware_ver = NULL, *device_name = NULL;
+ int input_ep = 0, output_ep = 0;
+
+ /* find first input and output endpoint */
+ for (i = 0; i < interface->desc.bNumEndpoints; i++) {
+ if (!input_ep &&
+ usb_endpoint_dir_in(&interface->endpoint[i].desc))
+ input_ep = interface->endpoint[i].desc.bEndpointAddress;
+ if (!output_ep &&
+ usb_endpoint_dir_out(&interface->endpoint[i].desc))
+ output_ep = interface->endpoint[i].desc.bEndpointAddress;
+ }
+ if (!input_ep || !output_ep)
+ return -ENXIO;
+
+ buf = kmalloc(NEXIO_BUFSIZE, GFP_NOIO);
+ if (!buf)
+ goto out_buf;
+
+ /* two empty reads */
+ for (i = 0; i < 2; i++) {
+ ret = usb_bulk_msg(dev, usb_rcvbulkpipe(dev, input_ep),
+ buf, NEXIO_BUFSIZE, &actual_len,
+ NEXIO_TIMEOUT);
+ if (ret < 0)
+ goto out_buf;
+ }
+
+ /* send init command */
+ memcpy(buf, nexio_init_pkt, sizeof(nexio_init_pkt));
+ ret = usb_bulk_msg(dev, usb_sndbulkpipe(dev, output_ep),
+ buf, sizeof(nexio_init_pkt), &actual_len,
+ NEXIO_TIMEOUT);
+ if (ret < 0)
+ goto out_buf;
+
+ /* read replies */
+ for (i = 0; i < 3; i++) {
+ memset(buf, 0, NEXIO_BUFSIZE);
+ ret = usb_bulk_msg(dev, usb_rcvbulkpipe(dev, input_ep),
+ buf, NEXIO_BUFSIZE, &actual_len,
+ NEXIO_TIMEOUT);
+ if (ret < 0 || actual_len < 1 || buf[1] != actual_len)
+ continue;
+ switch (buf[0]) {
+ case 0x83: /* firmware version */
+ if (!firmware_ver)
+ firmware_ver = kstrdup(&buf[2], GFP_NOIO);
+ break;
+ case 0x84: /* device name */
+ if (!device_name)
+ device_name = kstrdup(&buf[2], GFP_NOIO);
+ break;
+ }
+ }
+
+ printk(KERN_INFO "Nexio device: %s, firmware version: %s\n",
+ device_name, firmware_ver);
+
+ kfree(firmware_ver);
+ kfree(device_name);
+
+ usb_fill_bulk_urb(priv->ack, dev, usb_sndbulkpipe(dev, output_ep),
+ priv->ack_buf, sizeof(nexio_ack_pkt),
+ nexio_ack_complete, usbtouch);
+ ret = 0;
+
+out_buf:
+ kfree(buf);
+ return ret;
+}
+
+static void nexio_exit(struct usbtouch_usb *usbtouch)
+{
+ struct nexio_priv *priv = usbtouch->priv;
+
+ usb_kill_urb(priv->ack);
+ usb_free_urb(priv->ack);
+ kfree(priv->ack_buf);
+ kfree(priv);
+}
+
+static int nexio_read_data(struct usbtouch_usb *usbtouch, unsigned char *pkt)
+{
+ struct device *dev = &usbtouch->interface->dev;
+ struct nexio_touch_packet *packet = (void *) pkt;
+ struct nexio_priv *priv = usbtouch->priv;
+ unsigned int data_len = be16_to_cpu(packet->data_len);
+ unsigned int x_len = be16_to_cpu(packet->x_len);
+ unsigned int y_len = be16_to_cpu(packet->y_len);
+ int x, y, begin_x, begin_y, end_x, end_y, w, h, ret;
+
+ /* got touch data? */
+ if ((pkt[0] & 0xe0) != 0xe0)
+ return 0;
+
+ if (data_len > 0xff)
+ data_len -= 0x100;
+ if (x_len > 0xff)
+ x_len -= 0x80;
+
+ /* send ACK */
+ ret = usb_submit_urb(priv->ack, GFP_ATOMIC);
+ if (ret)
+ dev_warn(dev, "Failed to submit ACK URB: %d\n", ret);
+
+ if (!usbtouch->type->max_xc) {
+ usbtouch->type->max_xc = 2 * x_len;
+ input_set_abs_params(usbtouch->input, ABS_X,
+ 0, usbtouch->type->max_xc, 0, 0);
+ usbtouch->type->max_yc = 2 * y_len;
+ input_set_abs_params(usbtouch->input, ABS_Y,
+ 0, usbtouch->type->max_yc, 0, 0);
+ }
+ /*
+ * The device reports state of IR sensors on X and Y axes.
+ * Each byte represents "darkness" percentage (0-100) of one element.
+ * 17" touchscreen reports only 64 x 52 bytes so the resolution is low.
+ * This also means that there's a limited multi-touch capability but
+ * it's disabled (and untested) here as there's no X driver for that.
+ */
+ begin_x = end_x = begin_y = end_y = -1;
+ for (x = 0; x < x_len; x++) {
+ if (begin_x == -1 && packet->data[x] > NEXIO_THRESHOLD) {
+ begin_x = x;
+ continue;
+ }
+ if (end_x == -1 && begin_x != -1 && packet->data[x] < NEXIO_THRESHOLD) {
+ end_x = x - 1;
+ for (y = x_len; y < data_len; y++) {
+ if (begin_y == -1 && packet->data[y] > NEXIO_THRESHOLD) {
+ begin_y = y - x_len;
+ continue;
+ }
+ if (end_y == -1 &&
+ begin_y != -1 && packet->data[y] < NEXIO_THRESHOLD) {
+ end_y = y - 1 - x_len;
+ w = end_x - begin_x;
+ h = end_y - begin_y;
+#if 0
+ /* multi-touch */
+ input_report_abs(usbtouch->input,
+ ABS_MT_TOUCH_MAJOR, max(w,h));
+ input_report_abs(usbtouch->input,
+ ABS_MT_TOUCH_MINOR, min(x,h));
+ input_report_abs(usbtouch->input,
+ ABS_MT_POSITION_X, 2*begin_x+w);
+ input_report_abs(usbtouch->input,
+ ABS_MT_POSITION_Y, 2*begin_y+h);
+ input_report_abs(usbtouch->input,
+ ABS_MT_ORIENTATION, w > h);
+ input_mt_sync(usbtouch->input);
+#endif
+ /* single touch */
+ usbtouch->x = 2 * begin_x + w;
+ usbtouch->y = 2 * begin_y + h;
+ usbtouch->touch = packet->flags & 0x01;
+ begin_y = end_y = -1;
+ return 1;
+ }
+ }
+ begin_x = end_x = -1;
+ }
+
+ }
+ return 0;
+}
+#endif
+
+
+/*****************************************************************************
+ * ELO part
+ */
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ELO
+
+static int elo_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
+{
+ dev->x = (pkt[3] << 8) | pkt[2];
+ dev->y = (pkt[5] << 8) | pkt[4];
+ dev->touch = pkt[6] > 0;
+ dev->press = pkt[6];
+
+ return 1;
+}
+#endif
+
+
+/*****************************************************************************
+ * the different device descriptors
+ */
+#ifdef MULTI_PACKET
+static void usbtouch_process_multi(struct usbtouch_usb *usbtouch,
+ unsigned char *pkt, int len);
+#endif
+
+static struct usbtouch_device_info usbtouch_dev_info[] = {
+#ifdef CONFIG_TOUCHSCREEN_USB_ELO
+ [DEVTYPE_ELO] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .max_press = 0xff,
+ .rept_size = 8,
+ .read_data = elo_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
+ [DEVTYPE_EGALAX] = {
+ .min_xc = 0x0,
+ .max_xc = 0x07ff,
+ .min_yc = 0x0,
+ .max_yc = 0x07ff,
+ .rept_size = 16,
+ .process_pkt = usbtouch_process_multi,
+ .get_pkt_len = egalax_get_pkt_len,
+ .read_data = egalax_read_data,
+ .init = egalax_init,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT
+ [DEVTYPE_PANJIT] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .rept_size = 8,
+ .read_data = panjit_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_3M
+ [DEVTYPE_3M] = {
+ .min_xc = 0x0,
+ .max_xc = 0x4000,
+ .min_yc = 0x0,
+ .max_yc = 0x4000,
+ .rept_size = 11,
+ .read_data = mtouch_read_data,
+ .alloc = mtouch_alloc,
+ .init = mtouch_init,
+ .exit = mtouch_exit,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ITM
+ [DEVTYPE_ITM] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .max_press = 0xff,
+ .rept_size = 8,
+ .read_data = itm_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO
+ [DEVTYPE_ETURBO] = {
+ .min_xc = 0x0,
+ .max_xc = 0x07ff,
+ .min_yc = 0x0,
+ .max_yc = 0x07ff,
+ .rept_size = 8,
+ .process_pkt = usbtouch_process_multi,
+ .get_pkt_len = eturbo_get_pkt_len,
+ .read_data = eturbo_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE
+ [DEVTYPE_GUNZE] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .rept_size = 4,
+ .read_data = gunze_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10
+ [DEVTYPE_DMC_TSC10] = {
+ .min_xc = 0x0,
+ .max_xc = 0x03ff,
+ .min_yc = 0x0,
+ .max_yc = 0x03ff,
+ .rept_size = 5,
+ .init = dmc_tsc10_init,
+ .read_data = dmc_tsc10_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH
+ [DEVTYPE_IRTOUCH] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .rept_size = 8,
+ .read_data = irtouch_read_data,
+ },
+
+ [DEVTYPE_IRTOUCH_HIRES] = {
+ .min_xc = 0x0,
+ .max_xc = 0x7fff,
+ .min_yc = 0x0,
+ .max_yc = 0x7fff,
+ .rept_size = 8,
+ .read_data = irtouch_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK
+ [DEVTYPE_IDEALTEK] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .rept_size = 8,
+ .process_pkt = usbtouch_process_multi,
+ .get_pkt_len = idealtek_get_pkt_len,
+ .read_data = idealtek_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH
+ [DEVTYPE_GENERAL_TOUCH] = {
+ .min_xc = 0x0,
+ .max_xc = 0x7fff,
+ .min_yc = 0x0,
+ .max_yc = 0x7fff,
+ .rept_size = 7,
+ .read_data = general_touch_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP
+ [DEVTYPE_GOTOP] = {
+ .min_xc = 0x0,
+ .max_xc = 0x03ff,
+ .min_yc = 0x0,
+ .max_yc = 0x03ff,
+ .rept_size = 4,
+ .read_data = gotop_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_JASTEC
+ [DEVTYPE_JASTEC] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .rept_size = 4,
+ .read_data = jastec_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_E2I
+ [DEVTYPE_E2I] = {
+ .min_xc = 0x0,
+ .max_xc = 0x7fff,
+ .min_yc = 0x0,
+ .max_yc = 0x7fff,
+ .rept_size = 6,
+ .init = e2i_init,
+ .read_data = e2i_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ZYTRONIC
+ [DEVTYPE_ZYTRONIC] = {
+ .min_xc = 0x0,
+ .max_xc = 0x03ff,
+ .min_yc = 0x0,
+ .max_yc = 0x03ff,
+ .rept_size = 5,
+ .read_data = zytronic_read_data,
+ .irq_always = true,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_ETT_TC45USB
+ [DEVTYPE_TC45USB] = {
+ .min_xc = 0x0,
+ .max_xc = 0x0fff,
+ .min_yc = 0x0,
+ .max_yc = 0x0fff,
+ .rept_size = 5,
+ .read_data = tc45usb_read_data,
+ },
+#endif
+
+#ifdef CONFIG_TOUCHSCREEN_USB_NEXIO
+ [DEVTYPE_NEXIO] = {
+ .rept_size = 1024,
+ .irq_always = true,
+ .read_data = nexio_read_data,
+ .alloc = nexio_alloc,
+ .init = nexio_init,
+ .exit = nexio_exit,
+ },
+#endif
+#ifdef CONFIG_TOUCHSCREEN_USB_EASYTOUCH
+ [DEVTYPE_ETOUCH] = {
+ .min_xc = 0x0,
+ .max_xc = 0x07ff,
+ .min_yc = 0x0,
+ .max_yc = 0x07ff,
+ .rept_size = 16,
+ .process_pkt = usbtouch_process_multi,
+ .get_pkt_len = etouch_get_pkt_len,
+ .read_data = etouch_read_data,
+ },
+#endif
+};
+
+
+/*****************************************************************************
+ * Generic Part
+ */
+static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch,
+ unsigned char *pkt, int len)
+{
+ struct usbtouch_device_info *type = usbtouch->type;
+
+ if (!type->read_data(usbtouch, pkt))
+ return;
+
+ input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch);
+
+ if (swap_xy) {
+ input_report_abs(usbtouch->input, ABS_X, usbtouch->y);
+ input_report_abs(usbtouch->input, ABS_Y, usbtouch->x);
+ } else {
+ input_report_abs(usbtouch->input, ABS_X, usbtouch->x);
+ input_report_abs(usbtouch->input, ABS_Y, usbtouch->y);
+ }
+ if (type->max_press)
+ input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press);
+ input_sync(usbtouch->input);
+}
+
+
+#ifdef MULTI_PACKET
+static void usbtouch_process_multi(struct usbtouch_usb *usbtouch,
+ unsigned char *pkt, int len)
+{
+ unsigned char *buffer;
+ int pkt_len, pos, buf_len, tmp;
+
+ /* process buffer */
+ if (unlikely(usbtouch->buf_len)) {
+ /* try to get size */
+ pkt_len = usbtouch->type->get_pkt_len(
+ usbtouch->buffer, usbtouch->buf_len);
+
+ /* drop? */
+ if (unlikely(!pkt_len))
+ goto out_flush_buf;
+
+ /* need to append -pkt_len bytes before able to get size */
+ if (unlikely(pkt_len < 0)) {
+ int append = -pkt_len;
+ if (unlikely(append > len))
+ append = len;
+ if (usbtouch->buf_len + append >= usbtouch->type->rept_size)
+ goto out_flush_buf;
+ memcpy(usbtouch->buffer + usbtouch->buf_len, pkt, append);
+ usbtouch->buf_len += append;
+
+ pkt_len = usbtouch->type->get_pkt_len(
+ usbtouch->buffer, usbtouch->buf_len);
+ if (pkt_len < 0)
+ return;
+ }
+
+ /* append */
+ tmp = pkt_len - usbtouch->buf_len;
+ if (usbtouch->buf_len + tmp >= usbtouch->type->rept_size)
+ goto out_flush_buf;
+ memcpy(usbtouch->buffer + usbtouch->buf_len, pkt, tmp);
+ usbtouch_process_pkt(usbtouch, usbtouch->buffer, pkt_len);
+
+ buffer = pkt + tmp;
+ buf_len = len - tmp;
+ } else {
+ buffer = pkt;
+ buf_len = len;
+ }
+
+ /* loop over the received packet, process */
+ pos = 0;
+ while (pos < buf_len) {
+ /* get packet len */
+ pkt_len = usbtouch->type->get_pkt_len(buffer + pos,
+ buf_len - pos);
+
+ /* unknown packet: skip one byte */
+ if (unlikely(!pkt_len)) {
+ pos++;
+ continue;
+ }
+
+ /* full packet: process */
+ if (likely((pkt_len > 0) && (pkt_len <= buf_len - pos))) {
+ usbtouch_process_pkt(usbtouch, buffer + pos, pkt_len);
+ } else {
+ /* incomplete packet: save in buffer */
+ memcpy(usbtouch->buffer, buffer + pos, buf_len - pos);
+ usbtouch->buf_len = buf_len - pos;
+ return;
+ }
+ pos += pkt_len;
+ }
+
+out_flush_buf:
+ usbtouch->buf_len = 0;
+ return;
+}
+#endif
+
+
+static void usbtouch_irq(struct urb *urb)
+{
+ struct usbtouch_usb *usbtouch = urb->context;
+ struct device *dev = &usbtouch->interface->dev;
+ int retval;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ETIME:
+ /* this urb is timing out */
+ dev_dbg(dev,
+ "%s - urb timed out - was the device unplugged?\n",
+ __func__);
+ return;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -EPIPE:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, urb->status);
+ return;
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+ __func__, urb->status);
+ goto exit;
+ }
+
+ usbtouch->type->process_pkt(usbtouch, usbtouch->data, urb->actual_length);
+
+exit:
+ usb_mark_last_busy(interface_to_usbdev(usbtouch->interface));
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(dev, "%s - usb_submit_urb failed with result: %d\n",
+ __func__, retval);
+}
+
+static int usbtouch_open(struct input_dev *input)
+{
+ struct usbtouch_usb *usbtouch = input_get_drvdata(input);
+ int r;
+
+ usbtouch->irq->dev = interface_to_usbdev(usbtouch->interface);
+
+ r = usb_autopm_get_interface(usbtouch->interface) ? -EIO : 0;
+ if (r < 0)
+ goto out;
+
+ mutex_lock(&usbtouch->pm_mutex);
+ if (!usbtouch->type->irq_always) {
+ if (usb_submit_urb(usbtouch->irq, GFP_KERNEL)) {
+ r = -EIO;
+ goto out_put;
+ }
+ }
+
+ usbtouch->interface->needs_remote_wakeup = 1;
+ usbtouch->is_open = true;
+out_put:
+ mutex_unlock(&usbtouch->pm_mutex);
+ usb_autopm_put_interface(usbtouch->interface);
+out:
+ return r;
+}
+
+static void usbtouch_close(struct input_dev *input)
+{
+ struct usbtouch_usb *usbtouch = input_get_drvdata(input);
+ int r;
+
+ mutex_lock(&usbtouch->pm_mutex);
+ if (!usbtouch->type->irq_always)
+ usb_kill_urb(usbtouch->irq);
+ usbtouch->is_open = false;
+ mutex_unlock(&usbtouch->pm_mutex);
+
+ r = usb_autopm_get_interface(usbtouch->interface);
+ usbtouch->interface->needs_remote_wakeup = 0;
+ if (!r)
+ usb_autopm_put_interface(usbtouch->interface);
+}
+
+static int usbtouch_suspend
+(struct usb_interface *intf, pm_message_t message)
+{
+ struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
+
+ usb_kill_urb(usbtouch->irq);
+
+ return 0;
+}
+
+static int usbtouch_resume(struct usb_interface *intf)
+{
+ struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
+ int result = 0;
+
+ mutex_lock(&usbtouch->pm_mutex);
+ if (usbtouch->is_open || usbtouch->type->irq_always)
+ result = usb_submit_urb(usbtouch->irq, GFP_NOIO);
+ mutex_unlock(&usbtouch->pm_mutex);
+
+ return result;
+}
+
+static int usbtouch_reset_resume(struct usb_interface *intf)
+{
+ struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
+ int err = 0;
+
+ /* reinit the device */
+ if (usbtouch->type->init) {
+ err = usbtouch->type->init(usbtouch);
+ if (err) {
+ dev_dbg(&intf->dev,
+ "%s - type->init() failed, err: %d\n",
+ __func__, err);
+ return err;
+ }
+ }
+
+ /* restart IO if needed */
+ mutex_lock(&usbtouch->pm_mutex);
+ if (usbtouch->is_open)
+ err = usb_submit_urb(usbtouch->irq, GFP_NOIO);
+ mutex_unlock(&usbtouch->pm_mutex);
+
+ return err;
+}
+
+static void usbtouch_free_buffers(struct usb_device *udev,
+ struct usbtouch_usb *usbtouch)
+{
+ usb_free_coherent(udev, usbtouch->data_size,
+ usbtouch->data, usbtouch->data_dma);
+ kfree(usbtouch->buffer);
+}
+
+static struct usb_endpoint_descriptor *
+usbtouch_get_input_endpoint(struct usb_host_interface *interface)
+{
+ int i;
+
+ for (i = 0; i < interface->desc.bNumEndpoints; i++)
+ if (usb_endpoint_dir_in(&interface->endpoint[i].desc))
+ return &interface->endpoint[i].desc;
+
+ return NULL;
+}
+
+static int usbtouch_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usbtouch_usb *usbtouch;
+ struct input_dev *input_dev;
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct usbtouch_device_info *type;
+ int err = -ENOMEM;
+
+ /* some devices are ignored */
+ if (id->driver_info == DEVTYPE_IGNORE)
+ return -ENODEV;
+
+ if (id->driver_info >= ARRAY_SIZE(usbtouch_dev_info))
+ return -ENODEV;
+
+ endpoint = usbtouch_get_input_endpoint(intf->cur_altsetting);
+ if (!endpoint)
+ return -ENXIO;
+
+ usbtouch = kzalloc(sizeof(struct usbtouch_usb), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!usbtouch || !input_dev)
+ goto out_free;
+
+ mutex_init(&usbtouch->pm_mutex);
+
+ type = &usbtouch_dev_info[id->driver_info];
+ usbtouch->type = type;
+ if (!type->process_pkt)
+ type->process_pkt = usbtouch_process_pkt;
+
+ usbtouch->data_size = type->rept_size;
+ if (type->get_pkt_len) {
+ /*
+ * When dealing with variable-length packets we should
+ * not request more than wMaxPacketSize bytes at once
+ * as we do not know if there is more data coming or
+ * we filled exactly wMaxPacketSize bytes and there is
+ * nothing else.
+ */
+ usbtouch->data_size = min(usbtouch->data_size,
+ usb_endpoint_maxp(endpoint));
+ }
+
+ usbtouch->data = usb_alloc_coherent(udev, usbtouch->data_size,
+ GFP_KERNEL, &usbtouch->data_dma);
+ if (!usbtouch->data)
+ goto out_free;
+
+ if (type->get_pkt_len) {
+ usbtouch->buffer = kmalloc(type->rept_size, GFP_KERNEL);
+ if (!usbtouch->buffer)
+ goto out_free_buffers;
+ }
+
+ usbtouch->irq = usb_alloc_urb(0, GFP_KERNEL);
+ if (!usbtouch->irq) {
+ dev_dbg(&intf->dev,
+ "%s - usb_alloc_urb failed: usbtouch->irq\n", __func__);
+ goto out_free_buffers;
+ }
+
+ usbtouch->interface = intf;
+ usbtouch->input = input_dev;
+
+ if (udev->manufacturer)
+ strscpy(usbtouch->name, udev->manufacturer, sizeof(usbtouch->name));
+
+ if (udev->product) {
+ if (udev->manufacturer)
+ strlcat(usbtouch->name, " ", sizeof(usbtouch->name));
+ strlcat(usbtouch->name, udev->product, sizeof(usbtouch->name));
+ }
+
+ if (!strlen(usbtouch->name))
+ snprintf(usbtouch->name, sizeof(usbtouch->name),
+ "USB Touchscreen %04x:%04x",
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ usb_make_path(udev, usbtouch->phys, sizeof(usbtouch->phys));
+ strlcat(usbtouch->phys, "/input0", sizeof(usbtouch->phys));
+
+ input_dev->name = usbtouch->name;
+ input_dev->phys = usbtouch->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &intf->dev;
+
+ input_set_drvdata(input_dev, usbtouch);
+
+ input_dev->open = usbtouch_open;
+ input_dev->close = usbtouch_close;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0);
+ if (type->max_press)
+ input_set_abs_params(input_dev, ABS_PRESSURE, type->min_press,
+ type->max_press, 0, 0);
+
+ if (usb_endpoint_type(endpoint) == USB_ENDPOINT_XFER_INT)
+ usb_fill_int_urb(usbtouch->irq, udev,
+ usb_rcvintpipe(udev, endpoint->bEndpointAddress),
+ usbtouch->data, usbtouch->data_size,
+ usbtouch_irq, usbtouch, endpoint->bInterval);
+ else
+ usb_fill_bulk_urb(usbtouch->irq, udev,
+ usb_rcvbulkpipe(udev, endpoint->bEndpointAddress),
+ usbtouch->data, usbtouch->data_size,
+ usbtouch_irq, usbtouch);
+
+ usbtouch->irq->dev = udev;
+ usbtouch->irq->transfer_dma = usbtouch->data_dma;
+ usbtouch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ /* device specific allocations */
+ if (type->alloc) {
+ err = type->alloc(usbtouch);
+ if (err) {
+ dev_dbg(&intf->dev,
+ "%s - type->alloc() failed, err: %d\n",
+ __func__, err);
+ goto out_free_urb;
+ }
+ }
+
+ /* device specific initialisation*/
+ if (type->init) {
+ err = type->init(usbtouch);
+ if (err) {
+ dev_dbg(&intf->dev,
+ "%s - type->init() failed, err: %d\n",
+ __func__, err);
+ goto out_do_exit;
+ }
+ }
+
+ err = input_register_device(usbtouch->input);
+ if (err) {
+ dev_dbg(&intf->dev,
+ "%s - input_register_device failed, err: %d\n",
+ __func__, err);
+ goto out_do_exit;
+ }
+
+ usb_set_intfdata(intf, usbtouch);
+
+ if (usbtouch->type->irq_always) {
+ /* this can't fail */
+ usb_autopm_get_interface(intf);
+ err = usb_submit_urb(usbtouch->irq, GFP_KERNEL);
+ if (err) {
+ usb_autopm_put_interface(intf);
+ dev_err(&intf->dev,
+ "%s - usb_submit_urb failed with result: %d\n",
+ __func__, err);
+ goto out_unregister_input;
+ }
+ }
+
+ return 0;
+
+out_unregister_input:
+ input_unregister_device(input_dev);
+ input_dev = NULL;
+out_do_exit:
+ if (type->exit)
+ type->exit(usbtouch);
+out_free_urb:
+ usb_free_urb(usbtouch->irq);
+out_free_buffers:
+ usbtouch_free_buffers(udev, usbtouch);
+out_free:
+ input_free_device(input_dev);
+ kfree(usbtouch);
+ return err;
+}
+
+static void usbtouch_disconnect(struct usb_interface *intf)
+{
+ struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
+
+ if (!usbtouch)
+ return;
+
+ dev_dbg(&intf->dev,
+ "%s - usbtouch is initialized, cleaning up\n", __func__);
+
+ usb_set_intfdata(intf, NULL);
+ /* this will stop IO via close */
+ input_unregister_device(usbtouch->input);
+ usb_free_urb(usbtouch->irq);
+ if (usbtouch->type->exit)
+ usbtouch->type->exit(usbtouch);
+ usbtouch_free_buffers(interface_to_usbdev(intf), usbtouch);
+ kfree(usbtouch);
+}
+
+MODULE_DEVICE_TABLE(usb, usbtouch_devices);
+
+static struct usb_driver usbtouch_driver = {
+ .name = "usbtouchscreen",
+ .probe = usbtouch_probe,
+ .disconnect = usbtouch_disconnect,
+ .suspend = usbtouch_suspend,
+ .resume = usbtouch_resume,
+ .reset_resume = usbtouch_reset_resume,
+ .id_table = usbtouch_devices,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(usbtouch_driver);
+
+MODULE_AUTHOR("Daniel Ritz <daniel.ritz@gmx.ch>");
+MODULE_DESCRIPTION("USB Touchscreen Driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("touchkitusb");
+MODULE_ALIAS("itmtouch");
+MODULE_ALIAS("mtouchusb");
diff --git a/drivers/input/touchscreen/wacom_i2c.c b/drivers/input/touchscreen/wacom_i2c.c
new file mode 100644
index 000000000..141754b27
--- /dev/null
+++ b/drivers/input/touchscreen/wacom_i2c.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Wacom Penabled Driver for I2C
+ *
+ * Copyright (c) 2011 - 2013 Tatsunosuke Tobita, Wacom.
+ * <tobita.tatsunosuke@wacom.co.jp>
+ */
+
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <asm/unaligned.h>
+
+/* Bitmasks (for data[3]) */
+#define WACOM_TIP_SWITCH BIT(0)
+#define WACOM_BARREL_SWITCH BIT(1)
+#define WACOM_ERASER BIT(2)
+#define WACOM_INVERT BIT(3)
+#define WACOM_BARREL_SWITCH_2 BIT(4)
+#define WACOM_IN_PROXIMITY BIT(5)
+
+/* Registers */
+#define WACOM_COMMAND_LSB 0x04
+#define WACOM_COMMAND_MSB 0x00
+
+#define WACOM_DATA_LSB 0x05
+#define WACOM_DATA_MSB 0x00
+
+/* Report types */
+#define REPORT_FEATURE 0x30
+
+/* Requests / operations */
+#define OPCODE_GET_REPORT 0x02
+
+#define WACOM_QUERY_REPORT 3
+#define WACOM_QUERY_SIZE 19
+
+struct wacom_features {
+ int x_max;
+ int y_max;
+ int pressure_max;
+ char fw_version;
+};
+
+struct wacom_i2c {
+ struct i2c_client *client;
+ struct input_dev *input;
+ u8 data[WACOM_QUERY_SIZE];
+ bool prox;
+ int tool;
+};
+
+static int wacom_query_device(struct i2c_client *client,
+ struct wacom_features *features)
+{
+ u8 get_query_data_cmd[] = {
+ WACOM_COMMAND_LSB,
+ WACOM_COMMAND_MSB,
+ REPORT_FEATURE | WACOM_QUERY_REPORT,
+ OPCODE_GET_REPORT,
+ WACOM_DATA_LSB,
+ WACOM_DATA_MSB,
+ };
+ u8 data[WACOM_QUERY_SIZE];
+ int ret;
+
+ struct i2c_msg msgs[] = {
+ /* Request reading of feature ReportID: 3 (Pen Query Data) */
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = sizeof(get_query_data_cmd),
+ .buf = get_query_data_cmd,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(data),
+ .buf = data,
+ },
+ };
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0)
+ return ret;
+ if (ret != ARRAY_SIZE(msgs))
+ return -EIO;
+
+ features->x_max = get_unaligned_le16(&data[3]);
+ features->y_max = get_unaligned_le16(&data[5]);
+ features->pressure_max = get_unaligned_le16(&data[11]);
+ features->fw_version = get_unaligned_le16(&data[13]);
+
+ dev_dbg(&client->dev,
+ "x_max:%d, y_max:%d, pressure:%d, fw:%d\n",
+ features->x_max, features->y_max,
+ features->pressure_max, features->fw_version);
+
+ return 0;
+}
+
+static irqreturn_t wacom_i2c_irq(int irq, void *dev_id)
+{
+ struct wacom_i2c *wac_i2c = dev_id;
+ struct input_dev *input = wac_i2c->input;
+ u8 *data = wac_i2c->data;
+ unsigned int x, y, pressure;
+ unsigned char tsw, f1, f2, ers;
+ int error;
+
+ error = i2c_master_recv(wac_i2c->client,
+ wac_i2c->data, sizeof(wac_i2c->data));
+ if (error < 0)
+ goto out;
+
+ tsw = data[3] & WACOM_TIP_SWITCH;
+ ers = data[3] & WACOM_ERASER;
+ f1 = data[3] & WACOM_BARREL_SWITCH;
+ f2 = data[3] & WACOM_BARREL_SWITCH_2;
+ x = le16_to_cpup((__le16 *)&data[4]);
+ y = le16_to_cpup((__le16 *)&data[6]);
+ pressure = le16_to_cpup((__le16 *)&data[8]);
+
+ if (!wac_i2c->prox)
+ wac_i2c->tool = (data[3] & (WACOM_ERASER | WACOM_INVERT)) ?
+ BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+
+ wac_i2c->prox = data[3] & WACOM_IN_PROXIMITY;
+
+ input_report_key(input, BTN_TOUCH, tsw || ers);
+ input_report_key(input, wac_i2c->tool, wac_i2c->prox);
+ input_report_key(input, BTN_STYLUS, f1);
+ input_report_key(input, BTN_STYLUS2, f2);
+ input_report_abs(input, ABS_X, x);
+ input_report_abs(input, ABS_Y, y);
+ input_report_abs(input, ABS_PRESSURE, pressure);
+ input_sync(input);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int wacom_i2c_open(struct input_dev *dev)
+{
+ struct wacom_i2c *wac_i2c = input_get_drvdata(dev);
+ struct i2c_client *client = wac_i2c->client;
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static void wacom_i2c_close(struct input_dev *dev)
+{
+ struct wacom_i2c *wac_i2c = input_get_drvdata(dev);
+ struct i2c_client *client = wac_i2c->client;
+
+ disable_irq(client->irq);
+}
+
+static int wacom_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct wacom_i2c *wac_i2c;
+ struct input_dev *input;
+ struct wacom_features features = { 0 };
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c_check_functionality error\n");
+ return -EIO;
+ }
+
+ error = wacom_query_device(client, &features);
+ if (error)
+ return error;
+
+ wac_i2c = devm_kzalloc(dev, sizeof(*wac_i2c), GFP_KERNEL);
+ if (!wac_i2c)
+ return -ENOMEM;
+
+ wac_i2c->client = client;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ wac_i2c->input = input;
+
+ input->name = "Wacom I2C Digitizer";
+ input->id.bustype = BUS_I2C;
+ input->id.vendor = 0x56a;
+ input->id.version = features.fw_version;
+ input->open = wacom_i2c_open;
+ input->close = wacom_i2c_close;
+
+ input->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+
+ __set_bit(BTN_TOOL_PEN, input->keybit);
+ __set_bit(BTN_TOOL_RUBBER, input->keybit);
+ __set_bit(BTN_STYLUS, input->keybit);
+ __set_bit(BTN_STYLUS2, input->keybit);
+ __set_bit(BTN_TOUCH, input->keybit);
+
+ input_set_abs_params(input, ABS_X, 0, features.x_max, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, features.y_max, 0, 0);
+ input_set_abs_params(input, ABS_PRESSURE,
+ 0, features.pressure_max, 0, 0);
+
+ input_set_drvdata(input, wac_i2c);
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL, wacom_i2c_irq,
+ IRQF_ONESHOT, "wacom_i2c", wac_i2c);
+ if (error) {
+ dev_err(dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ /* Disable the IRQ, we'll enable it in wac_i2c_open() */
+ disable_irq(client->irq);
+
+ error = input_register_device(wac_i2c->input);
+ if (error) {
+ dev_err(dev, "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused wacom_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ disable_irq(client->irq);
+
+ return 0;
+}
+
+static int __maybe_unused wacom_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(wacom_i2c_pm, wacom_i2c_suspend, wacom_i2c_resume);
+
+static const struct i2c_device_id wacom_i2c_id[] = {
+ { "WAC_I2C_EMR", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, wacom_i2c_id);
+
+static struct i2c_driver wacom_i2c_driver = {
+ .driver = {
+ .name = "wacom_i2c",
+ .pm = &wacom_i2c_pm,
+ },
+
+ .probe = wacom_i2c_probe,
+ .id_table = wacom_i2c_id,
+};
+module_i2c_driver(wacom_i2c_driver);
+
+MODULE_AUTHOR("Tatsunosuke Tobita <tobita.tatsunosuke@wacom.co.jp>");
+MODULE_DESCRIPTION("WACOM EMR I2C Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c
new file mode 100644
index 000000000..928c5ee3a
--- /dev/null
+++ b/drivers/input/touchscreen/wacom_w8001.c
@@ -0,0 +1,709 @@
+/*
+ * Wacom W8001 penabled serial touchscreen driver
+ *
+ * Copyright (c) 2008 Jaya Kumar
+ * Copyright (c) 2010 Red Hat, Inc.
+ * Copyright (c) 2010 - 2011 Ping Cheng, Wacom. <pingc@wacom.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ * Layout based on Elo serial touchscreen driver by Vojtech Pavlik
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input/mt.h>
+#include <linux/serio.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+
+#define DRIVER_DESC "Wacom W8001 serial touchscreen driver"
+
+MODULE_AUTHOR("Jaya Kumar <jayakumar.lkml@gmail.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+#define W8001_MAX_PHYS 42
+
+#define W8001_MAX_LENGTH 13
+#define W8001_LEAD_MASK 0x80
+#define W8001_LEAD_BYTE 0x80
+#define W8001_TAB_MASK 0x40
+#define W8001_TAB_BYTE 0x40
+/* set in first byte of touch data packets */
+#define W8001_TOUCH_MASK (0x10 | W8001_LEAD_MASK)
+#define W8001_TOUCH_BYTE (0x10 | W8001_LEAD_BYTE)
+
+#define W8001_QUERY_PACKET 0x20
+
+#define W8001_CMD_STOP '0'
+#define W8001_CMD_START '1'
+#define W8001_CMD_QUERY '*'
+#define W8001_CMD_TOUCHQUERY '%'
+
+/* length of data packets in bytes, depends on device. */
+#define W8001_PKTLEN_TOUCH93 5
+#define W8001_PKTLEN_TOUCH9A 7
+#define W8001_PKTLEN_TPCPEN 9
+#define W8001_PKTLEN_TPCCTL 11 /* control packet */
+#define W8001_PKTLEN_TOUCH2FG 13
+
+/* resolution in points/mm */
+#define W8001_PEN_RESOLUTION 100
+#define W8001_TOUCH_RESOLUTION 10
+
+struct w8001_coord {
+ u8 rdy;
+ u8 tsw;
+ u8 f1;
+ u8 f2;
+ u16 x;
+ u16 y;
+ u16 pen_pressure;
+ u8 tilt_x;
+ u8 tilt_y;
+};
+
+/* touch query reply packet */
+struct w8001_touch_query {
+ u16 x;
+ u16 y;
+ u8 panel_res;
+ u8 capacity_res;
+ u8 sensor_id;
+};
+
+/*
+ * Per-touchscreen data.
+ */
+
+struct w8001 {
+ struct input_dev *pen_dev;
+ struct input_dev *touch_dev;
+ struct serio *serio;
+ struct completion cmd_done;
+ int id;
+ int idx;
+ unsigned char response_type;
+ unsigned char response[W8001_MAX_LENGTH];
+ unsigned char data[W8001_MAX_LENGTH];
+ char phys[W8001_MAX_PHYS];
+ int type;
+ unsigned int pktlen;
+ u16 max_touch_x;
+ u16 max_touch_y;
+ u16 max_pen_x;
+ u16 max_pen_y;
+ char pen_name[64];
+ char touch_name[64];
+ int open_count;
+ struct mutex mutex;
+};
+
+static void parse_pen_data(u8 *data, struct w8001_coord *coord)
+{
+ memset(coord, 0, sizeof(*coord));
+
+ coord->rdy = data[0] & 0x20;
+ coord->tsw = data[0] & 0x01;
+ coord->f1 = data[0] & 0x02;
+ coord->f2 = data[0] & 0x04;
+
+ coord->x = (data[1] & 0x7F) << 9;
+ coord->x |= (data[2] & 0x7F) << 2;
+ coord->x |= (data[6] & 0x60) >> 5;
+
+ coord->y = (data[3] & 0x7F) << 9;
+ coord->y |= (data[4] & 0x7F) << 2;
+ coord->y |= (data[6] & 0x18) >> 3;
+
+ coord->pen_pressure = data[5] & 0x7F;
+ coord->pen_pressure |= (data[6] & 0x07) << 7 ;
+
+ coord->tilt_x = data[7] & 0x7F;
+ coord->tilt_y = data[8] & 0x7F;
+}
+
+static void parse_single_touch(u8 *data, struct w8001_coord *coord)
+{
+ coord->x = (data[1] << 7) | data[2];
+ coord->y = (data[3] << 7) | data[4];
+ coord->tsw = data[0] & 0x01;
+}
+
+static void scale_touch_coordinates(struct w8001 *w8001,
+ unsigned int *x, unsigned int *y)
+{
+ if (w8001->max_pen_x && w8001->max_touch_x)
+ *x = *x * w8001->max_pen_x / w8001->max_touch_x;
+
+ if (w8001->max_pen_y && w8001->max_touch_y)
+ *y = *y * w8001->max_pen_y / w8001->max_touch_y;
+}
+
+static void parse_multi_touch(struct w8001 *w8001)
+{
+ struct input_dev *dev = w8001->touch_dev;
+ unsigned char *data = w8001->data;
+ unsigned int x, y;
+ int i;
+ int count = 0;
+
+ for (i = 0; i < 2; i++) {
+ bool touch = data[0] & (1 << i);
+
+ input_mt_slot(dev, i);
+ input_mt_report_slot_state(dev, MT_TOOL_FINGER, touch);
+ if (touch) {
+ x = (data[6 * i + 1] << 7) | data[6 * i + 2];
+ y = (data[6 * i + 3] << 7) | data[6 * i + 4];
+ /* data[5,6] and [11,12] is finger capacity */
+
+ /* scale to pen maximum */
+ scale_touch_coordinates(w8001, &x, &y);
+
+ input_report_abs(dev, ABS_MT_POSITION_X, x);
+ input_report_abs(dev, ABS_MT_POSITION_Y, y);
+ count++;
+ }
+ }
+
+ /* emulate single touch events when stylus is out of proximity.
+ * This is to make single touch backward support consistent
+ * across all Wacom single touch devices.
+ */
+ if (w8001->type != BTN_TOOL_PEN &&
+ w8001->type != BTN_TOOL_RUBBER) {
+ w8001->type = count == 1 ? BTN_TOOL_FINGER : KEY_RESERVED;
+ input_mt_report_pointer_emulation(dev, true);
+ }
+
+ input_sync(dev);
+}
+
+static void parse_touchquery(u8 *data, struct w8001_touch_query *query)
+{
+ memset(query, 0, sizeof(*query));
+
+ query->panel_res = data[1];
+ query->sensor_id = data[2] & 0x7;
+ query->capacity_res = data[7];
+
+ query->x = data[3] << 9;
+ query->x |= data[4] << 2;
+ query->x |= (data[2] >> 5) & 0x3;
+
+ query->y = data[5] << 9;
+ query->y |= data[6] << 2;
+ query->y |= (data[2] >> 3) & 0x3;
+
+ /* Early days' single-finger touch models need the following defaults */
+ if (!query->x && !query->y) {
+ query->x = 1024;
+ query->y = 1024;
+ if (query->panel_res)
+ query->x = query->y = (1 << query->panel_res);
+ query->panel_res = W8001_TOUCH_RESOLUTION;
+ }
+}
+
+static void report_pen_events(struct w8001 *w8001, struct w8001_coord *coord)
+{
+ struct input_dev *dev = w8001->pen_dev;
+
+ /*
+ * We have 1 bit for proximity (rdy) and 3 bits for tip, side,
+ * side2/eraser. If rdy && f2 are set, this can be either pen + side2,
+ * or eraser. Assume:
+ * - if dev is already in proximity and f2 is toggled → pen + side2
+ * - if dev comes into proximity with f2 set → eraser
+ * If f2 disappears after assuming eraser, fake proximity out for
+ * eraser and in for pen.
+ */
+
+ switch (w8001->type) {
+ case BTN_TOOL_RUBBER:
+ if (!coord->f2) {
+ input_report_abs(dev, ABS_PRESSURE, 0);
+ input_report_key(dev, BTN_TOUCH, 0);
+ input_report_key(dev, BTN_STYLUS, 0);
+ input_report_key(dev, BTN_STYLUS2, 0);
+ input_report_key(dev, BTN_TOOL_RUBBER, 0);
+ input_sync(dev);
+ w8001->type = BTN_TOOL_PEN;
+ }
+ break;
+
+ case BTN_TOOL_FINGER:
+ case KEY_RESERVED:
+ w8001->type = coord->f2 ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+ break;
+
+ default:
+ input_report_key(dev, BTN_STYLUS2, coord->f2);
+ break;
+ }
+
+ input_report_abs(dev, ABS_X, coord->x);
+ input_report_abs(dev, ABS_Y, coord->y);
+ input_report_abs(dev, ABS_PRESSURE, coord->pen_pressure);
+ input_report_key(dev, BTN_TOUCH, coord->tsw);
+ input_report_key(dev, BTN_STYLUS, coord->f1);
+ input_report_key(dev, w8001->type, coord->rdy);
+ input_sync(dev);
+
+ if (!coord->rdy)
+ w8001->type = KEY_RESERVED;
+}
+
+static void report_single_touch(struct w8001 *w8001, struct w8001_coord *coord)
+{
+ struct input_dev *dev = w8001->touch_dev;
+ unsigned int x = coord->x;
+ unsigned int y = coord->y;
+
+ /* scale to pen maximum */
+ scale_touch_coordinates(w8001, &x, &y);
+
+ input_report_abs(dev, ABS_X, x);
+ input_report_abs(dev, ABS_Y, y);
+ input_report_key(dev, BTN_TOUCH, coord->tsw);
+
+ input_sync(dev);
+
+ w8001->type = coord->tsw ? BTN_TOOL_FINGER : KEY_RESERVED;
+}
+
+static irqreturn_t w8001_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags)
+{
+ struct w8001 *w8001 = serio_get_drvdata(serio);
+ struct w8001_coord coord;
+ unsigned char tmp;
+
+ w8001->data[w8001->idx] = data;
+ switch (w8001->idx++) {
+ case 0:
+ if ((data & W8001_LEAD_MASK) != W8001_LEAD_BYTE) {
+ pr_debug("w8001: unsynchronized data: 0x%02x\n", data);
+ w8001->idx = 0;
+ }
+ break;
+
+ case W8001_PKTLEN_TOUCH93 - 1:
+ case W8001_PKTLEN_TOUCH9A - 1:
+ tmp = w8001->data[0] & W8001_TOUCH_BYTE;
+ if (tmp != W8001_TOUCH_BYTE)
+ break;
+
+ if (w8001->pktlen == w8001->idx) {
+ w8001->idx = 0;
+ if (w8001->type != BTN_TOOL_PEN &&
+ w8001->type != BTN_TOOL_RUBBER) {
+ parse_single_touch(w8001->data, &coord);
+ report_single_touch(w8001, &coord);
+ }
+ }
+ break;
+
+ /* Pen coordinates packet */
+ case W8001_PKTLEN_TPCPEN - 1:
+ tmp = w8001->data[0] & W8001_TAB_MASK;
+ if (unlikely(tmp == W8001_TAB_BYTE))
+ break;
+
+ tmp = w8001->data[0] & W8001_TOUCH_BYTE;
+ if (tmp == W8001_TOUCH_BYTE)
+ break;
+
+ w8001->idx = 0;
+ parse_pen_data(w8001->data, &coord);
+ report_pen_events(w8001, &coord);
+ break;
+
+ /* control packet */
+ case W8001_PKTLEN_TPCCTL - 1:
+ tmp = w8001->data[0] & W8001_TOUCH_MASK;
+ if (tmp == W8001_TOUCH_BYTE)
+ break;
+
+ w8001->idx = 0;
+ memcpy(w8001->response, w8001->data, W8001_MAX_LENGTH);
+ w8001->response_type = W8001_QUERY_PACKET;
+ complete(&w8001->cmd_done);
+ break;
+
+ /* 2 finger touch packet */
+ case W8001_PKTLEN_TOUCH2FG - 1:
+ w8001->idx = 0;
+ parse_multi_touch(w8001);
+ break;
+
+ default:
+ /*
+ * ThinkPad X60 Tablet PC (pen only device) sometimes
+ * sends invalid data packets that are larger than
+ * W8001_PKTLEN_TPCPEN. Let's start over again.
+ */
+ if (!w8001->touch_dev && w8001->idx > W8001_PKTLEN_TPCPEN - 1)
+ w8001->idx = 0;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int w8001_command(struct w8001 *w8001, unsigned char command,
+ bool wait_response)
+{
+ int rc;
+
+ w8001->response_type = 0;
+ init_completion(&w8001->cmd_done);
+
+ rc = serio_write(w8001->serio, command);
+ if (rc == 0 && wait_response) {
+
+ wait_for_completion_timeout(&w8001->cmd_done, HZ);
+ if (w8001->response_type != W8001_QUERY_PACKET)
+ rc = -EIO;
+ }
+
+ return rc;
+}
+
+static int w8001_open(struct input_dev *dev)
+{
+ struct w8001 *w8001 = input_get_drvdata(dev);
+ int err;
+
+ err = mutex_lock_interruptible(&w8001->mutex);
+ if (err)
+ return err;
+
+ if (w8001->open_count++ == 0) {
+ err = w8001_command(w8001, W8001_CMD_START, false);
+ if (err)
+ w8001->open_count--;
+ }
+
+ mutex_unlock(&w8001->mutex);
+ return err;
+}
+
+static void w8001_close(struct input_dev *dev)
+{
+ struct w8001 *w8001 = input_get_drvdata(dev);
+
+ mutex_lock(&w8001->mutex);
+
+ if (--w8001->open_count == 0)
+ w8001_command(w8001, W8001_CMD_STOP, false);
+
+ mutex_unlock(&w8001->mutex);
+}
+
+static int w8001_detect(struct w8001 *w8001)
+{
+ int error;
+
+ error = w8001_command(w8001, W8001_CMD_STOP, false);
+ if (error)
+ return error;
+
+ msleep(250); /* wait 250ms before querying the device */
+
+ return 0;
+}
+
+static int w8001_setup_pen(struct w8001 *w8001, char *basename,
+ size_t basename_sz)
+{
+ struct input_dev *dev = w8001->pen_dev;
+ struct w8001_coord coord;
+ int error;
+
+ /* penabled? */
+ error = w8001_command(w8001, W8001_CMD_QUERY, true);
+ if (error)
+ return error;
+
+ __set_bit(EV_KEY, dev->evbit);
+ __set_bit(EV_ABS, dev->evbit);
+ __set_bit(BTN_TOUCH, dev->keybit);
+ __set_bit(BTN_TOOL_PEN, dev->keybit);
+ __set_bit(BTN_TOOL_RUBBER, dev->keybit);
+ __set_bit(BTN_STYLUS, dev->keybit);
+ __set_bit(BTN_STYLUS2, dev->keybit);
+ __set_bit(INPUT_PROP_DIRECT, dev->propbit);
+
+ parse_pen_data(w8001->response, &coord);
+ w8001->max_pen_x = coord.x;
+ w8001->max_pen_y = coord.y;
+
+ input_set_abs_params(dev, ABS_X, 0, coord.x, 0, 0);
+ input_set_abs_params(dev, ABS_Y, 0, coord.y, 0, 0);
+ input_abs_set_res(dev, ABS_X, W8001_PEN_RESOLUTION);
+ input_abs_set_res(dev, ABS_Y, W8001_PEN_RESOLUTION);
+ input_set_abs_params(dev, ABS_PRESSURE, 0, coord.pen_pressure, 0, 0);
+ if (coord.tilt_x && coord.tilt_y) {
+ input_set_abs_params(dev, ABS_TILT_X, 0, coord.tilt_x, 0, 0);
+ input_set_abs_params(dev, ABS_TILT_Y, 0, coord.tilt_y, 0, 0);
+ }
+
+ w8001->id = 0x90;
+ strlcat(basename, " Penabled", basename_sz);
+
+ return 0;
+}
+
+static int w8001_setup_touch(struct w8001 *w8001, char *basename,
+ size_t basename_sz)
+{
+ struct input_dev *dev = w8001->touch_dev;
+ struct w8001_touch_query touch;
+ int error;
+
+
+ /* Touch enabled? */
+ error = w8001_command(w8001, W8001_CMD_TOUCHQUERY, true);
+ if (error)
+ return error;
+ /*
+ * Some non-touch devices may reply to the touch query. But their
+ * second byte is empty, which indicates touch is not supported.
+ */
+ if (!w8001->response[1])
+ return -ENXIO;
+
+ __set_bit(EV_KEY, dev->evbit);
+ __set_bit(EV_ABS, dev->evbit);
+ __set_bit(BTN_TOUCH, dev->keybit);
+ __set_bit(INPUT_PROP_DIRECT, dev->propbit);
+
+ parse_touchquery(w8001->response, &touch);
+ w8001->max_touch_x = touch.x;
+ w8001->max_touch_y = touch.y;
+
+ if (w8001->max_pen_x && w8001->max_pen_y) {
+ /* if pen is supported scale to pen maximum */
+ touch.x = w8001->max_pen_x;
+ touch.y = w8001->max_pen_y;
+ touch.panel_res = W8001_PEN_RESOLUTION;
+ }
+
+ input_set_abs_params(dev, ABS_X, 0, touch.x, 0, 0);
+ input_set_abs_params(dev, ABS_Y, 0, touch.y, 0, 0);
+ input_abs_set_res(dev, ABS_X, touch.panel_res);
+ input_abs_set_res(dev, ABS_Y, touch.panel_res);
+
+ switch (touch.sensor_id) {
+ case 0:
+ case 2:
+ w8001->pktlen = W8001_PKTLEN_TOUCH93;
+ w8001->id = 0x93;
+ strlcat(basename, " 1FG", basename_sz);
+ break;
+
+ case 1:
+ case 3:
+ case 4:
+ w8001->pktlen = W8001_PKTLEN_TOUCH9A;
+ strlcat(basename, " 1FG", basename_sz);
+ w8001->id = 0x9a;
+ break;
+
+ case 5:
+ w8001->pktlen = W8001_PKTLEN_TOUCH2FG;
+
+ __set_bit(BTN_TOOL_DOUBLETAP, dev->keybit);
+ error = input_mt_init_slots(dev, 2, 0);
+ if (error) {
+ dev_err(&w8001->serio->dev,
+ "failed to initialize MT slots: %d\n", error);
+ return error;
+ }
+
+ input_set_abs_params(dev, ABS_MT_POSITION_X,
+ 0, touch.x, 0, 0);
+ input_set_abs_params(dev, ABS_MT_POSITION_Y,
+ 0, touch.y, 0, 0);
+ input_set_abs_params(dev, ABS_MT_TOOL_TYPE,
+ 0, MT_TOOL_MAX, 0, 0);
+ input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res);
+ input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res);
+
+ strlcat(basename, " 2FG", basename_sz);
+ if (w8001->max_pen_x && w8001->max_pen_y)
+ w8001->id = 0xE3;
+ else
+ w8001->id = 0xE2;
+ break;
+ }
+
+ strlcat(basename, " Touchscreen", basename_sz);
+
+ return 0;
+}
+
+static void w8001_set_devdata(struct input_dev *dev, struct w8001 *w8001,
+ struct serio *serio)
+{
+ dev->phys = w8001->phys;
+ dev->id.bustype = BUS_RS232;
+ dev->id.product = w8001->id;
+ dev->id.vendor = 0x056a;
+ dev->id.version = 0x0100;
+ dev->open = w8001_open;
+ dev->close = w8001_close;
+
+ dev->dev.parent = &serio->dev;
+
+ input_set_drvdata(dev, w8001);
+}
+
+/*
+ * w8001_disconnect() is the opposite of w8001_connect()
+ */
+
+static void w8001_disconnect(struct serio *serio)
+{
+ struct w8001 *w8001 = serio_get_drvdata(serio);
+
+ serio_close(serio);
+
+ if (w8001->pen_dev)
+ input_unregister_device(w8001->pen_dev);
+ if (w8001->touch_dev)
+ input_unregister_device(w8001->touch_dev);
+ kfree(w8001);
+
+ serio_set_drvdata(serio, NULL);
+}
+
+/*
+ * w8001_connect() is the routine that is called when someone adds a
+ * new serio device that supports the w8001 protocol and registers it as
+ * an input device.
+ */
+
+static int w8001_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct w8001 *w8001;
+ struct input_dev *input_dev_pen;
+ struct input_dev *input_dev_touch;
+ char basename[64];
+ int err, err_pen, err_touch;
+
+ w8001 = kzalloc(sizeof(struct w8001), GFP_KERNEL);
+ input_dev_pen = input_allocate_device();
+ input_dev_touch = input_allocate_device();
+ if (!w8001 || !input_dev_pen || !input_dev_touch) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ w8001->serio = serio;
+ w8001->pen_dev = input_dev_pen;
+ w8001->touch_dev = input_dev_touch;
+ mutex_init(&w8001->mutex);
+ init_completion(&w8001->cmd_done);
+ snprintf(w8001->phys, sizeof(w8001->phys), "%s/input0", serio->phys);
+
+ serio_set_drvdata(serio, w8001);
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = w8001_detect(w8001);
+ if (err)
+ goto fail3;
+
+ /* For backwards-compatibility we compose the basename based on
+ * capabilities and then just append the tool type
+ */
+ strscpy(basename, "Wacom Serial", sizeof(basename));
+
+ err_pen = w8001_setup_pen(w8001, basename, sizeof(basename));
+ err_touch = w8001_setup_touch(w8001, basename, sizeof(basename));
+ if (err_pen && err_touch) {
+ err = -ENXIO;
+ goto fail3;
+ }
+
+ if (!err_pen) {
+ strscpy(w8001->pen_name, basename, sizeof(w8001->pen_name));
+ strlcat(w8001->pen_name, " Pen", sizeof(w8001->pen_name));
+ input_dev_pen->name = w8001->pen_name;
+
+ w8001_set_devdata(input_dev_pen, w8001, serio);
+
+ err = input_register_device(w8001->pen_dev);
+ if (err)
+ goto fail3;
+ } else {
+ input_free_device(input_dev_pen);
+ input_dev_pen = NULL;
+ w8001->pen_dev = NULL;
+ }
+
+ if (!err_touch) {
+ strscpy(w8001->touch_name, basename, sizeof(w8001->touch_name));
+ strlcat(w8001->touch_name, " Finger",
+ sizeof(w8001->touch_name));
+ input_dev_touch->name = w8001->touch_name;
+
+ w8001_set_devdata(input_dev_touch, w8001, serio);
+
+ err = input_register_device(w8001->touch_dev);
+ if (err)
+ goto fail4;
+ } else {
+ input_free_device(input_dev_touch);
+ input_dev_touch = NULL;
+ w8001->touch_dev = NULL;
+ }
+
+ return 0;
+
+fail4:
+ if (w8001->pen_dev)
+ input_unregister_device(w8001->pen_dev);
+fail3:
+ serio_close(serio);
+fail2:
+ serio_set_drvdata(serio, NULL);
+fail1:
+ input_free_device(input_dev_pen);
+ input_free_device(input_dev_touch);
+ kfree(w8001);
+ return err;
+}
+
+static const struct serio_device_id w8001_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_W8001,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, w8001_serio_ids);
+
+static struct serio_driver w8001_drv = {
+ .driver = {
+ .name = "w8001",
+ },
+ .description = DRIVER_DESC,
+ .id_table = w8001_serio_ids,
+ .interrupt = w8001_interrupt,
+ .connect = w8001_connect,
+ .disconnect = w8001_disconnect,
+};
+
+module_serio_driver(w8001_drv);
diff --git a/drivers/input/touchscreen/wdt87xx_i2c.c b/drivers/input/touchscreen/wdt87xx_i2c.c
new file mode 100644
index 000000000..166edeb77
--- /dev/null
+++ b/drivers/input/touchscreen/wdt87xx_i2c.c
@@ -0,0 +1,1185 @@
+/*
+ * Weida HiTech WDT87xx TouchScreen I2C driver
+ *
+ * Copyright (c) 2015 Weida Hi-Tech Co., Ltd.
+ * HN Chen <hn.chen@weidahitech.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/input/mt.h>
+#include <linux/acpi.h>
+#include <asm/unaligned.h>
+
+#define WDT87XX_NAME "wdt87xx_i2c"
+#define WDT87XX_FW_NAME "wdt87xx_fw.bin"
+#define WDT87XX_CFG_NAME "wdt87xx_cfg.bin"
+
+#define MODE_ACTIVE 0x01
+#define MODE_READY 0x02
+#define MODE_IDLE 0x03
+#define MODE_SLEEP 0x04
+#define MODE_STOP 0xFF
+
+#define WDT_MAX_FINGER 10
+#define WDT_RAW_BUF_COUNT 54
+#define WDT_V1_RAW_BUF_COUNT 74
+#define WDT_FIRMWARE_ID 0xa9e368f5
+
+#define PG_SIZE 0x1000
+#define MAX_RETRIES 3
+
+#define MAX_UNIT_AXIS 0x7FFF
+
+#define PKT_READ_SIZE 72
+#define PKT_WRITE_SIZE 80
+
+/* the finger definition of the report event */
+#define FINGER_EV_OFFSET_ID 0
+#define FINGER_EV_OFFSET_X 1
+#define FINGER_EV_OFFSET_Y 3
+#define FINGER_EV_SIZE 5
+
+#define FINGER_EV_V1_OFFSET_ID 0
+#define FINGER_EV_V1_OFFSET_W 1
+#define FINGER_EV_V1_OFFSET_P 2
+#define FINGER_EV_V1_OFFSET_X 3
+#define FINGER_EV_V1_OFFSET_Y 5
+#define FINGER_EV_V1_SIZE 7
+
+/* The definition of a report packet */
+#define TOUCH_PK_OFFSET_REPORT_ID 0
+#define TOUCH_PK_OFFSET_EVENT 1
+#define TOUCH_PK_OFFSET_SCAN_TIME 51
+#define TOUCH_PK_OFFSET_FNGR_NUM 53
+
+#define TOUCH_PK_V1_OFFSET_REPORT_ID 0
+#define TOUCH_PK_V1_OFFSET_EVENT 1
+#define TOUCH_PK_V1_OFFSET_SCAN_TIME 71
+#define TOUCH_PK_V1_OFFSET_FNGR_NUM 73
+
+/* The definition of the controller parameters */
+#define CTL_PARAM_OFFSET_FW_ID 0
+#define CTL_PARAM_OFFSET_PLAT_ID 2
+#define CTL_PARAM_OFFSET_XMLS_ID1 4
+#define CTL_PARAM_OFFSET_XMLS_ID2 6
+#define CTL_PARAM_OFFSET_PHY_CH_X 8
+#define CTL_PARAM_OFFSET_PHY_CH_Y 10
+#define CTL_PARAM_OFFSET_PHY_X0 12
+#define CTL_PARAM_OFFSET_PHY_X1 14
+#define CTL_PARAM_OFFSET_PHY_Y0 16
+#define CTL_PARAM_OFFSET_PHY_Y1 18
+#define CTL_PARAM_OFFSET_PHY_W 22
+#define CTL_PARAM_OFFSET_PHY_H 24
+#define CTL_PARAM_OFFSET_FACTOR 32
+
+/* The definition of the device descriptor */
+#define WDT_GD_DEVICE 1
+#define DEV_DESC_OFFSET_VID 8
+#define DEV_DESC_OFFSET_PID 10
+
+/* Communication commands */
+#define PACKET_SIZE 56
+#define VND_REQ_READ 0x06
+#define VND_READ_DATA 0x07
+#define VND_REQ_WRITE 0x08
+
+#define VND_CMD_START 0x00
+#define VND_CMD_STOP 0x01
+#define VND_CMD_RESET 0x09
+
+#define VND_CMD_ERASE 0x1A
+
+#define VND_GET_CHECKSUM 0x66
+
+#define VND_SET_DATA 0x83
+#define VND_SET_COMMAND_DATA 0x84
+#define VND_SET_CHECKSUM_CALC 0x86
+#define VND_SET_CHECKSUM_LENGTH 0x87
+
+#define VND_CMD_SFLCK 0xFC
+#define VND_CMD_SFUNL 0xFD
+
+#define CMD_SFLCK_KEY 0xC39B
+#define CMD_SFUNL_KEY 0x95DA
+
+#define STRIDX_PLATFORM_ID 0x80
+#define STRIDX_PARAMETERS 0x81
+
+#define CMD_BUF_SIZE 8
+#define PKT_BUF_SIZE 64
+
+/* The definition of the command packet */
+#define CMD_REPORT_ID_OFFSET 0x0
+#define CMD_TYPE_OFFSET 0x1
+#define CMD_INDEX_OFFSET 0x2
+#define CMD_KEY_OFFSET 0x3
+#define CMD_LENGTH_OFFSET 0x4
+#define CMD_DATA_OFFSET 0x8
+
+/* The definition of firmware chunk tags */
+#define FOURCC_ID_RIFF 0x46464952
+#define FOURCC_ID_WHIF 0x46494857
+#define FOURCC_ID_FRMT 0x544D5246
+#define FOURCC_ID_FRWR 0x52575246
+#define FOURCC_ID_CNFG 0x47464E43
+
+#define CHUNK_ID_FRMT FOURCC_ID_FRMT
+#define CHUNK_ID_FRWR FOURCC_ID_FRWR
+#define CHUNK_ID_CNFG FOURCC_ID_CNFG
+
+#define FW_FOURCC1_OFFSET 0
+#define FW_SIZE_OFFSET 4
+#define FW_FOURCC2_OFFSET 8
+#define FW_PAYLOAD_OFFSET 40
+
+#define FW_CHUNK_ID_OFFSET 0
+#define FW_CHUNK_SIZE_OFFSET 4
+#define FW_CHUNK_TGT_START_OFFSET 8
+#define FW_CHUNK_PAYLOAD_LEN_OFFSET 12
+#define FW_CHUNK_SRC_START_OFFSET 16
+#define FW_CHUNK_VERSION_OFFSET 20
+#define FW_CHUNK_ATTR_OFFSET 24
+#define FW_CHUNK_PAYLOAD_OFFSET 32
+
+/* Controller requires minimum 300us between commands */
+#define WDT_COMMAND_DELAY_MS 2
+#define WDT_FLASH_WRITE_DELAY_MS 4
+#define WDT_FLASH_ERASE_DELAY_MS 200
+#define WDT_FW_RESET_TIME 2500
+
+struct wdt87xx_sys_param {
+ u16 fw_id;
+ u16 plat_id;
+ u16 xmls_id1;
+ u16 xmls_id2;
+ u16 phy_ch_x;
+ u16 phy_ch_y;
+ u16 phy_w;
+ u16 phy_h;
+ u16 scaling_factor;
+ u32 max_x;
+ u32 max_y;
+ u16 vendor_id;
+ u16 product_id;
+};
+
+struct wdt87xx_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ /* Mutex for fw update to prevent concurrent access */
+ struct mutex fw_mutex;
+ struct wdt87xx_sys_param param;
+ u8 phys[32];
+};
+
+static int wdt87xx_i2c_xfer(struct i2c_client *client,
+ void *txdata, size_t txlen,
+ void *rxdata, size_t rxlen)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = txlen,
+ .buf = txdata,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = rxlen,
+ .buf = rxdata,
+ },
+ };
+ int error;
+ int ret;
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret != ARRAY_SIZE(msgs)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&client->dev, "%s: i2c transfer failed: %d\n",
+ __func__, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int wdt87xx_get_desc(struct i2c_client *client, u8 desc_idx,
+ u8 *buf, size_t len)
+{
+ u8 tx_buf[] = { 0x22, 0x00, 0x10, 0x0E, 0x23, 0x00 };
+ int error;
+
+ tx_buf[2] |= desc_idx & 0xF;
+
+ error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf),
+ buf, len);
+ if (error) {
+ dev_err(&client->dev, "get desc failed: %d\n", error);
+ return error;
+ }
+
+ if (buf[0] != len) {
+ dev_err(&client->dev, "unexpected response to get desc: %d\n",
+ buf[0]);
+ return -EINVAL;
+ }
+
+ mdelay(WDT_COMMAND_DELAY_MS);
+
+ return 0;
+}
+
+static int wdt87xx_get_string(struct i2c_client *client, u8 str_idx,
+ u8 *buf, size_t len)
+{
+ u8 tx_buf[] = { 0x22, 0x00, 0x13, 0x0E, str_idx, 0x23, 0x00 };
+ u8 rx_buf[PKT_WRITE_SIZE];
+ size_t rx_len = len + 2;
+ int error;
+
+ if (rx_len > sizeof(rx_buf))
+ return -EINVAL;
+
+ error = wdt87xx_i2c_xfer(client, tx_buf, sizeof(tx_buf),
+ rx_buf, rx_len);
+ if (error) {
+ dev_err(&client->dev, "get string failed: %d\n", error);
+ return error;
+ }
+
+ if (rx_buf[1] != 0x03) {
+ dev_err(&client->dev, "unexpected response to get string: %d\n",
+ rx_buf[1]);
+ return -EINVAL;
+ }
+
+ rx_len = min_t(size_t, len, rx_buf[0]);
+ memcpy(buf, &rx_buf[2], rx_len);
+
+ mdelay(WDT_COMMAND_DELAY_MS);
+
+ return 0;
+}
+
+static int wdt87xx_get_feature(struct i2c_client *client,
+ u8 *buf, size_t buf_size)
+{
+ u8 tx_buf[8];
+ u8 rx_buf[PKT_WRITE_SIZE];
+ size_t tx_len = 0;
+ size_t rx_len = buf_size + 2;
+ int error;
+
+ if (rx_len > sizeof(rx_buf))
+ return -EINVAL;
+
+ /* Get feature command packet */
+ tx_buf[tx_len++] = 0x22;
+ tx_buf[tx_len++] = 0x00;
+ if (buf[CMD_REPORT_ID_OFFSET] > 0xF) {
+ tx_buf[tx_len++] = 0x30;
+ tx_buf[tx_len++] = 0x02;
+ tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET];
+ } else {
+ tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET];
+ tx_buf[tx_len++] = 0x02;
+ }
+ tx_buf[tx_len++] = 0x23;
+ tx_buf[tx_len++] = 0x00;
+
+ error = wdt87xx_i2c_xfer(client, tx_buf, tx_len, rx_buf, rx_len);
+ if (error) {
+ dev_err(&client->dev, "get feature failed: %d\n", error);
+ return error;
+ }
+
+ rx_len = min_t(size_t, buf_size, get_unaligned_le16(rx_buf));
+ memcpy(buf, &rx_buf[2], rx_len);
+
+ mdelay(WDT_COMMAND_DELAY_MS);
+
+ return 0;
+}
+
+static int wdt87xx_set_feature(struct i2c_client *client,
+ const u8 *buf, size_t buf_size)
+{
+ u8 tx_buf[PKT_WRITE_SIZE];
+ int tx_len = 0;
+ int error;
+
+ /* Set feature command packet */
+ tx_buf[tx_len++] = 0x22;
+ tx_buf[tx_len++] = 0x00;
+ if (buf[CMD_REPORT_ID_OFFSET] > 0xF) {
+ tx_buf[tx_len++] = 0x30;
+ tx_buf[tx_len++] = 0x03;
+ tx_buf[tx_len++] = buf[CMD_REPORT_ID_OFFSET];
+ } else {
+ tx_buf[tx_len++] = 0x30 | buf[CMD_REPORT_ID_OFFSET];
+ tx_buf[tx_len++] = 0x03;
+ }
+ tx_buf[tx_len++] = 0x23;
+ tx_buf[tx_len++] = 0x00;
+ tx_buf[tx_len++] = (buf_size & 0xFF);
+ tx_buf[tx_len++] = ((buf_size & 0xFF00) >> 8);
+
+ if (tx_len + buf_size > sizeof(tx_buf))
+ return -EINVAL;
+
+ memcpy(&tx_buf[tx_len], buf, buf_size);
+ tx_len += buf_size;
+
+ error = i2c_master_send(client, tx_buf, tx_len);
+ if (error < 0) {
+ dev_err(&client->dev, "set feature failed: %d\n", error);
+ return error;
+ }
+
+ mdelay(WDT_COMMAND_DELAY_MS);
+
+ return 0;
+}
+
+static int wdt87xx_send_command(struct i2c_client *client, int cmd, int value)
+{
+ u8 cmd_buf[CMD_BUF_SIZE];
+
+ /* Set the command packet */
+ cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE;
+ cmd_buf[CMD_TYPE_OFFSET] = VND_SET_COMMAND_DATA;
+ put_unaligned_le16((u16)cmd, &cmd_buf[CMD_INDEX_OFFSET]);
+
+ switch (cmd) {
+ case VND_CMD_START:
+ case VND_CMD_STOP:
+ case VND_CMD_RESET:
+ /* Mode selector */
+ put_unaligned_le32((value & 0xFF), &cmd_buf[CMD_LENGTH_OFFSET]);
+ break;
+
+ case VND_CMD_SFLCK:
+ put_unaligned_le16(CMD_SFLCK_KEY, &cmd_buf[CMD_KEY_OFFSET]);
+ break;
+
+ case VND_CMD_SFUNL:
+ put_unaligned_le16(CMD_SFUNL_KEY, &cmd_buf[CMD_KEY_OFFSET]);
+ break;
+
+ case VND_CMD_ERASE:
+ case VND_SET_CHECKSUM_CALC:
+ case VND_SET_CHECKSUM_LENGTH:
+ put_unaligned_le32(value, &cmd_buf[CMD_KEY_OFFSET]);
+ break;
+
+ default:
+ cmd_buf[CMD_REPORT_ID_OFFSET] = 0;
+ dev_err(&client->dev, "Invalid command: %d\n", cmd);
+ return -EINVAL;
+ }
+
+ return wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf));
+}
+
+static int wdt87xx_sw_reset(struct i2c_client *client)
+{
+ int error;
+
+ dev_dbg(&client->dev, "resetting device now\n");
+
+ error = wdt87xx_send_command(client, VND_CMD_RESET, 0);
+ if (error) {
+ dev_err(&client->dev, "reset failed\n");
+ return error;
+ }
+
+ /* Wait the device to be ready */
+ msleep(WDT_FW_RESET_TIME);
+
+ return 0;
+}
+
+static const void *wdt87xx_get_fw_chunk(const struct firmware *fw, u32 id)
+{
+ size_t pos = FW_PAYLOAD_OFFSET;
+ u32 chunk_id, chunk_size;
+
+ while (pos < fw->size) {
+ chunk_id = get_unaligned_le32(fw->data +
+ pos + FW_CHUNK_ID_OFFSET);
+ if (chunk_id == id)
+ return fw->data + pos;
+
+ chunk_size = get_unaligned_le32(fw->data +
+ pos + FW_CHUNK_SIZE_OFFSET);
+ pos += chunk_size + 2 * sizeof(u32); /* chunk ID + size */
+ }
+
+ return NULL;
+}
+
+static int wdt87xx_get_sysparam(struct i2c_client *client,
+ struct wdt87xx_sys_param *param)
+{
+ u8 buf[PKT_READ_SIZE];
+ int error;
+
+ error = wdt87xx_get_desc(client, WDT_GD_DEVICE, buf, 18);
+ if (error) {
+ dev_err(&client->dev, "failed to get device desc\n");
+ return error;
+ }
+
+ param->vendor_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_VID);
+ param->product_id = get_unaligned_le16(buf + DEV_DESC_OFFSET_PID);
+
+ error = wdt87xx_get_string(client, STRIDX_PARAMETERS, buf, 34);
+ if (error) {
+ dev_err(&client->dev, "failed to get parameters\n");
+ return error;
+ }
+
+ param->xmls_id1 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID1);
+ param->xmls_id2 = get_unaligned_le16(buf + CTL_PARAM_OFFSET_XMLS_ID2);
+ param->phy_ch_x = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_X);
+ param->phy_ch_y = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_CH_Y);
+ param->phy_w = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_W) / 10;
+ param->phy_h = get_unaligned_le16(buf + CTL_PARAM_OFFSET_PHY_H) / 10;
+
+ /* Get the scaling factor of pixel to logical coordinate */
+ param->scaling_factor =
+ get_unaligned_le16(buf + CTL_PARAM_OFFSET_FACTOR);
+
+ param->max_x = MAX_UNIT_AXIS;
+ param->max_y = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS * param->phy_h,
+ param->phy_w);
+
+ error = wdt87xx_get_string(client, STRIDX_PLATFORM_ID, buf, 8);
+ if (error) {
+ dev_err(&client->dev, "failed to get platform id\n");
+ return error;
+ }
+
+ param->plat_id = buf[1];
+
+ buf[0] = 0xf2;
+ error = wdt87xx_get_feature(client, buf, 16);
+ if (error) {
+ dev_err(&client->dev, "failed to get firmware id\n");
+ return error;
+ }
+
+ if (buf[0] != 0xf2) {
+ dev_err(&client->dev, "wrong id of fw response: 0x%x\n",
+ buf[0]);
+ return -EINVAL;
+ }
+
+ param->fw_id = get_unaligned_le16(&buf[1]);
+
+ dev_info(&client->dev,
+ "fw_id: 0x%x, plat_id: 0x%x, xml_id1: %04x, xml_id2: %04x\n",
+ param->fw_id, param->plat_id,
+ param->xmls_id1, param->xmls_id2);
+
+ return 0;
+}
+
+static int wdt87xx_validate_firmware(struct wdt87xx_data *wdt,
+ const struct firmware *fw)
+{
+ const void *fw_chunk;
+ u32 data1, data2;
+ u32 size;
+ u8 fw_chip_id;
+ u8 chip_id;
+
+ data1 = get_unaligned_le32(fw->data + FW_FOURCC1_OFFSET);
+ data2 = get_unaligned_le32(fw->data + FW_FOURCC2_OFFSET);
+ if (data1 != FOURCC_ID_RIFF || data2 != FOURCC_ID_WHIF) {
+ dev_err(&wdt->client->dev, "check fw tag failed\n");
+ return -EINVAL;
+ }
+
+ size = get_unaligned_le32(fw->data + FW_SIZE_OFFSET);
+ if (size != fw->size) {
+ dev_err(&wdt->client->dev,
+ "fw size mismatch: expected %d, actual %zu\n",
+ size, fw->size);
+ return -EINVAL;
+ }
+
+ /*
+ * Get the chip_id from the firmware. Make sure that it is the
+ * right controller to do the firmware and config update.
+ */
+ fw_chunk = wdt87xx_get_fw_chunk(fw, CHUNK_ID_FRWR);
+ if (!fw_chunk) {
+ dev_err(&wdt->client->dev,
+ "unable to locate firmware chunk\n");
+ return -EINVAL;
+ }
+
+ fw_chip_id = (get_unaligned_le32(fw_chunk +
+ FW_CHUNK_VERSION_OFFSET) >> 12) & 0xF;
+ chip_id = (wdt->param.fw_id >> 12) & 0xF;
+
+ if (fw_chip_id != chip_id) {
+ dev_err(&wdt->client->dev,
+ "fw version mismatch: fw %d vs. chip %d\n",
+ fw_chip_id, chip_id);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int wdt87xx_validate_fw_chunk(const void *data, int id)
+{
+ if (id == CHUNK_ID_FRWR) {
+ u32 fw_id;
+
+ fw_id = get_unaligned_le32(data + FW_CHUNK_PAYLOAD_OFFSET);
+ if (fw_id != WDT_FIRMWARE_ID)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wdt87xx_write_data(struct i2c_client *client, const char *data,
+ u32 address, int length)
+{
+ u16 packet_size;
+ int count = 0;
+ int error;
+ u8 pkt_buf[PKT_BUF_SIZE];
+
+ /* Address and length should be 4 bytes aligned */
+ if ((address & 0x3) != 0 || (length & 0x3) != 0) {
+ dev_err(&client->dev,
+ "addr & len must be 4 bytes aligned %x, %x\n",
+ address, length);
+ return -EINVAL;
+ }
+
+ while (length) {
+ packet_size = min(length, PACKET_SIZE);
+
+ pkt_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_WRITE;
+ pkt_buf[CMD_TYPE_OFFSET] = VND_SET_DATA;
+ put_unaligned_le16(packet_size, &pkt_buf[CMD_INDEX_OFFSET]);
+ put_unaligned_le32(address, &pkt_buf[CMD_LENGTH_OFFSET]);
+ memcpy(&pkt_buf[CMD_DATA_OFFSET], data, packet_size);
+
+ error = wdt87xx_set_feature(client, pkt_buf, sizeof(pkt_buf));
+ if (error)
+ return error;
+
+ length -= packet_size;
+ data += packet_size;
+ address += packet_size;
+
+ /* Wait for the controller to finish the write */
+ mdelay(WDT_FLASH_WRITE_DELAY_MS);
+
+ if ((++count % 32) == 0) {
+ /* Delay for fw to clear watch dog */
+ msleep(20);
+ }
+ }
+
+ return 0;
+}
+
+static u16 misr(u16 cur_value, u8 new_value)
+{
+ u32 a, b;
+ u32 bit0;
+ u32 y;
+
+ a = cur_value;
+ b = new_value;
+ bit0 = a ^ (b & 1);
+ bit0 ^= a >> 1;
+ bit0 ^= a >> 2;
+ bit0 ^= a >> 4;
+ bit0 ^= a >> 5;
+ bit0 ^= a >> 7;
+ bit0 ^= a >> 11;
+ bit0 ^= a >> 15;
+ y = (a << 1) ^ b;
+ y = (y & ~1) | (bit0 & 1);
+
+ return (u16)y;
+}
+
+static u16 wdt87xx_calculate_checksum(const u8 *data, size_t length)
+{
+ u16 checksum = 0;
+ size_t i;
+
+ for (i = 0; i < length; i++)
+ checksum = misr(checksum, data[i]);
+
+ return checksum;
+}
+
+static int wdt87xx_get_checksum(struct i2c_client *client, u16 *checksum,
+ u32 address, int length)
+{
+ int error;
+ int time_delay;
+ u8 pkt_buf[PKT_BUF_SIZE];
+ u8 cmd_buf[CMD_BUF_SIZE];
+
+ error = wdt87xx_send_command(client, VND_SET_CHECKSUM_LENGTH, length);
+ if (error) {
+ dev_err(&client->dev, "failed to set checksum length\n");
+ return error;
+ }
+
+ error = wdt87xx_send_command(client, VND_SET_CHECKSUM_CALC, address);
+ if (error) {
+ dev_err(&client->dev, "failed to set checksum address\n");
+ return error;
+ }
+
+ /* Wait the operation to complete */
+ time_delay = DIV_ROUND_UP(length, 1024);
+ msleep(time_delay * 30);
+
+ memset(cmd_buf, 0, sizeof(cmd_buf));
+ cmd_buf[CMD_REPORT_ID_OFFSET] = VND_REQ_READ;
+ cmd_buf[CMD_TYPE_OFFSET] = VND_GET_CHECKSUM;
+ error = wdt87xx_set_feature(client, cmd_buf, sizeof(cmd_buf));
+ if (error) {
+ dev_err(&client->dev, "failed to request checksum\n");
+ return error;
+ }
+
+ memset(pkt_buf, 0, sizeof(pkt_buf));
+ pkt_buf[CMD_REPORT_ID_OFFSET] = VND_READ_DATA;
+ error = wdt87xx_get_feature(client, pkt_buf, sizeof(pkt_buf));
+ if (error) {
+ dev_err(&client->dev, "failed to read checksum\n");
+ return error;
+ }
+
+ *checksum = get_unaligned_le16(&pkt_buf[CMD_DATA_OFFSET]);
+ return 0;
+}
+
+static int wdt87xx_write_firmware(struct i2c_client *client, const void *chunk)
+{
+ u32 start_addr = get_unaligned_le32(chunk + FW_CHUNK_TGT_START_OFFSET);
+ u32 size = get_unaligned_le32(chunk + FW_CHUNK_PAYLOAD_LEN_OFFSET);
+ const void *data = chunk + FW_CHUNK_PAYLOAD_OFFSET;
+ int error;
+ int err1;
+ int page_size;
+ int retry = 0;
+ u16 device_checksum, firmware_checksum;
+
+ dev_dbg(&client->dev, "start 4k page program\n");
+
+ error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_STOP);
+ if (error) {
+ dev_err(&client->dev, "stop report mode failed\n");
+ return error;
+ }
+
+ error = wdt87xx_send_command(client, VND_CMD_SFUNL, 0);
+ if (error) {
+ dev_err(&client->dev, "unlock failed\n");
+ goto out_enable_reporting;
+ }
+
+ mdelay(10);
+
+ while (size) {
+ dev_dbg(&client->dev, "%s: %x, %x\n", __func__,
+ start_addr, size);
+
+ page_size = min_t(u32, size, PG_SIZE);
+ size -= page_size;
+
+ for (retry = 0; retry < MAX_RETRIES; retry++) {
+ error = wdt87xx_send_command(client, VND_CMD_ERASE,
+ start_addr);
+ if (error) {
+ dev_err(&client->dev,
+ "erase failed at %#08x\n", start_addr);
+ break;
+ }
+
+ msleep(WDT_FLASH_ERASE_DELAY_MS);
+
+ error = wdt87xx_write_data(client, data, start_addr,
+ page_size);
+ if (error) {
+ dev_err(&client->dev,
+ "write failed at %#08x (%d bytes)\n",
+ start_addr, page_size);
+ break;
+ }
+
+ error = wdt87xx_get_checksum(client, &device_checksum,
+ start_addr, page_size);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to retrieve checksum for %#08x (len: %d)\n",
+ start_addr, page_size);
+ break;
+ }
+
+ firmware_checksum =
+ wdt87xx_calculate_checksum(data, page_size);
+
+ if (device_checksum == firmware_checksum)
+ break;
+
+ dev_err(&client->dev,
+ "checksum fail: %d vs %d, retry %d\n",
+ device_checksum, firmware_checksum, retry);
+ }
+
+ if (retry == MAX_RETRIES) {
+ dev_err(&client->dev, "page write failed\n");
+ error = -EIO;
+ goto out_lock_device;
+ }
+
+ start_addr = start_addr + page_size;
+ data = data + page_size;
+ }
+
+out_lock_device:
+ err1 = wdt87xx_send_command(client, VND_CMD_SFLCK, 0);
+ if (err1)
+ dev_err(&client->dev, "lock failed\n");
+
+ mdelay(10);
+
+out_enable_reporting:
+ err1 = wdt87xx_send_command(client, VND_CMD_START, 0);
+ if (err1)
+ dev_err(&client->dev, "start to report failed\n");
+
+ return error ? error : err1;
+}
+
+static int wdt87xx_load_chunk(struct i2c_client *client,
+ const struct firmware *fw, u32 ck_id)
+{
+ const void *chunk;
+ int error;
+
+ chunk = wdt87xx_get_fw_chunk(fw, ck_id);
+ if (!chunk) {
+ dev_err(&client->dev, "unable to locate chunk (type %d)\n",
+ ck_id);
+ return -EINVAL;
+ }
+
+ error = wdt87xx_validate_fw_chunk(chunk, ck_id);
+ if (error) {
+ dev_err(&client->dev, "invalid chunk (type %d): %d\n",
+ ck_id, error);
+ return error;
+ }
+
+ error = wdt87xx_write_firmware(client, chunk);
+ if (error) {
+ dev_err(&client->dev,
+ "failed to write fw chunk (type %d): %d\n",
+ ck_id, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int wdt87xx_do_update_firmware(struct i2c_client *client,
+ const struct firmware *fw,
+ unsigned int chunk_id)
+{
+ struct wdt87xx_data *wdt = i2c_get_clientdata(client);
+ int error;
+
+ error = wdt87xx_validate_firmware(wdt, fw);
+ if (error)
+ return error;
+
+ error = mutex_lock_interruptible(&wdt->fw_mutex);
+ if (error)
+ return error;
+
+ disable_irq(client->irq);
+
+ error = wdt87xx_load_chunk(client, fw, chunk_id);
+ if (error) {
+ dev_err(&client->dev,
+ "firmware load failed (type: %d): %d\n",
+ chunk_id, error);
+ goto out;
+ }
+
+ error = wdt87xx_sw_reset(client);
+ if (error) {
+ dev_err(&client->dev, "soft reset failed: %d\n", error);
+ goto out;
+ }
+
+ /* Refresh the parameters */
+ error = wdt87xx_get_sysparam(client, &wdt->param);
+ if (error)
+ dev_err(&client->dev,
+ "failed to refresh system parameters: %d\n", error);
+out:
+ enable_irq(client->irq);
+ mutex_unlock(&wdt->fw_mutex);
+
+ return error ? error : 0;
+}
+
+static int wdt87xx_update_firmware(struct device *dev,
+ const char *fw_name, unsigned int chunk_id)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ const struct firmware *fw;
+ int error;
+
+ error = request_firmware(&fw, fw_name, dev);
+ if (error) {
+ dev_err(&client->dev, "unable to retrieve firmware %s: %d\n",
+ fw_name, error);
+ return error;
+ }
+
+ error = wdt87xx_do_update_firmware(client, fw, chunk_id);
+
+ release_firmware(fw);
+
+ return error ? error : 0;
+}
+
+static ssize_t config_csum_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wdt87xx_data *wdt = i2c_get_clientdata(client);
+ u32 cfg_csum;
+
+ cfg_csum = wdt->param.xmls_id1;
+ cfg_csum = (cfg_csum << 16) | wdt->param.xmls_id2;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", cfg_csum);
+}
+
+static ssize_t fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wdt87xx_data *wdt = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.fw_id);
+}
+
+static ssize_t plat_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wdt87xx_data *wdt = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", wdt->param.plat_id);
+}
+
+static ssize_t update_config_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int error;
+
+ error = wdt87xx_update_firmware(dev, WDT87XX_CFG_NAME, CHUNK_ID_CNFG);
+
+ return error ? error : count;
+}
+
+static ssize_t update_fw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int error;
+
+ error = wdt87xx_update_firmware(dev, WDT87XX_FW_NAME, CHUNK_ID_FRWR);
+
+ return error ? error : count;
+}
+
+static DEVICE_ATTR_RO(config_csum);
+static DEVICE_ATTR_RO(fw_version);
+static DEVICE_ATTR_RO(plat_id);
+static DEVICE_ATTR_WO(update_config);
+static DEVICE_ATTR_WO(update_fw);
+
+static struct attribute *wdt87xx_attrs[] = {
+ &dev_attr_config_csum.attr,
+ &dev_attr_fw_version.attr,
+ &dev_attr_plat_id.attr,
+ &dev_attr_update_config.attr,
+ &dev_attr_update_fw.attr,
+ NULL
+};
+
+static const struct attribute_group wdt87xx_attr_group = {
+ .attrs = wdt87xx_attrs,
+};
+
+static void wdt87xx_report_contact(struct input_dev *input,
+ struct wdt87xx_sys_param *param,
+ u8 *buf)
+{
+ int finger_id;
+ u32 x, y, w;
+ u8 p;
+
+ finger_id = (buf[FINGER_EV_V1_OFFSET_ID] >> 3) - 1;
+ if (finger_id < 0)
+ return;
+
+ /* Check if this is an active contact */
+ if (!(buf[FINGER_EV_V1_OFFSET_ID] & 0x1))
+ return;
+
+ w = buf[FINGER_EV_V1_OFFSET_W];
+ w *= param->scaling_factor;
+
+ p = buf[FINGER_EV_V1_OFFSET_P];
+
+ x = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_X);
+
+ y = get_unaligned_le16(buf + FINGER_EV_V1_OFFSET_Y);
+ y = DIV_ROUND_CLOSEST(y * param->phy_h, param->phy_w);
+
+ /* Refuse incorrect coordinates */
+ if (x > param->max_x || y > param->max_y)
+ return;
+
+ dev_dbg(input->dev.parent, "tip on (%d), x(%d), y(%d)\n",
+ finger_id, x, y);
+
+ input_mt_slot(input, finger_id);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, w);
+ input_report_abs(input, ABS_MT_PRESSURE, p);
+ input_report_abs(input, ABS_MT_POSITION_X, x);
+ input_report_abs(input, ABS_MT_POSITION_Y, y);
+}
+
+static irqreturn_t wdt87xx_ts_interrupt(int irq, void *dev_id)
+{
+ struct wdt87xx_data *wdt = dev_id;
+ struct i2c_client *client = wdt->client;
+ int i, fingers;
+ int error;
+ u8 raw_buf[WDT_V1_RAW_BUF_COUNT] = {0};
+
+ error = i2c_master_recv(client, raw_buf, WDT_V1_RAW_BUF_COUNT);
+ if (error < 0) {
+ dev_err(&client->dev, "read v1 raw data failed: %d\n", error);
+ goto irq_exit;
+ }
+
+ fingers = raw_buf[TOUCH_PK_V1_OFFSET_FNGR_NUM];
+ if (!fingers)
+ goto irq_exit;
+
+ for (i = 0; i < WDT_MAX_FINGER; i++)
+ wdt87xx_report_contact(wdt->input,
+ &wdt->param,
+ &raw_buf[TOUCH_PK_V1_OFFSET_EVENT +
+ i * FINGER_EV_V1_SIZE]);
+
+ input_mt_sync_frame(wdt->input);
+ input_sync(wdt->input);
+
+irq_exit:
+ return IRQ_HANDLED;
+}
+
+static int wdt87xx_ts_create_input_device(struct wdt87xx_data *wdt)
+{
+ struct device *dev = &wdt->client->dev;
+ struct input_dev *input;
+ unsigned int res = DIV_ROUND_CLOSEST(MAX_UNIT_AXIS, wdt->param.phy_w);
+ int error;
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+ wdt->input = input;
+
+ input->name = "WDT87xx Touchscreen";
+ input->id.bustype = BUS_I2C;
+ input->id.vendor = wdt->param.vendor_id;
+ input->id.product = wdt->param.product_id;
+ input->phys = wdt->phys;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0,
+ wdt->param.max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
+ wdt->param.max_y, 0, 0);
+ input_abs_set_res(input, ABS_MT_POSITION_X, res);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, res);
+
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR,
+ 0, wdt->param.max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xFF, 0, 0);
+
+ input_mt_init_slots(input, WDT_MAX_FINGER,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int wdt87xx_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct wdt87xx_data *wdt;
+ int error;
+
+ dev_dbg(&client->dev, "adapter=%d, client irq: %d\n",
+ client->adapter->nr, client->irq);
+
+ /* Check if the I2C function is ok in this adaptor */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENXIO;
+
+ wdt = devm_kzalloc(&client->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->client = client;
+ mutex_init(&wdt->fw_mutex);
+ i2c_set_clientdata(client, wdt);
+
+ snprintf(wdt->phys, sizeof(wdt->phys), "i2c-%u-%04x/input0",
+ client->adapter->nr, client->addr);
+
+ error = wdt87xx_get_sysparam(client, &wdt->param);
+ if (error)
+ return error;
+
+ error = wdt87xx_ts_create_input_device(wdt);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, wdt87xx_ts_interrupt,
+ IRQF_ONESHOT,
+ client->name, wdt);
+ if (error) {
+ dev_err(&client->dev, "request irq failed: %d\n", error);
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &wdt87xx_attr_group);
+ if (error) {
+ dev_err(&client->dev, "create sysfs failed: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused wdt87xx_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int error;
+
+ disable_irq(client->irq);
+
+ error = wdt87xx_send_command(client, VND_CMD_STOP, MODE_IDLE);
+ if (error) {
+ enable_irq(client->irq);
+ dev_err(&client->dev,
+ "failed to stop device when suspending: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused wdt87xx_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int error;
+
+ /*
+ * The chip may have been reset while system is resuming,
+ * give it some time to settle.
+ */
+ msleep(100);
+
+ error = wdt87xx_send_command(client, VND_CMD_START, 0);
+ if (error)
+ dev_err(&client->dev,
+ "failed to start device when resuming: %d\n",
+ error);
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(wdt87xx_pm_ops, wdt87xx_suspend, wdt87xx_resume);
+
+static const struct i2c_device_id wdt87xx_dev_id[] = {
+ { WDT87XX_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id);
+
+static const struct acpi_device_id wdt87xx_acpi_id[] = {
+ { "WDHT0001", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, wdt87xx_acpi_id);
+
+static struct i2c_driver wdt87xx_driver = {
+ .probe = wdt87xx_ts_probe,
+ .id_table = wdt87xx_dev_id,
+ .driver = {
+ .name = WDT87XX_NAME,
+ .pm = &wdt87xx_pm_ops,
+ .acpi_match_table = ACPI_PTR(wdt87xx_acpi_id),
+ },
+};
+module_i2c_driver(wdt87xx_driver);
+
+MODULE_AUTHOR("HN Chen <hn.chen@weidahitech.com>");
+MODULE_DESCRIPTION("WeidaHiTech WDT87XX Touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm831x-ts.c b/drivers/input/touchscreen/wm831x-ts.c
new file mode 100644
index 000000000..319f57fb9
--- /dev/null
+++ b/drivers/input/touchscreen/wm831x-ts.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Touchscreen driver for WM831x PMICs
+ *
+ * Copyright 2011 Wolfson Microelectronics plc.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/pm.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/irq.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+/*
+ * R16424 (0x4028) - Touch Control 1
+ */
+#define WM831X_TCH_ENA 0x8000 /* TCH_ENA */
+#define WM831X_TCH_CVT_ENA 0x4000 /* TCH_CVT_ENA */
+#define WM831X_TCH_SLPENA 0x1000 /* TCH_SLPENA */
+#define WM831X_TCH_Z_ENA 0x0400 /* TCH_Z_ENA */
+#define WM831X_TCH_Y_ENA 0x0200 /* TCH_Y_ENA */
+#define WM831X_TCH_X_ENA 0x0100 /* TCH_X_ENA */
+#define WM831X_TCH_DELAY_MASK 0x00E0 /* TCH_DELAY - [7:5] */
+#define WM831X_TCH_DELAY_SHIFT 5 /* TCH_DELAY - [7:5] */
+#define WM831X_TCH_DELAY_WIDTH 3 /* TCH_DELAY - [7:5] */
+#define WM831X_TCH_RATE_MASK 0x001F /* TCH_RATE - [4:0] */
+#define WM831X_TCH_RATE_SHIFT 0 /* TCH_RATE - [4:0] */
+#define WM831X_TCH_RATE_WIDTH 5 /* TCH_RATE - [4:0] */
+
+/*
+ * R16425 (0x4029) - Touch Control 2
+ */
+#define WM831X_TCH_PD_WK 0x2000 /* TCH_PD_WK */
+#define WM831X_TCH_5WIRE 0x1000 /* TCH_5WIRE */
+#define WM831X_TCH_PDONLY 0x0800 /* TCH_PDONLY */
+#define WM831X_TCH_ISEL 0x0100 /* TCH_ISEL */
+#define WM831X_TCH_RPU_MASK 0x000F /* TCH_RPU - [3:0] */
+#define WM831X_TCH_RPU_SHIFT 0 /* TCH_RPU - [3:0] */
+#define WM831X_TCH_RPU_WIDTH 4 /* TCH_RPU - [3:0] */
+
+/*
+ * R16426-8 (0x402A-C) - Touch Data X/Y/X
+ */
+#define WM831X_TCH_PD 0x8000 /* TCH_PD1 */
+#define WM831X_TCH_DATA_MASK 0x0FFF /* TCH_DATA - [11:0] */
+#define WM831X_TCH_DATA_SHIFT 0 /* TCH_DATA - [11:0] */
+#define WM831X_TCH_DATA_WIDTH 12 /* TCH_DATA - [11:0] */
+
+struct wm831x_ts {
+ struct input_dev *input_dev;
+ struct wm831x *wm831x;
+ unsigned int data_irq;
+ unsigned int pd_irq;
+ bool pressure;
+ bool pen_down;
+ struct work_struct pd_data_work;
+};
+
+static void wm831x_pd_data_work(struct work_struct *work)
+{
+ struct wm831x_ts *wm831x_ts =
+ container_of(work, struct wm831x_ts, pd_data_work);
+
+ if (wm831x_ts->pen_down) {
+ enable_irq(wm831x_ts->data_irq);
+ dev_dbg(wm831x_ts->wm831x->dev, "IRQ PD->DATA done\n");
+ } else {
+ enable_irq(wm831x_ts->pd_irq);
+ dev_dbg(wm831x_ts->wm831x->dev, "IRQ DATA->PD done\n");
+ }
+}
+
+static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
+{
+ struct wm831x_ts *wm831x_ts = irq_data;
+ struct wm831x *wm831x = wm831x_ts->wm831x;
+ static int data_types[] = { ABS_X, ABS_Y, ABS_PRESSURE };
+ u16 data[3];
+ int count;
+ int i, ret;
+
+ if (wm831x_ts->pressure)
+ count = 3;
+ else
+ count = 2;
+
+ wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
+ WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
+
+ ret = wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count,
+ data);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to read touch data: %d\n",
+ ret);
+ return IRQ_NONE;
+ }
+
+ /*
+ * We get a pen down reading on every reading, report pen up if any
+ * individual reading does so.
+ */
+ wm831x_ts->pen_down = true;
+ for (i = 0; i < count; i++) {
+ if (!(data[i] & WM831X_TCH_PD)) {
+ wm831x_ts->pen_down = false;
+ continue;
+ }
+ input_report_abs(wm831x_ts->input_dev, data_types[i],
+ data[i] & WM831X_TCH_DATA_MASK);
+ }
+
+ if (!wm831x_ts->pen_down) {
+ /* Switch from data to pen down */
+ dev_dbg(wm831x->dev, "IRQ DATA->PD\n");
+
+ disable_irq_nosync(wm831x_ts->data_irq);
+
+ /* Don't need data any more */
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+ WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
+ WM831X_TCH_Z_ENA, 0);
+
+ /* Flush any final samples that arrived while reading */
+ wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
+ WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
+
+ wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count, data);
+
+ if (wm831x_ts->pressure)
+ input_report_abs(wm831x_ts->input_dev,
+ ABS_PRESSURE, 0);
+
+ input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 0);
+
+ schedule_work(&wm831x_ts->pd_data_work);
+ } else {
+ input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 1);
+ }
+
+ input_sync(wm831x_ts->input_dev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_ts_pen_down_irq(int irq, void *irq_data)
+{
+ struct wm831x_ts *wm831x_ts = irq_data;
+ struct wm831x *wm831x = wm831x_ts->wm831x;
+ int ena = 0;
+
+ if (wm831x_ts->pen_down)
+ return IRQ_HANDLED;
+
+ disable_irq_nosync(wm831x_ts->pd_irq);
+
+ /* Start collecting data */
+ if (wm831x_ts->pressure)
+ ena |= WM831X_TCH_Z_ENA;
+
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+ WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA,
+ WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | ena);
+
+ wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
+ WM831X_TCHPD_EINT, WM831X_TCHPD_EINT);
+
+ wm831x_ts->pen_down = true;
+
+ /* Switch from pen down to data */
+ dev_dbg(wm831x->dev, "IRQ PD->DATA\n");
+ schedule_work(&wm831x_ts->pd_data_work);
+
+ return IRQ_HANDLED;
+}
+
+static int wm831x_ts_input_open(struct input_dev *idev)
+{
+ struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
+ struct wm831x *wm831x = wm831x_ts->wm831x;
+
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+ WM831X_TCH_ENA | WM831X_TCH_CVT_ENA |
+ WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
+ WM831X_TCH_Z_ENA, WM831X_TCH_ENA);
+
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+ WM831X_TCH_CVT_ENA, WM831X_TCH_CVT_ENA);
+
+ return 0;
+}
+
+static void wm831x_ts_input_close(struct input_dev *idev)
+{
+ struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
+ struct wm831x *wm831x = wm831x_ts->wm831x;
+
+ /* Shut the controller down, disabling all other functionality too */
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+ WM831X_TCH_ENA | WM831X_TCH_X_ENA |
+ WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA, 0);
+
+ /* Make sure any pending IRQs are done, the above will prevent
+ * new ones firing.
+ */
+ synchronize_irq(wm831x_ts->data_irq);
+ synchronize_irq(wm831x_ts->pd_irq);
+
+ /* Make sure the IRQ completion work is quiesced */
+ flush_work(&wm831x_ts->pd_data_work);
+
+ /* If we ended up with the pen down then make sure we revert back
+ * to pen detection state for the next time we start up.
+ */
+ if (wm831x_ts->pen_down) {
+ disable_irq(wm831x_ts->data_irq);
+ enable_irq(wm831x_ts->pd_irq);
+ wm831x_ts->pen_down = false;
+ }
+}
+
+static int wm831x_ts_probe(struct platform_device *pdev)
+{
+ struct wm831x_ts *wm831x_ts;
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *core_pdata = dev_get_platdata(pdev->dev.parent);
+ struct wm831x_touch_pdata *pdata = NULL;
+ struct input_dev *input_dev;
+ int error, irqf;
+
+ if (core_pdata)
+ pdata = core_pdata->touch;
+
+ wm831x_ts = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ts),
+ GFP_KERNEL);
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!wm831x_ts || !input_dev) {
+ error = -ENOMEM;
+ goto err_alloc;
+ }
+
+ wm831x_ts->wm831x = wm831x;
+ wm831x_ts->input_dev = input_dev;
+ INIT_WORK(&wm831x_ts->pd_data_work, wm831x_pd_data_work);
+
+ /*
+ * If we have a direct IRQ use it, otherwise use the interrupt
+ * from the WM831x IRQ controller.
+ */
+ wm831x_ts->data_irq = wm831x_irq(wm831x,
+ platform_get_irq_byname(pdev,
+ "TCHDATA"));
+ if (pdata && pdata->data_irq)
+ wm831x_ts->data_irq = pdata->data_irq;
+
+ wm831x_ts->pd_irq = wm831x_irq(wm831x,
+ platform_get_irq_byname(pdev, "TCHPD"));
+ if (pdata && pdata->pd_irq)
+ wm831x_ts->pd_irq = pdata->pd_irq;
+
+ if (pdata)
+ wm831x_ts->pressure = pdata->pressure;
+ else
+ wm831x_ts->pressure = true;
+
+ /* Five wire touchscreens can't report pressure */
+ if (pdata && pdata->fivewire) {
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+ WM831X_TCH_5WIRE, WM831X_TCH_5WIRE);
+
+ /* Pressure measurements are not possible for five wire mode */
+ WARN_ON(pdata->pressure && pdata->fivewire);
+ wm831x_ts->pressure = false;
+ } else {
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+ WM831X_TCH_5WIRE, 0);
+ }
+
+ if (pdata) {
+ switch (pdata->isel) {
+ default:
+ dev_err(&pdev->dev, "Unsupported ISEL setting: %d\n",
+ pdata->isel);
+ fallthrough;
+ case 200:
+ case 0:
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+ WM831X_TCH_ISEL, 0);
+ break;
+ case 400:
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+ WM831X_TCH_ISEL, WM831X_TCH_ISEL);
+ break;
+ }
+ }
+
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+ WM831X_TCH_PDONLY, 0);
+
+ /* Default to 96 samples/sec */
+ wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+ WM831X_TCH_RATE_MASK, 6);
+
+ if (pdata && pdata->data_irqf)
+ irqf = pdata->data_irqf;
+ else
+ irqf = IRQF_TRIGGER_HIGH;
+
+ error = request_threaded_irq(wm831x_ts->data_irq,
+ NULL, wm831x_ts_data_irq,
+ irqf | IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "Touchscreen data", wm831x_ts);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request data IRQ %d: %d\n",
+ wm831x_ts->data_irq, error);
+ goto err_alloc;
+ }
+
+ if (pdata && pdata->pd_irqf)
+ irqf = pdata->pd_irqf;
+ else
+ irqf = IRQF_TRIGGER_HIGH;
+
+ error = request_threaded_irq(wm831x_ts->pd_irq,
+ NULL, wm831x_ts_pen_down_irq,
+ irqf | IRQF_ONESHOT,
+ "Touchscreen pen down", wm831x_ts);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request pen down IRQ %d: %d\n",
+ wm831x_ts->pd_irq, error);
+ goto err_data_irq;
+ }
+
+ /* set up touch configuration */
+ input_dev->name = "WM831x touchscreen";
+ input_dev->phys = "wm831x";
+ input_dev->open = wm831x_ts_input_open;
+ input_dev->close = wm831x_ts_input_close;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
+ if (wm831x_ts->pressure)
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 4095, 5, 0);
+
+ input_set_drvdata(input_dev, wm831x_ts);
+ input_dev->dev.parent = &pdev->dev;
+
+ error = input_register_device(input_dev);
+ if (error)
+ goto err_pd_irq;
+
+ platform_set_drvdata(pdev, wm831x_ts);
+ return 0;
+
+err_pd_irq:
+ free_irq(wm831x_ts->pd_irq, wm831x_ts);
+err_data_irq:
+ free_irq(wm831x_ts->data_irq, wm831x_ts);
+err_alloc:
+
+ return error;
+}
+
+static int wm831x_ts_remove(struct platform_device *pdev)
+{
+ struct wm831x_ts *wm831x_ts = platform_get_drvdata(pdev);
+
+ free_irq(wm831x_ts->pd_irq, wm831x_ts);
+ free_irq(wm831x_ts->data_irq, wm831x_ts);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_ts_driver = {
+ .driver = {
+ .name = "wm831x-touch",
+ },
+ .probe = wm831x_ts_probe,
+ .remove = wm831x_ts_remove,
+};
+module_platform_driver(wm831x_ts_driver);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM831x PMIC touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-touch");
diff --git a/drivers/input/touchscreen/wm9705.c b/drivers/input/touchscreen/wm9705.c
new file mode 100644
index 000000000..4b55d5e1e
--- /dev/null
+++ b/drivers/input/touchscreen/wm9705.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * wm9705.c -- Codec driver for Wolfson WM9705 AC97 Codec.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/wm97xx.h>
+
+#define TS_NAME "wm97xx"
+#define WM9705_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA
+ * pil = 1 to use 200uA and
+ * pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure.
+ */
+static int pil;
+module_param(pil, int, 0);
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+
+/*
+ * Set threshold for pressure measurement.
+ *
+ * Pen down pressure below threshold is ignored.
+ */
+static int pressure = DEFAULT_PRESSURE & 0xfff;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement.");
+
+/*
+ * Set adc sample delay.
+ *
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+static int delay = 4;
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+
+/*
+ * Pen detect comparator threshold.
+ *
+ * 0 to Vmid in 15 steps, 0 = use zero power comparator with Vmid threshold
+ * i.e. 1 = Vmid/15 threshold
+ * 15 = Vmid/1 threshold
+ *
+ * Adjust this value if you are having problems with pen detect not
+ * detecting any down events.
+ */
+static int pdd = 8;
+module_param(pdd, int, 0);
+MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold");
+
+/*
+ * Set adc mask function.
+ *
+ * Sources of glitch noise, such as signals driving an LCD display, may feed
+ * through to the touch screen plates and affect measurement accuracy. In
+ * order to minimise this, a signal may be applied to the MASK pin to delay or
+ * synchronise the sampling.
+ *
+ * 0 = No delay or sync
+ * 1 = High on pin stops conversions
+ * 2 = Edge triggered, edge on pin delays conversion by delay param (above)
+ * 3 = Edge triggered, edge on pin starts conversion after delay param
+ */
+static int mask;
+module_param(mask, int, 0);
+MODULE_PARM_DESC(mask, "Set adc mask function.");
+
+/*
+ * ADC sample delay times in uS
+ */
+static const int delay_table[] = {
+ 21, /* 1 AC97 Link frames */
+ 42, /* 2 */
+ 84, /* 4 */
+ 167, /* 8 */
+ 333, /* 16 */
+ 667, /* 32 */
+ 1000, /* 48 */
+ 1333, /* 64 */
+ 2000, /* 96 */
+ 2667, /* 128 */
+ 3333, /* 160 */
+ 4000, /* 192 */
+ 4667, /* 224 */
+ 5333, /* 256 */
+ 6000, /* 288 */
+ 0 /* No delay, switch matrix always on */
+};
+
+/*
+ * Delay after issuing a POLL command.
+ *
+ * The delay is 3 AC97 link frames + the touchpanel settling delay
+ */
+static inline void poll_delay(int d)
+{
+ udelay(3 * AC97_LINK_FRAME + delay_table[d]);
+}
+
+/*
+ * set up the physical settings of the WM9705
+ */
+static void wm9705_phy_init(struct wm97xx *wm)
+{
+ u16 dig1 = 0, dig2 = WM97XX_RPR;
+
+ /*
+ * mute VIDEO and AUX as they share X and Y touchscreen
+ * inputs on the WM9705
+ */
+ wm97xx_reg_write(wm, AC97_AUX, 0x8000);
+ wm97xx_reg_write(wm, AC97_VIDEO, 0x8000);
+
+ /* touchpanel pressure current*/
+ if (pil == 2) {
+ dig2 |= WM9705_PIL;
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 400uA.");
+ } else if (pil)
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 200uA.");
+ if (!pil)
+ pressure = 0;
+
+ /* polling mode sample settling delay */
+ if (delay != 4) {
+ if (delay < 0 || delay > 15) {
+ dev_dbg(wm->dev, "supplied delay out of range.");
+ delay = 4;
+ }
+ }
+ dig1 &= 0xff0f;
+ dig1 |= WM97XX_DELAY(delay);
+ dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.",
+ delay_table[delay]);
+
+ /* WM9705 pdd */
+ dig2 |= (pdd & 0x000f);
+ dev_dbg(wm->dev, "setting pdd to Vmid/%d", 1 - (pdd & 0x000f));
+
+ /* mask */
+ dig2 |= ((mask & 0x3) << 4);
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+}
+
+static void wm9705_dig_enable(struct wm97xx *wm, int enable)
+{
+ if (enable) {
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ wm->dig[2] | WM97XX_PRP_DET_DIG);
+ wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */
+ } else
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ wm->dig[2] & ~WM97XX_PRP_DET_DIG);
+}
+
+static void wm9705_aux_prepare(struct wm97xx *wm)
+{
+ memcpy(wm->dig_save, wm->dig, sizeof(wm->dig));
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG);
+}
+
+static void wm9705_dig_restore(struct wm97xx *wm)
+{
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]);
+}
+
+static inline int is_pden(struct wm97xx *wm)
+{
+ return wm->dig[2] & WM9705_PDEN;
+}
+
+/*
+ * Read a sample from the WM9705 adc in polling mode.
+ */
+static int wm9705_poll_sample(struct wm97xx *wm, int adcsel, int *sample)
+{
+ int timeout = 5 * delay;
+ bool wants_pen = adcsel & WM97XX_PEN_DOWN;
+
+ if (wants_pen && !wm->pen_probably_down) {
+ u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(adcsel);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, (adcsel & WM97XX_ADCSEL_MASK)
+ | WM97XX_POLL | WM97XX_DELAY(delay));
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(adcsel);
+
+ /* check we have correct sample */
+ if ((*sample ^ adcsel) & WM97XX_ADCSEL_MASK) {
+ dev_dbg(wm->dev, "adc wrong sample, wanted %x got %x",
+ adcsel & WM97XX_ADCSEL_MASK,
+ *sample & WM97XX_ADCSEL_MASK);
+ return RC_PENUP;
+ }
+
+ if (wants_pen && !(*sample & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+
+ return RC_VALID;
+}
+
+/*
+ * Sample the WM9705 touchscreen in polling mode
+ */
+static int wm9705_poll_touch(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int rc;
+
+ rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_X | WM97XX_PEN_DOWN, &data->x);
+ if (rc != RC_VALID)
+ return rc;
+ rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_Y | WM97XX_PEN_DOWN, &data->y);
+ if (rc != RC_VALID)
+ return rc;
+ if (pil) {
+ rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_PRES | WM97XX_PEN_DOWN, &data->p);
+ if (rc != RC_VALID)
+ return rc;
+ } else
+ data->p = DEFAULT_PRESSURE;
+
+ return RC_VALID;
+}
+
+/*
+ * Enable WM9705 continuous mode, i.e. touch data is streamed across
+ * an AC97 slot
+ */
+static int wm9705_acc_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig1, dig2;
+ int ret = 0;
+
+ dig1 = wm->dig[1];
+ dig2 = wm->dig[2];
+
+ if (enable) {
+ /* continuous mode */
+ if (wm->mach_ops->acc_startup &&
+ (ret = wm->mach_ops->acc_startup(wm)) < 0)
+ return ret;
+ dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK |
+ WM97XX_DELAY_MASK | WM97XX_SLT_MASK);
+ dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN |
+ WM97XX_DELAY(delay) |
+ WM97XX_SLT(wm->acc_slot) |
+ WM97XX_RATE(wm->acc_rate);
+ if (pil)
+ dig1 |= WM97XX_ADCSEL_PRES;
+ dig2 |= WM9705_PDEN;
+ } else {
+ dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN);
+ dig2 &= ~WM9705_PDEN;
+ if (wm->mach_ops->acc_shutdown)
+ wm->mach_ops->acc_shutdown(wm);
+ }
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+
+ return ret;
+}
+
+struct wm97xx_codec_drv wm9705_codec = {
+ .id = WM9705_ID2,
+ .name = "wm9705",
+ .poll_sample = wm9705_poll_sample,
+ .poll_touch = wm9705_poll_touch,
+ .acc_enable = wm9705_acc_enable,
+ .phy_init = wm9705_phy_init,
+ .dig_enable = wm9705_dig_enable,
+ .dig_restore = wm9705_dig_restore,
+ .aux_prepare = wm9705_aux_prepare,
+};
+EXPORT_SYMBOL_GPL(wm9705_codec);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>");
+MODULE_DESCRIPTION("WM9705 Touch Screen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c
new file mode 100644
index 000000000..6947714df
--- /dev/null
+++ b/drivers/input/touchscreen/wm9712.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * wm9712.c -- Codec driver for Wolfson WM9712 AC97 Codecs.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/wm97xx.h>
+
+#define TS_NAME "wm97xx"
+#define WM9712_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Set internal pull up for pen detect.
+ *
+ * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive)
+ * i.e. pull up resistance = 64k Ohms / rpu.
+ *
+ * Adjust this value if you are having problems with pen detect not
+ * detecting any down event.
+ */
+static int rpu = 8;
+module_param(rpu, int, 0);
+MODULE_PARM_DESC(rpu, "Set internal pull up resistor for pen detect.");
+
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA
+ * pil = 1 to use 200uA and
+ * pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure.
+ */
+static int pil;
+module_param(pil, int, 0);
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+
+/*
+ * Set threshold for pressure measurement.
+ *
+ * Pen down pressure below threshold is ignored.
+ */
+static int pressure = DEFAULT_PRESSURE & 0xfff;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement.");
+
+/*
+ * Set adc sample delay.
+ *
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+static int delay = 3;
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+
+/*
+ * Set five_wire = 1 to use a 5 wire touchscreen.
+ *
+ * NOTE: Five wire mode does not allow for readback of pressure.
+ */
+static int five_wire;
+module_param(five_wire, int, 0);
+MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen.");
+
+/*
+ * Set adc mask function.
+ *
+ * Sources of glitch noise, such as signals driving an LCD display, may feed
+ * through to the touch screen plates and affect measurement accuracy. In
+ * order to minimise this, a signal may be applied to the MASK pin to delay or
+ * synchronise the sampling.
+ *
+ * 0 = No delay or sync
+ * 1 = High on pin stops conversions
+ * 2 = Edge triggered, edge on pin delays conversion by delay param (above)
+ * 3 = Edge triggered, edge on pin starts conversion after delay param
+ */
+static int mask;
+module_param(mask, int, 0);
+MODULE_PARM_DESC(mask, "Set adc mask function.");
+
+/*
+ * Coordinate Polling Enable.
+ *
+ * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together
+ * for every poll.
+ */
+static int coord;
+module_param(coord, int, 0);
+MODULE_PARM_DESC(coord, "Polling coordinate mode");
+
+/*
+ * ADC sample delay times in uS
+ */
+static const int delay_table[] = {
+ 21, /* 1 AC97 Link frames */
+ 42, /* 2 */
+ 84, /* 4 */
+ 167, /* 8 */
+ 333, /* 16 */
+ 667, /* 32 */
+ 1000, /* 48 */
+ 1333, /* 64 */
+ 2000, /* 96 */
+ 2667, /* 128 */
+ 3333, /* 160 */
+ 4000, /* 192 */
+ 4667, /* 224 */
+ 5333, /* 256 */
+ 6000, /* 288 */
+ 0 /* No delay, switch matrix always on */
+};
+
+/*
+ * Delay after issuing a POLL command.
+ *
+ * The delay is 3 AC97 link frames + the touchpanel settling delay
+ */
+static inline void poll_delay(int d)
+{
+ udelay(3 * AC97_LINK_FRAME + delay_table[d]);
+}
+
+/*
+ * set up the physical settings of the WM9712
+ */
+static void wm9712_phy_init(struct wm97xx *wm)
+{
+ u16 dig1 = 0;
+ u16 dig2 = WM97XX_RPR | WM9712_RPU(1);
+
+ /* WM9712 rpu */
+ if (rpu) {
+ dig2 &= 0xffc0;
+ dig2 |= WM9712_RPU(rpu);
+ dev_dbg(wm->dev, "setting pen detect pull-up to %d Ohms\n",
+ 64000 / rpu);
+ }
+
+ /* WM9712 five wire */
+ if (five_wire) {
+ dig2 |= WM9712_45W;
+ dev_dbg(wm->dev, "setting 5-wire touchscreen mode.\n");
+
+ if (pil) {
+ dev_warn(wm->dev, "pressure measurement is not "
+ "supported in 5-wire mode\n");
+ pil = 0;
+ }
+ }
+
+ /* touchpanel pressure current*/
+ if (pil == 2) {
+ dig2 |= WM9712_PIL;
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 400uA.\n");
+ } else if (pil)
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 200uA.\n");
+ if (!pil)
+ pressure = 0;
+
+ /* polling mode sample settling delay */
+ if (delay < 0 || delay > 15) {
+ dev_dbg(wm->dev, "supplied delay out of range.\n");
+ delay = 4;
+ }
+ dig1 &= 0xff0f;
+ dig1 |= WM97XX_DELAY(delay);
+ dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.\n",
+ delay_table[delay]);
+
+ /* mask */
+ dig2 |= ((mask & 0x3) << 6);
+ if (mask) {
+ u16 reg;
+ /* Set GPIO4 as Mask Pin*/
+ reg = wm97xx_reg_read(wm, AC97_MISC_AFE);
+ wm97xx_reg_write(wm, AC97_MISC_AFE, reg | WM97XX_GPIO_4);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_CFG);
+ wm97xx_reg_write(wm, AC97_GPIO_CFG, reg | WM97XX_GPIO_4);
+ }
+
+ /* wait - coord mode */
+ if (coord)
+ dig2 |= WM9712_WAIT;
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+}
+
+static void wm9712_dig_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig2 = wm->dig[2];
+
+ if (enable) {
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ dig2 | WM97XX_PRP_DET_DIG);
+ wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */
+ } else
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ dig2 & ~WM97XX_PRP_DET_DIG);
+}
+
+static void wm9712_aux_prepare(struct wm97xx *wm)
+{
+ memcpy(wm->dig_save, wm->dig, sizeof(wm->dig));
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG);
+}
+
+static void wm9712_dig_restore(struct wm97xx *wm)
+{
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]);
+}
+
+static inline int is_pden(struct wm97xx *wm)
+{
+ return wm->dig[2] & WM9712_PDEN;
+}
+
+/*
+ * Read a sample from the WM9712 adc in polling mode.
+ */
+static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample)
+{
+ int timeout = 5 * delay;
+ bool wants_pen = adcsel & WM97XX_PEN_DOWN;
+
+ if (wants_pen && !wm->pen_probably_down) {
+ u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(adcsel);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, (adcsel & WM97XX_ADCSEL_MASK)
+ | WM97XX_POLL | WM97XX_DELAY(delay));
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout\n");
+ return RC_PENUP;
+ }
+
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(adcsel);
+
+ /* check we have correct sample */
+ if ((*sample ^ adcsel) & WM97XX_ADCSEL_MASK) {
+ dev_dbg(wm->dev, "adc wrong sample, wanted %x got %x\n",
+ adcsel & WM97XX_ADCSEL_MASK,
+ *sample & WM97XX_ADCSEL_MASK);
+ return RC_AGAIN;
+ }
+
+ if (wants_pen && !(*sample & WM97XX_PEN_DOWN)) {
+ /* Sometimes it reads a wrong value the first time. */
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(*sample & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+ }
+
+ return RC_VALID;
+}
+
+/*
+ * Read a coord from the WM9712 adc in polling mode.
+ */
+static int wm9712_poll_coord(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 data_rd = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data_rd & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1,
+ WM97XX_COO | WM97XX_POLL | WM97XX_DELAY(delay));
+
+ /* wait 3 AC97 time slots + delay for conversion and read x */
+ poll_delay(delay);
+ data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout\n");
+ return RC_PENUP;
+ }
+
+ /* read back y data */
+ data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (pil)
+ data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ else
+ data->p = DEFAULT_PRESSURE;
+
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+
+ /* check we have correct sample */
+ if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y))
+ goto err;
+ if (pil && !(data->p & WM97XX_ADCSEL_PRES))
+ goto err;
+
+ if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+ return RC_VALID;
+err:
+ return 0;
+}
+
+/*
+ * Sample the WM9712 touchscreen in polling mode
+ */
+static int wm9712_poll_touch(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int rc;
+
+ if (coord) {
+ rc = wm9712_poll_coord(wm, data);
+ if (rc != RC_VALID)
+ return rc;
+ } else {
+ rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_X | WM97XX_PEN_DOWN,
+ &data->x);
+ if (rc != RC_VALID)
+ return rc;
+
+ rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_Y | WM97XX_PEN_DOWN,
+ &data->y);
+ if (rc != RC_VALID)
+ return rc;
+
+ if (pil && !five_wire) {
+ rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_PRES | WM97XX_PEN_DOWN,
+ &data->p);
+ if (rc != RC_VALID)
+ return rc;
+ } else
+ data->p = DEFAULT_PRESSURE;
+ }
+ return RC_VALID;
+}
+
+/*
+ * Enable WM9712 continuous mode, i.e. touch data is streamed across
+ * an AC97 slot
+ */
+static int wm9712_acc_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig1, dig2;
+ int ret = 0;
+
+ dig1 = wm->dig[1];
+ dig2 = wm->dig[2];
+
+ if (enable) {
+ /* continuous mode */
+ if (wm->mach_ops->acc_startup) {
+ ret = wm->mach_ops->acc_startup(wm);
+ if (ret < 0)
+ return ret;
+ }
+ dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK |
+ WM97XX_DELAY_MASK | WM97XX_SLT_MASK);
+ dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN |
+ WM97XX_DELAY(delay) |
+ WM97XX_SLT(wm->acc_slot) |
+ WM97XX_RATE(wm->acc_rate);
+ if (pil)
+ dig1 |= WM97XX_ADCSEL_PRES;
+ dig2 |= WM9712_PDEN;
+ } else {
+ dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN);
+ dig2 &= ~WM9712_PDEN;
+ if (wm->mach_ops->acc_shutdown)
+ wm->mach_ops->acc_shutdown(wm);
+ }
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+
+ return 0;
+}
+
+struct wm97xx_codec_drv wm9712_codec = {
+ .id = WM9712_ID2,
+ .name = "wm9712",
+ .poll_sample = wm9712_poll_sample,
+ .poll_touch = wm9712_poll_touch,
+ .acc_enable = wm9712_acc_enable,
+ .phy_init = wm9712_phy_init,
+ .dig_enable = wm9712_dig_enable,
+ .dig_restore = wm9712_dig_restore,
+ .aux_prepare = wm9712_aux_prepare,
+};
+EXPORT_SYMBOL_GPL(wm9712_codec);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>");
+MODULE_DESCRIPTION("WM9712 Touch Screen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm9713.c b/drivers/input/touchscreen/wm9713.c
new file mode 100644
index 000000000..a67fbe304
--- /dev/null
+++ b/drivers/input/touchscreen/wm9713.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * wm9713.c -- Codec touch driver for Wolfson WM9713 AC97 Codec.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/wm97xx.h>
+
+#define TS_NAME "wm97xx"
+#define WM9713_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Set internal pull up for pen detect.
+ *
+ * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive)
+ * i.e. pull up resistance = 64k Ohms / rpu.
+ *
+ * Adjust this value if you are having problems with pen detect not
+ * detecting any down event.
+ */
+static int rpu = 8;
+module_param(rpu, int, 0);
+MODULE_PARM_DESC(rpu, "Set internal pull up resistor for pen detect.");
+
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA
+ * pil = 1 to use 200uA and
+ * pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure.
+ */
+static int pil;
+module_param(pil, int, 0);
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+
+/*
+ * Set threshold for pressure measurement.
+ *
+ * Pen down pressure below threshold is ignored.
+ */
+static int pressure = DEFAULT_PRESSURE & 0xfff;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement.");
+
+/*
+ * Set adc sample delay.
+ *
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+static int delay = 4;
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+
+/*
+ * Set five_wire = 1 to use a 5 wire touchscreen.
+ *
+ * NOTE: Five wire mode does not allow for readback of pressure.
+ */
+static int five_wire;
+module_param(five_wire, int, 0);
+MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen.");
+
+/*
+ * Set adc mask function.
+ *
+ * Sources of glitch noise, such as signals driving an LCD display, may feed
+ * through to the touch screen plates and affect measurement accuracy. In
+ * order to minimise this, a signal may be applied to the MASK pin to delay or
+ * synchronise the sampling.
+ *
+ * 0 = No delay or sync
+ * 1 = High on pin stops conversions
+ * 2 = Edge triggered, edge on pin delays conversion by delay param (above)
+ * 3 = Edge triggered, edge on pin starts conversion after delay param
+ */
+static int mask;
+module_param(mask, int, 0);
+MODULE_PARM_DESC(mask, "Set adc mask function.");
+
+/*
+ * Coordinate Polling Enable.
+ *
+ * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together
+ * for every poll.
+ */
+static int coord;
+module_param(coord, int, 0);
+MODULE_PARM_DESC(coord, "Polling coordinate mode");
+
+/*
+ * ADC sample delay times in uS
+ */
+static const int delay_table[] = {
+ 21, /* 1 AC97 Link frames */
+ 42, /* 2 */
+ 84, /* 4 */
+ 167, /* 8 */
+ 333, /* 16 */
+ 667, /* 32 */
+ 1000, /* 48 */
+ 1333, /* 64 */
+ 2000, /* 96 */
+ 2667, /* 128 */
+ 3333, /* 160 */
+ 4000, /* 192 */
+ 4667, /* 224 */
+ 5333, /* 256 */
+ 6000, /* 288 */
+ 0 /* No delay, switch matrix always on */
+};
+
+/*
+ * Delay after issuing a POLL command.
+ *
+ * The delay is 3 AC97 link frames + the touchpanel settling delay
+ */
+static inline void poll_delay(int d)
+{
+ udelay(3 * AC97_LINK_FRAME + delay_table[d]);
+}
+
+/*
+ * set up the physical settings of the WM9713
+ */
+static void wm9713_phy_init(struct wm97xx *wm)
+{
+ u16 dig1 = 0, dig2, dig3;
+
+ /* default values */
+ dig2 = WM97XX_DELAY(4) | WM97XX_SLT(5);
+ dig3 = WM9712_RPU(1);
+
+ /* rpu */
+ if (rpu) {
+ dig3 &= 0xffc0;
+ dig3 |= WM9712_RPU(rpu);
+ dev_info(wm->dev, "setting pen detect pull-up to %d Ohms\n",
+ 64000 / rpu);
+ }
+
+ /* Five wire panel? */
+ if (five_wire) {
+ dig3 |= WM9713_45W;
+ dev_info(wm->dev, "setting 5-wire touchscreen mode.");
+
+ if (pil) {
+ dev_warn(wm->dev,
+ "Pressure measurement not supported in 5 "
+ "wire mode, disabling\n");
+ pil = 0;
+ }
+ }
+
+ /* touchpanel pressure */
+ if (pil == 2) {
+ dig3 |= WM9712_PIL;
+ dev_info(wm->dev,
+ "setting pressure measurement current to 400uA.");
+ } else if (pil)
+ dev_info(wm->dev,
+ "setting pressure measurement current to 200uA.");
+ if (!pil)
+ pressure = 0;
+
+ /* sample settling delay */
+ if (delay < 0 || delay > 15) {
+ dev_info(wm->dev, "supplied delay out of range.");
+ delay = 4;
+ dev_info(wm->dev, "setting adc sample delay to %d u Secs.",
+ delay_table[delay]);
+ }
+ dig2 &= 0xff0f;
+ dig2 |= WM97XX_DELAY(delay);
+
+ /* mask */
+ dig3 |= ((mask & 0x3) << 4);
+ if (coord)
+ dig3 |= WM9713_WAIT;
+
+ wm->misc = wm97xx_reg_read(wm, 0x5a);
+
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3);
+ wm97xx_reg_write(wm, AC97_GPIO_STICKY, 0x0);
+}
+
+static void wm9713_dig_enable(struct wm97xx *wm, int enable)
+{
+ u16 val;
+
+ if (enable) {
+ val = wm97xx_reg_read(wm, AC97_EXTENDED_MID);
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, val & 0x7fff);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] |
+ WM97XX_PRP_DET_DIG);
+ wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */
+ } else {
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] &
+ ~WM97XX_PRP_DET_DIG);
+ val = wm97xx_reg_read(wm, AC97_EXTENDED_MID);
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, val | 0x8000);
+ }
+}
+
+static void wm9713_dig_restore(struct wm97xx *wm)
+{
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig_save[0]);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig_save[1]);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig_save[2]);
+}
+
+static void wm9713_aux_prepare(struct wm97xx *wm)
+{
+ memcpy(wm->dig_save, wm->dig, sizeof(wm->dig));
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, 0);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, 0);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, WM97XX_PRP_DET_DIG);
+}
+
+static inline int is_pden(struct wm97xx *wm)
+{
+ return wm->dig[2] & WM9713_PDEN;
+}
+
+/*
+ * Read a sample from the WM9713 adc in polling mode.
+ */
+static int wm9713_poll_sample(struct wm97xx *wm, int adcsel, int *sample)
+{
+ u16 dig1;
+ int timeout = 5 * delay;
+ bool wants_pen = adcsel & WM97XX_PEN_DOWN;
+
+ if (wants_pen && !wm->pen_probably_down) {
+ u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1);
+ dig1 &= ~WM9713_ADCSEL_MASK;
+ /* WM97XX_ADCSEL_* channels need to be converted to WM9713 format */
+ dig1 |= 1 << ((adcsel & WM97XX_ADCSEL_MASK) >> 12);
+
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(adcsel);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1 | WM9713_POLL);
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) &&
+ timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(adcsel);
+
+ /* check we have correct sample */
+ if ((*sample ^ adcsel) & WM97XX_ADCSEL_MASK) {
+ dev_dbg(wm->dev, "adc wrong sample, wanted %x got %x",
+ adcsel & WM97XX_ADCSEL_MASK,
+ *sample & WM97XX_ADCSEL_MASK);
+ return RC_PENUP;
+ }
+
+ if (wants_pen && !(*sample & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+
+ return RC_VALID;
+}
+
+/*
+ * Read a coordinate from the WM9713 adc in polling mode.
+ */
+static int wm9713_poll_coord(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ u16 dig1;
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 val = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(val & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1);
+ dig1 &= ~WM9713_ADCSEL_MASK;
+ if (pil)
+ dig1 |= WM9713_ADCSEL_PRES;
+
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1,
+ dig1 | WM9713_POLL | WM9713_COO);
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+ data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ /* read back data */
+ data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (pil)
+ data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ else
+ data->p = DEFAULT_PRESSURE;
+
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+
+ /* check we have correct sample */
+ if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y))
+ goto err;
+ if (pil && !(data->p & WM97XX_ADCSEL_PRES))
+ goto err;
+
+ if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+ return RC_VALID;
+err:
+ return 0;
+}
+
+/*
+ * Sample the WM9713 touchscreen in polling mode
+ */
+static int wm9713_poll_touch(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int rc;
+
+ if (coord) {
+ rc = wm9713_poll_coord(wm, data);
+ if (rc != RC_VALID)
+ return rc;
+ } else {
+ rc = wm9713_poll_sample(wm, WM97XX_ADCSEL_X | WM97XX_PEN_DOWN, &data->x);
+ if (rc != RC_VALID)
+ return rc;
+ rc = wm9713_poll_sample(wm, WM97XX_ADCSEL_Y | WM97XX_PEN_DOWN, &data->y);
+ if (rc != RC_VALID)
+ return rc;
+ if (pil) {
+ rc = wm9713_poll_sample(wm, WM97XX_ADCSEL_PRES | WM97XX_PEN_DOWN,
+ &data->p);
+ if (rc != RC_VALID)
+ return rc;
+ } else
+ data->p = DEFAULT_PRESSURE;
+ }
+ return RC_VALID;
+}
+
+/*
+ * Enable WM9713 continuous mode, i.e. touch data is streamed across
+ * an AC97 slot
+ */
+static int wm9713_acc_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig1, dig2, dig3;
+ int ret = 0;
+
+ dig1 = wm->dig[0];
+ dig2 = wm->dig[1];
+ dig3 = wm->dig[2];
+
+ if (enable) {
+ /* continuous mode */
+ if (wm->mach_ops->acc_startup &&
+ (ret = wm->mach_ops->acc_startup(wm)) < 0)
+ return ret;
+
+ dig1 &= ~WM9713_ADCSEL_MASK;
+ dig1 |= WM9713_CTC | WM9713_COO | WM9713_ADCSEL_X |
+ WM9713_ADCSEL_Y;
+ if (pil)
+ dig1 |= WM9713_ADCSEL_PRES;
+ dig2 &= ~(WM97XX_DELAY_MASK | WM97XX_SLT_MASK |
+ WM97XX_CM_RATE_MASK);
+ dig2 |= WM97XX_SLEN | WM97XX_DELAY(delay) |
+ WM97XX_SLT(wm->acc_slot) | WM97XX_RATE(wm->acc_rate);
+ dig3 |= WM9713_PDEN;
+ } else {
+ dig1 &= ~(WM9713_CTC | WM9713_COO);
+ dig2 &= ~WM97XX_SLEN;
+ dig3 &= ~WM9713_PDEN;
+ if (wm->mach_ops->acc_shutdown)
+ wm->mach_ops->acc_shutdown(wm);
+ }
+
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3);
+
+ return ret;
+}
+
+struct wm97xx_codec_drv wm9713_codec = {
+ .id = WM9713_ID2,
+ .name = "wm9713",
+ .poll_sample = wm9713_poll_sample,
+ .poll_touch = wm9713_poll_touch,
+ .acc_enable = wm9713_acc_enable,
+ .phy_init = wm9713_phy_init,
+ .dig_enable = wm9713_dig_enable,
+ .dig_restore = wm9713_dig_restore,
+ .aux_prepare = wm9713_aux_prepare,
+};
+EXPORT_SYMBOL_GPL(wm9713_codec);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>");
+MODULE_DESCRIPTION("WM9713 Touch Screen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c
new file mode 100644
index 000000000..f51ab5614
--- /dev/null
+++ b/drivers/input/touchscreen/wm97xx-core.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * wm97xx-core.c -- Touch screen driver core for Wolfson WM9705, WM9712
+ * and WM9713 AC97 Codecs.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ *
+ * Notes:
+ *
+ * Features:
+ * - supports WM9705, WM9712, WM9713
+ * - polling mode
+ * - continuous mode (arch-dependent)
+ * - adjustable rpu/dpp settings
+ * - adjustable pressure current
+ * - adjustable sample settle delay
+ * - 4 and 5 wire touchscreens (5 wire is WM9712 only)
+ * - pen down detection
+ * - battery monitor
+ * - sample AUX adcs
+ * - power management
+ * - codec GPIO
+ * - codec event notification
+ * Todo
+ * - Support for async sampling control for noisy LCDs.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/proc_fs.h>
+#include <linux/pm.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/mfd/wm97xx.h>
+#include <linux/workqueue.h>
+#include <linux/wm97xx.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#define TS_NAME "wm97xx"
+#define WM_CORE_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+
+/*
+ * Touchscreen absolute values
+ *
+ * These parameters are used to help the input layer discard out of
+ * range readings and reduce jitter etc.
+ *
+ * o min, max:- indicate the min and max values your touch screen returns
+ * o fuzz:- use a higher number to reduce jitter
+ *
+ * The default values correspond to Mainstone II in QVGA mode
+ *
+ * Please read
+ * Documentation/input/input-programming.rst for more details.
+ */
+
+static int abs_x[3] = {150, 4000, 5};
+module_param_array(abs_x, int, NULL, 0);
+MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz");
+
+static int abs_y[3] = {200, 4000, 40};
+module_param_array(abs_y, int, NULL, 0);
+MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz");
+
+static int abs_p[3] = {0, 150, 4};
+module_param_array(abs_p, int, NULL, 0);
+MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max, fuzz");
+
+/*
+ * wm97xx IO access, all IO locking done by AC97 layer
+ */
+int wm97xx_reg_read(struct wm97xx *wm, u16 reg)
+{
+ if (wm->ac97)
+ return wm->ac97->bus->ops->read(wm->ac97, reg);
+ else
+ return -1;
+}
+EXPORT_SYMBOL_GPL(wm97xx_reg_read);
+
+void wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val)
+{
+ /* cache digitiser registers */
+ if (reg >= AC97_WM9713_DIG1 && reg <= AC97_WM9713_DIG3)
+ wm->dig[(reg - AC97_WM9713_DIG1) >> 1] = val;
+
+ /* cache gpio regs */
+ if (reg >= AC97_GPIO_CFG && reg <= AC97_MISC_AFE)
+ wm->gpio[(reg - AC97_GPIO_CFG) >> 1] = val;
+
+ /* wm9713 irq reg */
+ if (reg == 0x5a)
+ wm->misc = val;
+
+ if (wm->ac97)
+ wm->ac97->bus->ops->write(wm->ac97, reg, val);
+}
+EXPORT_SYMBOL_GPL(wm97xx_reg_write);
+
+/**
+ * wm97xx_read_aux_adc - Read the aux adc.
+ * @wm: wm97xx device.
+ * @adcsel: codec ADC to be read
+ *
+ * Reads the selected AUX ADC.
+ */
+
+int wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel)
+{
+ int power_adc = 0, auxval;
+ u16 power = 0;
+ int rc = 0;
+ int timeout = 0;
+
+ /* get codec */
+ mutex_lock(&wm->codec_mutex);
+
+ /* When the touchscreen is not in use, we may have to power up
+ * the AUX ADC before we can use sample the AUX inputs->
+ */
+ if (wm->id == WM9713_ID2 &&
+ (power = wm97xx_reg_read(wm, AC97_EXTENDED_MID)) & 0x8000) {
+ power_adc = 1;
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, power & 0x7fff);
+ }
+
+ /* Prepare the codec for AUX reading */
+ wm->codec->aux_prepare(wm);
+
+ /* Turn polling mode on to read AUX ADC */
+ wm->pen_probably_down = 1;
+
+ while (rc != RC_VALID && timeout++ < 5)
+ rc = wm->codec->poll_sample(wm, adcsel, &auxval);
+
+ if (power_adc)
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, power | 0x8000);
+
+ wm->codec->dig_restore(wm);
+
+ wm->pen_probably_down = 0;
+
+ if (timeout >= 5) {
+ dev_err(wm->dev,
+ "timeout reading auxadc %d, disabling digitiser\n",
+ adcsel);
+ wm->codec->dig_enable(wm, false);
+ }
+
+ mutex_unlock(&wm->codec_mutex);
+ return (rc == RC_VALID ? auxval & 0xfff : -EBUSY);
+}
+EXPORT_SYMBOL_GPL(wm97xx_read_aux_adc);
+
+/**
+ * wm97xx_get_gpio - Get the status of a codec GPIO.
+ * @wm: wm97xx device.
+ * @gpio: gpio
+ *
+ * Get the status of a codec GPIO pin
+ */
+
+enum wm97xx_gpio_status wm97xx_get_gpio(struct wm97xx *wm, u32 gpio)
+{
+ u16 status;
+ enum wm97xx_gpio_status ret;
+
+ mutex_lock(&wm->codec_mutex);
+ status = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+
+ if (status & gpio)
+ ret = WM97XX_GPIO_HIGH;
+ else
+ ret = WM97XX_GPIO_LOW;
+
+ mutex_unlock(&wm->codec_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm97xx_get_gpio);
+
+/**
+ * wm97xx_set_gpio - Set the status of a codec GPIO.
+ * @wm: wm97xx device.
+ * @gpio: gpio
+ * @status: status
+ *
+ * Set the status of a codec GPIO pin
+ */
+
+void wm97xx_set_gpio(struct wm97xx *wm, u32 gpio,
+ enum wm97xx_gpio_status status)
+{
+ u16 reg;
+
+ mutex_lock(&wm->codec_mutex);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+
+ if (status == WM97XX_GPIO_HIGH)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ if (wm->id == WM9712_ID2 && wm->variant != WM97xx_WM1613)
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg << 1);
+ else
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg);
+ mutex_unlock(&wm->codec_mutex);
+}
+EXPORT_SYMBOL_GPL(wm97xx_set_gpio);
+
+/*
+ * Codec GPIO pin configuration, this sets pin direction, polarity,
+ * stickyness and wake up.
+ */
+void wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, enum wm97xx_gpio_dir dir,
+ enum wm97xx_gpio_pol pol, enum wm97xx_gpio_sticky sticky,
+ enum wm97xx_gpio_wake wake)
+{
+ u16 reg;
+
+ mutex_lock(&wm->codec_mutex);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_POLARITY);
+
+ if (pol == WM97XX_GPIO_POL_HIGH)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, reg);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_STICKY);
+
+ if (sticky == WM97XX_GPIO_STICKY)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_STICKY, reg);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP);
+
+ if (wake == WM97XX_GPIO_WAKE)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, reg);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_CFG);
+
+ if (dir == WM97XX_GPIO_IN)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_CFG, reg);
+ mutex_unlock(&wm->codec_mutex);
+}
+EXPORT_SYMBOL_GPL(wm97xx_config_gpio);
+
+/*
+ * Configure the WM97XX_PRP value to use while system is suspended.
+ * If a value other than 0 is set then WM97xx pen detection will be
+ * left enabled in the configured mode while the system is in suspend,
+ * the device has users and suspend has not been disabled via the
+ * wakeup sysfs entries.
+ *
+ * @wm: WM97xx device to configure
+ * @mode: WM97XX_PRP value to configure while suspended
+ */
+void wm97xx_set_suspend_mode(struct wm97xx *wm, u16 mode)
+{
+ wm->suspend_mode = mode;
+ device_init_wakeup(&wm->input_dev->dev, mode != 0);
+}
+EXPORT_SYMBOL_GPL(wm97xx_set_suspend_mode);
+
+/*
+ * Codec PENDOWN irq handler
+ *
+ */
+static irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id)
+{
+ struct wm97xx *wm = dev_id;
+ int pen_was_down = wm->pen_is_down;
+
+ /* do we need to enable the touch panel reader */
+ if (wm->id == WM9705_ID2) {
+ if (wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD) &
+ WM97XX_PEN_DOWN)
+ wm->pen_is_down = 1;
+ else
+ wm->pen_is_down = 0;
+ } else {
+ u16 status, pol;
+ mutex_lock(&wm->codec_mutex);
+ status = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+ pol = wm97xx_reg_read(wm, AC97_GPIO_POLARITY);
+
+ if (WM97XX_GPIO_13 & pol & status) {
+ wm->pen_is_down = 1;
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol &
+ ~WM97XX_GPIO_13);
+ } else {
+ wm->pen_is_down = 0;
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol |
+ WM97XX_GPIO_13);
+ }
+
+ if (wm->id == WM9712_ID2 && wm->variant != WM97xx_WM1613)
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, (status &
+ ~WM97XX_GPIO_13) << 1);
+ else
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, status &
+ ~WM97XX_GPIO_13);
+ mutex_unlock(&wm->codec_mutex);
+ }
+
+ /* If the system is not using continuous mode or it provides a
+ * pen down operation then we need to schedule polls while the
+ * pen is down. Otherwise the machine driver is responsible
+ * for scheduling reads.
+ */
+ if (!wm->mach_ops->acc_enabled || wm->mach_ops->acc_pen_down) {
+ if (wm->pen_is_down && !pen_was_down) {
+ /* Data is not available immediately on pen down */
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader, 1);
+ }
+
+ /* Let ts_reader report the pen up for debounce. */
+ if (!wm->pen_is_down && pen_was_down)
+ wm->pen_is_down = 1;
+ }
+
+ if (!wm->pen_is_down && wm->mach_ops->acc_enabled)
+ wm->mach_ops->acc_pen_up(wm);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * initialise pen IRQ handler and workqueue
+ */
+static int wm97xx_init_pen_irq(struct wm97xx *wm)
+{
+ u16 reg;
+
+ if (request_threaded_irq(wm->pen_irq, NULL, wm97xx_pen_interrupt,
+ IRQF_SHARED | IRQF_ONESHOT,
+ "wm97xx-pen", wm)) {
+ dev_err(wm->dev,
+ "Failed to register pen down interrupt, polling");
+ wm->pen_irq = 0;
+ return -EINVAL;
+ }
+
+ /* Configure GPIO as interrupt source on WM971x */
+ if (wm->id != WM9705_ID2) {
+ BUG_ON(!wm->mach_ops->irq_gpio);
+ reg = wm97xx_reg_read(wm, AC97_MISC_AFE);
+ wm97xx_reg_write(wm, AC97_MISC_AFE,
+ reg & ~(wm->mach_ops->irq_gpio));
+ reg = wm97xx_reg_read(wm, 0x5a);
+ wm97xx_reg_write(wm, 0x5a, reg & ~0x0001);
+ }
+
+ return 0;
+}
+
+static int wm97xx_read_samples(struct wm97xx *wm)
+{
+ struct wm97xx_data data;
+ int rc;
+
+ mutex_lock(&wm->codec_mutex);
+
+ if (wm->mach_ops && wm->mach_ops->acc_enabled)
+ rc = wm->mach_ops->acc_pen_down(wm);
+ else
+ rc = wm->codec->poll_touch(wm, &data);
+
+ if (rc & RC_PENUP) {
+ if (wm->pen_is_down) {
+ wm->pen_is_down = 0;
+ dev_dbg(wm->dev, "pen up\n");
+ input_report_abs(wm->input_dev, ABS_PRESSURE, 0);
+ input_report_key(wm->input_dev, BTN_TOUCH, 0);
+ input_sync(wm->input_dev);
+ } else if (!(rc & RC_AGAIN)) {
+ /* We need high frequency updates only while
+ * pen is down, the user never will be able to
+ * touch screen faster than a few times per
+ * second... On the other hand, when the user
+ * is actively working with the touchscreen we
+ * don't want to lose the quick response. So we
+ * will slowly increase sleep time after the
+ * pen is up and quicky restore it to ~one task
+ * switch when pen is down again.
+ */
+ if (wm->ts_reader_interval < HZ / 10)
+ wm->ts_reader_interval++;
+ }
+
+ } else if (rc & RC_VALID) {
+ dev_dbg(wm->dev,
+ "pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n",
+ data.x >> 12, data.x & 0xfff, data.y >> 12,
+ data.y & 0xfff, data.p >> 12, data.p & 0xfff);
+
+ if (abs_x[0] > (data.x & 0xfff) ||
+ abs_x[1] < (data.x & 0xfff) ||
+ abs_y[0] > (data.y & 0xfff) ||
+ abs_y[1] < (data.y & 0xfff)) {
+ dev_dbg(wm->dev, "Measurement out of range, dropping it\n");
+ rc = RC_AGAIN;
+ goto out;
+ }
+
+ input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff);
+ input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff);
+ input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff);
+ input_report_key(wm->input_dev, BTN_TOUCH, 1);
+ input_sync(wm->input_dev);
+ wm->pen_is_down = 1;
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+ } else if (rc & RC_PENDOWN) {
+ dev_dbg(wm->dev, "pen down\n");
+ wm->pen_is_down = 1;
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+ }
+
+out:
+ mutex_unlock(&wm->codec_mutex);
+ return rc;
+}
+
+/*
+* The touchscreen sample reader.
+*/
+static void wm97xx_ts_reader(struct work_struct *work)
+{
+ int rc;
+ struct wm97xx *wm = container_of(work, struct wm97xx, ts_reader.work);
+
+ BUG_ON(!wm->codec);
+
+ do {
+ rc = wm97xx_read_samples(wm);
+ } while (rc & RC_AGAIN);
+
+ if (wm->pen_is_down || !wm->pen_irq)
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader,
+ wm->ts_reader_interval);
+}
+
+/**
+ * wm97xx_ts_input_open - Open the touch screen input device.
+ * @idev: Input device to be opened.
+ *
+ * Called by the input sub system to open a wm97xx touchscreen device.
+ * Starts the touchscreen thread and touch digitiser.
+ */
+static int wm97xx_ts_input_open(struct input_dev *idev)
+{
+ struct wm97xx *wm = input_get_drvdata(idev);
+
+ wm->ts_workq = alloc_ordered_workqueue("kwm97xx", 0);
+ if (wm->ts_workq == NULL) {
+ dev_err(wm->dev,
+ "Failed to create workqueue\n");
+ return -EINVAL;
+ }
+
+ /* start digitiser */
+ if (wm->mach_ops && wm->mach_ops->acc_enabled)
+ wm->codec->acc_enable(wm, 1);
+ wm->codec->dig_enable(wm, 1);
+
+ INIT_DELAYED_WORK(&wm->ts_reader, wm97xx_ts_reader);
+
+ wm->ts_reader_min_interval = HZ >= 100 ? HZ / 100 : 1;
+ if (wm->ts_reader_min_interval < 1)
+ wm->ts_reader_min_interval = 1;
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+
+ wm->pen_is_down = 0;
+ if (wm->pen_irq)
+ wm97xx_init_pen_irq(wm);
+ else
+ dev_err(wm->dev, "No IRQ specified\n");
+
+ /* If we either don't have an interrupt for pen down events or
+ * failed to acquire it then we need to poll.
+ */
+ if (wm->pen_irq == 0)
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader,
+ wm->ts_reader_interval);
+
+ return 0;
+}
+
+/**
+ * wm97xx_ts_input_close - Close the touch screen input device.
+ * @idev: Input device to be closed.
+ *
+ * Called by the input sub system to close a wm97xx touchscreen
+ * device. Kills the touchscreen thread and stops the touch
+ * digitiser.
+ */
+
+static void wm97xx_ts_input_close(struct input_dev *idev)
+{
+ struct wm97xx *wm = input_get_drvdata(idev);
+ u16 reg;
+
+ if (wm->pen_irq) {
+ /* Return the interrupt to GPIO usage (disabling it) */
+ if (wm->id != WM9705_ID2) {
+ BUG_ON(!wm->mach_ops->irq_gpio);
+ reg = wm97xx_reg_read(wm, AC97_MISC_AFE);
+ wm97xx_reg_write(wm, AC97_MISC_AFE,
+ reg | wm->mach_ops->irq_gpio);
+ }
+
+ free_irq(wm->pen_irq, wm);
+ }
+
+ wm->pen_is_down = 0;
+
+ /* ts_reader rearms itself so we need to explicitly stop it
+ * before we destroy the workqueue.
+ */
+ cancel_delayed_work_sync(&wm->ts_reader);
+
+ destroy_workqueue(wm->ts_workq);
+
+ /* stop digitiser */
+ wm->codec->dig_enable(wm, 0);
+ if (wm->mach_ops && wm->mach_ops->acc_enabled)
+ wm->codec->acc_enable(wm, 0);
+}
+
+static int wm97xx_register_touch(struct wm97xx *wm)
+{
+ struct wm97xx_pdata *pdata = dev_get_platdata(wm->dev);
+ int ret;
+
+ wm->input_dev = devm_input_allocate_device(wm->dev);
+ if (wm->input_dev == NULL)
+ return -ENOMEM;
+
+ /* set up touch configuration */
+ wm->input_dev->name = "wm97xx touchscreen";
+ wm->input_dev->phys = "wm97xx";
+ wm->input_dev->open = wm97xx_ts_input_open;
+ wm->input_dev->close = wm97xx_ts_input_close;
+
+ __set_bit(EV_ABS, wm->input_dev->evbit);
+ __set_bit(EV_KEY, wm->input_dev->evbit);
+ __set_bit(BTN_TOUCH, wm->input_dev->keybit);
+
+ input_set_abs_params(wm->input_dev, ABS_X, abs_x[0], abs_x[1],
+ abs_x[2], 0);
+ input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0], abs_y[1],
+ abs_y[2], 0);
+ input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0], abs_p[1],
+ abs_p[2], 0);
+
+ input_set_drvdata(wm->input_dev, wm);
+ wm->input_dev->dev.parent = wm->dev;
+
+ ret = input_register_device(wm->input_dev);
+ if (ret)
+ return ret;
+
+ /*
+ * register our extended touch device (for machine specific
+ * extensions)
+ */
+ wm->touch_dev = platform_device_alloc("wm97xx-touch", -1);
+ if (!wm->touch_dev)
+ return -ENOMEM;
+
+ platform_set_drvdata(wm->touch_dev, wm);
+ wm->touch_dev->dev.parent = wm->dev;
+ wm->touch_dev->dev.platform_data = pdata;
+ ret = platform_device_add(wm->touch_dev);
+ if (ret < 0)
+ goto touch_reg_err;
+
+ return 0;
+touch_reg_err:
+ platform_device_put(wm->touch_dev);
+
+ return ret;
+}
+
+static void wm97xx_unregister_touch(struct wm97xx *wm)
+{
+ platform_device_unregister(wm->touch_dev);
+}
+
+static int _wm97xx_probe(struct wm97xx *wm)
+{
+ int id = 0;
+
+ mutex_init(&wm->codec_mutex);
+ dev_set_drvdata(wm->dev, wm);
+
+ /* check that we have a supported codec */
+ id = wm97xx_reg_read(wm, AC97_VENDOR_ID1);
+ if (id != WM97XX_ID1) {
+ dev_err(wm->dev,
+ "Device with vendor %04x is not a wm97xx\n", id);
+ return -ENODEV;
+ }
+
+ wm->id = wm97xx_reg_read(wm, AC97_VENDOR_ID2);
+
+ wm->variant = WM97xx_GENERIC;
+
+ dev_info(wm->dev, "detected a wm97%02x codec\n", wm->id & 0xff);
+
+ switch (wm->id & 0xff) {
+#ifdef CONFIG_TOUCHSCREEN_WM9705
+ case 0x05:
+ wm->codec = &wm9705_codec;
+ break;
+#endif
+#ifdef CONFIG_TOUCHSCREEN_WM9712
+ case 0x12:
+ wm->codec = &wm9712_codec;
+ break;
+#endif
+#ifdef CONFIG_TOUCHSCREEN_WM9713
+ case 0x13:
+ wm->codec = &wm9713_codec;
+ break;
+#endif
+ default:
+ dev_err(wm->dev, "Support for wm97%02x not compiled in.\n",
+ wm->id & 0xff);
+ return -ENODEV;
+ }
+
+ /* set up physical characteristics */
+ wm->codec->phy_init(wm);
+
+ /* load gpio cache */
+ wm->gpio[0] = wm97xx_reg_read(wm, AC97_GPIO_CFG);
+ wm->gpio[1] = wm97xx_reg_read(wm, AC97_GPIO_POLARITY);
+ wm->gpio[2] = wm97xx_reg_read(wm, AC97_GPIO_STICKY);
+ wm->gpio[3] = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP);
+ wm->gpio[4] = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+ wm->gpio[5] = wm97xx_reg_read(wm, AC97_MISC_AFE);
+
+ return wm97xx_register_touch(wm);
+}
+
+static void wm97xx_remove_battery(struct wm97xx *wm)
+{
+ platform_device_unregister(wm->battery_dev);
+}
+
+static int wm97xx_add_battery(struct wm97xx *wm,
+ struct wm97xx_batt_pdata *pdata)
+{
+ int ret;
+
+ wm->battery_dev = platform_device_alloc("wm97xx-battery", -1);
+ if (!wm->battery_dev)
+ return -ENOMEM;
+
+ platform_set_drvdata(wm->battery_dev, wm);
+ wm->battery_dev->dev.parent = wm->dev;
+ wm->battery_dev->dev.platform_data = pdata;
+ ret = platform_device_add(wm->battery_dev);
+ if (ret)
+ platform_device_put(wm->battery_dev);
+
+ return ret;
+}
+
+static int wm97xx_probe(struct device *dev)
+{
+ struct wm97xx *wm;
+ int ret;
+ struct wm97xx_pdata *pdata = dev_get_platdata(dev);
+
+ wm = devm_kzalloc(dev, sizeof(struct wm97xx), GFP_KERNEL);
+ if (!wm)
+ return -ENOMEM;
+
+ wm->dev = dev;
+ wm->ac97 = to_ac97_t(dev);
+
+ ret = _wm97xx_probe(wm);
+ if (ret)
+ return ret;
+
+ ret = wm97xx_add_battery(wm, pdata ? pdata->batt_pdata : NULL);
+ if (ret < 0)
+ goto batt_err;
+
+ return ret;
+
+batt_err:
+ wm97xx_unregister_touch(wm);
+ return ret;
+}
+
+static int wm97xx_remove(struct device *dev)
+{
+ struct wm97xx *wm = dev_get_drvdata(dev);
+
+ wm97xx_remove_battery(wm);
+ wm97xx_unregister_touch(wm);
+
+ return 0;
+}
+
+static int wm97xx_mfd_probe(struct platform_device *pdev)
+{
+ struct wm97xx *wm;
+ struct wm97xx_platform_data *mfd_pdata = dev_get_platdata(&pdev->dev);
+ int ret;
+
+ wm = devm_kzalloc(&pdev->dev, sizeof(struct wm97xx), GFP_KERNEL);
+ if (!wm)
+ return -ENOMEM;
+
+ wm->dev = &pdev->dev;
+ wm->ac97 = mfd_pdata->ac97;
+
+ ret = _wm97xx_probe(wm);
+ if (ret)
+ return ret;
+
+ ret = wm97xx_add_battery(wm, mfd_pdata->batt_pdata);
+ if (ret < 0)
+ goto batt_err;
+
+ return ret;
+
+batt_err:
+ wm97xx_unregister_touch(wm);
+ return ret;
+}
+
+static int wm97xx_mfd_remove(struct platform_device *pdev)
+{
+ wm97xx_remove(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused wm97xx_suspend(struct device *dev)
+{
+ struct wm97xx *wm = dev_get_drvdata(dev);
+ u16 reg;
+ int suspend_mode;
+
+ if (device_may_wakeup(&wm->input_dev->dev))
+ suspend_mode = wm->suspend_mode;
+ else
+ suspend_mode = 0;
+
+ mutex_lock(&wm->input_dev->mutex);
+ if (input_device_enabled(wm->input_dev))
+ cancel_delayed_work_sync(&wm->ts_reader);
+
+ /* Power down the digitiser (bypassing the cache for resume) */
+ reg = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER2);
+ reg &= ~WM97XX_PRP_DET_DIG;
+ if (input_device_enabled(wm->input_dev))
+ reg |= suspend_mode;
+ wm->ac97->bus->ops->write(wm->ac97, AC97_WM97XX_DIGITISER2, reg);
+
+ /* WM9713 has an additional power bit - turn it off if there
+ * are no users or if suspend mode is zero. */
+ if (wm->id == WM9713_ID2 &&
+ (!input_device_enabled(wm->input_dev) || !suspend_mode)) {
+ reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) | 0x8000;
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg);
+ }
+ mutex_unlock(&wm->input_dev->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused wm97xx_resume(struct device *dev)
+{
+ struct wm97xx *wm = dev_get_drvdata(dev);
+
+ mutex_lock(&wm->input_dev->mutex);
+ /* restore digitiser and gpios */
+ if (wm->id == WM9713_ID2) {
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig[0]);
+ wm97xx_reg_write(wm, 0x5a, wm->misc);
+ if (input_device_enabled(wm->input_dev)) {
+ u16 reg;
+ reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) & 0x7fff;
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg);
+ }
+ }
+
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig[1]);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2]);
+
+ wm97xx_reg_write(wm, AC97_GPIO_CFG, wm->gpio[0]);
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, wm->gpio[1]);
+ wm97xx_reg_write(wm, AC97_GPIO_STICKY, wm->gpio[2]);
+ wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, wm->gpio[3]);
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, wm->gpio[4]);
+ wm97xx_reg_write(wm, AC97_MISC_AFE, wm->gpio[5]);
+
+ if (input_device_enabled(wm->input_dev) && !wm->pen_irq) {
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader,
+ wm->ts_reader_interval);
+ }
+ mutex_unlock(&wm->input_dev->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(wm97xx_pm_ops, wm97xx_suspend, wm97xx_resume);
+
+/*
+ * Machine specific operations
+ */
+int wm97xx_register_mach_ops(struct wm97xx *wm,
+ struct wm97xx_mach_ops *mach_ops)
+{
+ mutex_lock(&wm->codec_mutex);
+ if (wm->mach_ops) {
+ mutex_unlock(&wm->codec_mutex);
+ return -EINVAL;
+ }
+ wm->mach_ops = mach_ops;
+ mutex_unlock(&wm->codec_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm97xx_register_mach_ops);
+
+void wm97xx_unregister_mach_ops(struct wm97xx *wm)
+{
+ mutex_lock(&wm->codec_mutex);
+ wm->mach_ops = NULL;
+ mutex_unlock(&wm->codec_mutex);
+}
+EXPORT_SYMBOL_GPL(wm97xx_unregister_mach_ops);
+
+static struct device_driver wm97xx_driver = {
+ .name = "wm97xx-ts",
+#ifdef CONFIG_AC97_BUS
+ .bus = &ac97_bus_type,
+#endif
+ .owner = THIS_MODULE,
+ .probe = wm97xx_probe,
+ .remove = wm97xx_remove,
+ .pm = &wm97xx_pm_ops,
+};
+
+static struct platform_driver wm97xx_mfd_driver = {
+ .driver = {
+ .name = "wm97xx-ts",
+ .pm = &wm97xx_pm_ops,
+ },
+ .probe = wm97xx_mfd_probe,
+ .remove = wm97xx_mfd_remove,
+};
+
+static int __init wm97xx_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&wm97xx_mfd_driver);
+ if (ret)
+ return ret;
+
+ if (IS_BUILTIN(CONFIG_AC97_BUS))
+ ret = driver_register(&wm97xx_driver);
+ return ret;
+}
+
+static void __exit wm97xx_exit(void)
+{
+ if (IS_BUILTIN(CONFIG_AC97_BUS))
+ driver_unregister(&wm97xx_driver);
+ platform_driver_unregister(&wm97xx_mfd_driver);
+}
+
+module_init(wm97xx_init);
+module_exit(wm97xx_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>");
+MODULE_DESCRIPTION("WM97xx Core - Touch Screen / AUX ADC / GPIO Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/zet6223.c b/drivers/input/touchscreen/zet6223.c
new file mode 100644
index 000000000..3b6f7ee1e
--- /dev/null
+++ b/drivers/input/touchscreen/zet6223.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016, Jelle van der Waa <jelle@vdwaa.nl>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <asm/unaligned.h>
+
+#define ZET6223_MAX_FINGERS 16
+#define ZET6223_MAX_PKT_SIZE (3 + 4 * ZET6223_MAX_FINGERS)
+
+#define ZET6223_CMD_INFO 0xB2
+#define ZET6223_CMD_INFO_LENGTH 17
+#define ZET6223_VALID_PACKET 0x3c
+
+#define ZET6223_POWER_ON_DELAY_MSEC 30
+
+struct zet6223_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct regulator *vcc;
+ struct regulator *vio;
+ struct touchscreen_properties prop;
+ struct regulator_bulk_data supplies[2];
+ u16 max_x;
+ u16 max_y;
+ u8 fingernum;
+};
+
+static int zet6223_start(struct input_dev *dev)
+{
+ struct zet6223_ts *ts = input_get_drvdata(dev);
+
+ enable_irq(ts->client->irq);
+
+ return 0;
+}
+
+static void zet6223_stop(struct input_dev *dev)
+{
+ struct zet6223_ts *ts = input_get_drvdata(dev);
+
+ disable_irq(ts->client->irq);
+}
+
+static irqreturn_t zet6223_irq(int irq, void *dev_id)
+{
+ struct zet6223_ts *ts = dev_id;
+ u16 finger_bits;
+
+ /*
+ * First 3 bytes are an identifier, two bytes of finger data.
+ * X, Y data per finger is 4 bytes.
+ */
+ u8 bufsize = 3 + 4 * ts->fingernum;
+ u8 buf[ZET6223_MAX_PKT_SIZE];
+ int i;
+ int ret;
+ int error;
+
+ ret = i2c_master_recv(ts->client, buf, bufsize);
+ if (ret != bufsize) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err_ratelimited(&ts->client->dev,
+ "Error reading input data: %d\n", error);
+ return IRQ_HANDLED;
+ }
+
+ if (buf[0] != ZET6223_VALID_PACKET)
+ return IRQ_HANDLED;
+
+ finger_bits = get_unaligned_be16(buf + 1);
+ for (i = 0; i < ts->fingernum; i++) {
+ if (!(finger_bits & BIT(15 - i)))
+ continue;
+
+ input_mt_slot(ts->input, i);
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
+ input_event(ts->input, EV_ABS, ABS_MT_POSITION_X,
+ ((buf[i + 3] >> 4) << 8) + buf[i + 4]);
+ input_event(ts->input, EV_ABS, ABS_MT_POSITION_Y,
+ ((buf[i + 3] & 0xF) << 8) + buf[i + 5]);
+ }
+
+ input_mt_sync_frame(ts->input);
+ input_sync(ts->input);
+
+ return IRQ_HANDLED;
+}
+
+static void zet6223_power_off(void *_ts)
+{
+ struct zet6223_ts *ts = _ts;
+
+ regulator_bulk_disable(ARRAY_SIZE(ts->supplies), ts->supplies);
+}
+
+static int zet6223_power_on(struct zet6223_ts *ts)
+{
+ struct device *dev = &ts->client->dev;
+ int error;
+
+ ts->supplies[0].supply = "vio";
+ ts->supplies[1].supply = "vcc";
+
+ error = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->supplies),
+ ts->supplies);
+ if (error)
+ return error;
+
+ error = regulator_bulk_enable(ARRAY_SIZE(ts->supplies), ts->supplies);
+ if (error)
+ return error;
+
+ msleep(ZET6223_POWER_ON_DELAY_MSEC);
+
+ error = devm_add_action_or_reset(dev, zet6223_power_off, ts);
+ if (error) {
+ dev_err(dev, "failed to install poweroff action: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int zet6223_query_device(struct zet6223_ts *ts)
+{
+ u8 buf[ZET6223_CMD_INFO_LENGTH];
+ u8 cmd = ZET6223_CMD_INFO;
+ int ret;
+ int error;
+
+ ret = i2c_master_send(ts->client, &cmd, sizeof(cmd));
+ if (ret != sizeof(cmd)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "touchpanel info cmd failed: %d\n", error);
+ return error;
+ }
+
+ ret = i2c_master_recv(ts->client, buf, sizeof(buf));
+ if (ret != sizeof(buf)) {
+ error = ret < 0 ? ret : -EIO;
+ dev_err(&ts->client->dev,
+ "failed to retrieve touchpanel info: %d\n", error);
+ return error;
+ }
+
+ ts->fingernum = buf[15] & 0x7F;
+ if (ts->fingernum > ZET6223_MAX_FINGERS) {
+ dev_warn(&ts->client->dev,
+ "touchpanel reports %d fingers, limiting to %d\n",
+ ts->fingernum, ZET6223_MAX_FINGERS);
+ ts->fingernum = ZET6223_MAX_FINGERS;
+ }
+
+ ts->max_x = get_unaligned_le16(&buf[8]);
+ ts->max_y = get_unaligned_le16(&buf[10]);
+
+ return 0;
+}
+
+static int zet6223_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct zet6223_ts *ts;
+ struct input_dev *input;
+ int error;
+
+ if (!client->irq) {
+ dev_err(dev, "no irq specified\n");
+ return -EINVAL;
+ }
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->client = client;
+
+ error = zet6223_power_on(ts);
+ if (error)
+ return error;
+
+ error = zet6223_query_device(ts);
+ if (error)
+ return error;
+
+ ts->input = input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input_set_drvdata(input, ts);
+
+ input->name = client->name;
+ input->id.bustype = BUS_I2C;
+ input->open = zet6223_start;
+ input->close = zet6223_stop;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
+
+ touchscreen_parse_properties(input, true, &ts->prop);
+
+ error = input_mt_init_slots(input, ts->fingernum,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL, zet6223_irq,
+ IRQF_ONESHOT, client->name, ts);
+ if (error) {
+ dev_err(dev, "failed to request irq %d: %d\n",
+ client->irq, error);
+ return error;
+ }
+
+ zet6223_stop(input);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct of_device_id zet6223_of_match[] = {
+ { .compatible = "zeitec,zet6223" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, zet6223_of_match);
+
+static const struct i2c_device_id zet6223_id[] = {
+ { "zet6223", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, zet6223_id);
+
+static struct i2c_driver zet6223_driver = {
+ .driver = {
+ .name = "zet6223",
+ .of_match_table = zet6223_of_match,
+ },
+ .probe = zet6223_probe,
+ .id_table = zet6223_id
+};
+module_i2c_driver(zet6223_driver);
+
+MODULE_AUTHOR("Jelle van der Waa <jelle@vdwaa.nl>");
+MODULE_DESCRIPTION("ZEITEC zet622x I2C touchscreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c
new file mode 100644
index 000000000..495629628
--- /dev/null
+++ b/drivers/input/touchscreen/zforce_ts.c
@@ -0,0 +1,956 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2012-2013 MundoReader S.L.
+ * Author: Heiko Stuebner <heiko@sntech.de>
+ *
+ * based in parts on Nook zforce driver
+ *
+ * Copyright (C) 2010 Barnes & Noble, Inc.
+ * Author: Pieter Truter<ptruter@intrinsyc.com>
+ */
+
+#include <linux/module.h>
+#include <linux/hrtimer.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/input/mt.h>
+#include <linux/platform_data/zforce_ts.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of.h>
+
+#define WAIT_TIMEOUT msecs_to_jiffies(1000)
+
+#define FRAME_START 0xee
+#define FRAME_MAXSIZE 257
+
+/* Offsets of the different parts of the payload the controller sends */
+#define PAYLOAD_HEADER 0
+#define PAYLOAD_LENGTH 1
+#define PAYLOAD_BODY 2
+
+/* Response offsets */
+#define RESPONSE_ID 0
+#define RESPONSE_DATA 1
+
+/* Commands */
+#define COMMAND_DEACTIVATE 0x00
+#define COMMAND_INITIALIZE 0x01
+#define COMMAND_RESOLUTION 0x02
+#define COMMAND_SETCONFIG 0x03
+#define COMMAND_DATAREQUEST 0x04
+#define COMMAND_SCANFREQ 0x08
+#define COMMAND_STATUS 0X1e
+
+/*
+ * Responses the controller sends as a result of
+ * command requests
+ */
+#define RESPONSE_DEACTIVATE 0x00
+#define RESPONSE_INITIALIZE 0x01
+#define RESPONSE_RESOLUTION 0x02
+#define RESPONSE_SETCONFIG 0x03
+#define RESPONSE_SCANFREQ 0x08
+#define RESPONSE_STATUS 0X1e
+
+/*
+ * Notifications are sent by the touch controller without
+ * being requested by the driver and include for example
+ * touch indications
+ */
+#define NOTIFICATION_TOUCH 0x04
+#define NOTIFICATION_BOOTCOMPLETE 0x07
+#define NOTIFICATION_OVERRUN 0x25
+#define NOTIFICATION_PROXIMITY 0x26
+#define NOTIFICATION_INVALID_COMMAND 0xfe
+
+#define ZFORCE_REPORT_POINTS 2
+#define ZFORCE_MAX_AREA 0xff
+
+#define STATE_DOWN 0
+#define STATE_MOVE 1
+#define STATE_UP 2
+
+#define SETCONFIG_DUALTOUCH (1 << 0)
+
+struct zforce_point {
+ int coord_x;
+ int coord_y;
+ int state;
+ int id;
+ int area_major;
+ int area_minor;
+ int orientation;
+ int pressure;
+ int prblty;
+};
+
+/*
+ * @client the i2c_client
+ * @input the input device
+ * @suspending in the process of going to suspend (don't emit wakeup
+ * events for commands executed to suspend the device)
+ * @suspended device suspended
+ * @access_mutex serialize i2c-access, to keep multipart reads together
+ * @command_done completion to wait for the command result
+ * @command_mutex serialize commands sent to the ic
+ * @command_waiting the id of the command that is currently waiting
+ * for a result
+ * @command_result returned result of the command
+ */
+struct zforce_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ const struct zforce_ts_platdata *pdata;
+ char phys[32];
+
+ struct regulator *reg_vdd;
+
+ struct gpio_desc *gpio_int;
+ struct gpio_desc *gpio_rst;
+
+ bool suspending;
+ bool suspended;
+ bool boot_complete;
+
+ /* Firmware version information */
+ u16 version_major;
+ u16 version_minor;
+ u16 version_build;
+ u16 version_rev;
+
+ struct mutex access_mutex;
+
+ struct completion command_done;
+ struct mutex command_mutex;
+ int command_waiting;
+ int command_result;
+};
+
+static int zforce_command(struct zforce_ts *ts, u8 cmd)
+{
+ struct i2c_client *client = ts->client;
+ char buf[3];
+ int ret;
+
+ dev_dbg(&client->dev, "%s: 0x%x\n", __func__, cmd);
+
+ buf[0] = FRAME_START;
+ buf[1] = 1; /* data size, command only */
+ buf[2] = cmd;
+
+ mutex_lock(&ts->access_mutex);
+ ret = i2c_master_send(client, &buf[0], ARRAY_SIZE(buf));
+ mutex_unlock(&ts->access_mutex);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c send data request error: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void zforce_reset_assert(struct zforce_ts *ts)
+{
+ gpiod_set_value_cansleep(ts->gpio_rst, 1);
+}
+
+static void zforce_reset_deassert(struct zforce_ts *ts)
+{
+ gpiod_set_value_cansleep(ts->gpio_rst, 0);
+}
+
+static int zforce_send_wait(struct zforce_ts *ts, const char *buf, int len)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = mutex_trylock(&ts->command_mutex);
+ if (!ret) {
+ dev_err(&client->dev, "already waiting for a command\n");
+ return -EBUSY;
+ }
+
+ dev_dbg(&client->dev, "sending %d bytes for command 0x%x\n",
+ buf[1], buf[2]);
+
+ ts->command_waiting = buf[2];
+
+ mutex_lock(&ts->access_mutex);
+ ret = i2c_master_send(client, buf, len);
+ mutex_unlock(&ts->access_mutex);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c send data request error: %d\n", ret);
+ goto unlock;
+ }
+
+ dev_dbg(&client->dev, "waiting for result for command 0x%x\n", buf[2]);
+
+ if (wait_for_completion_timeout(&ts->command_done, WAIT_TIMEOUT) == 0) {
+ ret = -ETIME;
+ goto unlock;
+ }
+
+ ret = ts->command_result;
+
+unlock:
+ mutex_unlock(&ts->command_mutex);
+ return ret;
+}
+
+static int zforce_command_wait(struct zforce_ts *ts, u8 cmd)
+{
+ struct i2c_client *client = ts->client;
+ char buf[3];
+ int ret;
+
+ dev_dbg(&client->dev, "%s: 0x%x\n", __func__, cmd);
+
+ buf[0] = FRAME_START;
+ buf[1] = 1; /* data size, command only */
+ buf[2] = cmd;
+
+ ret = zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c send data request error: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int zforce_resolution(struct zforce_ts *ts, u16 x, u16 y)
+{
+ struct i2c_client *client = ts->client;
+ char buf[7] = { FRAME_START, 5, COMMAND_RESOLUTION,
+ (x & 0xff), ((x >> 8) & 0xff),
+ (y & 0xff), ((y >> 8) & 0xff) };
+
+ dev_dbg(&client->dev, "set resolution to (%d,%d)\n", x, y);
+
+ return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+}
+
+static int zforce_scan_frequency(struct zforce_ts *ts, u16 idle, u16 finger,
+ u16 stylus)
+{
+ struct i2c_client *client = ts->client;
+ char buf[9] = { FRAME_START, 7, COMMAND_SCANFREQ,
+ (idle & 0xff), ((idle >> 8) & 0xff),
+ (finger & 0xff), ((finger >> 8) & 0xff),
+ (stylus & 0xff), ((stylus >> 8) & 0xff) };
+
+ dev_dbg(&client->dev,
+ "set scan frequency to (idle: %d, finger: %d, stylus: %d)\n",
+ idle, finger, stylus);
+
+ return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+}
+
+static int zforce_setconfig(struct zforce_ts *ts, char b1)
+{
+ struct i2c_client *client = ts->client;
+ char buf[7] = { FRAME_START, 5, COMMAND_SETCONFIG,
+ b1, 0, 0, 0 };
+
+ dev_dbg(&client->dev, "set config to (%d)\n", b1);
+
+ return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+}
+
+static int zforce_start(struct zforce_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ const struct zforce_ts_platdata *pdata = ts->pdata;
+ int ret;
+
+ dev_dbg(&client->dev, "starting device\n");
+
+ ret = zforce_command_wait(ts, COMMAND_INITIALIZE);
+ if (ret) {
+ dev_err(&client->dev, "Unable to initialize, %d\n", ret);
+ return ret;
+ }
+
+ ret = zforce_resolution(ts, pdata->x_max, pdata->y_max);
+ if (ret) {
+ dev_err(&client->dev, "Unable to set resolution, %d\n", ret);
+ goto error;
+ }
+
+ ret = zforce_scan_frequency(ts, 10, 50, 50);
+ if (ret) {
+ dev_err(&client->dev, "Unable to set scan frequency, %d\n",
+ ret);
+ goto error;
+ }
+
+ ret = zforce_setconfig(ts, SETCONFIG_DUALTOUCH);
+ if (ret) {
+ dev_err(&client->dev, "Unable to set config\n");
+ goto error;
+ }
+
+ /* start sending touch events */
+ ret = zforce_command(ts, COMMAND_DATAREQUEST);
+ if (ret) {
+ dev_err(&client->dev, "Unable to request data\n");
+ goto error;
+ }
+
+ /*
+ * Per NN, initial cal. take max. of 200msec.
+ * Allow time to complete this calibration
+ */
+ msleep(200);
+
+ return 0;
+
+error:
+ zforce_command_wait(ts, COMMAND_DEACTIVATE);
+ return ret;
+}
+
+static int zforce_stop(struct zforce_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ dev_dbg(&client->dev, "stopping device\n");
+
+ /* Deactivates touch sensing and puts the device into sleep. */
+ ret = zforce_command_wait(ts, COMMAND_DEACTIVATE);
+ if (ret != 0) {
+ dev_err(&client->dev, "could not deactivate device, %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int zforce_touch_event(struct zforce_ts *ts, u8 *payload)
+{
+ struct i2c_client *client = ts->client;
+ const struct zforce_ts_platdata *pdata = ts->pdata;
+ struct zforce_point point;
+ int count, i, num = 0;
+
+ count = payload[0];
+ if (count > ZFORCE_REPORT_POINTS) {
+ dev_warn(&client->dev,
+ "too many coordinates %d, expected max %d\n",
+ count, ZFORCE_REPORT_POINTS);
+ count = ZFORCE_REPORT_POINTS;
+ }
+
+ for (i = 0; i < count; i++) {
+ point.coord_x =
+ payload[9 * i + 2] << 8 | payload[9 * i + 1];
+ point.coord_y =
+ payload[9 * i + 4] << 8 | payload[9 * i + 3];
+
+ if (point.coord_x > pdata->x_max ||
+ point.coord_y > pdata->y_max) {
+ dev_warn(&client->dev, "coordinates (%d,%d) invalid\n",
+ point.coord_x, point.coord_y);
+ point.coord_x = point.coord_y = 0;
+ }
+
+ point.state = payload[9 * i + 5] & 0x0f;
+ point.id = (payload[9 * i + 5] & 0xf0) >> 4;
+
+ /* determine touch major, minor and orientation */
+ point.area_major = max(payload[9 * i + 6],
+ payload[9 * i + 7]);
+ point.area_minor = min(payload[9 * i + 6],
+ payload[9 * i + 7]);
+ point.orientation = payload[9 * i + 6] > payload[9 * i + 7];
+
+ point.pressure = payload[9 * i + 8];
+ point.prblty = payload[9 * i + 9];
+
+ dev_dbg(&client->dev,
+ "point %d/%d: state %d, id %d, pressure %d, prblty %d, x %d, y %d, amajor %d, aminor %d, ori %d\n",
+ i, count, point.state, point.id,
+ point.pressure, point.prblty,
+ point.coord_x, point.coord_y,
+ point.area_major, point.area_minor,
+ point.orientation);
+
+ /* the zforce id starts with "1", so needs to be decreased */
+ input_mt_slot(ts->input, point.id - 1);
+
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER,
+ point.state != STATE_UP);
+
+ if (point.state != STATE_UP) {
+ input_report_abs(ts->input, ABS_MT_POSITION_X,
+ point.coord_x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y,
+ point.coord_y);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR,
+ point.area_major);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MINOR,
+ point.area_minor);
+ input_report_abs(ts->input, ABS_MT_ORIENTATION,
+ point.orientation);
+ num++;
+ }
+ }
+
+ input_mt_sync_frame(ts->input);
+
+ input_mt_report_finger_count(ts->input, num);
+
+ input_sync(ts->input);
+
+ return 0;
+}
+
+static int zforce_read_packet(struct zforce_ts *ts, u8 *buf)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ mutex_lock(&ts->access_mutex);
+
+ /* read 2 byte message header */
+ ret = i2c_master_recv(client, buf, 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "error reading header: %d\n", ret);
+ goto unlock;
+ }
+
+ if (buf[PAYLOAD_HEADER] != FRAME_START) {
+ dev_err(&client->dev, "invalid frame start: %d\n", buf[0]);
+ ret = -EIO;
+ goto unlock;
+ }
+
+ if (buf[PAYLOAD_LENGTH] == 0) {
+ dev_err(&client->dev, "invalid payload length: %d\n",
+ buf[PAYLOAD_LENGTH]);
+ ret = -EIO;
+ goto unlock;
+ }
+
+ /* read the message */
+ ret = i2c_master_recv(client, &buf[PAYLOAD_BODY], buf[PAYLOAD_LENGTH]);
+ if (ret < 0) {
+ dev_err(&client->dev, "error reading payload: %d\n", ret);
+ goto unlock;
+ }
+
+ dev_dbg(&client->dev, "read %d bytes for response command 0x%x\n",
+ buf[PAYLOAD_LENGTH], buf[PAYLOAD_BODY]);
+
+unlock:
+ mutex_unlock(&ts->access_mutex);
+ return ret;
+}
+
+static void zforce_complete(struct zforce_ts *ts, int cmd, int result)
+{
+ struct i2c_client *client = ts->client;
+
+ if (ts->command_waiting == cmd) {
+ dev_dbg(&client->dev, "completing command 0x%x\n", cmd);
+ ts->command_result = result;
+ complete(&ts->command_done);
+ } else {
+ dev_dbg(&client->dev, "command %d not for us\n", cmd);
+ }
+}
+
+static irqreturn_t zforce_irq(int irq, void *dev_id)
+{
+ struct zforce_ts *ts = dev_id;
+ struct i2c_client *client = ts->client;
+
+ if (ts->suspended && device_may_wakeup(&client->dev))
+ pm_wakeup_event(&client->dev, 500);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t zforce_irq_thread(int irq, void *dev_id)
+{
+ struct zforce_ts *ts = dev_id;
+ struct i2c_client *client = ts->client;
+ int ret;
+ u8 payload_buffer[FRAME_MAXSIZE];
+ u8 *payload;
+
+ /*
+ * When still suspended, return.
+ * Due to the level-interrupt we will get re-triggered later.
+ */
+ if (ts->suspended) {
+ msleep(20);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(&client->dev, "handling interrupt\n");
+
+ /* Don't emit wakeup events from commands run by zforce_suspend */
+ if (!ts->suspending && device_may_wakeup(&client->dev))
+ pm_stay_awake(&client->dev);
+
+ /*
+ * Run at least once and exit the loop if
+ * - the optional interrupt GPIO isn't specified
+ * (there is only one packet read per ISR invocation, then)
+ * or
+ * - the GPIO isn't active any more
+ * (packet read until the level GPIO indicates that there is
+ * no IRQ any more)
+ */
+ do {
+ ret = zforce_read_packet(ts, payload_buffer);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "could not read packet, ret: %d\n", ret);
+ break;
+ }
+
+ payload = &payload_buffer[PAYLOAD_BODY];
+
+ switch (payload[RESPONSE_ID]) {
+ case NOTIFICATION_TOUCH:
+ /*
+ * Always report touch-events received while
+ * suspending, when being a wakeup source
+ */
+ if (ts->suspending && device_may_wakeup(&client->dev))
+ pm_wakeup_event(&client->dev, 500);
+ zforce_touch_event(ts, &payload[RESPONSE_DATA]);
+ break;
+
+ case NOTIFICATION_BOOTCOMPLETE:
+ ts->boot_complete = payload[RESPONSE_DATA];
+ zforce_complete(ts, payload[RESPONSE_ID], 0);
+ break;
+
+ case RESPONSE_INITIALIZE:
+ case RESPONSE_DEACTIVATE:
+ case RESPONSE_SETCONFIG:
+ case RESPONSE_RESOLUTION:
+ case RESPONSE_SCANFREQ:
+ zforce_complete(ts, payload[RESPONSE_ID],
+ payload[RESPONSE_DATA]);
+ break;
+
+ case RESPONSE_STATUS:
+ /*
+ * Version Payload Results
+ * [2:major] [2:minor] [2:build] [2:rev]
+ */
+ ts->version_major = (payload[RESPONSE_DATA + 1] << 8) |
+ payload[RESPONSE_DATA];
+ ts->version_minor = (payload[RESPONSE_DATA + 3] << 8) |
+ payload[RESPONSE_DATA + 2];
+ ts->version_build = (payload[RESPONSE_DATA + 5] << 8) |
+ payload[RESPONSE_DATA + 4];
+ ts->version_rev = (payload[RESPONSE_DATA + 7] << 8) |
+ payload[RESPONSE_DATA + 6];
+ dev_dbg(&ts->client->dev,
+ "Firmware Version %04x:%04x %04x:%04x\n",
+ ts->version_major, ts->version_minor,
+ ts->version_build, ts->version_rev);
+
+ zforce_complete(ts, payload[RESPONSE_ID], 0);
+ break;
+
+ case NOTIFICATION_INVALID_COMMAND:
+ dev_err(&ts->client->dev, "invalid command: 0x%x\n",
+ payload[RESPONSE_DATA]);
+ break;
+
+ default:
+ dev_err(&ts->client->dev,
+ "unrecognized response id: 0x%x\n",
+ payload[RESPONSE_ID]);
+ break;
+ }
+ } while (gpiod_get_value_cansleep(ts->gpio_int));
+
+ if (!ts->suspending && device_may_wakeup(&client->dev))
+ pm_relax(&client->dev);
+
+ dev_dbg(&client->dev, "finished interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int zforce_input_open(struct input_dev *dev)
+{
+ struct zforce_ts *ts = input_get_drvdata(dev);
+
+ return zforce_start(ts);
+}
+
+static void zforce_input_close(struct input_dev *dev)
+{
+ struct zforce_ts *ts = input_get_drvdata(dev);
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = zforce_stop(ts);
+ if (ret)
+ dev_warn(&client->dev, "stopping zforce failed\n");
+
+ return;
+}
+
+static int __maybe_unused zforce_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct zforce_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+ ts->suspending = true;
+
+ /*
+ * When configured as a wakeup source device should always wake
+ * the system, therefore start device if necessary.
+ */
+ if (device_may_wakeup(&client->dev)) {
+ dev_dbg(&client->dev, "suspend while being a wakeup source\n");
+
+ /* Need to start device, if not open, to be a wakeup source. */
+ if (!input_device_enabled(input)) {
+ ret = zforce_start(ts);
+ if (ret)
+ goto unlock;
+ }
+
+ enable_irq_wake(client->irq);
+ } else if (input_device_enabled(input)) {
+ dev_dbg(&client->dev,
+ "suspend without being a wakeup source\n");
+
+ ret = zforce_stop(ts);
+ if (ret)
+ goto unlock;
+
+ disable_irq(client->irq);
+ }
+
+ ts->suspended = true;
+
+unlock:
+ ts->suspending = false;
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static int __maybe_unused zforce_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct zforce_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+
+ ts->suspended = false;
+
+ if (device_may_wakeup(&client->dev)) {
+ dev_dbg(&client->dev, "resume from being a wakeup source\n");
+
+ disable_irq_wake(client->irq);
+
+ /* need to stop device if it was not open on suspend */
+ if (!input_device_enabled(input)) {
+ ret = zforce_stop(ts);
+ if (ret)
+ goto unlock;
+ }
+ } else if (input_device_enabled(input)) {
+ dev_dbg(&client->dev, "resume without being a wakeup source\n");
+
+ enable_irq(client->irq);
+
+ ret = zforce_start(ts);
+ if (ret < 0)
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(zforce_pm_ops, zforce_suspend, zforce_resume);
+
+static void zforce_reset(void *data)
+{
+ struct zforce_ts *ts = data;
+
+ zforce_reset_assert(ts);
+
+ udelay(10);
+
+ if (!IS_ERR(ts->reg_vdd))
+ regulator_disable(ts->reg_vdd);
+}
+
+static struct zforce_ts_platdata *zforce_parse_dt(struct device *dev)
+{
+ struct zforce_ts_platdata *pdata;
+ struct device_node *np = dev->of_node;
+
+ if (!np)
+ return ERR_PTR(-ENOENT);
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "failed to allocate platform data\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ if (of_property_read_u32(np, "x-size", &pdata->x_max)) {
+ dev_err(dev, "failed to get x-size property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (of_property_read_u32(np, "y-size", &pdata->y_max)) {
+ dev_err(dev, "failed to get y-size property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ return pdata;
+}
+
+static int zforce_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev);
+ struct zforce_ts *ts;
+ struct input_dev *input_dev;
+ int ret;
+
+ if (!pdata) {
+ pdata = zforce_parse_dt(&client->dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ }
+
+ ts = devm_kzalloc(&client->dev, sizeof(struct zforce_ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->gpio_rst = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->gpio_rst)) {
+ ret = PTR_ERR(ts->gpio_rst);
+ dev_err(&client->dev,
+ "failed to request reset GPIO: %d\n", ret);
+ return ret;
+ }
+
+ if (ts->gpio_rst) {
+ ts->gpio_int = devm_gpiod_get_optional(&client->dev, "irq",
+ GPIOD_IN);
+ if (IS_ERR(ts->gpio_int)) {
+ ret = PTR_ERR(ts->gpio_int);
+ dev_err(&client->dev,
+ "failed to request interrupt GPIO: %d\n", ret);
+ return ret;
+ }
+ } else {
+ /*
+ * Deprecated GPIO handling for compatibility
+ * with legacy binding.
+ */
+
+ /* INT GPIO */
+ ts->gpio_int = devm_gpiod_get_index(&client->dev, NULL, 0,
+ GPIOD_IN);
+ if (IS_ERR(ts->gpio_int)) {
+ ret = PTR_ERR(ts->gpio_int);
+ dev_err(&client->dev,
+ "failed to request interrupt GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* RST GPIO */
+ ts->gpio_rst = devm_gpiod_get_index(&client->dev, NULL, 1,
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->gpio_rst)) {
+ ret = PTR_ERR(ts->gpio_rst);
+ dev_err(&client->dev,
+ "failed to request reset GPIO: %d\n", ret);
+ return ret;
+ }
+ }
+
+ ts->reg_vdd = devm_regulator_get_optional(&client->dev, "vdd");
+ if (IS_ERR(ts->reg_vdd)) {
+ ret = PTR_ERR(ts->reg_vdd);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ } else {
+ ret = regulator_enable(ts->reg_vdd);
+ if (ret)
+ return ret;
+
+ /*
+ * according to datasheet add 100us grace time after regular
+ * regulator enable delay.
+ */
+ udelay(100);
+ }
+
+ ret = devm_add_action(&client->dev, zforce_reset, ts);
+ if (ret) {
+ dev_err(&client->dev, "failed to register reset action, %d\n",
+ ret);
+
+ /* hereafter the regulator will be disabled by the action */
+ if (!IS_ERR(ts->reg_vdd))
+ regulator_disable(ts->reg_vdd);
+
+ return ret;
+ }
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input0", dev_name(&client->dev));
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev) {
+ dev_err(&client->dev, "could not allocate input device\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&ts->access_mutex);
+ mutex_init(&ts->command_mutex);
+
+ ts->pdata = pdata;
+ ts->client = client;
+ ts->input = input_dev;
+
+ input_dev->name = "Neonode zForce touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->id.bustype = BUS_I2C;
+
+ input_dev->open = zforce_input_open;
+ input_dev->close = zforce_input_close;
+
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(EV_SYN, input_dev->evbit);
+ __set_bit(EV_ABS, input_dev->evbit);
+
+ /* For multi touch */
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0,
+ pdata->x_max, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0,
+ pdata->y_max, 0, 0);
+
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0,
+ ZFORCE_MAX_AREA, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0,
+ ZFORCE_MAX_AREA, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+ input_mt_init_slots(input_dev, ZFORCE_REPORT_POINTS, INPUT_MT_DIRECT);
+
+ input_set_drvdata(ts->input, ts);
+
+ init_completion(&ts->command_done);
+
+ /*
+ * The zforce pulls the interrupt low when it has data ready.
+ * After it is triggered the isr thread runs until all the available
+ * packets have been read and the interrupt is high again.
+ * Therefore we can trigger the interrupt anytime it is low and do
+ * not need to limit it to the interrupt edge.
+ */
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ zforce_irq, zforce_irq_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ input_dev->name, ts);
+ if (ret) {
+ dev_err(&client->dev, "irq %d request failed\n", client->irq);
+ return ret;
+ }
+
+ i2c_set_clientdata(client, ts);
+
+ /* let the controller boot */
+ zforce_reset_deassert(ts);
+
+ ts->command_waiting = NOTIFICATION_BOOTCOMPLETE;
+ if (wait_for_completion_timeout(&ts->command_done, WAIT_TIMEOUT) == 0)
+ dev_warn(&client->dev, "bootcomplete timed out\n");
+
+ /* need to start device to get version information */
+ ret = zforce_command_wait(ts, COMMAND_INITIALIZE);
+ if (ret) {
+ dev_err(&client->dev, "unable to initialize, %d\n", ret);
+ return ret;
+ }
+
+ /* this gets the firmware version among other information */
+ ret = zforce_command_wait(ts, COMMAND_STATUS);
+ if (ret < 0) {
+ dev_err(&client->dev, "couldn't get status, %d\n", ret);
+ zforce_stop(ts);
+ return ret;
+ }
+
+ /* stop device and put it into sleep until it is opened */
+ ret = zforce_stop(ts);
+ if (ret < 0)
+ return ret;
+
+ device_set_wakeup_capable(&client->dev, true);
+
+ ret = input_register_device(input_dev);
+ if (ret) {
+ dev_err(&client->dev, "could not register input device, %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct i2c_device_id zforce_idtable[] = {
+ { "zforce-ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, zforce_idtable);
+
+#ifdef CONFIG_OF
+static const struct of_device_id zforce_dt_idtable[] = {
+ { .compatible = "neonode,zforce" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, zforce_dt_idtable);
+#endif
+
+static struct i2c_driver zforce_driver = {
+ .driver = {
+ .name = "zforce-ts",
+ .pm = &zforce_pm_ops,
+ .of_match_table = of_match_ptr(zforce_dt_idtable),
+ },
+ .probe = zforce_probe,
+ .id_table = zforce_idtable,
+};
+
+module_i2c_driver(zforce_driver);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("zForce TouchScreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/zinitix.c b/drivers/input/touchscreen/zinitix.c
new file mode 100644
index 000000000..52f9e9eaa
--- /dev/null
+++ b/drivers/input/touchscreen/zinitix.c
@@ -0,0 +1,631 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Register Map */
+
+#define ZINITIX_SWRESET_CMD 0x0000
+#define ZINITIX_WAKEUP_CMD 0x0001
+
+#define ZINITIX_IDLE_CMD 0x0004
+#define ZINITIX_SLEEP_CMD 0x0005
+
+#define ZINITIX_CLEAR_INT_STATUS_CMD 0x0003
+#define ZINITIX_CALIBRATE_CMD 0x0006
+#define ZINITIX_SAVE_STATUS_CMD 0x0007
+#define ZINITIX_SAVE_CALIBRATION_CMD 0x0008
+#define ZINITIX_RECALL_FACTORY_CMD 0x000f
+
+#define ZINITIX_THRESHOLD 0x0020
+
+#define ZINITIX_LARGE_PALM_REJECT_AREA_TH 0x003F
+
+#define ZINITIX_DEBUG_REG 0x0115 /* 0~7 */
+
+#define ZINITIX_TOUCH_MODE 0x0010
+#define ZINITIX_CHIP_REVISION 0x0011
+#define ZINITIX_FIRMWARE_VERSION 0x0012
+
+#define ZINITIX_USB_DETECT 0x116
+
+#define ZINITIX_MINOR_FW_VERSION 0x0121
+
+#define ZINITIX_VENDOR_ID 0x001C
+#define ZINITIX_HW_ID 0x0014
+
+#define ZINITIX_DATA_VERSION_REG 0x0013
+#define ZINITIX_SUPPORTED_FINGER_NUM 0x0015
+#define ZINITIX_EEPROM_INFO 0x0018
+#define ZINITIX_INITIAL_TOUCH_MODE 0x0019
+
+#define ZINITIX_TOTAL_NUMBER_OF_X 0x0060
+#define ZINITIX_TOTAL_NUMBER_OF_Y 0x0061
+
+#define ZINITIX_DELAY_RAW_FOR_HOST 0x007f
+
+#define ZINITIX_BUTTON_SUPPORTED_NUM 0x00B0
+#define ZINITIX_BUTTON_SENSITIVITY 0x00B2
+#define ZINITIX_DUMMY_BUTTON_SENSITIVITY 0X00C8
+
+#define ZINITIX_X_RESOLUTION 0x00C0
+#define ZINITIX_Y_RESOLUTION 0x00C1
+
+#define ZINITIX_POINT_STATUS_REG 0x0080
+#define ZINITIX_ICON_STATUS_REG 0x00AA
+
+#define ZINITIX_POINT_COORD_REG (ZINITIX_POINT_STATUS_REG + 2)
+
+#define ZINITIX_AFE_FREQUENCY 0x0100
+#define ZINITIX_DND_N_COUNT 0x0122
+#define ZINITIX_DND_U_COUNT 0x0135
+
+#define ZINITIX_RAWDATA_REG 0x0200
+
+#define ZINITIX_EEPROM_INFO_REG 0x0018
+
+#define ZINITIX_INT_ENABLE_FLAG 0x00f0
+#define ZINITIX_PERIODICAL_INTERRUPT_INTERVAL 0x00f1
+
+#define ZINITIX_BTN_WIDTH 0x016d
+
+#define ZINITIX_CHECKSUM_RESULT 0x012c
+
+#define ZINITIX_INIT_FLASH 0x01d0
+#define ZINITIX_WRITE_FLASH 0x01d1
+#define ZINITIX_READ_FLASH 0x01d2
+
+#define ZINITIX_INTERNAL_FLAG_02 0x011e
+#define ZINITIX_INTERNAL_FLAG_03 0x011f
+
+#define ZINITIX_I2C_CHECKSUM_WCNT 0x016a
+#define ZINITIX_I2C_CHECKSUM_RESULT 0x016c
+
+/* Interrupt & status register flags */
+
+#define BIT_PT_CNT_CHANGE BIT(0)
+#define BIT_DOWN BIT(1)
+#define BIT_MOVE BIT(2)
+#define BIT_UP BIT(3)
+#define BIT_PALM BIT(4)
+#define BIT_PALM_REJECT BIT(5)
+#define BIT_RESERVED_0 BIT(6)
+#define BIT_RESERVED_1 BIT(7)
+#define BIT_WEIGHT_CHANGE BIT(8)
+#define BIT_PT_NO_CHANGE BIT(9)
+#define BIT_REJECT BIT(10)
+#define BIT_PT_EXIST BIT(11)
+#define BIT_RESERVED_2 BIT(12)
+#define BIT_ERROR BIT(13)
+#define BIT_DEBUG BIT(14)
+#define BIT_ICON_EVENT BIT(15)
+
+#define SUB_BIT_EXIST BIT(0)
+#define SUB_BIT_DOWN BIT(1)
+#define SUB_BIT_MOVE BIT(2)
+#define SUB_BIT_UP BIT(3)
+#define SUB_BIT_UPDATE BIT(4)
+#define SUB_BIT_WAIT BIT(5)
+
+#define DEFAULT_TOUCH_POINT_MODE 2
+#define MAX_SUPPORTED_FINGER_NUM 5
+
+#define CHIP_ON_DELAY 15 // ms
+#define FIRMWARE_ON_DELAY 40 // ms
+
+struct point_coord {
+ __le16 x;
+ __le16 y;
+ u8 width;
+ u8 sub_status;
+ // currently unused, but needed as padding:
+ u8 minor_width;
+ u8 angle;
+};
+
+struct touch_event {
+ __le16 status;
+ u8 finger_mask;
+ u8 time_stamp;
+ struct point_coord point_coord[MAX_SUPPORTED_FINGER_NUM];
+};
+
+struct bt541_ts_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct touchscreen_properties prop;
+ struct regulator_bulk_data supplies[2];
+ u32 zinitix_mode;
+};
+
+static int zinitix_read_data(struct i2c_client *client,
+ u16 reg, void *values, size_t length)
+{
+ __le16 reg_le = cpu_to_le16(reg);
+ int ret;
+
+ /* A single i2c_transfer() transaction does not work here. */
+ ret = i2c_master_send(client, (u8 *)&reg_le, sizeof(reg_le));
+ if (ret != sizeof(reg_le))
+ return ret < 0 ? ret : -EIO;
+
+ ret = i2c_master_recv(client, (u8 *)values, length);
+ if (ret != length)
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int zinitix_write_u16(struct i2c_client *client, u16 reg, u16 value)
+{
+ __le16 packet[2] = {cpu_to_le16(reg), cpu_to_le16(value)};
+ int ret;
+
+ ret = i2c_master_send(client, (u8 *)packet, sizeof(packet));
+ if (ret != sizeof(packet))
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int zinitix_write_cmd(struct i2c_client *client, u16 reg)
+{
+ __le16 reg_le = cpu_to_le16(reg);
+ int ret;
+
+ ret = i2c_master_send(client, (u8 *)&reg_le, sizeof(reg_le));
+ if (ret != sizeof(reg_le))
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int zinitix_init_touch(struct bt541_ts_data *bt541)
+{
+ struct i2c_client *client = bt541->client;
+ int i;
+ int error;
+
+ error = zinitix_write_cmd(client, ZINITIX_SWRESET_CMD);
+ if (error) {
+ dev_err(&client->dev, "Failed to write reset command\n");
+ return error;
+ }
+
+ error = zinitix_write_u16(client, ZINITIX_INT_ENABLE_FLAG, 0x0);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to reset interrupt enable flag\n");
+ return error;
+ }
+
+ /* initialize */
+ error = zinitix_write_u16(client, ZINITIX_X_RESOLUTION,
+ bt541->prop.max_x);
+ if (error)
+ return error;
+
+ error = zinitix_write_u16(client, ZINITIX_Y_RESOLUTION,
+ bt541->prop.max_y);
+ if (error)
+ return error;
+
+ error = zinitix_write_u16(client, ZINITIX_SUPPORTED_FINGER_NUM,
+ MAX_SUPPORTED_FINGER_NUM);
+ if (error)
+ return error;
+
+ error = zinitix_write_u16(client, ZINITIX_INITIAL_TOUCH_MODE,
+ bt541->zinitix_mode);
+ if (error)
+ return error;
+
+ error = zinitix_write_u16(client, ZINITIX_TOUCH_MODE,
+ bt541->zinitix_mode);
+ if (error)
+ return error;
+
+ error = zinitix_write_u16(client, ZINITIX_INT_ENABLE_FLAG,
+ BIT_PT_CNT_CHANGE | BIT_DOWN | BIT_MOVE |
+ BIT_UP);
+ if (error)
+ return error;
+
+ /* clear queue */
+ for (i = 0; i < 10; i++) {
+ zinitix_write_cmd(client, ZINITIX_CLEAR_INT_STATUS_CMD);
+ udelay(10);
+ }
+
+ return 0;
+}
+
+static int zinitix_init_regulators(struct bt541_ts_data *bt541)
+{
+ struct device *dev = &bt541->client->dev;
+ int error;
+
+ /*
+ * Some older device trees have erroneous names for the regulators,
+ * so check if "vddo" is present and in that case use these names.
+ * Else use the proper supply names on the component.
+ */
+ if (of_find_property(dev->of_node, "vddo-supply", NULL)) {
+ bt541->supplies[0].supply = "vdd";
+ bt541->supplies[1].supply = "vddo";
+ } else {
+ /* Else use the proper supply names */
+ bt541->supplies[0].supply = "vcca";
+ bt541->supplies[1].supply = "vdd";
+ }
+ error = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(bt541->supplies),
+ bt541->supplies);
+ if (error < 0) {
+ dev_err(dev, "Failed to get regulators: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int zinitix_send_power_on_sequence(struct bt541_ts_data *bt541)
+{
+ int error;
+ struct i2c_client *client = bt541->client;
+
+ error = zinitix_write_u16(client, 0xc000, 0x0001);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to send power sequence(vendor cmd enable)\n");
+ return error;
+ }
+ udelay(10);
+
+ error = zinitix_write_cmd(client, 0xc004);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to send power sequence (intn clear)\n");
+ return error;
+ }
+ udelay(10);
+
+ error = zinitix_write_u16(client, 0xc002, 0x0001);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to send power sequence (nvm init)\n");
+ return error;
+ }
+ mdelay(2);
+
+ error = zinitix_write_u16(client, 0xc001, 0x0001);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to send power sequence (program start)\n");
+ return error;
+ }
+ msleep(FIRMWARE_ON_DELAY);
+
+ return 0;
+}
+
+static void zinitix_report_finger(struct bt541_ts_data *bt541, int slot,
+ const struct point_coord *p)
+{
+ u16 x, y;
+
+ if (unlikely(!(p->sub_status &
+ (SUB_BIT_UP | SUB_BIT_DOWN | SUB_BIT_MOVE)))) {
+ dev_dbg(&bt541->client->dev, "unknown finger event %#02x\n",
+ p->sub_status);
+ return;
+ }
+
+ x = le16_to_cpu(p->x);
+ y = le16_to_cpu(p->y);
+
+ input_mt_slot(bt541->input_dev, slot);
+ if (input_mt_report_slot_state(bt541->input_dev, MT_TOOL_FINGER,
+ !(p->sub_status & SUB_BIT_UP))) {
+ touchscreen_report_pos(bt541->input_dev,
+ &bt541->prop, x, y, true);
+ input_report_abs(bt541->input_dev,
+ ABS_MT_TOUCH_MAJOR, p->width);
+ dev_dbg(&bt541->client->dev, "finger %d %s (%u, %u)\n",
+ slot, p->sub_status & SUB_BIT_DOWN ? "down" : "move",
+ x, y);
+ } else {
+ dev_dbg(&bt541->client->dev, "finger %d up (%u, %u)\n",
+ slot, x, y);
+ }
+}
+
+static irqreturn_t zinitix_ts_irq_handler(int irq, void *bt541_handler)
+{
+ struct bt541_ts_data *bt541 = bt541_handler;
+ struct i2c_client *client = bt541->client;
+ struct touch_event touch_event;
+ unsigned long finger_mask;
+ int error;
+ int i;
+
+ memset(&touch_event, 0, sizeof(struct touch_event));
+
+ error = zinitix_read_data(bt541->client, ZINITIX_POINT_STATUS_REG,
+ &touch_event, sizeof(struct touch_event));
+ if (error) {
+ dev_err(&client->dev, "Failed to read in touchpoint struct\n");
+ goto out;
+ }
+
+ finger_mask = touch_event.finger_mask;
+ for_each_set_bit(i, &finger_mask, MAX_SUPPORTED_FINGER_NUM) {
+ const struct point_coord *p = &touch_event.point_coord[i];
+
+ /* Only process contacts that are actually reported */
+ if (p->sub_status & SUB_BIT_EXIST)
+ zinitix_report_finger(bt541, i, p);
+ }
+
+ input_mt_sync_frame(bt541->input_dev);
+ input_sync(bt541->input_dev);
+
+out:
+ zinitix_write_cmd(bt541->client, ZINITIX_CLEAR_INT_STATUS_CMD);
+ return IRQ_HANDLED;
+}
+
+static int zinitix_start(struct bt541_ts_data *bt541)
+{
+ int error;
+
+ error = regulator_bulk_enable(ARRAY_SIZE(bt541->supplies),
+ bt541->supplies);
+ if (error) {
+ dev_err(&bt541->client->dev,
+ "Failed to enable regulators: %d\n", error);
+ return error;
+ }
+
+ msleep(CHIP_ON_DELAY);
+
+ error = zinitix_send_power_on_sequence(bt541);
+ if (error) {
+ dev_err(&bt541->client->dev,
+ "Error while sending power-on sequence: %d\n", error);
+ return error;
+ }
+
+ error = zinitix_init_touch(bt541);
+ if (error) {
+ dev_err(&bt541->client->dev,
+ "Error while configuring touch IC\n");
+ return error;
+ }
+
+ enable_irq(bt541->client->irq);
+
+ return 0;
+}
+
+static int zinitix_stop(struct bt541_ts_data *bt541)
+{
+ int error;
+
+ disable_irq(bt541->client->irq);
+
+ error = regulator_bulk_disable(ARRAY_SIZE(bt541->supplies),
+ bt541->supplies);
+ if (error) {
+ dev_err(&bt541->client->dev,
+ "Failed to disable regulators: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int zinitix_input_open(struct input_dev *dev)
+{
+ struct bt541_ts_data *bt541 = input_get_drvdata(dev);
+
+ return zinitix_start(bt541);
+}
+
+static void zinitix_input_close(struct input_dev *dev)
+{
+ struct bt541_ts_data *bt541 = input_get_drvdata(dev);
+
+ zinitix_stop(bt541);
+}
+
+static int zinitix_init_input_dev(struct bt541_ts_data *bt541)
+{
+ struct input_dev *input_dev;
+ int error;
+
+ input_dev = devm_input_allocate_device(&bt541->client->dev);
+ if (!input_dev) {
+ dev_err(&bt541->client->dev,
+ "Failed to allocate input device.");
+ return -ENOMEM;
+ }
+
+ input_set_drvdata(input_dev, bt541);
+ bt541->input_dev = input_dev;
+
+ input_dev->name = "Zinitix Capacitive TouchScreen";
+ input_dev->phys = "input/ts";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->open = zinitix_input_open;
+ input_dev->close = zinitix_input_close;
+
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X);
+ input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y);
+ input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(input_dev, true, &bt541->prop);
+ if (!bt541->prop.max_x || !bt541->prop.max_y) {
+ dev_err(&bt541->client->dev,
+ "Touchscreen-size-x and/or touchscreen-size-y not set in dts\n");
+ return -EINVAL;
+ }
+
+ error = input_mt_init_slots(input_dev, MAX_SUPPORTED_FINGER_NUM,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error) {
+ dev_err(&bt541->client->dev,
+ "Failed to initialize MT slots: %d", error);
+ return error;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&bt541->client->dev,
+ "Failed to register input device: %d", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int zinitix_ts_probe(struct i2c_client *client)
+{
+ struct bt541_ts_data *bt541;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev,
+ "Failed to assert adapter's support for plain I2C.\n");
+ return -ENXIO;
+ }
+
+ bt541 = devm_kzalloc(&client->dev, sizeof(*bt541), GFP_KERNEL);
+ if (!bt541)
+ return -ENOMEM;
+
+ bt541->client = client;
+ i2c_set_clientdata(client, bt541);
+
+ error = zinitix_init_regulators(bt541);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to initialize regulators: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, zinitix_ts_irq_handler,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ client->name, bt541);
+ if (error) {
+ dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ error = zinitix_init_input_dev(bt541);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to initialize input device: %d\n", error);
+ return error;
+ }
+
+ error = device_property_read_u32(&client->dev, "zinitix,mode",
+ &bt541->zinitix_mode);
+ if (error < 0) {
+ /* fall back to mode 2 */
+ bt541->zinitix_mode = DEFAULT_TOUCH_POINT_MODE;
+ }
+
+ if (bt541->zinitix_mode != 2) {
+ /*
+ * If there are devices that don't support mode 2, support
+ * for other modes (0, 1) will be needed.
+ */
+ dev_err(&client->dev,
+ "Malformed zinitix,mode property, must be 2 (supplied: %d)\n",
+ bt541->zinitix_mode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused zinitix_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bt541_ts_data *bt541 = i2c_get_clientdata(client);
+
+ mutex_lock(&bt541->input_dev->mutex);
+
+ if (input_device_enabled(bt541->input_dev))
+ zinitix_stop(bt541);
+
+ mutex_unlock(&bt541->input_dev->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused zinitix_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bt541_ts_data *bt541 = i2c_get_clientdata(client);
+ int ret = 0;
+
+ mutex_lock(&bt541->input_dev->mutex);
+
+ if (input_device_enabled(bt541->input_dev))
+ ret = zinitix_start(bt541);
+
+ mutex_unlock(&bt541->input_dev->mutex);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(zinitix_pm_ops, zinitix_suspend, zinitix_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id zinitix_of_match[] = {
+ { .compatible = "zinitix,bt402" },
+ { .compatible = "zinitix,bt403" },
+ { .compatible = "zinitix,bt404" },
+ { .compatible = "zinitix,bt412" },
+ { .compatible = "zinitix,bt413" },
+ { .compatible = "zinitix,bt431" },
+ { .compatible = "zinitix,bt432" },
+ { .compatible = "zinitix,bt531" },
+ { .compatible = "zinitix,bt532" },
+ { .compatible = "zinitix,bt538" },
+ { .compatible = "zinitix,bt541" },
+ { .compatible = "zinitix,bt548" },
+ { .compatible = "zinitix,bt554" },
+ { .compatible = "zinitix,at100" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, zinitix_of_match);
+#endif
+
+static struct i2c_driver zinitix_ts_driver = {
+ .probe_new = zinitix_ts_probe,
+ .driver = {
+ .name = "Zinitix-TS",
+ .pm = &zinitix_pm_ops,
+ .of_match_table = of_match_ptr(zinitix_of_match),
+ },
+};
+module_i2c_driver(zinitix_ts_driver);
+
+MODULE_AUTHOR("Michael Srba <Michael.Srba@seznam.cz>");
+MODULE_DESCRIPTION("Zinitix touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/touchscreen/zylonite-wm97xx.c b/drivers/input/touchscreen/zylonite-wm97xx.c
new file mode 100644
index 000000000..a70fe4abe
--- /dev/null
+++ b/drivers/input/touchscreen/zylonite-wm97xx.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * zylonite-wm97xx.c -- Zylonite Continuous Touch screen driver
+ *
+ * Copyright 2004, 2007, 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ *
+ * Notes:
+ * This is a wm97xx extended touch driver supporting interrupt driven
+ * and continuous operation on Marvell Zylonite development systems
+ * (which have a WM9713 on board).
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/soc/pxa/cpu.h>
+#include <linux/wm97xx.h>
+
+#include <sound/pxa2xx-lib.h>
+
+struct continuous {
+ u16 id; /* codec id */
+ u8 code; /* continuous code */
+ u8 reads; /* number of coord reads per read cycle */
+ u32 speed; /* number of coords per second */
+};
+
+#define WM_READS(sp) ((sp / HZ) + 1)
+
+static const struct continuous cinfo[] = {
+ { WM9713_ID2, 0, WM_READS(94), 94 },
+ { WM9713_ID2, 1, WM_READS(120), 120 },
+ { WM9713_ID2, 2, WM_READS(154), 154 },
+ { WM9713_ID2, 3, WM_READS(188), 188 },
+};
+
+/* continuous speed index */
+static int sp_idx;
+
+/*
+ * Pen sampling frequency (Hz) in continuous mode.
+ */
+static int cont_rate = 200;
+module_param(cont_rate, int, 0);
+MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)");
+
+/*
+ * Pressure readback.
+ *
+ * Set to 1 to read back pen down pressure
+ */
+static int pressure;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)");
+
+/*
+ * AC97 touch data slot.
+ *
+ * Touch screen readback data ac97 slot
+ */
+static int ac97_touch_slot = 5;
+module_param(ac97_touch_slot, int, 0);
+MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number");
+
+
+/* flush AC97 slot 5 FIFO machines */
+static void wm97xx_acc_pen_up(struct wm97xx *wm)
+{
+ int i;
+
+ msleep(1);
+
+ for (i = 0; i < 16; i++)
+ pxa2xx_ac97_read_modr();
+}
+
+static int wm97xx_acc_pen_down(struct wm97xx *wm)
+{
+ u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES;
+ int reads = 0;
+ static u16 last, tries;
+
+ /* When the AC97 queue has been drained we need to allow time
+ * to buffer up samples otherwise we end up spinning polling
+ * for samples. The controller can't have a suitably low
+ * threshold set to use the notifications it gives.
+ */
+ msleep(1);
+
+ if (tries > 5) {
+ tries = 0;
+ return RC_PENUP;
+ }
+
+ x = pxa2xx_ac97_read_modr();
+ if (x == last) {
+ tries++;
+ return RC_AGAIN;
+ }
+ last = x;
+ do {
+ if (reads)
+ x = pxa2xx_ac97_read_modr();
+ y = pxa2xx_ac97_read_modr();
+ if (pressure)
+ p = pxa2xx_ac97_read_modr();
+
+ dev_dbg(wm->dev, "Raw coordinates: x=%x, y=%x, p=%x\n",
+ x, y, p);
+
+ /* are samples valid */
+ if ((x & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_X ||
+ (y & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_Y ||
+ (p & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_PRES)
+ goto up;
+
+ /* coordinate is good */
+ tries = 0;
+ input_report_abs(wm->input_dev, ABS_X, x & 0xfff);
+ input_report_abs(wm->input_dev, ABS_Y, y & 0xfff);
+ input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff);
+ input_report_key(wm->input_dev, BTN_TOUCH, (p != 0));
+ input_sync(wm->input_dev);
+ reads++;
+ } while (reads < cinfo[sp_idx].reads);
+up:
+ return RC_PENDOWN | RC_AGAIN;
+}
+
+static int wm97xx_acc_startup(struct wm97xx *wm)
+{
+ int idx;
+
+ /* check we have a codec */
+ if (wm->ac97 == NULL)
+ return -ENODEV;
+
+ /* Go you big red fire engine */
+ for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) {
+ if (wm->id != cinfo[idx].id)
+ continue;
+ sp_idx = idx;
+ if (cont_rate <= cinfo[idx].speed)
+ break;
+ }
+ wm->acc_rate = cinfo[sp_idx].code;
+ wm->acc_slot = ac97_touch_slot;
+ dev_info(wm->dev,
+ "zylonite accelerated touchscreen driver, %d samples/sec\n",
+ cinfo[sp_idx].speed);
+
+ return 0;
+}
+
+static struct wm97xx_mach_ops zylonite_mach_ops = {
+ .acc_enabled = 1,
+ .acc_pen_up = wm97xx_acc_pen_up,
+ .acc_pen_down = wm97xx_acc_pen_down,
+ .acc_startup = wm97xx_acc_startup,
+ .irq_gpio = WM97XX_GPIO_2,
+};
+
+static int zylonite_wm97xx_probe(struct platform_device *pdev)
+{
+ struct wm97xx *wm = platform_get_drvdata(pdev);
+ struct gpio_desc *gpio_touch_irq;
+ int err;
+
+ gpio_touch_irq = devm_gpiod_get(&pdev->dev, "touch", GPIOD_IN);
+ err = PTR_ERR_OR_ZERO(gpio_touch_irq);
+ if (err) {
+ dev_err(&pdev->dev, "Cannot get irq gpio: %d\n", err);
+ return err;
+ }
+
+ wm->pen_irq = gpiod_to_irq(gpio_touch_irq);
+ irq_set_irq_type(wm->pen_irq, IRQ_TYPE_EDGE_BOTH);
+
+ wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN,
+ WM97XX_GPIO_POL_HIGH,
+ WM97XX_GPIO_STICKY,
+ WM97XX_GPIO_WAKE);
+ wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT,
+ WM97XX_GPIO_POL_HIGH,
+ WM97XX_GPIO_NOTSTICKY,
+ WM97XX_GPIO_NOWAKE);
+
+ return wm97xx_register_mach_ops(wm, &zylonite_mach_ops);
+}
+
+static int zylonite_wm97xx_remove(struct platform_device *pdev)
+{
+ struct wm97xx *wm = platform_get_drvdata(pdev);
+
+ wm97xx_unregister_mach_ops(wm);
+
+ return 0;
+}
+
+static struct platform_driver zylonite_wm97xx_driver = {
+ .probe = zylonite_wm97xx_probe,
+ .remove = zylonite_wm97xx_remove,
+ .driver = {
+ .name = "wm97xx-touch",
+ },
+};
+module_platform_driver(zylonite_wm97xx_driver);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("wm97xx continuous touch driver for Zylonite");
+MODULE_LICENSE("GPL");