diff options
Diffstat (limited to 'drivers/input/touchscreen')
84 files changed, 38916 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..251ff2aa0 --- /dev/null +++ b/drivers/input/touchscreen/88pm860x-ts.c @@ -0,0 +1,304 @@ +/* + * Touchscreen driver for Marvell 88PM860x + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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/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_find_node_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) + return -EINVAL; + } + /* 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) + return -EINVAL; + } + /* 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) + return -EINVAL; + } + of_property_read_u32(np, "marvell,88pm860x-resistor-X", res_x); + return 0; +} +#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) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + 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; + + platform_set_drvdata(pdev, touch); + + 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; + } + + platform_set_drvdata(pdev, touch); + 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..80f638670 --- /dev/null +++ b/drivers/input/touchscreen/Kconfig @@ -0,0 +1,1030 @@ +# +# 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 OF_TOUCHSCREEN + def_tristate INPUT + depends on INPUT && OF + +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 + 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 + 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_AR1021_I2C + tristate "Microchip AR1021 i2c touchscreen" + depends on I2C && OF + help + Say Y here if you have the Microchip 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_AUO_PIXCIR + tristate "AUO in-cell touchscreen using Pixcir ICs" + depends on I2C + depends on GPIOLIB + 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_CHIPONE_ICN8318 + tristate "chipone icn8318 touchscreen controller" + depends on GPIOLIB + 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_CY8CTMG110 + tristate "cy8ctmg110 touchscreen" + depends on I2C + depends on GPIOLIB + 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_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 + 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_ILI210X + tristate "Ilitek ILI210X based touchscreen" + depends on I2C + 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_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 + select 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_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_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_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_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_INTEL_MID + tristate "Intel MID platform resistive touchscreen" + depends on INTEL_SCU_IPC + help + Say Y here if you have a Intel MID based touchscreen in + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called intel_mid_touch. + +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_MIGOR + tristate "Renesas MIGO-R touchscreen" + depends on SH_MIGOR && 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_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 + 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_ATMEL + tristate "WM97xx Atmel accelerated touch" + depends on TOUCHSCREEN_WM97XX && AVR32 + help + Say Y here for support for streaming mode with WM97xx touchscreens + on Atmel AT91 or AVR32 systems with an AC97C module. + + Be aware that this will use channel B in the controller for + streaming data, this must not conflict with other AC97C drivers. + + If unsure, say N. + + To compile this driver as a module, choose M here: the module will + be called atmel-wm97xx. + +config TOUCHSCREEN_WM97XX_MAINSTONE + tristate "WM97xx Mainstone/Palm accelerated touch" + depends on TOUCHSCREEN_WM97XX && ARCH_PXA + 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 + 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 Modul + - 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_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 EMBEDDED + 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_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_TSC2005 + tristate "TSC2005 based touchscreens" + depends on SPI_MASTER + 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_W90X900 + tristate "W90P910 touchscreen driver" + depends on ARCH_W90X900 + help + Say Y here if you have a W90P910 based touchscreen. + + To compile this driver as a module, choose M here: the + module will be called w90p910_ts. + +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_ST1232 + tristate "Sitronix ST1232 touchscreen controllers" + depends on I2C + help + Say Y here if you want to support Sitronix ST1232 + 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_STMPE + tristate "STMicroelectronics STMPE touchscreens" + depends on MFD_STMPE + 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 + depends on MEDIA_USB_SUPPORT + select INPUT_POLLDEV + 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_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 + select INPUT_POLLDEV + 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_ZFORCE + tristate "Neonode zForce infrared touchscreens" + depends on I2C + depends on GPIOLIB + 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. + +endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile new file mode 100644 index 000000000..44deea743 --- /dev/null +++ b/drivers/input/touchscreen/Makefile @@ -0,0 +1,86 @@ +# +# Makefile for the touchscreen drivers. +# + +# Each configuration option enables a list of files. + +wm97xx-ts-y := wm97xx-core.o + +obj-$(CONFIG_OF_TOUCHSCREEN) += of_touchscreen.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_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_CHIPONE_ICN8318) += chipone_icn8318.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_GUNZE) += gunze.o +obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o +obj-$(CONFIG_TOUCHSCREEN_ELAN) += elants_i2c.o +obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o +obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o +obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o +obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o +obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o +obj-$(CONFIG_TOUCHSCREEN_INTEL_MID) += intel-mid-touch.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_MC13783) += mc13783_ts.o +obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o +obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o +obj-$(CONFIG_TOUCHSCREEN_MMS114) += mms114.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_S3C2410) += s3c2410_ts.o +obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o +obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o +obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o +obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.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_TSC_SERIO) += tsc40.o +obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.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_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_ATMEL) += atmel-wm97xx.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o +obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o +obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o +obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o +obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o diff --git a/drivers/input/touchscreen/ad7877.c b/drivers/input/touchscreen/ad7877.c new file mode 100644 index 000000000..da4e5bb5e --- /dev/null +++ b/drivers/input/touchscreen/ad7877.c @@ -0,0 +1,858 @@ +/* + * 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/ + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * 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_usecs = ts->vref_delay_usecs; + req->xfer[1].cs_change = 1; + + req->xfer[2].tx_buf = &req->command; + req->xfer[2].len = 2; + req->xfer[2].delay_usecs = ts->vref_delay_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(unsigned long handle) +{ + struct ad7877 *ts = (void *)handle; + 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(struct ad7877 *ts) +{ + 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 = kzalloc(sizeof(struct ad7877), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + spi_set_drvdata(spi, ts); + ts->spi = spi; + ts->input = input_dev; + + setup_timer(&ts->timer, ad7877_timer, (unsigned long) ts); + 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); + err = -ENODEV; + goto err_free_mem; + } + + 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 = request_threaded_irq(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); + goto err_free_mem; + } + + err = sysfs_create_group(&spi->dev.kobj, &ad7877_attr_group); + if (err) + goto err_free_irq; + + err = input_register_device(input_dev); + if (err) + goto err_remove_attr_group; + + return 0; + +err_remove_attr_group: + sysfs_remove_group(&spi->dev.kobj, &ad7877_attr_group); +err_free_irq: + free_irq(spi->irq, ts); +err_free_mem: + input_free_device(input_dev); + kfree(ts); + return err; +} + +static int ad7877_remove(struct spi_device *spi) +{ + struct ad7877 *ts = spi_get_drvdata(spi); + + sysfs_remove_group(&spi->dev.kobj, &ad7877_attr_group); + + ad7877_disable(ts); + free_irq(ts->spi->irq, ts); + + input_unregister_device(ts->input); + kfree(ts); + + dev_dbg(&spi->dev, "unregistered touchscreen\n"); + + 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", + .owner = THIS_MODULE, + .pm = &ad7877_pm, + }, + .probe = ad7877_probe, + .remove = ad7877_remove, +}; + +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..dcf390771 --- /dev/null +++ b/drivers/input/touchscreen/ad7879-i2c.c @@ -0,0 +1,109 @@ +/* + * AD7879-1/AD7889-1 touchscreen (I2C bus) + * + * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/input.h> /* BUS_I2C */ +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/pm.h> + +#include "ad7879.h" + +#define AD7879_DEVID 0x79 /* AD7879-1/AD7889-1 */ + +/* All registers are word-sized. + * AD7879 uses a high-byte first convention. + */ +static int ad7879_i2c_read(struct device *dev, u8 reg) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_read_word_swapped(client, reg); +} + +static int ad7879_i2c_multi_read(struct device *dev, + u8 first_reg, u8 count, u16 *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + u8 idx; + + i2c_smbus_read_i2c_block_data(client, first_reg, count * 2, (u8 *)buf); + + for (idx = 0; idx < count; ++idx) + buf[idx] = swab16(buf[idx]); + + return 0; +} + +static int ad7879_i2c_write(struct device *dev, u8 reg, u16 val) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_write_word_swapped(client, reg, val); +} + +static const struct ad7879_bus_ops ad7879_i2c_bus_ops = { + .bustype = BUS_I2C, + .read = ad7879_i2c_read, + .multi_read = ad7879_i2c_multi_read, + .write = ad7879_i2c_write, +}; + +static int ad7879_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ad7879 *ts; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "SMBUS Word Data not Supported\n"); + return -EIO; + } + + ts = ad7879_probe(&client->dev, AD7879_DEVID, client->irq, + &ad7879_i2c_bus_ops); + if (IS_ERR(ts)) + return PTR_ERR(ts); + + i2c_set_clientdata(client, ts); + + return 0; +} + +static int ad7879_i2c_remove(struct i2c_client *client) +{ + struct ad7879 *ts = i2c_get_clientdata(client); + + ad7879_remove(ts); + + return 0; +} + +static const struct i2c_device_id ad7879_id[] = { + { "ad7879", 0 }, + { "ad7889", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad7879_id); + +static struct i2c_driver ad7879_i2c_driver = { + .driver = { + .name = "ad7879", + .owner = THIS_MODULE, + .pm = &ad7879_pm_ops, + }, + .probe = ad7879_i2c_probe, + .remove = ad7879_i2c_remove, + .id_table = ad7879_id, +}; + +module_i2c_driver(ad7879_i2c_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +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..1a7b11435 --- /dev/null +++ b/drivers/input/touchscreen/ad7879-spi.c @@ -0,0 +1,164 @@ +/* + * AD7879/AD7889 touchscreen (SPI bus) + * + * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/input.h> /* BUS_SPI */ +#include <linux/pm.h> +#include <linux/spi/spi.h> +#include <linux/module.h> + +#include "ad7879.h" + +#define AD7879_DEVID 0x7A /* AD7879/AD7889 */ + +#define MAX_SPI_FREQ_HZ 5000000 +#define AD7879_CMD_MAGIC 0xE000 +#define AD7879_CMD_READ (1 << 10) +#define AD7879_CMD(reg) (AD7879_CMD_MAGIC | ((reg) & 0xF)) +#define AD7879_WRITECMD(reg) (AD7879_CMD(reg)) +#define AD7879_READCMD(reg) (AD7879_CMD(reg) | AD7879_CMD_READ) + +/* + * ad7879_read/write are only used for initial setup and for sysfs controls. + * The main traffic is done in ad7879_collect(). + */ + +static int ad7879_spi_xfer(struct spi_device *spi, + u16 cmd, u8 count, u16 *tx_buf, u16 *rx_buf) +{ + struct spi_message msg; + struct spi_transfer *xfers; + void *spi_data; + u16 *command; + u16 *_rx_buf = _rx_buf; /* shut gcc up */ + u8 idx; + int ret; + + xfers = spi_data = kzalloc(sizeof(*xfers) * (count + 2), GFP_KERNEL); + if (!spi_data) + return -ENOMEM; + + spi_message_init(&msg); + + command = spi_data; + command[0] = cmd; + if (count == 1) { + /* ad7879_spi_{read,write} gave us buf on stack */ + command[1] = *tx_buf; + tx_buf = &command[1]; + _rx_buf = rx_buf; + rx_buf = &command[2]; + } + + ++xfers; + xfers[0].tx_buf = command; + xfers[0].len = 2; + spi_message_add_tail(&xfers[0], &msg); + ++xfers; + + for (idx = 0; idx < count; ++idx) { + if (rx_buf) + xfers[idx].rx_buf = &rx_buf[idx]; + if (tx_buf) + xfers[idx].tx_buf = &tx_buf[idx]; + xfers[idx].len = 2; + spi_message_add_tail(&xfers[idx], &msg); + } + + ret = spi_sync(spi, &msg); + + if (count == 1) + _rx_buf[0] = command[2]; + + kfree(spi_data); + + return ret; +} + +static int ad7879_spi_multi_read(struct device *dev, + u8 first_reg, u8 count, u16 *buf) +{ + struct spi_device *spi = to_spi_device(dev); + + return ad7879_spi_xfer(spi, AD7879_READCMD(first_reg), count, NULL, buf); +} + +static int ad7879_spi_read(struct device *dev, u8 reg) +{ + struct spi_device *spi = to_spi_device(dev); + u16 ret, dummy; + + return ad7879_spi_xfer(spi, AD7879_READCMD(reg), 1, &dummy, &ret) ? : ret; +} + +static int ad7879_spi_write(struct device *dev, u8 reg, u16 val) +{ + struct spi_device *spi = to_spi_device(dev); + u16 dummy; + + return ad7879_spi_xfer(spi, AD7879_WRITECMD(reg), 1, &val, &dummy); +} + +static const struct ad7879_bus_ops ad7879_spi_bus_ops = { + .bustype = BUS_SPI, + .read = ad7879_spi_read, + .multi_read = ad7879_spi_multi_read, + .write = ad7879_spi_write, +}; + +static int ad7879_spi_probe(struct spi_device *spi) +{ + struct ad7879 *ts; + int err; + + /* 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; + } + + 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 = ad7879_probe(&spi->dev, AD7879_DEVID, spi->irq, &ad7879_spi_bus_ops); + if (IS_ERR(ts)) + return PTR_ERR(ts); + + spi_set_drvdata(spi, ts); + + return 0; +} + +static int ad7879_spi_remove(struct spi_device *spi) +{ + struct ad7879 *ts = spi_get_drvdata(spi); + + ad7879_remove(ts); + + return 0; +} + +static struct spi_driver ad7879_spi_driver = { + .driver = { + .name = "ad7879", + .owner = THIS_MODULE, + .pm = &ad7879_pm_ops, + }, + .probe = ad7879_spi_probe, + .remove = ad7879_spi_remove, +}; + +module_spi_driver(ad7879_spi_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +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..fec66ad80 --- /dev/null +++ b/drivers/input/touchscreen/ad7879.c @@ -0,0 +1,647 @@ +/* + * AD7879/AD7889 based touchscreen and GPIO driver + * + * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + * + * 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/slab.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/gpio.h> + +#include <linux/spi/ad7879.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_XPOS = 0, + AD7879_SEQ_YPOS = 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 { + const struct ad7879_bus_ops *bops; + + 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 pressure_max; + u16 cmd_crtl1; + u16 cmd_crtl2; + u16 cmd_crtl3; + int x; + int y; + int Rt; +}; + +static int ad7879_read(struct ad7879 *ts, u8 reg) +{ + return ts->bops->read(ts->dev, reg); +} + +static int ad7879_multi_read(struct ad7879 *ts, u8 first_reg, u8 count, u16 *buf) +{ + return ts->bops->multi_read(ts->dev, first_reg, count, buf); +} + +static int ad7879_write(struct ad7879 *ts, u8 reg, u16 val) +{ + return ts->bops->write(ts->dev, reg, val); +} + +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/spi/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 > ts->pressure_max) + 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(unsigned long handle) +{ + struct ad7879 *ts = (void *)handle; + + ad7879_ts_event_release(ts); +} + +static irqreturn_t ad7879_irq(int irq, void *handle) +{ + struct ad7879 *ts = handle; + + ad7879_multi_read(ts, AD7879_REG_XPLUS, AD7879_NR_SENSE, ts->conversion_data); + + 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 && ts->input->users) + __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 && ts->input->users) + __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 && ts->input->users != 0) { + + 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 = container_of(chip, struct ad7879, gc); + 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 = container_of(chip, struct ad7879, gc); + 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 = container_of(chip, struct ad7879, gc); + 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 = container_of(chip, struct ad7879, gc); + + 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, + const struct ad7879_platform_data *pdata) +{ + int ret = 0; + + mutex_init(&ts->mutex); + + if (pdata->gpio_export) { + 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 = pdata->gpio_base; + ts->gc.ngpio = 1; + ts->gc.label = "AD7879-GPIO"; + ts->gc.owner = THIS_MODULE; + ts->gc.dev = ts->dev; + + ret = gpiochip_add(&ts->gc); + if (ret) + dev_err(ts->dev, "failed to register gpio %d\n", + ts->gc.base); + } + + return ret; +} + +static void ad7879_gpio_remove(struct ad7879 *ts) +{ + const struct ad7879_platform_data *pdata = dev_get_platdata(ts->dev); + + if (pdata->gpio_export) + gpiochip_remove(&ts->gc); + +} +#else +static inline int ad7879_gpio_add(struct ad7879 *ts, + const struct ad7879_platform_data *pdata) +{ + return 0; +} + +static inline void ad7879_gpio_remove(struct ad7879 *ts) +{ +} +#endif + +struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq, + const struct ad7879_bus_ops *bops) +{ + struct ad7879_platform_data *pdata = dev_get_platdata(dev); + struct ad7879 *ts; + struct input_dev *input_dev; + int err; + u16 revid; + + if (!irq) { + dev_err(dev, "no IRQ?\n"); + err = -EINVAL; + goto err_out; + } + + if (!pdata) { + dev_err(dev, "no platform data?\n"); + err = -EINVAL; + goto err_out; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + ts->bops = bops; + ts->dev = dev; + ts->input = input_dev; + ts->irq = irq; + ts->swap_xy = pdata->swap_xy; + + setup_timer(&ts->timer, ad7879_timer, (unsigned long) ts); + + ts->x_plate_ohms = pdata->x_plate_ohms ? : 400; + ts->pressure_max = pdata->pressure_max ? : ~0; + + 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; + ts->median = pdata->median; + + 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 = bops->bustype; + + input_dev->open = ad7879_open; + input_dev->close = ad7879_close; + + input_set_drvdata(input_dev, ts); + + __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); + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + + 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); + + err = ad7879_write(ts, AD7879_REG_CTRL2, AD7879_RESET); + if (err < 0) { + dev_err(dev, "Failed to write %s\n", input_dev->name); + goto err_free_mem; + } + + 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); + err = -ENODEV; + goto err_free_mem; + } + + 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 = request_threaded_irq(ts->irq, NULL, ad7879_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), ts); + if (err) { + dev_err(dev, "irq %d busy?\n", ts->irq); + goto err_free_mem; + } + + __ad7879_disable(ts); + + err = sysfs_create_group(&dev->kobj, &ad7879_attr_group); + if (err) + goto err_free_irq; + + err = ad7879_gpio_add(ts, pdata); + if (err) + goto err_remove_attr; + + err = input_register_device(input_dev); + if (err) + goto err_remove_gpio; + + return ts; + +err_remove_gpio: + ad7879_gpio_remove(ts); +err_remove_attr: + sysfs_remove_group(&dev->kobj, &ad7879_attr_group); +err_free_irq: + free_irq(ts->irq, ts); +err_free_mem: + input_free_device(input_dev); + kfree(ts); +err_out: + return ERR_PTR(err); +} +EXPORT_SYMBOL(ad7879_probe); + +void ad7879_remove(struct ad7879 *ts) +{ + ad7879_gpio_remove(ts); + sysfs_remove_group(&ts->dev->kobj, &ad7879_attr_group); + free_irq(ts->irq, ts); + input_unregister_device(ts->input); + kfree(ts); +} +EXPORT_SYMBOL(ad7879_remove); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +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..6fd13c48d --- /dev/null +++ b/drivers/input/touchscreen/ad7879.h @@ -0,0 +1,30 @@ +/* + * AD7879/AD7889 touchscreen (bus interfaces) + * + * Copyright (C) 2008-2010 Michael Hennerich, Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _AD7879_H_ +#define _AD7879_H_ + +#include <linux/types.h> + +struct ad7879; +struct device; + +struct ad7879_bus_ops { + u16 bustype; + int (*read)(struct device *dev, u8 reg); + int (*multi_read)(struct device *dev, u8 first_reg, u8 count, u16 *buf); + int (*write)(struct device *dev, u8 reg, u16 val); +}; + +extern const struct dev_pm_ops ad7879_pm_ops; + +struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned irq, + const struct ad7879_bus_ops *bops); +void ad7879_remove(struct ad7879 *); + +#endif diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c new file mode 100644 index 000000000..e4eb8a6c6 --- /dev/null +++ b/drivers/input/touchscreen/ads7846.c @@ -0,0 +1,1510 @@ +/* + * 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 + * + * 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/types.h> +#include <linux/hwmon.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/input.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/irq.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 ts_event { + /* + * For portability, we can't read 12 bit values using SPI (which + * would make the controller deliver them as native byte order u16 + * with msbs zeroed). Instead, we read them as two 8-bit values, + * *** WHICH NEED BYTESWAPPING *** and range adjustment. + */ + u16 x; + u16 y; + u16 z1, z2; + bool ignore; + u8 x_buf[3]; + u8 y_buf[3]; +}; + +/* + * 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 { + u8 read_x, read_y, read_z1, read_z2, pwrdown; + u16 dummy; /* for the pwrdown read */ + struct ts_event tc; + /* for ads7845 with mpc5121 psc spi we use 3-byte buffers */ + u8 read_x_cmd[3], read_y_cmd[3], pwrdown_cmd[3]; +}; + +struct ads7846 { + struct input_dev *input; + char phys[32]; + char name[32]; + + struct spi_device *spi; + struct regulator *reg; + +#if IS_ENABLED(CONFIG_HWMON) + struct device *hwmon; +#endif + + 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 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; + void (*filter_cleanup)(void *data); + int (*get_pendown_state)(void); + int gpio_pendown; + + void (*wait_for_sync)(void); +}; + +/* 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)) + +/* 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) { + /* 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_usecs = ts->vref_delay_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 = be16_to_cpu(*((u16 *)&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 = container_of(kobj, struct device, 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 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) +{ + /* 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; + } + + ts->hwmon = hwmon_device_register_with_groups(&spi->dev, spi->modalias, + ts, ads7846_attr_groups); + if (IS_ERR(ts->hwmon)) + return PTR_ERR(ts->hwmon); + + return 0; +} + +static void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ + if (ts->hwmon) + hwmon_device_unregister(ts->hwmon); +} + +#else +static inline int ads784x_hwmon_register(struct spi_device *spi, + struct ads7846 *ts) +{ + return 0; +} + +static inline void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ +} +#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 struct attribute_group ads784x_attr_group = { + .attrs = ads784x_attributes, +}; + +/*--------------------------------------------------------------------------*/ + +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 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 *ts, struct spi_message *m) +{ + struct spi_transfer *t = + list_entry(m->transfers.prev, struct spi_transfer, transfer_list); + + if (ts->model == 7845) { + return be16_to_cpup((__be16 *)&(((char*)t->rx_buf)[1])) >> 3; + } else { + /* + * adjust: on-wire is a must-ignore bit, a BE12 value, then + * padding; built from two 8 bit values written msb-first. + */ + return be16_to_cpup((__be16 *)t->rx_buf) >> 3; + } +} + +static void ads7846_update_value(struct spi_message *m, int val) +{ + struct spi_transfer *t = + list_entry(m->transfers.prev, struct spi_transfer, transfer_list); + + *(u16 *)t->rx_buf = val; +} + +static void ads7846_read_state(struct ads7846 *ts) +{ + struct ads7846_packet *packet = ts->packet; + struct spi_message *m; + int msg_idx = 0; + int val; + int action; + int error; + + while (msg_idx < ts->msg_count) { + + 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->tc.ignore = true; + return; + } + + /* + * Last message is power down request, no need to convert + * or filter the value. + */ + if (msg_idx < ts->msg_count - 1) { + + val = ads7846_get_value(ts, m); + + action = ts->filter(ts->filter_data, msg_idx, &val); + switch (action) { + case ADS7846_FILTER_REPEAT: + continue; + + case ADS7846_FILTER_IGNORE: + packet->tc.ignore = true; + msg_idx = ts->msg_count - 1; + continue; + + case ADS7846_FILTER_OK: + ads7846_update_value(m, val); + packet->tc.ignore = false; + msg_idx++; + break; + + default: + BUG(); + } + } else { + msg_idx++; + } + } +} + +static void ads7846_report_state(struct ads7846 *ts) +{ + struct ads7846_packet *packet = ts->packet; + unsigned int Rt; + u16 x, y, z1, z2; + + /* + * ads7846_get_value() does in-place conversion (including byte swap) + * from on-the-wire format as part of debouncing to get stable + * readings. + */ + if (ts->model == 7845) { + x = *(u16 *)packet->tc.x_buf; + y = *(u16 *)packet->tc.y_buf; + z1 = 0; + z2 = 0; + } else { + x = packet->tc.x; + y = packet->tc.y; + z1 = packet->tc.z1; + z2 = packet->tc.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 *= x; + Rt *= ts->x_plate_ohms; + Rt /= z1; + Rt = (Rt + 2047) >> 12; + } 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->tc.ignore || Rt > ts->pressure_max) { + dev_vdbg(&ts->spi->dev, "ignored %d pressure %d\n", + packet->tc.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->swap_xy) + swap(x, y); + + if (!ts->pendown) { + input_report_key(input, BTN_TOUCH, 1); + ts->pendown = true; + dev_vdbg(&ts->spi->dev, "DOWN\n"); + } + + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + 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) { + 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"); + } + + 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 = gpio_request_one(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 void 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; + + 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; + + if (ts->model == 7845) { + packet->read_y_cmd[0] = READ_Y(vref); + packet->read_y_cmd[1] = 0; + packet->read_y_cmd[2] = 0; + x->tx_buf = &packet->read_y_cmd[0]; + x->rx_buf = &packet->tc.y_buf[0]; + x->len = 3; + spi_message_add_tail(x, m); + } else { + /* y- still on; turn on only y+ (and ADC) */ + packet->read_y = READ_Y(vref); + x->tx_buf = &packet->read_y; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.y; + x->len = 2; + spi_message_add_tail(x, m); + } + + /* + * The first sample after switching drivers can be low quality; + * optionally discard it, using a second one after the signals + * have had enough time to stabilize. + */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_y; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.y; + x->len = 2; + spi_message_add_tail(x, m); + } + + ts->msg_count++; + m++; + spi_message_init(m); + m->context = ts; + + if (ts->model == 7845) { + x++; + packet->read_x_cmd[0] = READ_X(vref); + packet->read_x_cmd[1] = 0; + packet->read_x_cmd[2] = 0; + x->tx_buf = &packet->read_x_cmd[0]; + x->rx_buf = &packet->tc.x_buf[0]; + x->len = 3; + spi_message_add_tail(x, m); + } else { + /* turn y- off, x+ on, then leave in lowpower */ + x++; + packet->read_x = READ_X(vref); + x->tx_buf = &packet->read_x; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.x; + x->len = 2; + spi_message_add_tail(x, m); + } + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_x; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.x; + x->len = 2; + spi_message_add_tail(x, m); + } + + /* turn y+ off, x- on; we'll use formula #2 */ + if (ts->model == 7846) { + ts->msg_count++; + m++; + spi_message_init(m); + m->context = ts; + + x++; + packet->read_z1 = READ_Z1(vref); + x->tx_buf = &packet->read_z1; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z1; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_z1; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z1; + x->len = 2; + spi_message_add_tail(x, m); + } + + ts->msg_count++; + m++; + spi_message_init(m); + m->context = ts; + + x++; + packet->read_z2 = READ_Z2(vref); + x->tx_buf = &packet->read_z2; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z2; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_z2; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z2; + x->len = 2; + spi_message_add_tail(x, m); + } + } + + /* power down */ + ts->msg_count++; + m++; + spi_message_init(m); + m->context = ts; + + if (ts->model == 7845) { + x++; + packet->pwrdown_cmd[0] = PWRDOWN; + packet->pwrdown_cmd[1] = 0; + packet->pwrdown_cmd[2] = 0; + x->tx_buf = &packet->pwrdown_cmd[0]; + x->len = 3; + } else { + x++; + packet->pwrdown = PWRDOWN; + x->tx_buf = &packet->pwrdown; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->dummy; + x->len = 2; + } + + CS_CHANGE(*x); + spi_message_add_tail(x, m); +} + +#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; + + 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); + + of_property_read_u16(node, "ti,pressure-min", &pdata->pressure_min); + of_property_read_u16(node, "ti,pressure-max", &pdata->pressure_max); + + of_property_read_u16(node, "ti,debounce-max", &pdata->debounce_max); + 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, "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 int ads7846_probe(struct spi_device *spi) +{ + const struct ads7846_platform_data *pdata; + struct ads7846 *ts; + struct ads7846_packet *packet; + struct input_dev *input_dev; + unsigned long irq_flags; + int err; + + if (!spi->irq) { + dev_dbg(&spi->dev, "no IRQ?\n"); + return -EINVAL; + } + + /* don't exceed max specified sample rate */ + if (spi->max_speed_hz > (125000 * SAMPLE_BITS)) { + dev_err(&spi->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_0; + err = spi_setup(spi); + if (err < 0) + return err; + + ts = kzalloc(sizeof(struct ads7846), GFP_KERNEL); + packet = kzalloc(sizeof(struct ads7846_packet), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !packet || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + 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(&spi->dev); + if (!pdata) { + pdata = ads7846_probe_dt(&spi->dev); + if (IS_ERR(pdata)) { + err = PTR_ERR(pdata); + goto err_free_mem; + } + } + + ts->model = pdata->model ? : 7846; + 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->vref_mv = pdata->vref_mv; + ts->swap_xy = pdata->swap_xy; + + if (pdata->filter != NULL) { + if (pdata->filter_init != NULL) { + err = pdata->filter_init(pdata, &ts->filter_data); + if (err < 0) + goto err_free_mem; + } + ts->filter = pdata->filter; + ts->filter_cleanup = pdata->filter_cleanup; + } else 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) + goto err_cleanup_filter; + + 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(&spi->dev)); + snprintf(ts->name, sizeof(ts->name), "ADS%d Touchscreen", ts->model); + + input_dev->name = ts->name; + input_dev->phys = ts->phys; + input_dev->dev.parent = &spi->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, + 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); + + ads7846_setup_spi_msg(ts, pdata); + + ts->reg = regulator_get(&spi->dev, "vcc"); + if (IS_ERR(ts->reg)) { + err = PTR_ERR(ts->reg); + dev_err(&spi->dev, "unable to get regulator: %d\n", err); + goto err_free_gpio; + } + + err = regulator_enable(ts->reg); + if (err) { + dev_err(&spi->dev, "unable to enable regulator: %d\n", err); + goto err_put_regulator; + } + + irq_flags = pdata->irq_flags ? : IRQF_TRIGGER_FALLING; + irq_flags |= IRQF_ONESHOT; + + err = request_threaded_irq(spi->irq, ads7846_hard_irq, ads7846_irq, + irq_flags, spi->dev.driver->name, ts); + if (err && !pdata->irq_flags) { + dev_info(&spi->dev, + "trying pin change workaround on irq %d\n", spi->irq); + irq_flags |= IRQF_TRIGGER_RISING; + err = request_threaded_irq(spi->irq, + ads7846_hard_irq, ads7846_irq, + irq_flags, spi->dev.driver->name, ts); + } + + if (err) { + dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq); + goto err_disable_regulator; + } + + err = ads784x_hwmon_register(spi, ts); + if (err) + goto err_free_irq; + + dev_info(&spi->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(&spi->dev, PWRDOWN); + else + (void) ads7846_read12_ser(&spi->dev, READ_12BIT_SER(vaux)); + + err = sysfs_create_group(&spi->dev.kobj, &ads784x_attr_group); + if (err) + goto err_remove_hwmon; + + err = input_register_device(input_dev); + if (err) + goto err_remove_attr_group; + + device_init_wakeup(&spi->dev, pdata->wakeup); + + /* + * If device does not carry platform data we must have allocated it + * when parsing DT data. + */ + if (!dev_get_platdata(&spi->dev)) + devm_kfree(&spi->dev, (void *)pdata); + + return 0; + + err_remove_attr_group: + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + err_remove_hwmon: + ads784x_hwmon_unregister(spi, ts); + err_free_irq: + free_irq(spi->irq, ts); + err_disable_regulator: + regulator_disable(ts->reg); + err_put_regulator: + regulator_put(ts->reg); + err_free_gpio: + if (!ts->get_pendown_state) + gpio_free(ts->gpio_pendown); + err_cleanup_filter: + if (ts->filter_cleanup) + ts->filter_cleanup(ts->filter_data); + err_free_mem: + input_free_device(input_dev); + kfree(packet); + kfree(ts); + return err; +} + +static int ads7846_remove(struct spi_device *spi) +{ + struct ads7846 *ts = spi_get_drvdata(spi); + + device_init_wakeup(&spi->dev, false); + + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + + ads7846_disable(ts); + free_irq(ts->spi->irq, ts); + + input_unregister_device(ts->input); + + ads784x_hwmon_unregister(spi, ts); + + regulator_disable(ts->reg); + regulator_put(ts->reg); + + if (!ts->get_pendown_state) { + /* + * If we are not using specialized pendown method we must + * have been relying on gpio we set up ourselves. + */ + gpio_free(ts->gpio_pendown); + } + + if (ts->filter_cleanup) + ts->filter_cleanup(ts->filter_data); + + kfree(ts->packet); + kfree(ts); + + dev_dbg(&spi->dev, "unregistered touchscreen\n"); + + return 0; +} + +static struct spi_driver ads7846_driver = { + .driver = { + .name = "ads7846", + .owner = THIS_MODULE, + .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..f0b954d46 --- /dev/null +++ b/drivers/input/touchscreen/ar1021_i2c.c @@ -0,0 +1,181 @@ +/* + * Microchip AR1021 driver for I2C + * + * Author: Christian Gmeiner <christian.gmeiner@gmail.com> + * + * License: GPLv2 as published by the FSF. + */ + +#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_TOCUH_PKG_SIZE 5 + +#define AR1021_MAX_X 4095 +#define AR1021_MAX_Y 4095 + +struct ar1021_i2c { + struct i2c_client *client; + struct input_dev *input; + u8 data[AR1021_TOCUH_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] & 0x80) == 0) + 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) +{ + struct ar1021_i2c *ar1021 = input_get_drvdata(dev); + struct i2c_client *client = ar1021->client; + + 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; + + 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_TRIGGER_RISING | IRQF_ONESHOT, + "ar1021_i2c", ar1021); + if (error) { + dev_err(&client->dev, + "Failed to enable IRQ, error: %d\n", error); + return error; + } + + /* Disable the IRQ, we'll enable it in ar1021_i2c_open() */ + disable_irq(client->irq); + + error = input_register_device(ar1021->input); + if (error) { + dev_err(&client->dev, + "Failed to register input device, error: %d\n", error); + return error; + } + + i2c_set_clientdata(client, ar1021); + 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[] = { + { "MICROCHIP_AR1021_I2C", 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", + .owner = THIS_MODULE, + .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 AR1021 I2C Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/atmel-wm97xx.c b/drivers/input/touchscreen/atmel-wm97xx.c new file mode 100644 index 000000000..7ec0421c0 --- /dev/null +++ b/drivers/input/touchscreen/atmel-wm97xx.c @@ -0,0 +1,436 @@ +/* + * Atmel AT91 and AVR32 continuous touch screen driver for Wolfson WM97xx AC97 + * codecs. + * + * Copyright (C) 2008 - 2009 Atmel Corporation + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/wm97xx.h> +#include <linux/timer.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/slab.h> + +#define AC97C_ICA 0x10 +#define AC97C_CBRHR 0x30 +#define AC97C_CBSR 0x38 +#define AC97C_CBMR 0x3c +#define AC97C_IER 0x54 +#define AC97C_IDR 0x58 + +#define AC97C_RXRDY (1 << 4) +#define AC97C_OVRUN (1 << 5) + +#define AC97C_CMR_SIZE_20 (0 << 16) +#define AC97C_CMR_SIZE_18 (1 << 16) +#define AC97C_CMR_SIZE_16 (2 << 16) +#define AC97C_CMR_SIZE_10 (3 << 16) +#define AC97C_CMR_CEM_LITTLE (1 << 18) +#define AC97C_CMR_CEM_BIG (0 << 18) +#define AC97C_CMR_CENA (1 << 21) + +#define AC97C_INT_CBEVT (1 << 4) + +#define AC97C_SR_CAEVT (1 << 3) + +#define AC97C_CH_MASK(slot) \ + (0x7 << (3 * (slot - 3))) +#define AC97C_CH_ASSIGN(slot, channel) \ + (AC97C_CHANNEL_##channel << (3 * (slot - 3))) +#define AC97C_CHANNEL_NONE 0x0 +#define AC97C_CHANNEL_B 0x2 + +#define ac97c_writel(chip, reg, val) \ + __raw_writel((val), (chip)->regs + AC97C_##reg) +#define ac97c_readl(chip, reg) \ + __raw_readl((chip)->regs + AC97C_##reg) + +#ifdef CONFIG_CPU_AT32AP700X +#define ATMEL_WM97XX_AC97C_IOMEM (0xfff02800) +#define ATMEL_WM97XX_AC97C_IRQ (29) +#define ATMEL_WM97XX_GPIO_DEFAULT (32+16) /* Pin 16 on port B. */ +#else +#error Unknown CPU, this driver only supports AT32AP700X CPUs. +#endif + +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; + +/* + * Pen sampling frequency (Hz) in continuous mode. + */ +static int cont_rate = 188; +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 = 1; +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"); + +/* + * GPIO line number. + * + * Set to GPIO number where the signal from the WM97xx device is hooked up. + */ +static int atmel_gpio_line = ATMEL_WM97XX_GPIO_DEFAULT; +module_param(atmel_gpio_line, int, 0); +MODULE_PARM_DESC(atmel_gpio_line, "GPIO line number connected to WM97xx"); + +struct atmel_wm97xx { + struct wm97xx *wm; + struct timer_list pen_timer; + void __iomem *regs; + unsigned long ac97c_irq; + unsigned long gpio_pen; + unsigned long gpio_irq; + unsigned short x; + unsigned short y; +}; + +static irqreturn_t atmel_wm97xx_channel_b_interrupt(int irq, void *dev_id) +{ + struct atmel_wm97xx *atmel_wm97xx = dev_id; + struct wm97xx *wm = atmel_wm97xx->wm; + int status = ac97c_readl(atmel_wm97xx, CBSR); + irqreturn_t retval = IRQ_NONE; + + if (status & AC97C_OVRUN) { + dev_dbg(&wm->touch_dev->dev, "AC97C overrun\n"); + ac97c_readl(atmel_wm97xx, CBRHR); + retval = IRQ_HANDLED; + } else if (status & AC97C_RXRDY) { + u16 data; + u16 value; + u16 source; + u16 pen_down; + + data = ac97c_readl(atmel_wm97xx, CBRHR); + value = data & 0x0fff; + source = data & WM97XX_ADCSEL_MASK; + pen_down = (data & WM97XX_PEN_DOWN) >> 8; + + if (source == WM97XX_ADCSEL_X) + atmel_wm97xx->x = value; + if (source == WM97XX_ADCSEL_Y) + atmel_wm97xx->y = value; + + if (!pressure && source == WM97XX_ADCSEL_Y) { + input_report_abs(wm->input_dev, ABS_X, atmel_wm97xx->x); + input_report_abs(wm->input_dev, ABS_Y, atmel_wm97xx->y); + input_report_key(wm->input_dev, BTN_TOUCH, pen_down); + input_sync(wm->input_dev); + } else if (pressure && source == WM97XX_ADCSEL_PRES) { + input_report_abs(wm->input_dev, ABS_X, atmel_wm97xx->x); + input_report_abs(wm->input_dev, ABS_Y, atmel_wm97xx->y); + input_report_abs(wm->input_dev, ABS_PRESSURE, value); + input_report_key(wm->input_dev, BTN_TOUCH, value); + input_sync(wm->input_dev); + } + + retval = IRQ_HANDLED; + } + + return retval; +} + +static void atmel_wm97xx_acc_pen_up(struct wm97xx *wm) +{ + struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(wm->touch_dev); + struct input_dev *input_dev = wm->input_dev; + int pen_down = gpio_get_value(atmel_wm97xx->gpio_pen); + + if (pen_down != 0) { + mod_timer(&atmel_wm97xx->pen_timer, + jiffies + msecs_to_jiffies(1)); + } else { + if (pressure) + input_report_abs(input_dev, ABS_PRESSURE, 0); + input_report_key(input_dev, BTN_TOUCH, 0); + input_sync(input_dev); + } +} + +static void atmel_wm97xx_pen_timer(unsigned long data) +{ + atmel_wm97xx_acc_pen_up((struct wm97xx *)data); +} + +static int atmel_wm97xx_acc_startup(struct wm97xx *wm) +{ + struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(wm->touch_dev); + int idx = 0; + + if (wm->ac97 == NULL) + return -ENODEV; + + 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->touch_dev->dev, "atmel accelerated touchscreen driver, " + "%d samples/sec\n", cinfo[sp_idx].speed); + + if (pen_int) { + unsigned long reg; + + wm->pen_irq = atmel_wm97xx->gpio_irq; + + switch (wm->id) { + case WM9712_ID2: /* Fall through. */ + case WM9713_ID2: + /* + * Use GPIO 13 (PEN_DOWN) to assert GPIO line 3 + * (PENDOWN). + */ + 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_3, WM97XX_GPIO_OUT, + WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_NOTSTICKY, + WM97XX_GPIO_NOWAKE); + case WM9705_ID2: /* Fall through. */ + /* + * Enable touch data slot in AC97 controller channel B. + */ + reg = ac97c_readl(atmel_wm97xx, ICA); + reg &= ~AC97C_CH_MASK(wm->acc_slot); + reg |= AC97C_CH_ASSIGN(wm->acc_slot, B); + ac97c_writel(atmel_wm97xx, ICA, reg); + + /* + * Enable channel and interrupt for RXRDY and OVERRUN. + */ + ac97c_writel(atmel_wm97xx, CBMR, AC97C_CMR_CENA + | AC97C_CMR_CEM_BIG + | AC97C_CMR_SIZE_16 + | AC97C_OVRUN + | AC97C_RXRDY); + /* Dummy read to empty RXRHR. */ + ac97c_readl(atmel_wm97xx, CBRHR); + /* + * Enable interrupt for channel B in the AC97 + * controller. + */ + ac97c_writel(atmel_wm97xx, IER, AC97C_INT_CBEVT); + break; + default: + dev_err(&wm->touch_dev->dev, "pen down irq not " + "supported on this device\n"); + pen_int = 0; + break; + } + } + + return 0; +} + +static void atmel_wm97xx_acc_shutdown(struct wm97xx *wm) +{ + if (pen_int) { + struct atmel_wm97xx *atmel_wm97xx = + platform_get_drvdata(wm->touch_dev); + unsigned long ica; + + switch (wm->id & 0xffff) { + case WM9705_ID2: /* Fall through. */ + case WM9712_ID2: /* Fall through. */ + case WM9713_ID2: + /* Disable slot and turn off channel B interrupts. */ + ica = ac97c_readl(atmel_wm97xx, ICA); + ica &= ~AC97C_CH_MASK(wm->acc_slot); + ac97c_writel(atmel_wm97xx, ICA, ica); + ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT); + ac97c_writel(atmel_wm97xx, CBMR, 0); + wm->pen_irq = 0; + break; + default: + dev_err(&wm->touch_dev->dev, "unknown codec\n"); + break; + } + } +} + +static void atmel_wm97xx_irq_enable(struct wm97xx *wm, int enable) +{ + /* Intentionally left empty. */ +} + +static struct wm97xx_mach_ops atmel_mach_ops = { + .acc_enabled = 1, + .acc_pen_up = atmel_wm97xx_acc_pen_up, + .acc_startup = atmel_wm97xx_acc_startup, + .acc_shutdown = atmel_wm97xx_acc_shutdown, + .irq_enable = atmel_wm97xx_irq_enable, + .irq_gpio = WM97XX_GPIO_3, +}; + +static int __init atmel_wm97xx_probe(struct platform_device *pdev) +{ + struct wm97xx *wm = platform_get_drvdata(pdev); + struct atmel_wm97xx *atmel_wm97xx; + int ret; + + atmel_wm97xx = kzalloc(sizeof(struct atmel_wm97xx), GFP_KERNEL); + if (!atmel_wm97xx) { + dev_dbg(&pdev->dev, "out of memory\n"); + return -ENOMEM; + } + + atmel_wm97xx->wm = wm; + atmel_wm97xx->regs = (void *)ATMEL_WM97XX_AC97C_IOMEM; + atmel_wm97xx->ac97c_irq = ATMEL_WM97XX_AC97C_IRQ; + atmel_wm97xx->gpio_pen = atmel_gpio_line; + atmel_wm97xx->gpio_irq = gpio_to_irq(atmel_wm97xx->gpio_pen); + + setup_timer(&atmel_wm97xx->pen_timer, atmel_wm97xx_pen_timer, + (unsigned long)wm); + + ret = request_irq(atmel_wm97xx->ac97c_irq, + atmel_wm97xx_channel_b_interrupt, + IRQF_SHARED, "atmel-wm97xx-ch-b", atmel_wm97xx); + if (ret) { + dev_dbg(&pdev->dev, "could not request ac97c irq\n"); + goto err; + } + + platform_set_drvdata(pdev, atmel_wm97xx); + + ret = wm97xx_register_mach_ops(wm, &atmel_mach_ops); + if (ret) + goto err_irq; + + return ret; + +err_irq: + free_irq(atmel_wm97xx->ac97c_irq, atmel_wm97xx); +err: + kfree(atmel_wm97xx); + return ret; +} + +static int __exit atmel_wm97xx_remove(struct platform_device *pdev) +{ + struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev); + struct wm97xx *wm = atmel_wm97xx->wm; + + ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT); + free_irq(atmel_wm97xx->ac97c_irq, atmel_wm97xx); + del_timer_sync(&atmel_wm97xx->pen_timer); + wm97xx_unregister_mach_ops(wm); + kfree(atmel_wm97xx); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int atmel_wm97xx_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev); + + ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT); + disable_irq(atmel_wm97xx->gpio_irq); + del_timer_sync(&atmel_wm97xx->pen_timer); + + return 0; +} + +static int atmel_wm97xx_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev); + struct wm97xx *wm = atmel_wm97xx->wm; + + if (wm->input_dev->users) { + enable_irq(atmel_wm97xx->gpio_irq); + ac97c_writel(atmel_wm97xx, IER, AC97C_INT_CBEVT); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(atmel_wm97xx_pm_ops, + atmel_wm97xx_suspend, atmel_wm97xx_resume); + +static struct platform_driver atmel_wm97xx_driver = { + .remove = __exit_p(atmel_wm97xx_remove), + .driver = { + .name = "wm97xx-touch", + .pm = &atmel_wm97xx_pm_ops, + }, +}; + +module_platform_driver_probe(atmel_wm97xx_driver, atmel_wm97xx_probe); + +MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>"); +MODULE_DESCRIPTION("wm97xx continuous touch driver for Atmel AT91 and AVR32"); +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..b406ed0af --- /dev/null +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -0,0 +1,2683 @@ +/* + * Atmel maXTouch Touchscreen driver + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Copyright (C) 2011-2014 Atmel Corporation + * Copyright (C) 2012 Google, Inc. + * + * Author: Joonyoung Shim <jy0922.shim@samsung.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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#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/i2c/atmel_mxt_ts.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +/* Version */ +#define MXT_VER_20 20 +#define MXT_VER_21 21 +#define MXT_VER_22 22 + +/* Firmware files */ +#define MXT_FW_NAME "/*(DEBLOBBED)*/" +#define MXT_CFG_NAME "maxtouch.cfg" +#define MXT_CFG_MAGIC "OBP_RAW V1" + +/* Registers */ +#define MXT_INFO 0x00 +#define MXT_FAMILY_ID 0x00 +#define MXT_VARIANT_ID 0x01 +#define MXT_VERSION 0x02 +#define MXT_BUILD 0x03 +#define MXT_MATRIX_X_SIZE 0x04 +#define MXT_MATRIX_Y_SIZE 0x05 +#define MXT_OBJECT_NUM 0x06 +#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_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 (1 << 7) +#define MXT_T6_STATUS_OFL (1 << 6) +#define MXT_T6_STATUS_SIGERR (1 << 5) +#define MXT_T6_STATUS_CAL (1 << 4) +#define MXT_T6_STATUS_CFGERR (1 << 3) +#define MXT_T6_STATUS_COMSERR (1 << 2) + +/* MXT_GEN_POWER_T7 field */ +#define MXT_POWER_IDLEACQINT 0 +#define MXT_POWER_ACTVACQINT 1 +#define MXT_POWER_ACTV2IDLETO 2 + +/* MXT_GEN_ACQUIRE_T8 field */ +#define MXT_ACQUIRE_CHRGTIME 0 +#define MXT_ACQUIRE_TCHDRIFT 2 +#define MXT_ACQUIRE_DRIFTST 3 +#define MXT_ACQUIRE_TCHAUTOCAL 4 +#define MXT_ACQUIRE_SYNC 5 +#define MXT_ACQUIRE_ATCHCALST 6 +#define MXT_ACQUIRE_ATCHCALSTHR 7 + +/* MXT_TOUCH_MULTI_T9 field */ +#define MXT_TOUCH_CTRL 0 +#define MXT_T9_ORIENT 9 +#define MXT_T9_RANGE 18 + +/* MXT_TOUCH_MULTI_T9 status */ +#define MXT_T9_UNGRIP (1 << 0) +#define MXT_T9_SUPPRESS (1 << 1) +#define MXT_T9_AMP (1 << 2) +#define MXT_T9_VECTOR (1 << 3) +#define MXT_T9_MOVE (1 << 4) +#define MXT_T9_RELEASE (1 << 5) +#define MXT_T9_PRESS (1 << 6) +#define MXT_T9_DETECT (1 << 7) + +struct t9_range { + u16 x; + u16 y; +} __packed; + +/* MXT_TOUCH_MULTI_T9 orient */ +#define MXT_T9_ORIENT_SWITCH (1 << 0) + +/* MXT_PROCI_GRIPFACE_T20 field */ +#define MXT_GRIPFACE_CTRL 0 +#define MXT_GRIPFACE_XLOGRIP 1 +#define MXT_GRIPFACE_XHIGRIP 2 +#define MXT_GRIPFACE_YLOGRIP 3 +#define MXT_GRIPFACE_YHIGRIP 4 +#define MXT_GRIPFACE_MAXTCHS 5 +#define MXT_GRIPFACE_SZTHR1 7 +#define MXT_GRIPFACE_SZTHR2 8 +#define MXT_GRIPFACE_SHPTHR1 9 +#define MXT_GRIPFACE_SHPTHR2 10 +#define MXT_GRIPFACE_SUPEXTTO 11 + +/* MXT_PROCI_NOISE field */ +#define MXT_NOISE_CTRL 0 +#define MXT_NOISE_OUTFLEN 1 +#define MXT_NOISE_GCAFUL_LSB 3 +#define MXT_NOISE_GCAFUL_MSB 4 +#define MXT_NOISE_GCAFLL_LSB 5 +#define MXT_NOISE_GCAFLL_MSB 6 +#define MXT_NOISE_ACTVGCAFVALID 7 +#define MXT_NOISE_NOISETHR 8 +#define MXT_NOISE_FREQHOPSCALE 10 +#define MXT_NOISE_FREQ0 11 +#define MXT_NOISE_FREQ1 12 +#define MXT_NOISE_FREQ2 13 +#define MXT_NOISE_FREQ3 14 +#define MXT_NOISE_FREQ4 15 +#define MXT_NOISE_IDLEGCAFVALID 16 + +/* MXT_SPT_COMMSCONFIG_T18 */ +#define MXT_COMMS_CTRL 0 +#define MXT_COMMS_CMD 1 + +/* MXT_SPT_CTECONFIG_T28 field */ +#define MXT_CTE_CTRL 0 +#define MXT_CTE_CMD 1 +#define MXT_CTE_MODE 2 +#define MXT_CTE_IDLEGCAFDEPTH 3 +#define MXT_CTE_ACTVGCAFDEPTH 4 +#define MXT_CTE_VOLTAGE 5 + +#define MXT_VOLTAGE_DEFAULT 2700000 +#define MXT_VOLTAGE_STEP 10000 + +/* 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_XRANGE 13 +#define MXT_T100_YRANGE 24 + +#define MXT_T100_CFG_SWITCHXY BIT(5) + +#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_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 */ + +/* 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 (1 << 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; + +/* Each client has this additional data */ +struct mxt_data { + struct i2c_client *client; + struct input_dev *input_dev; + char phys[64]; /* device physical location */ + const struct mxt_platform_data *pdata; + struct mxt_object *object_table; + struct mxt_info info; + unsigned int irq; + unsigned int max_x; + unsigned int max_y; + 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; + + /* Cached parameters from object table */ + u16 T5_address; + u8 T5_msg_size; + u8 T6_reportid; + u16 T6_address; + u16 T7_address; + u8 T9_reportid_min; + u8 T9_reportid_max; + 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; +}; + +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_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: + 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; + + switch (appmode) { + case 0x4a: + case 0x4b: + /* Chips after 1664S use different scheme */ + if (retry || data->info.family_id >= 0xa2) { + bootloader = appmode - 0x24; + break; + } + /* Fall through 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) +{ + int ret; + u8 buf[2]; + + if (unlock) { + buf[0] = MXT_UNLOCK_CMD_LSB; + buf[1] = MXT_UNLOCK_CMD_MSB; + } else { + buf[0] = 0x01; + buf[1] = 0x01; + } + + ret = mxt_bootloader_write(data, buf, 2); + if (ret) + return ret; + + return 0; +} + +static int __mxt_read_reg(struct i2c_client *client, + u16 reg, u16 len, void *val) +{ + struct i2c_msg xfer[2]; + 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; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret == 2) { + ret = 0; + } 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) +{ + 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); + + ret = i2c_master_send(client, buf, count); + if (ret == count) { + ret = 0; + } 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); + + complete(&data->crc_completion); + + if (crc != data->config_crc) { + data->config_crc = crc; + dev_dbg(dev, "T6 Config Checksum: 0x%06X\n", crc); + } + + /* 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; + const struct mxt_platform_data *pdata = data->pdata; + bool button; + int i; + + /* Active-low switch */ + for (i = 0; i < pdata->t19_num_keys; i++) { + if (pdata->t19_keymap[i] == KEY_RESERVED) + continue; + button = !(message[1] & (1 << i)); + input_report_key(input, pdata->t19_keymap[i], button); + } +} + +static void mxt_input_sync(struct mxt_data *data) +{ + input_mt_report_pointer_emulation(data->input_dev, + data->pdata->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_state(input_dev, + MT_TOOL_FINGER, 0); + mxt_input_sync(data); + } + + /* 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_state(input_dev, MT_TOOL_FINGER, 0); + } + + 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_state(input_dev, 0, 0); + } + + 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]; + + if (count == 0) { + /* + * This condition is caused by the CHG line being configured + * in Mode 0. It results in unnecessary I2C operations but it + * is benign. + */ + dev_dbg(dev, "Interrupt triggered but zero messages\n"); + return IRQ_NONE; + } else if (count > data->max_reportid) { + dev_err(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_soft_reset(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + dev_info(dev, "Resetting chip\n"); + + reinit_completion(&data->reset_completion); + + ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_RESET_VALUE, false); + if (ret) + return ret; + + 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_prepare_cfg_mem(struct mxt_data *data, + const struct firmware *cfg, + unsigned int data_pos, + unsigned int cfg_start_ofs, + u8 *config_mem, + size_t config_mem_size) +{ + 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 (data_pos < cfg->size) { + /* Read type, instance, length */ + ret = sscanf(cfg->data + data_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; + } + data_pos += offset; + + object = mxt_get_object(data, type); + if (!object) { + /* Skip object */ + for (i = 0; i < size; i++) { + ret = sscanf(cfg->data + data_pos, "%hhx%n", + &val, &offset); + if (ret != 1) { + dev_err(dev, "Bad format in T%d at %d\n", + type, i); + return -EINVAL; + } + data_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->data + data_pos, "%hhx%n", + &val, + &offset); + if (ret != 1) { + dev_err(dev, "Bad format in T%d at %d\n", + type, i); + return -EINVAL; + } + data_pos += offset; + + if (i > mxt_obj_size(object)) + continue; + + byte_offset = reg + i - cfg_start_ofs; + + if (byte_offset >= 0 && byte_offset < config_mem_size) { + *(config_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, unsigned int cfg_start, + u8 *config_mem, size_t config_mem_size) +{ + unsigned int byte_offset = 0; + int error; + + /* Write configuration as blocks */ + while (byte_offset < config_mem_size) { + unsigned int size = config_mem_size - byte_offset; + + if (size > MXT_MAX_BLOCK_WRITE) + size = MXT_MAX_BLOCK_WRITE; + + error = __mxt_write_reg(data->client, + cfg_start + byte_offset, + size, config_mem + byte_offset); + if (error) { + dev_err(&data->client->dev, + "Config write error, ret=%d\n", error); + return error; + } + + byte_offset += size; + } + + return 0; +} + +/* + * 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 *cfg) +{ + struct device *dev = &data->client->dev; + struct mxt_info cfg_info; + int ret; + int offset; + int data_pos; + int i; + int cfg_start_ofs; + u32 info_crc, config_crc, calculated_crc; + u8 *config_mem; + size_t config_mem_size; + + mxt_update_crc(data, MXT_COMMAND_REPORTALL, 1); + + if (strncmp(cfg->data, MXT_CFG_MAGIC, strlen(MXT_CFG_MAGIC))) { + dev_err(dev, "Unrecognised config file\n"); + return -EINVAL; + } + + data_pos = strlen(MXT_CFG_MAGIC); + + /* Load information block and check */ + for (i = 0; i < sizeof(struct mxt_info); i++) { + ret = sscanf(cfg->data + data_pos, "%hhx%n", + (unsigned char *)&cfg_info + i, + &offset); + if (ret != 1) { + dev_err(dev, "Bad format\n"); + return -EINVAL; + } + + data_pos += offset; + } + + if (cfg_info.family_id != data->info.family_id) { + dev_err(dev, "Family ID mismatch!\n"); + return -EINVAL; + } + + if (cfg_info.variant_id != data->info.variant_id) { + dev_err(dev, "Variant ID mismatch!\n"); + return -EINVAL; + } + + /* Read CRCs */ + ret = sscanf(cfg->data + data_pos, "%x%n", &info_crc, &offset); + if (ret != 1) { + dev_err(dev, "Bad format: failed to parse Info CRC\n"); + return -EINVAL; + } + data_pos += offset; + + ret = sscanf(cfg->data + data_pos, "%x%n", &config_crc, &offset); + if (ret != 1) { + dev_err(dev, "Bad format: failed to parse Config CRC\n"); + return -EINVAL; + } + data_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); + return 0; + } 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; + config_mem_size = data->mem_size - cfg_start_ofs; + config_mem = kzalloc(config_mem_size, GFP_KERNEL); + if (!config_mem) { + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + ret = mxt_prepare_cfg_mem(data, cfg, data_pos, cfg_start_ofs, + config_mem, config_mem_size); + if (ret) + goto release_mem; + + /* Calculate crc of the received configs (not the raw config file) */ + if (data->T7_address < cfg_start_ofs) { + dev_err(dev, "Bad T7 address, T7addr = %x, config offset %x\n", + data->T7_address, cfg_start_ofs); + ret = 0; + goto release_mem; + } + + calculated_crc = mxt_calculate_crc(config_mem, + data->T7_address - cfg_start_ofs, + config_mem_size); + + if (config_crc > 0 && config_crc != calculated_crc) + dev_warn(dev, "Config CRC error, calculated=%06X, file=%06X\n", + calculated_crc, config_crc); + + ret = mxt_upload_cfg_mem(data, cfg_start_ofs, + config_mem, config_mem_size); + if (ret) + goto release_mem; + + mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + + ret = mxt_soft_reset(data); + if (ret) + goto release_mem; + + dev_info(dev, "Config successfully updated\n"); + +release_mem: + kfree(config_mem); + return ret; +} + +static int mxt_acquire_irq(struct mxt_data *data) +{ + int error; + + enable_irq(data->irq); + + error = mxt_process_messages_until_invalid(data); + if (error) + return error; + + return 0; +} + +static int mxt_get_info(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct mxt_info *info = &data->info; + int error; + + /* Read 7-byte info block starting at address 0 */ + error = __mxt_read_reg(client, MXT_INFO, sizeof(*info), info); + if (error) + return error; + + return 0; +} + +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) +{ + kfree(data->object_table); + data->object_table = 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->T9_reportid_min = 0; + data->T9_reportid_max = 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_get_object_table(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + size_t table_size; + struct mxt_object *object_table; + int error; + int i; + u8 reportid; + u16 end_address; + + table_size = data->info.object_num * sizeof(struct mxt_object); + object_table = kzalloc(table_size, GFP_KERNEL); + if (!object_table) { + dev_err(&data->client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + error = __mxt_read_reg(client, MXT_OBJECT_START, table_size, + object_table); + if (error) { + kfree(object_table); + return error; + } + + /* 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_TOUCH_MULTI_T9: + data->multitouch = MXT_TOUCH_MULTI_T9; + data->T9_reportid_min = min_id; + data->T9_reportid_max = max_id; + data->num_touchids = object->num_report_ids + * mxt_obj_instances(object); + 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"); + error = -EINVAL; + goto free_object_table; + } + + data->msg_buf = kcalloc(data->max_reportid, + data->T5_msg_size, GFP_KERNEL); + if (!data->msg_buf) { + dev_err(&client->dev, "Failed to allocate message buffer\n"); + error = -ENOMEM; + goto free_object_table; + } + + data->object_table = object_table; + + return 0; + +free_object_table: + mxt_free_object_table(data); + 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_RANGE, + sizeof(range), &range); + if (error) + return error; + + le16_to_cpus(&range.x); + le16_to_cpus(&range.y); + + error = __mxt_read_reg(client, + object->start_address + MXT_T9_ORIENT, + 1, &orient); + if (error) + return error; + + /* Handle default values */ + if (range.x == 0) + range.x = 1023; + + if (range.y == 0) + range.y = 1023; + + if (orient & MXT_T9_ORIENT_SWITCH) { + data->max_x = range.y; + data->max_y = range.x; + } else { + data->max_x = range.x; + data->max_y = range.y; + } + + dev_dbg(&client->dev, + "Touchscreen size X%uY%u\n", data->max_x, data->max_y); + + 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; + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_XRANGE, + sizeof(range_x), &range_x); + if (error) + return error; + + le16_to_cpus(&range_x); + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_YRANGE, + sizeof(range_y), &range_y); + if (error) + return error; + + le16_to_cpus(&range_y); + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_CFG1, + 1, &cfg); + if (error) + return error; + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_TCHAUX, + 1, &tchaux); + if (error) + return error; + + /* Handle default values */ + if (range_x == 0) + range_x = 1023; + + if (range_y == 0) + range_y = 1023; + + if (cfg & MXT_T100_CFG_SWITCHXY) { + data->max_x = range_y; + data->max_y = range_x; + } else { + data->max_x = range_x; + data->max_y = range_y; + } + + /* allocate aux bytes */ + 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); + + dev_info(&client->dev, + "T100 Touchscreen size X%uY%u\n", data->max_x, data->max_y); + + 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) +{ + const struct mxt_platform_data *pdata = data->pdata; + 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 < pdata->t19_num_keys; i++) + if (pdata->t19_keymap[i] != KEY_RESERVED) + input_set_capability(input_dev, EV_KEY, + pdata->t19_keymap[i]); +} + +static int mxt_initialize_input_device(struct mxt_data *data) +{ + const struct mxt_platform_data *pdata = data->pdata; + 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; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(dev, "Failed to allocate memory\n"); + 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 (pdata->t19_num_keys) { + mxt_set_up_as_touchpad(input_dev, data); + mt_flags |= INPUT_MT_POINTER; + } + + /* 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_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); + } + + 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_get_info(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); + } + + /* Get object table information */ + error = mxt_get_object_table(data); + if (error) { + dev_err(&client->dev, "Error %d reading object table\n", error); + return error; + } + + error = mxt_acquire_irq(data); + if (error) + goto err_free_object_table; + + error = reject_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); + goto err_free_object_table; + } + + return 0; + +err_free_object_table: + mxt_free_object_table(data); + return error; +} + +static int mxt_configure_objects(struct mxt_data *data, + const struct firmware *cfg) +{ + struct device *dev = &data->client->dev; + struct mxt_info *info = &data->info; + int 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"); + } + + dev_info(dev, + "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u\n", + info->family_id, info->variant_id, info->version >> 4, + info->version & 0xf, info->build, info->object_num); + + 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: + /*(DEBLOBBED)*/ + 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 = reject_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) +{ + /* Touch enable */ + mxt_write_object(data, data->multitouch, MXT_TOUCH_CTRL, 0x83); +} + +static void mxt_stop(struct mxt_data *data) +{ + /* Touch disable */ + mxt_write_object(data, data->multitouch, MXT_TOUCH_CTRL, 0); +} + +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); +} + +#ifdef CONFIG_OF +static const struct mxt_platform_data *mxt_parse_dt(struct i2c_client *client) +{ + struct mxt_platform_data *pdata; + u32 *keymap; + u32 keycode; + int proplen, i, ret; + + if (!client->dev.of_node) + return ERR_PTR(-ENOENT); + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + if (of_find_property(client->dev.of_node, "linux,gpio-keymap", + &proplen)) { + pdata->t19_num_keys = proplen / sizeof(u32); + + keymap = devm_kzalloc(&client->dev, + pdata->t19_num_keys * sizeof(keymap[0]), + GFP_KERNEL); + if (!keymap) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < pdata->t19_num_keys; i++) { + ret = of_property_read_u32_index(client->dev.of_node, + "linux,gpio-keymap", i, &keycode); + if (ret) + keycode = KEY_RESERVED; + + keymap[i] = keycode; + } + + pdata->t19_keymap = keymap; + } + + return pdata; +} +#else +static const struct mxt_platform_data *mxt_parse_dt(struct i2c_client *client) +{ + return ERR_PTR(-ENOENT); +} +#endif + +#ifdef CONFIG_ACPI + +struct mxt_acpi_platform_data { + const char *hid; + struct mxt_platform_data pdata; +}; + +static unsigned int samus_touchpad_buttons[] = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + BTN_LEFT +}; + +static struct mxt_acpi_platform_data samus_platform_data[] = { + { + /* Touchpad */ + .hid = "ATML0000", + .pdata = { + .t19_num_keys = ARRAY_SIZE(samus_touchpad_buttons), + .t19_keymap = samus_touchpad_buttons, + }, + }, + { + /* Touchscreen */ + .hid = "ATML0001", + }, + { } +}; + +static const struct dmi_system_id mxt_dmi_table[] = { + { + /* 2015 Google Pixel */ + .ident = "Chromebook Pixel 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Samus"), + }, + .driver_data = samus_platform_data, + }, + { } +}; + +static const struct mxt_platform_data *mxt_parse_acpi(struct i2c_client *client) +{ + struct acpi_device *adev; + const struct dmi_system_id *system_id; + const struct mxt_acpi_platform_data *acpi_pdata; + + /* + * 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 (client->addr < 0x40) + return ERR_PTR(-ENXIO); + + adev = ACPI_COMPANION(&client->dev); + if (!adev) + return ERR_PTR(-ENOENT); + + system_id = dmi_first_match(mxt_dmi_table); + if (!system_id) + return ERR_PTR(-ENOENT); + + acpi_pdata = system_id->driver_data; + if (!acpi_pdata) + return ERR_PTR(-ENOENT); + + while (acpi_pdata->hid) { + if (!strcmp(acpi_device_hid(adev), acpi_pdata->hid)) + return &acpi_pdata->pdata; + + acpi_pdata++; + } + + return ERR_PTR(-ENOENT); +} +#else +static const struct mxt_platform_data *mxt_parse_acpi(struct i2c_client *client) +{ + return ERR_PTR(-ENOENT); +} +#endif + +static const struct mxt_platform_data * +mxt_get_platform_data(struct i2c_client *client) +{ + const struct mxt_platform_data *pdata; + + pdata = dev_get_platdata(&client->dev); + if (pdata) + return pdata; + + pdata = mxt_parse_dt(client); + if (!IS_ERR(pdata) || PTR_ERR(pdata) != -ENOENT) + return pdata; + + pdata = mxt_parse_acpi(client); + if (!IS_ERR(pdata) || PTR_ERR(pdata) != -ENOENT) + return pdata; + + dev_err(&client->dev, "No platform data specified\n"); + return ERR_PTR(-EINVAL); +} + +static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct mxt_data *data; + const struct mxt_platform_data *pdata; + int error; + + pdata = mxt_get_platform_data(client); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + data = kzalloc(sizeof(struct mxt_data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0", + client->adapter->nr, client->addr); + + data->client = client; + data->pdata = pdata; + data->irq = client->irq; + i2c_set_clientdata(client, data); + + init_completion(&data->bl_completion); + init_completion(&data->reset_completion); + init_completion(&data->crc_completion); + + error = request_threaded_irq(client->irq, NULL, mxt_interrupt, + pdata->irqflags | IRQF_ONESHOT, + client->name, data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_free_mem; + } + + disable_irq(client->irq); + + error = mxt_initialize(data); + if (error) + goto err_free_irq; + + 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_free_irq: + free_irq(client->irq, data); +err_free_mem: + kfree(data); + return error; +} + +static int mxt_remove(struct i2c_client *client) +{ + struct mxt_data *data = i2c_get_clientdata(client); + + sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); + free_irq(data->irq, data); + mxt_free_input_device(data); + mxt_free_object_table(data); + kfree(data); + + return 0; +} + +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; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + mxt_stop(data); + + mutex_unlock(&input_dev->mutex); + + 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; + + mxt_soft_reset(data); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + 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", }, + {}, +}; +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 }, + { "mXT224", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxt_id); + +static struct i2c_driver mxt_driver = { + .driver = { + .name = "atmel_mxt_ts", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(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..40e02dd5b --- /dev/null +++ b/drivers/input/touchscreen/auo-pixcir-ts.c @@ -0,0 +1,702 @@ +/* + * 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. + * + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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/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.h> +#include <linux/input/auo-pixcir-ts.h> +#include <linux/of.h> +#include <linux/of_gpio.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) +#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; + const struct auo_pixcir_ts_platdata *pdata; + char phys[32]; + + /* special handling for touch_indicate interupt 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; + const struct auo_pixcir_ts_platdata *pdata = ts->pdata; + 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 > pdata->x_max || + point[i].coord_y > pdata->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; + const struct auo_pixcir_ts_platdata *pdata = ts->pdata; + 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 (gpio_get_value(pdata->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; + const struct auo_pixcir_ts_platdata *pdata = ts->pdata; + 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 = pdata->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); + int ret; + + ret = auo_pixcir_start(ts); + if (ret) + return ret; + + return 0; +} + +static void auo_pixcir_input_close(struct input_dev *dev) +{ + struct auo_pixcir_ts *ts = input_get_drvdata(dev); + + auo_pixcir_stop(ts); + + return; +} + +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->users) { + 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->users) { + 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->users) { + ret = auo_pixcir_stop(ts); + if (ret) + goto unlock; + } + + /* device wakes automatically from SLEEP */ + } else if (input->users) { + 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); + +#ifdef CONFIG_OF +static struct auo_pixcir_ts_platdata *auo_pixcir_parse_dt(struct device *dev) +{ + struct auo_pixcir_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); + } + + pdata->gpio_int = of_get_gpio(np, 0); + if (!gpio_is_valid(pdata->gpio_int)) { + dev_err(dev, "failed to get interrupt gpio\n"); + return ERR_PTR(-EINVAL); + } + + pdata->gpio_rst = of_get_gpio(np, 1); + if (!gpio_is_valid(pdata->gpio_rst)) { + dev_err(dev, "failed to get reset gpio\n"); + return ERR_PTR(-EINVAL); + } + + 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); + } + + /* default to asserting the interrupt when the screen is touched */ + pdata->int_setting = AUO_PIXCIR_INT_TOUCH_IND; + + return pdata; +} +#else +static struct auo_pixcir_ts_platdata *auo_pixcir_parse_dt(struct device *dev) +{ + return ERR_PTR(-EINVAL); +} +#endif + +static void auo_pixcir_reset(void *data) +{ + struct auo_pixcir_ts *ts = data; + + gpio_set_value(ts->pdata->gpio_rst, 0); +} + +static int auo_pixcir_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct auo_pixcir_ts_platdata *pdata; + struct auo_pixcir_ts *ts; + struct input_dev *input_dev; + int version; + int error; + + pdata = dev_get_platdata(&client->dev); + if (!pdata) { + pdata = auo_pixcir_parse_dt(&client->dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + ts = devm_kzalloc(&client->dev, + sizeof(struct auo_pixcir_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->pdata = pdata; + 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)); + + 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, pdata->x_max, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, pdata->y_max, 0, 0); + + /* 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, + 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); + + error = devm_gpio_request_one(&client->dev, pdata->gpio_int, + GPIOF_DIR_IN, "auo_pixcir_ts_int"); + if (error) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_int, error); + return error; + } + + error = devm_gpio_request_one(&client->dev, pdata->gpio_rst, + GPIOF_DIR_OUT | GPIOF_INIT_HIGH, + "auo_pixcir_ts_rst"); + if (error) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_rst, error); + return error; + } + + error = devm_add_action(&client->dev, auo_pixcir_reset, ts); + if (error) { + auo_pixcir_reset(ts); + 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); + + error = auo_pixcir_int_config(ts, pdata->int_setting); + if (error) + return error; + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, auo_pixcir_interrupt, + IRQF_TRIGGER_RISING | 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 = { + .owner = THIS_MODULE, + .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..ae460a5c9 --- /dev/null +++ b/drivers/input/touchscreen/bcm_iproc_tsc.c @@ -0,0 +1,522 @@ +/* +* Copyright (C) 2015 Broadcom Corporation +* +* 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/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> + +#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) \ + dev_dbg(dev, "%20s= 0x%08x\n", #reg, readl((priv)->regs + reg)) + +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; + + void __iomem *regs; + 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; + + intr_status = readl(priv->regs + INTERRUPT_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 */ + writel(intr_status, priv->regs + INTERRUPT_STATUS); + + /* Pen up/down */ + if (intr_status & TS_PEN_INTR_MASK) { + if (readl(priv->regs + CONTROLLER_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++) { + raw_coordinate = readl(priv->regs + FIFO_DATA); + 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) +{ + struct iproc_ts_priv *priv = input_get_drvdata(idev); + u32 val; + int error; + + /* 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; + writel(val, priv->regs + INTERRUPT_MASK); + + writel(priv->cfg_params.fifo_threshold, priv->regs + INTERRUPT_THRES); + + /* 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; + writel(val, priv->regs + REGCTL1); + + /* Try to clear all interrupt status */ + val = readl(priv->regs + INTERRUPT_STATUS); + val |= TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK; + writel(val, priv->regs + INTERRUPT_STATUS); + + /* Initialize control reg2 */ + val = readl(priv->regs + REGCTL2); + val |= TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT; + + val &= ~TS_CONTROLLER_AVGDATA_MASK; + val |= priv->cfg_params.average_data << TS_CONTROLLER_AVGDATA_SHIFT; + + val &= ~(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 */ + + writel(val, priv->regs + REGCTL2); + + 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); + + writel(0, priv->regs + INTERRUPT_MASK); /* Disable all interrupts */ + + /* Only power down touch screen controller */ + val = readl(priv->regs + REGCTL2); + val |= TS_CONTROLLER_PWR_TS; + writel(val, priv->regs + REGCTL2); + + 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; + struct resource *res; + int irq; + int error; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* touchscreen controller memory mapped regs */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->regs)) { + error = PTR_ERR(priv->regs); + 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) { + dev_err(&pdev->dev, "platform_get_irq failed: %d\n", irq); + 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..b9b5ddad6 --- /dev/null +++ b/drivers/input/touchscreen/bu21013_ts.c @@ -0,0 +1,733 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson + * License terms:GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/input/bu21013.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_gpio.h> + +#define PEN_DOWN_INTR 0 +#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_data - touch panel data structure + * @client: pointer to the i2c client + * @wait: variable to wait_queue_head_t structure + * @touch_stopped: touch stop flag + * @chip: pointer to the touch panel controller + * @in_dev: pointer to the input device structure + * @intr_pin: interrupt pin value + * @regulator: pointer to the Regulator used for touch screen + * + * Touch panel device data structure + */ +struct bu21013_ts_data { + struct i2c_client *client; + wait_queue_head_t wait; + const struct bu21013_platform_device *chip; + struct input_dev *in_dev; + struct regulator *regulator; + unsigned int irq; + unsigned int intr_pin; + bool touch_stopped; +}; + +/** + * bu21013_read_block_data(): read the touch co-ordinates + * @data: bu21013_ts_data structure pointer + * @buf: byte pointer + * + * Read the touch co-ordinates using i2c read block into buffer + * and returns integer. + */ +static int bu21013_read_block_data(struct bu21013_ts_data *data, u8 *buf) +{ + int ret, i; + + for (i = 0; i < I2C_RETRY_COUNT; i++) { + ret = i2c_smbus_read_i2c_block_data + (data->client, BU21013_SENSORS_BTN_0_7_REG, + LENGTH_OF_BUFFER, buf); + if (ret == LENGTH_OF_BUFFER) + return 0; + } + return -EINVAL; +} + +/** + * bu21013_do_touch_report(): Get the touch co-ordinates + * @data: bu21013_ts_data structure pointer + * + * Get the touch co-ordinates from touch sensor registers and writes + * into device structure and returns integer. + */ +static int bu21013_do_touch_report(struct bu21013_ts_data *data) +{ + u8 buf[LENGTH_OF_BUFFER]; + unsigned int pos_x[2], pos_y[2]; + bool has_x_sensors, has_y_sensors; + int finger_down_count = 0; + int i; + + if (data == NULL) + return -EINVAL; + + if (bu21013_read_block_data(data, 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 *p = &buf[4 * i + 3]; + unsigned int x = p[0] << SHIFT_2 | (p[1] & MASK_BITS); + unsigned int y = p[2] << SHIFT_2 | (p[3] & MASK_BITS); + if (x == 0 || y == 0) + continue; + pos_x[finger_down_count] = x; + pos_y[finger_down_count] = y; + finger_down_count++; + } + + if (finger_down_count) { + if (finger_down_count == 2 && + (abs(pos_x[0] - pos_x[1]) < DELTA_MIN || + abs(pos_y[0] - pos_y[1]) < DELTA_MIN)) { + return 0; + } + + for (i = 0; i < finger_down_count; i++) { + if (data->chip->x_flip) + pos_x[i] = data->chip->touch_x_max - pos_x[i]; + if (data->chip->y_flip) + pos_y[i] = data->chip->touch_y_max - pos_y[i]; + + input_report_abs(data->in_dev, + ABS_MT_POSITION_X, pos_x[i]); + input_report_abs(data->in_dev, + ABS_MT_POSITION_Y, pos_y[i]); + input_mt_sync(data->in_dev); + } + } else + input_mt_sync(data->in_dev); + + input_sync(data->in_dev); + + return 0; +} +/** + * bu21013_gpio_irq() - gpio thread function for touch interrupt + * @irq: irq value + * @device_data: void pointer + * + * This gpio thread function for touch interrupt + * and returns irqreturn_t. + */ +static irqreturn_t bu21013_gpio_irq(int irq, void *device_data) +{ + struct bu21013_ts_data *data = device_data; + struct i2c_client *i2c = data->client; + int retval; + + do { + retval = bu21013_do_touch_report(data); + if (retval < 0) { + dev_err(&i2c->dev, "bu21013_do_touch_report failed\n"); + return IRQ_NONE; + } + + data->intr_pin = gpio_get_value(data->chip->touch_pin); + if (data->intr_pin == PEN_DOWN_INTR) + wait_event_timeout(data->wait, data->touch_stopped, + msecs_to_jiffies(2)); + } while (!data->intr_pin && !data->touch_stopped); + + return IRQ_HANDLED; +} + +/** + * bu21013_init_chip() - power on sequence for the bu21013 controller + * @data: device structure pointer + * + * This function is used to power on + * the bu21013 controller and returns integer. + */ +static int bu21013_init_chip(struct bu21013_ts_data *data) +{ + int retval; + struct i2c_client *i2c = data->client; + + retval = i2c_smbus_write_byte_data(i2c, BU21013_RESET_REG, + BU21013_RESET_ENABLE); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_RESET reg write failed\n"); + return retval; + } + msleep(RESET_DELAY); + + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_0_7_REG, + BU21013_SENSORS_EN_0_7); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_SENSOR_0_7 reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_8_15_REG, + BU21013_SENSORS_EN_8_15); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_SENSOR_8_15 reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_16_23_REG, + BU21013_SENSORS_EN_16_23); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_SENSOR_16_23 reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE1_REG, + (BU21013_POS_MODE1_0 | BU21013_POS_MODE1_1)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_POS_MODE1 reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, 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 (retval < 0) { + dev_err(&i2c->dev, "BU21013_POS_MODE2 reg write failed\n"); + return retval; + } + + if (data->chip->ext_clk) + retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE_REG, + (BU21013_CLK_MODE_EXT | BU21013_CLK_MODE_CALIB)); + else + retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE_REG, + (BU21013_CLK_MODE_DIV | BU21013_CLK_MODE_CALIB)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_CLK_MODE reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_IDLE_REG, + (BU21013_IDLET_0 | BU21013_IDLE_INTERMIT_EN)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_IDLE reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_MODE_REG, + BU21013_INT_MODE_LEVEL); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_INT_MODE reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_FILTER_REG, + (BU21013_DELTA_0_6 | + BU21013_FILTER_EN)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_FILTER reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_ON_REG, + BU21013_TH_ON_5); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_TH_ON reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_OFF_REG, + BU21013_TH_OFF_4 | BU21013_TH_OFF_3); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_TH_OFF reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_GAIN_REG, + (BU21013_GAIN_0 | BU21013_GAIN_1)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_GAIN reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_OFFSET_MODE_REG, + BU21013_OFFSET_MODE_DEFAULT); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_OFFSET_MODE reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_XY_EDGE_REG, + (BU21013_X_EDGE_0 | BU21013_X_EDGE_2 | + BU21013_Y_EDGE_1 | BU21013_Y_EDGE_3)); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_XY_EDGE reg write failed\n"); + return retval; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_DONE_REG, + BU21013_DONE); + if (retval < 0) { + dev_err(&i2c->dev, "BU21013_REG_DONE reg write failed\n"); + return retval; + } + + return 0; +} + +/** + * bu21013_free_irq() - frees IRQ registered for touchscreen + * @bu21013_data: device structure pointer + * + * This function signals interrupt thread to stop processing and + * frees interrupt. + */ +static void bu21013_free_irq(struct bu21013_ts_data *bu21013_data) +{ + bu21013_data->touch_stopped = true; + wake_up(&bu21013_data->wait); + free_irq(bu21013_data->irq, bu21013_data); +} + +/** + * bu21013_cs_disable() - deconfigures the touch panel controller + * @bu21013_data: device structure pointer + * + * This function is used to deconfigure the chip selection + * for touch panel controller. + */ +static void bu21013_cs_disable(struct bu21013_ts_data *bu21013_data) +{ + int error; + + error = gpio_direction_output(bu21013_data->chip->cs_pin, 0); + if (error < 0) + dev_warn(&bu21013_data->client->dev, + "%s: gpio direction failed, error: %d\n", + __func__, error); + else + gpio_set_value(bu21013_data->chip->cs_pin, 0); + + gpio_free(bu21013_data->chip->cs_pin); +} + +#ifdef CONFIG_OF +static const struct bu21013_platform_device * +bu21013_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct bu21013_platform_device *pdata; + + if (!np) { + dev_err(dev, "no device tree or platform data\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->y_flip = pdata->x_flip = false; + + pdata->x_flip = of_property_read_bool(np, "rohm,flip-x"); + pdata->y_flip = of_property_read_bool(np, "rohm,flip-y"); + + of_property_read_u32(np, "rohm,touch-max-x", &pdata->touch_x_max); + of_property_read_u32(np, "rohm,touch-max-y", &pdata->touch_y_max); + + pdata->touch_pin = of_get_named_gpio(np, "touch-gpio", 0); + pdata->cs_pin = of_get_named_gpio(np, "reset-gpio", 0); + + pdata->ext_clk = false; + + return pdata; +} +#else +static inline const struct bu21013_platform_device * +bu21013_parse_dt(struct device *dev) +{ + dev_err(dev, "no platform data available\n"); + return ERR_PTR(-EINVAL); +} +#endif + +/** + * bu21013_probe() - initializes the i2c-client touchscreen driver + * @client: i2c client structure pointer + * @id: i2c device id pointer + * + * This function used to initializes the i2c-client touchscreen + * driver and returns integer. + */ +static int bu21013_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct bu21013_platform_device *pdata = + dev_get_platdata(&client->dev); + struct bu21013_ts_data *bu21013_data; + struct input_dev *in_dev; + 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 (!pdata) { + pdata = bu21013_parse_dt(&client->dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + if (!gpio_is_valid(pdata->touch_pin)) { + dev_err(&client->dev, "invalid touch_pin supplied\n"); + return -EINVAL; + } + + bu21013_data = kzalloc(sizeof(struct bu21013_ts_data), GFP_KERNEL); + in_dev = input_allocate_device(); + if (!bu21013_data || !in_dev) { + dev_err(&client->dev, "device memory alloc failed\n"); + error = -ENOMEM; + goto err_free_mem; + } + + bu21013_data->in_dev = in_dev; + bu21013_data->chip = pdata; + bu21013_data->client = client; + bu21013_data->irq = gpio_to_irq(pdata->touch_pin); + + bu21013_data->regulator = regulator_get(&client->dev, "avdd"); + if (IS_ERR(bu21013_data->regulator)) { + dev_err(&client->dev, "regulator_get failed\n"); + error = PTR_ERR(bu21013_data->regulator); + goto err_free_mem; + } + + error = regulator_enable(bu21013_data->regulator); + if (error < 0) { + dev_err(&client->dev, "regulator enable failed\n"); + goto err_put_regulator; + } + + bu21013_data->touch_stopped = false; + init_waitqueue_head(&bu21013_data->wait); + + /* configure the gpio pins */ + error = gpio_request_one(pdata->cs_pin, GPIOF_OUT_INIT_HIGH, + "touchp_reset"); + if (error < 0) { + dev_err(&client->dev, "Unable to request gpio reset_pin\n"); + goto err_disable_regulator; + } + + /* configure the touch panel controller */ + error = bu21013_init_chip(bu21013_data); + if (error) { + dev_err(&client->dev, "error in bu21013 config\n"); + goto err_cs_disable; + } + + /* register the device to input subsystem */ + in_dev->name = DRIVER_TP; + in_dev->id.bustype = BUS_I2C; + in_dev->dev.parent = &client->dev; + + __set_bit(EV_SYN, in_dev->evbit); + __set_bit(EV_KEY, in_dev->evbit); + __set_bit(EV_ABS, in_dev->evbit); + + input_set_abs_params(in_dev, ABS_MT_POSITION_X, 0, + pdata->touch_x_max, 0, 0); + input_set_abs_params(in_dev, ABS_MT_POSITION_Y, 0, + pdata->touch_y_max, 0, 0); + input_set_drvdata(in_dev, bu21013_data); + + error = request_threaded_irq(bu21013_data->irq, NULL, bu21013_gpio_irq, + IRQF_TRIGGER_FALLING | IRQF_SHARED | + IRQF_ONESHOT, + DRIVER_TP, bu21013_data); + if (error) { + dev_err(&client->dev, "request irq %d failed\n", + bu21013_data->irq); + goto err_cs_disable; + } + + error = input_register_device(in_dev); + if (error) { + dev_err(&client->dev, "failed to register input device\n"); + goto err_free_irq; + } + + device_init_wakeup(&client->dev, pdata->wakeup); + i2c_set_clientdata(client, bu21013_data); + + return 0; + +err_free_irq: + bu21013_free_irq(bu21013_data); +err_cs_disable: + bu21013_cs_disable(bu21013_data); +err_disable_regulator: + regulator_disable(bu21013_data->regulator); +err_put_regulator: + regulator_put(bu21013_data->regulator); +err_free_mem: + input_free_device(in_dev); + kfree(bu21013_data); + + return error; +} +/** + * bu21013_remove() - removes the i2c-client touchscreen driver + * @client: i2c client structure pointer + * + * This function uses to remove the i2c-client + * touchscreen driver and returns integer. + */ +static int bu21013_remove(struct i2c_client *client) +{ + struct bu21013_ts_data *bu21013_data = i2c_get_clientdata(client); + + bu21013_free_irq(bu21013_data); + + bu21013_cs_disable(bu21013_data); + + input_unregister_device(bu21013_data->in_dev); + + regulator_disable(bu21013_data->regulator); + regulator_put(bu21013_data->regulator); + + kfree(bu21013_data); + + device_init_wakeup(&client->dev, false); + + return 0; +} + +#ifdef CONFIG_PM +/** + * bu21013_suspend() - suspend the touch screen controller + * @dev: pointer to device structure + * + * This function is used to suspend the + * touch panel controller and returns integer + */ +static int bu21013_suspend(struct device *dev) +{ + struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); + struct i2c_client *client = bu21013_data->client; + + bu21013_data->touch_stopped = true; + if (device_may_wakeup(&client->dev)) + enable_irq_wake(bu21013_data->irq); + else + disable_irq(bu21013_data->irq); + + regulator_disable(bu21013_data->regulator); + + return 0; +} + +/** + * bu21013_resume() - resume the touch screen controller + * @dev: pointer to device structure + * + * This function is used to resume the touch panel + * controller and returns integer. + */ +static int bu21013_resume(struct device *dev) +{ + struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); + struct i2c_client *client = bu21013_data->client; + int retval; + + retval = regulator_enable(bu21013_data->regulator); + if (retval < 0) { + dev_err(&client->dev, "bu21013 regulator enable failed\n"); + return retval; + } + + retval = bu21013_init_chip(bu21013_data); + if (retval < 0) { + dev_err(&client->dev, "bu21013 controller config failed\n"); + return retval; + } + + bu21013_data->touch_stopped = false; + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(bu21013_data->irq); + else + enable_irq(bu21013_data->irq); + + return 0; +} + +static const struct dev_pm_ops bu21013_dev_pm_ops = { + .suspend = bu21013_suspend, + .resume = bu21013_resume, +}; +#endif + +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, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &bu21013_dev_pm_ops, +#endif + }, + .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/chipone_icn8318.c b/drivers/input/touchscreen/chipone_icn8318.c new file mode 100644 index 000000000..32e9db0e0 --- /dev/null +++ b/drivers/input/touchscreen/chipone_icn8318.c @@ -0,0 +1,316 @@ +/* + * Driver for ChipOne icn8318 i2c touchscreen controller + * + * Copyright (c) 2015 Red Hat Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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/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; + u32 max_x; + u32 max_y; + bool invert_x; + bool invert_y; + bool swap_x_y; +}; + +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 = ® + }, + { + .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, x, y; + + 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; + + x = be16_to_cpu(touch->x); + y = be16_to_cpu(touch->y); + + if (data->invert_x) + x = data->max_x - x; + + if (data->invert_y) + y = data->max_y - y; + + if (!data->swap_x_y) { + input_event(data->input, EV_ABS, ABS_MT_POSITION_X, x); + input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, y); + } else { + input_event(data->input, EV_ABS, ABS_MT_POSITION_X, y); + input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, x); + } + } + + 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 (data->input->users) + 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 (data->input->users) + 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 device_node *np = dev->of_node; + struct icn8318_data *data; + struct input_dev *input; + u32 fuzz_x = 0, fuzz_y = 0; + 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; + } + + if (of_property_read_u32(np, "touchscreen-size-x", &data->max_x) || + of_property_read_u32(np, "touchscreen-size-y", &data->max_y)) { + dev_err(dev, "Error touchscreen-size-x and/or -y missing\n"); + return -EINVAL; + } + + /* Optional */ + of_property_read_u32(np, "touchscreen-fuzz-x", &fuzz_x); + of_property_read_u32(np, "touchscreen-fuzz-y", &fuzz_y); + data->invert_x = of_property_read_bool(np, "touchscreen-inverted-x"); + data->invert_y = of_property_read_bool(np, "touchscreen-inverted-y"); + data->swap_x_y = of_property_read_bool(np, "touchscreen-swapped-x-y"); + + 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; + + if (!data->swap_x_y) { + input_set_abs_params(input, ABS_MT_POSITION_X, 0, + data->max_x, fuzz_x, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, + data->max_y, fuzz_y, 0); + } else { + input_set_abs_params(input, ABS_MT_POSITION_X, 0, + data->max_y, fuzz_y, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, + data->max_x, fuzz_x, 0); + } + + 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 = { + .owner = THIS_MODULE, + .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/cy8ctmg110_ts.c b/drivers/input/touchscreen/cy8ctmg110_ts.c new file mode 100644 index 000000000..f2119ee0e --- /dev/null +++ b/drivers/input/touchscreen/cy8ctmg110_ts.c @@ -0,0 +1,363 @@ +/* + * Driver for cypress touch screen controller + * + * Copyright (c) 2009 Aava Mobile + * + * Some cleanups by Alan Cox <alan@linux.intel.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/input/cy8ctmg110_pdata.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; + int reset_pin; + int irq_pin; +}; + +/* + * cy8ctmg110_power is the routine that is called when touch hardware + * will powered off or on. + */ +static void cy8ctmg110_power(struct cy8ctmg110 *ts, bool poweron) +{ + if (ts->reset_pin) + gpio_direction_output(ts->reset_pin, 1 - 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]; + int x, y; + + memset(reg_p, 0, CY8CTMG110_REG_MAX); + + /* Reading coordinates */ + if (cy8ctmg110_read_regs(tsc, reg_p, 9, CY8CTMG110_TOUCH_X1) != 0) + return -EIO; + + y = reg_p[2] << 8 | reg_p[3]; + x = reg_p[0] << 8 | reg_p[1]; + + /* 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, x); + input_report_abs(input, ABS_Y, y); + } + + 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 int cy8ctmg110_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct cy8ctmg110_pdata *pdata = dev_get_platdata(&client->dev); + struct cy8ctmg110 *ts; + struct input_dev *input_dev; + int err; + + /* No pdata no way forward */ + if (pdata == NULL) { + dev_err(&client->dev, "no pdata\n"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -EIO; + + ts = kzalloc(sizeof(struct cy8ctmg110), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + ts->client = client; + ts->input = input_dev; + ts->reset_pin = pdata->reset_pin; + ts->irq_pin = pdata->irq_pin; + + 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_dev->dev.parent = &client->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, + CY8CTMG110_X_MIN, CY8CTMG110_X_MAX, 4, 0); + input_set_abs_params(input_dev, ABS_Y, + CY8CTMG110_Y_MIN, CY8CTMG110_Y_MAX, 4, 0); + + if (ts->reset_pin) { + err = gpio_request(ts->reset_pin, NULL); + if (err) { + dev_err(&client->dev, + "Unable to request GPIO pin %d.\n", + ts->reset_pin); + goto err_free_mem; + } + } + + cy8ctmg110_power(ts, true); + cy8ctmg110_set_sleepmode(ts, false); + + err = gpio_request(ts->irq_pin, "touch_irq_key"); + if (err < 0) { + dev_err(&client->dev, + "Failed to request GPIO %d, error %d\n", + ts->irq_pin, err); + goto err_shutoff_device; + } + + err = gpio_direction_input(ts->irq_pin); + if (err < 0) { + dev_err(&client->dev, + "Failed to configure input direction for GPIO %d, error %d\n", + ts->irq_pin, err); + goto err_free_irq_gpio; + } + + client->irq = gpio_to_irq(ts->irq_pin); + if (client->irq < 0) { + err = client->irq; + dev_err(&client->dev, + "Unable to get irq number for GPIO %d, error %d\n", + ts->irq_pin, err); + goto err_free_irq_gpio; + } + + err = request_threaded_irq(client->irq, NULL, cy8ctmg110_irq_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "touch_reset_key", ts); + if (err < 0) { + dev_err(&client->dev, + "irq %d busy? error %d\n", client->irq, err); + goto err_free_irq_gpio; + } + + err = input_register_device(input_dev); + if (err) + goto err_free_irq; + + i2c_set_clientdata(client, ts); + device_init_wakeup(&client->dev, 1); + return 0; + +err_free_irq: + free_irq(client->irq, ts); +err_free_irq_gpio: + gpio_free(ts->irq_pin); +err_shutoff_device: + cy8ctmg110_set_sleepmode(ts, true); + cy8ctmg110_power(ts, false); + if (ts->reset_pin) + gpio_free(ts->reset_pin); +err_free_mem: + input_free_device(input_dev); + kfree(ts); + return err; +} + +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)) + enable_irq_wake(client->irq); + else { + 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)) + disable_irq_wake(client->irq); + else { + cy8ctmg110_power(ts, true); + cy8ctmg110_set_sleepmode(ts, false); + } + return 0; +} + +static SIMPLE_DEV_PM_OPS(cy8ctmg110_pm, cy8ctmg110_suspend, cy8ctmg110_resume); + +static int cy8ctmg110_remove(struct i2c_client *client) +{ + struct cy8ctmg110 *ts = i2c_get_clientdata(client); + + cy8ctmg110_set_sleepmode(ts, true); + cy8ctmg110_power(ts, false); + + free_irq(client->irq, ts); + input_unregister_device(ts->input); + gpio_free(ts->irq_pin); + if (ts->reset_pin) + gpio_free(ts->reset_pin); + kfree(ts); + + return 0; +} + +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 = { + .owner = THIS_MODULE, + .name = CY8CTMG110_DRIVER_NAME, + .pm = &cy8ctmg110_pm, + }, + .id_table = cy8ctmg110_idtable, + .probe = cy8ctmg110_probe, + .remove = cy8ctmg110_remove, +}; + +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..568a3d340 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp4_core.c @@ -0,0 +1,2160 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 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; + + 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: fail alloc 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; + + 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: fail alloc 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; + + 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) { + rc = -ENOMEM; + dev_err(cd->dev, "%s: fail alloc pcfg memory r=%d\n", + __func__, rc); + return rc; + } + 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; + + 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: fail alloc opcfg memory\n", __func__); + rc = -ENOMEM; + goto cyttsp4_si_get_opcfg_data_exit; + } + 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); + goto cyttsp4_si_get_opcfg_data_exit; + } + 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"); + +cyttsp4_si_get_opcfg_data_exit: + return rc; +} + +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_state(md->input, + MT_TOOL_FINGER, false); + } +} + +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; + int tmp; + 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) { + tmp = touch->abs[CY_TCH_X]; + touch->abs[CY_TCH_X] = touch->abs[CY_TCH_Y]; + touch->abs[CY_TCH_Y] = tmp; + 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_state(input, MT_TOOL_FINGER, false); + } + + 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(unsigned long handle) +{ + struct cyttsp4 *cd = (struct cyttsp4 *)handle; + + 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 BOOTLADER 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; + if (!md->si) { + dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, md->si); + goto error_get_sysinfo; + } + + rc = cyttsp4_setup_input_device(cd); + if (rc) + goto error_init_input; + + return 0; + +error_init_input: + input_free_device(md->input); +error_get_sysinfo: + input_set_drvdata(md->input, NULL); +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 */ + setup_timer(&cd->watchdog_timer, cyttsp4_watchdog_timer, + (unsigned long)cd); + + /* + * 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..8e0d4d490 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp4_core.h @@ -0,0 +1,472 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 +}; + +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" +}; + +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..8e2012c79 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp4_i2c.c @@ -0,0 +1,90 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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); + + if (IS_ERR(ts)) + return PTR_ERR(ts); + + return 0; +} + +static int cyttsp4_i2c_remove(struct i2c_client *client) +{ + struct cyttsp4 *ts = i2c_get_clientdata(client); + + cyttsp4_remove(ts); + + return 0; +} + +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, + .owner = THIS_MODULE, + .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"); +MODULE_ALIAS("i2c:cyttsp4"); diff --git a/drivers/input/touchscreen/cyttsp4_spi.c b/drivers/input/touchscreen/cyttsp4_spi.c new file mode 100644 index 000000000..b19434ceb --- /dev/null +++ b/drivers/input/touchscreen/cyttsp4_spi.c @@ -0,0 +1,200 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 int cyttsp4_spi_remove(struct spi_device *spi) +{ + struct cyttsp4 *ts = spi_get_drvdata(spi); + cyttsp4_remove(ts); + + return 0; +} + +static struct spi_driver cyttsp4_spi_driver = { + .driver = { + .name = CYTTSP4_SPI_NAME, + .owner = THIS_MODULE, + .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..5b74e8b84 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp_core.c @@ -0,0 +1,638 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * 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/gpio.h> +#include <linux/interrupt.h> +#include <linux/slab.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 +#define CY_ACT_DIST_DFLT 0xF8 +#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->pdata->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->pdata->bl_keys) + memcpy(&bl_cmd[sizeof(bl_command) - CY_NUM_BL_KEYS], + ts->pdata->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->pdata->act_intrvl != CY_ACT_INTRVL_DFLT || + ts->pdata->tch_tmout != CY_TCH_TMOUT_DFLT || + ts->pdata->lp_intrvl != CY_LP_INTRVL_DFLT) { + + u8 intrvl_ray[] = { + ts->pdata->act_intrvl, + ts->pdata->tch_tmout, + ts->pdata->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 int cyttsp_soft_reset(struct cyttsp *ts) +{ + unsigned long timeout; + 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) + goto out; + + timeout = wait_for_completion_timeout(&ts->bl_ready, + msecs_to_jiffies(CY_DELAY_DFLT * CY_DELAY_MAX)); + retval = timeout ? 0 : -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->pdata->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_state(input, MT_TOOL_FINGER, false); + } + + 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) + 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 (ts->input->users) { + 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 (ts->input->users) + 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); +} + +struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops, + struct device *dev, int irq, size_t xfer_buf_size) +{ + const struct cyttsp_platform_data *pdata = dev_get_platdata(dev); + struct cyttsp *ts; + struct input_dev *input_dev; + int error; + + if (!pdata || !pdata->name || irq <= 0) { + error = -EINVAL; + goto err_out; + } + + ts = kzalloc(sizeof(*ts) + xfer_buf_size, GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + ts->dev = dev; + ts->input = input_dev; + ts->pdata = dev_get_platdata(dev); + ts->bus_ops = bus_ops; + ts->irq = irq; + + init_completion(&ts->bl_ready); + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev)); + + if (pdata->init) { + error = pdata->init(); + if (error) { + dev_err(ts->dev, "platform init failed, err: %d\n", + error); + goto err_free_mem; + } + } + + input_dev->name = pdata->name; + input_dev->phys = ts->phys; + 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); + + __set_bit(EV_ABS, input_dev->evbit); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, pdata->maxx, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, pdata->maxy, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, CY_MAXZ, 0, 0); + + input_mt_init_slots(input_dev, CY_MAX_ID, 0); + + error = request_threaded_irq(ts->irq, NULL, cyttsp_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + pdata->name, ts); + if (error) { + dev_err(ts->dev, "failed to request IRQ %d, err: %d\n", + ts->irq, error); + goto err_platform_exit; + } + + disable_irq(ts->irq); + + error = cyttsp_power_on(ts); + if (error) + goto err_free_irq; + + error = input_register_device(input_dev); + if (error) { + dev_err(ts->dev, "failed to register input device: %d\n", + error); + goto err_free_irq; + } + + return ts; + +err_free_irq: + free_irq(ts->irq, ts); +err_platform_exit: + if (pdata->exit) + pdata->exit(); +err_free_mem: + input_free_device(input_dev); + kfree(ts); +err_out: + return ERR_PTR(error); +} +EXPORT_SYMBOL_GPL(cyttsp_probe); + +void cyttsp_remove(struct cyttsp *ts) +{ + free_irq(ts->irq, ts); + input_unregister_device(ts->input); + if (ts->pdata->exit) + ts->pdata->exit(); + kfree(ts); +} +EXPORT_SYMBOL_GPL(cyttsp_remove); + +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..07074110a --- /dev/null +++ b/drivers/input/touchscreen/cyttsp_core.h @@ -0,0 +1,154 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * 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/input/cyttsp.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; + char phys[32]; + const struct cyttsp_platform_data *pdata; + 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; + + 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); +void cyttsp_remove(struct cyttsp *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 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..63104a86a --- /dev/null +++ b/drivers/input/touchscreen/cyttsp_i2c.c @@ -0,0 +1,90 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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_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 int cyttsp_i2c_remove(struct i2c_client *client) +{ + struct cyttsp *ts = i2c_get_clientdata(client); + + cyttsp_remove(ts); + + return 0; +} + +static const struct i2c_device_id cyttsp_i2c_id[] = { + { CY_I2C_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cyttsp_i2c_id); + +static struct i2c_driver cyttsp_i2c_driver = { + .driver = { + .name = CY_I2C_NAME, + .owner = THIS_MODULE, + .pm = &cyttsp_pm_ops, + }, + .probe = cyttsp_i2c_probe, + .remove = cyttsp_i2c_remove, + .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"); +MODULE_ALIAS("i2c:cyttsp"); diff --git a/drivers/input/touchscreen/cyttsp_i2c_common.c b/drivers/input/touchscreen/cyttsp_i2c_common.c new file mode 100644 index 000000000..ccefa56ca --- /dev/null +++ b/drivers/input/touchscreen/cyttsp_i2c_common.c @@ -0,0 +1,95 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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..4728bcb19 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp_spi.c @@ -0,0 +1,197 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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_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 int cyttsp_spi_remove(struct spi_device *spi) +{ + struct cyttsp *ts = spi_get_drvdata(spi); + + cyttsp_remove(ts); + + return 0; +} + +static struct spi_driver cyttsp_spi_driver = { + .driver = { + .name = CY_SPI_NAME, + .owner = THIS_MODULE, + .pm = &cyttsp_pm_ops, + }, + .probe = cyttsp_spi_probe, + .remove = cyttsp_spi_remove, +}; + +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..8264822dc --- /dev/null +++ b/drivers/input/touchscreen/da9034-ts.c @@ -0,0 +1,368 @@ +/* + * 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> + * + * 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/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..5a013bb7b --- /dev/null +++ b/drivers/input/touchscreen/da9052_tsi.c @@ -0,0 +1,348 @@ +/* + * TSI driver for Dialog DA9052 + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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; either version 2 of the License, or (at your + * option) any later version. + * + */ +#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; + struct mutex mutex; + 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..86237a910 --- /dev/null +++ b/drivers/input/touchscreen/dynapro.c @@ -0,0 +1,190 @@ +/* + * Dynapro serial touchscreen driver + * + * Copyright (c) 2009 Tias Guns + * Based on the inexio driver (c) Vojtech Pavlik and Dan Streetman and + * Richard Lemon + * + */ + +/* + * 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. + */ + +/* + * 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 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..e6aef3e48 --- /dev/null +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -0,0 +1,1156 @@ +/* + * 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 software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * 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/module.h> +#include <linux/ratelimit.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> +#include <linux/input/edt-ft5x06.h> + +#define MAX_SUPPORT_POINTS 5 + +#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 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 NO_REGISTER 0xff + +#define WORK_REGISTER_OPMODE 0x3c +#define FACTORY_REGISTER_OPMODE 0x01 + +#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 1 /* msec */ + +enum edt_ver { + M06, + M09, +}; + +struct edt_reg_addr { + int reg_threshold; + int reg_report_rate; + int reg_gain; + int reg_offset; + int reg_num_x; + int reg_num_y; +}; + +struct edt_ft5x06_ts_data { + struct i2c_client *client; + struct input_dev *input; + u16 num_x; + u16 num_y; + + int reset_pin; + int irq_pin; + int wake_pin; + +#if defined(CONFIG_DEBUG_FS) + struct dentry *debug_dir; + u8 *raw_buffer; + size_t raw_bufsize; +#endif + + struct mutex mutex; + bool factory_mode; + int threshold; + int gain; + int offset; + int report_rate; + + char name[EDT_NAME_LEN]; + + struct edt_reg_addr reg_addr; + enum edt_ver version; +}; + +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]) { + 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[29]; + int i, type, x, y, id; + int offset, tplen, datalen; + int error; + + switch (tsdata->version) { + case 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 */ + datalen = 26; /* how much bytes to listen for */ + break; + + case M09: + cmd = 0x02; + offset = 1; + tplen = 6; + datalen = 29; + break; + + default: + goto out; + } + + memset(rdbuf, 0, sizeof(rdbuf)); + + 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 does not send header or CRC */ + if (tsdata->version == M06) { + if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa || + rdbuf[2] != datalen) { + 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 < MAX_SUPPORT_POINTS; i++) { + u8 *buf = &rdbuf[i * tplen + offset]; + bool down; + + type = buf[0] >> 6; + /* ignore Reserved events */ + if (type == TOUCH_EVENT_RESERVED) + continue; + + /* M06 sometimes sends bogus coordinates in TOUCH_DOWN */ + if (tsdata->version == M06 && type == TOUCH_EVENT_DOWN) + continue; + + x = ((buf[0] << 8) | buf[1]) & 0x0fff; + y = ((buf[2] << 8) | buf[3]) & 0x0fff; + id = (buf[2] >> 4) & 0x0f; + down = type != TOUCH_EVENT_UP; + + input_mt_slot(tsdata->input, id); + input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down); + + if (!down) + continue; + + input_report_abs(tsdata->input, ABS_MT_POSITION_X, x); + input_report_abs(tsdata->input, ABS_MT_POSITION_Y, y); + } + + 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 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 M09: + 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 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 M09: + 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; +}; + +#define EDT_ATTR(_field, _mode, _addr_m06, _addr_m09, \ + _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, \ + .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 M06: + addr = attr->addr_m06; + break; + + case M09: + addr = attr->addr_m09; + 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 M06: + addr = attr->addr_m06; + break; + + case M09: + addr = attr->addr_m09; + 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; +} + +static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN, + M09_REGISTER_GAIN, 0, 31); +static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET, + M09_REGISTER_OFFSET, 0, 31); +static EDT_ATTR(threshold, S_IWUSR | S_IRUGO, WORK_REGISTER_THRESHOLD, + M09_REGISTER_THRESHOLD, 20, 80); +static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, WORK_REGISTER_REPORT_RATE, + NO_REGISTER, 3, 14); + +static struct attribute *edt_ft5x06_attrs[] = { + &edt_ft5x06_attr_gain.dattr.attr, + &edt_ft5x06_attr_offset.dattr.attr, + &edt_ft5x06_attr_threshold.dattr.attr, + &edt_ft5x06_attr_report_rate.dattr.attr, + NULL +}; + +static const struct attribute_group edt_ft5x06_attr_group = { + .attrs = edt_ft5x06_attrs, +}; + +#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; + + 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 */ + if (tsdata->version == M09) + goto m09_out; + + 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; + +m09_out: + dev_err(&client->dev, "No factory mode support for M09\n"); + return -EINVAL; + +} + +static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata) +{ + struct i2c_client *client = tsdata->client; + int retries = EDT_SWITCH_MODE_RETRIES; + struct edt_reg_addr *reg_addr = &tsdata->reg_addr; + 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; + + /* restore parameters */ + edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold, + tsdata->threshold); + edt_ft5x06_register_write(tsdata, reg_addr->reg_gain, + tsdata->gain); + edt_ft5x06_register_write(tsdata, reg_addr->reg_offset, + tsdata->offset); + if (reg_addr->reg_report_rate) + edt_ft5x06_register_write(tsdata, reg_addr->reg_report_rate, + tsdata->report_rate); + + 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 { + msleep(EDT_RAW_DATA_DELAY); + 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); + if (!tsdata->debug_dir) + return; + + 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 inline void +edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, + const char *debugfs_name) +{ +} + +static inline void +edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) +{ +} + +#endif /* CONFIG_DEBUGFS */ + +static int edt_ft5x06_ts_reset(struct i2c_client *client, + struct edt_ft5x06_ts_data *tsdata) +{ + int error; + + if (gpio_is_valid(tsdata->wake_pin)) { + error = devm_gpio_request_one(&client->dev, + tsdata->wake_pin, GPIOF_OUT_INIT_LOW, + "edt-ft5x06 wake"); + if (error) { + dev_err(&client->dev, + "Failed to request GPIO %d as wake pin, error %d\n", + tsdata->wake_pin, error); + return error; + } + + msleep(5); + gpio_set_value(tsdata->wake_pin, 1); + } + if (gpio_is_valid(tsdata->reset_pin)) { + /* this pulls reset down, enabling the low active reset */ + error = devm_gpio_request_one(&client->dev, + tsdata->reset_pin, GPIOF_OUT_INIT_LOW, + "edt-ft5x06 reset"); + if (error) { + dev_err(&client->dev, + "Failed to request GPIO %d as reset pin, error %d\n", + tsdata->reset_pin, error); + return error; + } + + msleep(5); + gpio_set_value(tsdata->reset_pin, 1); + msleep(300); + } + + return 0; +} + +static int edt_ft5x06_ts_identify(struct i2c_client *client, + struct edt_ft5x06_ts_data *tsdata, + char *fw_version) +{ + u8 rdbuf[EDT_NAME_LEN]; + char *p; + int error; + char *model_name = tsdata->name; + + /* 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; + + /* if we find something consistent, stay with that assumption + * at least M09 won't send 3 bytes here + */ + if (!(strncasecmp(rdbuf + 1, "EP0", 3))) { + tsdata->version = 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'; + strlcpy(model_name, rdbuf + 1, EDT_NAME_LEN); + strlcpy(fw_version, p ? p : "", EDT_NAME_LEN); + } else { + /* since there are only two versions around (M06, M09) */ + tsdata->version = M09; + + error = edt_ft5x06_ts_readwrite(client, 1, "\xA6", + 2, rdbuf); + if (error) + return error; + + strlcpy(fw_version, rdbuf, 2); + + error = edt_ft5x06_ts_readwrite(client, 1, "\xA8", + 1, rdbuf); + if (error) + return error; + + snprintf(model_name, EDT_NAME_LEN, "EP0%i%i0M09", + rdbuf[0] >> 4, rdbuf[0] & 0x0F); + } + + return 0; +} + +#define EDT_ATTR_CHECKSET(name, reg) \ +do { \ + if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \ + pdata->name <= edt_ft5x06_attr_##name.limit_high) \ + edt_ft5x06_register_write(tsdata, reg, pdata->name); \ +} while (0) + +#define EDT_GET_PROP(name, reg) { \ + u32 val; \ + if (of_property_read_u32(np, #name, &val) == 0) \ + edt_ft5x06_register_write(tsdata, reg, val); \ +} + +static void edt_ft5x06_ts_get_dt_defaults(struct device_node *np, + struct edt_ft5x06_ts_data *tsdata) +{ + struct edt_reg_addr *reg_addr = &tsdata->reg_addr; + + EDT_GET_PROP(threshold, reg_addr->reg_threshold); + EDT_GET_PROP(gain, reg_addr->reg_gain); + EDT_GET_PROP(offset, reg_addr->reg_offset); +} + +static void +edt_ft5x06_ts_get_defaults(struct edt_ft5x06_ts_data *tsdata, + const struct edt_ft5x06_platform_data *pdata) +{ + struct edt_reg_addr *reg_addr = &tsdata->reg_addr; + + if (!pdata->use_parameters) + return; + + /* pick up defaults from the platform data */ + EDT_ATTR_CHECKSET(threshold, reg_addr->reg_threshold); + EDT_ATTR_CHECKSET(gain, reg_addr->reg_gain); + EDT_ATTR_CHECKSET(offset, reg_addr->reg_offset); + if (reg_addr->reg_report_rate != NO_REGISTER) + EDT_ATTR_CHECKSET(report_rate, reg_addr->reg_report_rate); +} + +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); + tsdata->offset = edt_ft5x06_register_read(tsdata, reg_addr->reg_offset); + 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_ft5x06_register_read(tsdata, reg_addr->reg_num_x); + 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 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_num_x = WORK_REGISTER_NUM_X; + reg_addr->reg_num_y = WORK_REGISTER_NUM_Y; + break; + + case M09: + reg_addr->reg_threshold = M09_REGISTER_THRESHOLD; + reg_addr->reg_gain = M09_REGISTER_GAIN; + reg_addr->reg_offset = M09_REGISTER_OFFSET; + reg_addr->reg_num_x = M09_REGISTER_NUM_X; + reg_addr->reg_num_y = M09_REGISTER_NUM_Y; + break; + } +} + +#ifdef CONFIG_OF +static int edt_ft5x06_i2c_ts_probe_dt(struct device *dev, + struct edt_ft5x06_ts_data *tsdata) +{ + struct device_node *np = dev->of_node; + + /* + * irq_pin is not needed for DT setup. + * irq is associated via 'interrupts' property in DT + */ + tsdata->irq_pin = -EINVAL; + tsdata->reset_pin = of_get_named_gpio(np, "reset-gpios", 0); + tsdata->wake_pin = of_get_named_gpio(np, "wake-gpios", 0); + + return 0; +} +#else +static inline int edt_ft5x06_i2c_ts_probe_dt(struct device *dev, + struct edt_ft5x06_ts_data *tsdata) +{ + return -ENODEV; +} +#endif + +static int edt_ft5x06_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct edt_ft5x06_platform_data *pdata = + dev_get_platdata(&client->dev); + struct edt_ft5x06_ts_data *tsdata; + struct input_dev *input; + int error; + char fw_version[EDT_NAME_LEN]; + + 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; + } + + if (!pdata) { + error = edt_ft5x06_i2c_ts_probe_dt(&client->dev, tsdata); + if (error) { + dev_err(&client->dev, + "DT probe failed and no platform data present\n"); + return error; + } + } else { + tsdata->reset_pin = pdata->reset_pin; + tsdata->irq_pin = pdata->irq_pin; + tsdata->wake_pin = -EINVAL; + } + + error = edt_ft5x06_ts_reset(client, tsdata); + if (error) + return error; + + if (gpio_is_valid(tsdata->irq_pin)) { + error = devm_gpio_request_one(&client->dev, tsdata->irq_pin, + GPIOF_IN, "edt-ft5x06 irq"); + if (error) { + dev_err(&client->dev, + "Failed to request GPIO %d, error %d\n", + tsdata->irq_pin, error); + return error; + } + } + + 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, fw_version); + if (error) { + dev_err(&client->dev, "touchscreen probe failed\n"); + return error; + } + + edt_ft5x06_ts_set_regs(tsdata); + + if (!pdata) + edt_ft5x06_ts_get_dt_defaults(client->dev.of_node, tsdata); + else + edt_ft5x06_ts_get_defaults(tsdata, pdata); + + edt_ft5x06_ts_get_parameters(tsdata); + + dev_dbg(&client->dev, + "Model \"%s\", Rev. \"%s\", %dx%d sensors\n", + tsdata->name, fw_version, tsdata->num_x, tsdata->num_y); + + input->name = tsdata->name; + input->id.bustype = BUS_I2C; + input->dev.parent = &client->dev; + + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_ABS, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_X, 0, tsdata->num_x * 64 - 1, 0, 0); + input_set_abs_params(input, ABS_Y, 0, tsdata->num_y * 64 - 1, 0, 0); + 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); + + if (!pdata) + touchscreen_parse_of_params(input); + + error = input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0); + if (error) { + dev_err(&client->dev, "Unable to init MT slots.\n"); + return error; + } + + input_set_drvdata(input, tsdata); + i2c_set_clientdata(client, tsdata); + + error = devm_request_threaded_irq(&client->dev, client->irq, NULL, + edt_ft5x06_ts_isr, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->name, tsdata); + if (error) { + dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + return error; + } + + error = sysfs_create_group(&client->dev.kobj, &edt_ft5x06_attr_group); + if (error) + return error; + + error = input_register_device(input); + if (error) + goto err_remove_attrs; + + edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev)); + device_init_wakeup(&client->dev, 1); + + dev_dbg(&client->dev, + "EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n", + client->irq, tsdata->wake_pin, tsdata->reset_pin); + + return 0; + +err_remove_attrs: + sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group); + return error; +} + +static int edt_ft5x06_ts_remove(struct i2c_client *client) +{ + struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); + + edt_ft5x06_ts_teardown_debugfs(tsdata); + sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group); + + return 0; +} + +static int __maybe_unused edt_ft5x06_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int __maybe_unused edt_ft5x06_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(client->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops, + edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume); + +static const struct i2c_device_id edt_ft5x06_ts_id[] = { + { "edt-ft5x06", 0, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, edt_ft5x06_ts_id); + +#ifdef CONFIG_OF +static const struct of_device_id edt_ft5x06_of_match[] = { + { .compatible = "edt,edt-ft5206", }, + { .compatible = "edt,edt-ft5306", }, + { .compatible = "edt,edt-ft5406", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, edt_ft5x06_of_match); +#endif + +static struct i2c_driver edt_ft5x06_ts_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "edt_ft5x06", + .of_match_table = of_match_ptr(edt_ft5x06_of_match), + .pm = &edt_ft5x06_ts_pm_ops, + }, + .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"); diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c new file mode 100644 index 000000000..09be6ced7 --- /dev/null +++ b/drivers/input/touchscreen/eeti_ts.c @@ -0,0 +1,327 @@ +/* + * Touch Screen driver for EETI's I2C connected touch screen panels + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * + * See EETI's software guide for the protocol specification: + * http://home.eeti.com.tw/web20/eg/guide.htm + * + * Based on migor_ts.c + * Copyright (c) 2008 Magnus Damm + * Copyright (c) 2007 Ujjwal Pande <ujjwal@kenati.com> + * + * This file 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; either + * version 2 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/timer.h> +#include <linux/gpio.h> +#include <linux/input/eeti_ts.h> +#include <linux/slab.h> + +static bool flip_x; +module_param(flip_x, bool, 0644); +MODULE_PARM_DESC(flip_x, "flip x coordinate"); + +static bool flip_y; +module_param(flip_y, bool, 0644); +MODULE_PARM_DESC(flip_y, "flip y coordinate"); + +struct eeti_ts_priv { + struct i2c_client *client; + struct input_dev *input; + struct work_struct work; + struct mutex mutex; + int irq_gpio, irq, irq_active_high; +}; + +#define EETI_TS_BITDEPTH (11) +#define EETI_MAXVAL ((1 << (EETI_TS_BITDEPTH + 1)) - 1) + +#define REPORT_BIT_PRESSED (1 << 0) +#define REPORT_BIT_AD0 (1 << 1) +#define REPORT_BIT_AD1 (1 << 2) +#define REPORT_BIT_HAS_PRESSURE (1 << 6) +#define REPORT_RES_BITS(v) (((v) >> 1) + EETI_TS_BITDEPTH) + +static inline int eeti_ts_irq_active(struct eeti_ts_priv *priv) +{ + return gpio_get_value(priv->irq_gpio) == priv->irq_active_high; +} + +static void eeti_ts_read(struct work_struct *work) +{ + char buf[6]; + unsigned int x, y, res, pressed, to = 100; + struct eeti_ts_priv *priv = + container_of(work, struct eeti_ts_priv, work); + + mutex_lock(&priv->mutex); + + while (eeti_ts_irq_active(priv) && --to) + i2c_master_recv(priv->client, buf, sizeof(buf)); + + if (!to) { + dev_err(&priv->client->dev, + "unable to clear IRQ - line stuck?\n"); + goto out; + } + + /* drop non-report packets */ + if (!(buf[0] & 0x80)) + goto out; + + pressed = buf[0] & REPORT_BIT_PRESSED; + res = REPORT_RES_BITS(buf[0] & (REPORT_BIT_AD0 | REPORT_BIT_AD1)); + x = buf[2] | (buf[1] << 8); + y = buf[4] | (buf[3] << 8); + + /* fix the range to 11 bits */ + x >>= res - EETI_TS_BITDEPTH; + y >>= res - EETI_TS_BITDEPTH; + + if (flip_x) + x = EETI_MAXVAL - x; + + if (flip_y) + y = EETI_MAXVAL - y; + + if (buf[0] & REPORT_BIT_HAS_PRESSURE) + input_report_abs(priv->input, ABS_PRESSURE, buf[5]); + + input_report_abs(priv->input, ABS_X, x); + input_report_abs(priv->input, ABS_Y, y); + input_report_key(priv->input, BTN_TOUCH, !!pressed); + input_sync(priv->input); + +out: + mutex_unlock(&priv->mutex); +} + +static irqreturn_t eeti_ts_isr(int irq, void *dev_id) +{ + struct eeti_ts_priv *priv = dev_id; + + /* postpone I2C transactions as we are atomic */ + schedule_work(&priv->work); + + return IRQ_HANDLED; +} + +static void eeti_ts_start(struct eeti_ts_priv *priv) +{ + enable_irq(priv->irq); + + /* Read the events once to arm the IRQ */ + eeti_ts_read(&priv->work); +} + +static void eeti_ts_stop(struct eeti_ts_priv *priv) +{ + disable_irq(priv->irq); + cancel_work_sync(&priv->work); +} + +static int eeti_ts_open(struct input_dev *dev) +{ + struct eeti_ts_priv *priv = input_get_drvdata(dev); + + eeti_ts_start(priv); + + return 0; +} + +static void eeti_ts_close(struct input_dev *dev) +{ + struct eeti_ts_priv *priv = input_get_drvdata(dev); + + eeti_ts_stop(priv); +} + +static int eeti_ts_probe(struct i2c_client *client, + const struct i2c_device_id *idp) +{ + struct eeti_ts_platform_data *pdata = dev_get_platdata(&client->dev); + struct eeti_ts_priv *priv; + struct input_dev *input; + unsigned int irq_flags; + int err = -ENOMEM; + + /* + * 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. + */ + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, "failed to allocate driver data\n"); + goto err0; + } + + mutex_init(&priv->mutex); + input = input_allocate_device(); + + if (!input) { + dev_err(&client->dev, "Failed to allocate input device.\n"); + goto err1; + } + + 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, 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); + + input->name = client->name; + input->id.bustype = BUS_I2C; + input->dev.parent = &client->dev; + input->open = eeti_ts_open; + input->close = eeti_ts_close; + + priv->client = client; + priv->input = input; + priv->irq_gpio = pdata->irq_gpio; + priv->irq = gpio_to_irq(pdata->irq_gpio); + + err = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); + if (err < 0) + goto err1; + + priv->irq_active_high = pdata->irq_active_high; + + irq_flags = priv->irq_active_high ? + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING; + + INIT_WORK(&priv->work, eeti_ts_read); + i2c_set_clientdata(client, priv); + input_set_drvdata(input, priv); + + err = input_register_device(input); + if (err) + goto err2; + + err = request_irq(priv->irq, eeti_ts_isr, irq_flags, + client->name, priv); + if (err) { + dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + goto err3; + } + + /* + * Disable the device for now. It will be enabled once the + * input device is opened. + */ + eeti_ts_stop(priv); + + device_init_wakeup(&client->dev, 0); + return 0; + +err3: + input_unregister_device(input); + input = NULL; /* so we dont try to free it below */ +err2: + gpio_free(pdata->irq_gpio); +err1: + input_free_device(input); + kfree(priv); +err0: + return err; +} + +static int eeti_ts_remove(struct i2c_client *client) +{ + struct eeti_ts_priv *priv = i2c_get_clientdata(client); + + free_irq(priv->irq, priv); + /* + * eeti_ts_stop() leaves IRQ disabled. We need to re-enable it + * so that device still works if we reload the driver. + */ + enable_irq(priv->irq); + + input_unregister_device(priv->input); + kfree(priv); + + return 0; +} + +static int __maybe_unused eeti_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct eeti_ts_priv *priv = i2c_get_clientdata(client); + struct input_dev *input_dev = priv->input; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + eeti_ts_stop(priv); + + mutex_unlock(&input_dev->mutex); + + if (device_may_wakeup(&client->dev)) + enable_irq_wake(priv->irq); + + return 0; +} + +static int __maybe_unused eeti_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct eeti_ts_priv *priv = i2c_get_clientdata(client); + struct input_dev *input_dev = priv->input; + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(priv->irq); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + eeti_ts_start(priv); + + 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); + +static struct i2c_driver eeti_ts_driver = { + .driver = { + .name = "eeti_ts", + .pm = &eeti_ts_pm, + }, + .probe = eeti_ts_probe, + .remove = eeti_ts_remove, + .id_table = eeti_ts_id, +}; + +module_i2c_driver(eeti_ts_driver); + +MODULE_DESCRIPTION("EETI Touchscreen driver"); +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); +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..4c5629928 --- /dev/null +++ b/drivers/input/touchscreen/egalax_ts.c @@ -0,0 +1,283 @@ +/* + * Driver for EETI eGalax Multiple Touch Controller + * + * Copyright (C) 2011 Freescale Semiconductor, Inc. + * + * based on max11801_ts.c + * + * 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. + */ + +/* 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); + + input_set_drvdata(input_dev, ts); + + 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; + + i2c_set_clientdata(client, ts); + 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; + + 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); + + 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 */ } +}; + +static struct i2c_driver egalax_ts_driver = { + .driver = { + .name = "egalax_ts", + .owner = THIS_MODULE, + .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/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c new file mode 100644 index 000000000..5e65be97f --- /dev/null +++ b/drivers/input/touchscreen/elants_i2c.c @@ -0,0 +1,1275 @@ +/* + * 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 + */ + +/* + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.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/acpi.h> +#include <linux/of.h> +#include <asm/unaligned.h> + +/* Device, Driver information */ +#define DEVICE_NAME "elants_i2c" +#define DRV_VERSION "1.0.9" + +/* 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 + +/* Command header definition */ +#define CMD_HEADER_WRITE 0x54 +#define CMD_HEADER_READ 0x53 +#define CMD_HEADER_6B_READ 0x5B +#define CMD_HEADER_RESP 0x52 +#define CMD_HEADER_6B_RESP 0x9B +#define CMD_HEADER_HELLO 0x55 +#define CMD_HEADER_REK 0x66 + +/* FW position data */ +#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_CHECKSUM 34 +#define FW_POS_WIDTH 35 +#define FW_POS_PRESSURE 45 + +#define HEADER_REPORT_10_FINGER 0x62 + +/* Header (4 bytes) plus 3 fill 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_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 + +#define MAX_RETRIES 3 +#define MAX_FW_UPDATE_RETRIES 30 + +#define ELAN_FW_PAGESIZE 132 + +/* calibration timeout definition */ +#define ELAN_CALI_TIMEOUT_MSEC 10000 + +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; + + u16 fw_version; + u8 test_version; + u8 solution_version; + u8 bc_version; + u8 iap_version; + u16 hw_version; + unsigned int x_res; /* resolution in units/mm */ + unsigned int y_res; + unsigned int x_max; + unsigned int y_max; + + enum elants_state state; + 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; + + u8 buf[MAX_PACKET_SIZE]; + + bool wake_irq_enabled; +}; + +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) +{ + 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; + + default: + dev_err(&client->dev, "%s: invalid command %*ph\n", + __func__, (int)cmd_size, cmd); + return -EINVAL; + } + + 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; + 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) + return ret; + + if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] != expected_response) + 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[] = { 0x54, 0xC0, 0xE1, 0x5A }; + static const u8 rek[] = { 0x54, 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_fw_id(struct elants_data *ts) +{ + struct i2c_client *client = ts->client; + int error, retry_cnt; + const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 }; + u8 resp[HEADER_SIZE]; + + for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) { + error = elants_i2c_execute_command(client, cmd, sizeof(cmd), + resp, sizeof(resp)); + if (!error) { + ts->hw_version = elants_i2c_parse_version(resp); + if (ts->hw_version != 0xffff) + return 0; + } + + dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n", + error, (int)sizeof(resp), resp); + } + + dev_err(&client->dev, + "Failed to read fw id or fw id is invalid\n"); + + return -EINVAL; +} + +static int elants_i2c_query_fw_version(struct elants_data *ts) +{ + struct i2c_client *client = ts->client; + int error, retry_cnt; + const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01 }; + u8 resp[HEADER_SIZE]; + + for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) { + error = elants_i2c_execute_command(client, cmd, sizeof(cmd), + resp, sizeof(resp)); + if (!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 error=%d, buf=%*phC\n", + error, (int)sizeof(resp), resp); + } + + dev_err(&client->dev, + "Failed to read fw version or fw version is invalid\n"); + + return -EINVAL; +} + +static int elants_i2c_query_test_version(struct elants_data *ts) +{ + struct i2c_client *client = ts->client; + int error, retry_cnt; + u16 version; + const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01 }; + u8 resp[HEADER_SIZE]; + + for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) { + error = elants_i2c_execute_command(client, cmd, sizeof(cmd), + resp, sizeof(resp)); + if (!error) { + version = elants_i2c_parse_version(resp); + ts->test_version = version >> 8; + ts->solution_version = version & 0xff; + + return 0; + } + + dev_dbg(&client->dev, + "read test version error rc=%d, buf=%*phC\n", + error, (int)sizeof(resp), resp); + } + + dev_err(&client->dev, "Failed to read test version\n"); + + return -EINVAL; +} + +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)); + if (error) { + dev_err(&client->dev, + "read BC version error=%d, buf=%*phC\n", + error, (int)sizeof(resp), resp); + 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(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)); + if (error) { + dev_err(&client->dev, "get resolution command failed: %d\n", + error); + return error; + } + + rows = resp[2] + resp[6] + resp[10]; + cols = resp[3] + resp[7] + resp[11]; + + /* Process mm_to_pixel information */ + error = elants_i2c_execute_command(client, + get_osr_cmd, sizeof(get_osr_cmd), + resp, sizeof(resp)); + if (error) { + dev_err(&client->dev, "get osr command failed: %d\n", + error); + return error; + } + + osr = resp[3]; + + error = elants_i2c_execute_command(client, + get_physical_scan_cmd, + sizeof(get_physical_scan_cmd), + resp, sizeof(resp)); + if (error) { + dev_err(&client->dev, "get physical scan command failed: %d\n", + 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)); + if (error) { + dev_err(&client->dev, "get physical drive command failed: %d\n", + 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); + } + + 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, 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); + } + } + + if (!error) + error = elants_i2c_query_fw_id(ts); + if (!error) + error = elants_i2c_query_fw_version(ts); + + if (error) { + ts->iap_mode = ELAN_IAP_RECOVERY; + } else { + elants_i2c_query_test_version(ts); + elants_i2c_query_bc_version(ts); + elants_i2c_query_ts_info(ts); + } + + 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_do_update_firmware(struct i2c_client *client, + const struct firmware *fw, + bool force) +{ + const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 }; + const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 }; + const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc }; + u8 buf[HEADER_SIZE]; + u16 send_id; + int page, n_fw_pages; + int error; + + /* Recovery mode detection! */ + if (force) { + dev_dbg(&client->dev, "Recovery mode procedure\n"); + error = elants_i2c_send(client, enter_iap2, sizeof(enter_iap2)); + } else { + /* Start IAP Procedure */ + dev_dbg(&client->dev, "Normal IAP procedure\n"); + elants_i2c_sw_reset(client); + + 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, "/*(DEBLOBBED)*/", ts->hw_version); + if (!fw_name) + return -ENOMEM; + + dev_info(&client->dev, "requesting fw name = %s\n", fw_name); + error = reject_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) +{ + struct input_dev *input = ts->input; + unsigned int n_fingers; + 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); + + 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]; + 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, MT_TOOL_FINGER, true); + input_event(input, EV_ABS, ABS_MT_POSITION_X, x); + input_event(input, EV_ABS, ABS_MT_POSITION_Y, y); + 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) +{ + 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); +} + +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(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; + /* fall through */ + + case ELAN_STATE_NORMAL: + + switch (ts->buf[FW_HDR_TYPE]) { + case CMD_HEADER_HELLO: + case CMD_HEADER_RESP: + case CMD_HEADER_REK: + 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]); + break; + + case QUEUE_HEADER_NORMAL: + report_count = ts->buf[FW_HDR_COUNT]; + if (report_count > 3) { + dev_err(&client->dev, + "too large report count: %*ph\n", + HEADER_SIZE, ts->buf); + break; + } + + report_len = ts->buf[FW_HDR_LENGTH] / report_count; + 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 * PACKET_SIZE; + elants_i2c_event(ts, buf); + } + 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 DEVICE_ATTR(calibrate, S_IWUSR, NULL, calibrate_store); +static DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, 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, + + &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 struct attribute_group elants_attribute_group = { + .attrs = elants_attributes, +}; + +static void elants_i2c_remove_sysfs_group(void *_data) +{ + struct elants_data *ts = _data; + + sysfs_remove_group(&ts->client->dev.kobj, &elants_attribute_group); +} + +static int elants_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + union i2c_smbus_data dummy; + struct elants_data *ts; + unsigned long irqflags; + int error; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, + "%s: i2c check functionality error\n", DEVICE_NAME); + return -ENXIO; + } + + /* 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; + } + + 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; + i2c_set_clientdata(client, ts); + + 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; + + __set_bit(BTN_TOUCH, ts->input->keybit); + __set_bit(EV_ABS, ts->input->evbit); + __set_bit(EV_KEY, ts->input->evbit); + + /* Single touch input params setup */ + input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0); + input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0); + input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0); + input_abs_set_res(ts->input, ABS_X, ts->x_res); + input_abs_set_res(ts->input, ABS_Y, ts->y_res); + + /* Multitouch input params setup */ + 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; + } + + 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_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_set_drvdata(ts->input, ts); + + error = input_register_device(ts->input); + if (error) { + dev_err(&client->dev, + "unable to register input device: %d\n", error); + return error; + } + + /* + * Systems using device tree should set up interrupt via DTS, + * the rest will use the default falling edge interrupts. + */ + irqflags = client->dev.of_node ? 0 : 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 = sysfs_create_group(&client->dev.kobj, &elants_attribute_group); + if (error) { + dev_err(&client->dev, "failed to create sysfs attributes: %d\n", + error); + return error; + } + + error = devm_add_action(&client->dev, + elants_i2c_remove_sysfs_group, ts); + if (error) { + elants_i2c_remove_sysfs_group(ts); + dev_err(&client->dev, + "Failed to add sysfs cleanup action: %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[] = { 0x54, 0x50, 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); + + 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); + } + + if (device_may_wakeup(dev)) + ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0); + + 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[] = { 0x54, 0x58, 0x00, 0x01 }; + int retry_cnt; + int error; + + if (device_may_wakeup(dev) && ts->wake_irq_enabled) + disable_irq_wake(client->irq); + + 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); + } + + 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, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, elants_i2c_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id elants_acpi_id[] = { + { "ELAN0001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, elants_acpi_id); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id elants_of_match[] = { + { .compatible = "elan,ekth3500" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, elants_of_match); +#endif + +static struct i2c_driver elants_i2c_driver = { + .probe = elants_i2c_probe, + .id_table = elants_i2c_id, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .pm = &elants_i2c_pm_ops, + .acpi_match_table = ACPI_PTR(elants_acpi_id), + .of_match_table = of_match_ptr(elants_of_match), + }, +}; +module_i2c_driver(elants_i2c_driver); + +MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>"); +MODULE_DESCRIPTION("Elan I2c Touchscreen driver"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c new file mode 100644 index 000000000..8051a4b70 --- /dev/null +++ b/drivers/input/touchscreen/elo.c @@ -0,0 +1,407 @@ +/* + * Elo serial touchscreen driver + * + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * 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. + */ + +/* + * 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)) + goto fail3; + + break; + + case 1: /* 6-byte protocol */ + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 15, 0, 0); + + 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 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/fujitsu_ts.c b/drivers/input/touchscreen/fujitsu_ts.c new file mode 100644 index 000000000..d0e46a7e1 --- /dev/null +++ b/drivers/input/touchscreen/fujitsu_ts.c @@ -0,0 +1,177 @@ +/* + * Fujitsu serial touchscreen driver + * + * Copyright (c) Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * 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/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 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..3af16984d --- /dev/null +++ b/drivers/input/touchscreen/goodix.c @@ -0,0 +1,417 @@ +/* + * Driver for Goodix Touchscreens + * + * Copyright (c) 2014 Red Hat Inc. + * + * This code is based on gt9xx.c authored by andrew@goodix.com: + * + * 2010 - 2012 Goodix Technology. + */ + +/* + * 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 of the License. + */ + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/of.h> +#include <asm/unaligned.h> + +struct goodix_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + int abs_x_max; + int abs_y_max; + unsigned int max_touch_num; + unsigned int int_trigger_type; +}; + +#define GOODIX_MAX_HEIGHT 4096 +#define GOODIX_MAX_WIDTH 4096 +#define GOODIX_INT_TRIGGER 1 +#define GOODIX_CONTACT_SIZE 8 +#define GOODIX_MAX_CONTACTS 10 + +#define GOODIX_CONFIG_MAX_LENGTH 240 + +/* Register defines */ +#define GOODIX_READ_COOR_ADDR 0x814E +#define GOODIX_REG_CONFIG_DATA 0x8047 +#define GOODIX_REG_VERSION 0x8140 + +#define RESOLUTION_LOC 1 +#define MAX_CONTACTS_LOC 5 +#define TRIGGER_LOC 6 + +static const unsigned long goodix_irq_flags[] = { + IRQ_TYPE_EDGE_RISING, + IRQ_TYPE_EDGE_FALLING, + IRQ_TYPE_LEVEL_LOW, + IRQ_TYPE_LEVEL_HIGH, +}; + +/** + * 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 + */ +static int goodix_i2c_read(struct i2c_client *client, + u16 reg, u8 *buf, int len) +{ + struct i2c_msg msgs[2]; + u16 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); + return ret < 0 ? ret : (ret != ARRAY_SIZE(msgs) ? -EIO : 0); +} + +static int goodix_ts_read_input_report(struct goodix_ts_data *ts, u8 *data) +{ + int touch_num; + int error; + + error = goodix_i2c_read(ts->client, GOODIX_READ_COOR_ADDR, data, + GOODIX_CONTACT_SIZE + 1); + if (error) { + dev_err(&ts->client->dev, "I2C transfer error: %d\n", error); + return error; + } + + touch_num = data[0] & 0x0f; + if (touch_num > ts->max_touch_num) + return -EPROTO; + + if (touch_num > 1) { + data += 1 + GOODIX_CONTACT_SIZE; + error = goodix_i2c_read(ts->client, + GOODIX_READ_COOR_ADDR + + 1 + GOODIX_CONTACT_SIZE, + data, + GOODIX_CONTACT_SIZE * (touch_num - 1)); + if (error) + return error; + } + + return touch_num; +} + +static void goodix_ts_report_touch(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); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w); +} + +/** + * 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[1 + GOODIX_CONTACT_SIZE * ts->max_touch_num]; + int touch_num; + int i; + + touch_num = goodix_ts_read_input_report(ts, point_data); + if (touch_num < 0) + return; + + for (i = 0; i < touch_num; i++) + goodix_ts_report_touch(ts, + &point_data[1 + GOODIX_CONTACT_SIZE * i]); + + 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) +{ + static const u8 end_cmd[] = { + GOODIX_READ_COOR_ADDR >> 8, + GOODIX_READ_COOR_ADDR & 0xff, + 0 + }; + struct goodix_ts_data *ts = dev_id; + + goodix_process_events(ts); + + if (i2c_master_send(ts->client, end_cmd, sizeof(end_cmd)) < 0) + dev_err(&ts->client->dev, "I2C write end_cmd error\n"); + + return IRQ_HANDLED; +} + +/** + * 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) +{ + u8 config[GOODIX_CONFIG_MAX_LENGTH]; + int error; + + error = goodix_i2c_read(ts->client, GOODIX_REG_CONFIG_DATA, + config, + GOODIX_CONFIG_MAX_LENGTH); + if (error) { + dev_warn(&ts->client->dev, + "Error reading config (%d), using defaults\n", + error); + ts->abs_x_max = GOODIX_MAX_WIDTH; + ts->abs_y_max = GOODIX_MAX_HEIGHT; + ts->int_trigger_type = GOODIX_INT_TRIGGER; + ts->max_touch_num = GOODIX_MAX_CONTACTS; + return; + } + + ts->abs_x_max = get_unaligned_le16(&config[RESOLUTION_LOC]); + ts->abs_y_max = get_unaligned_le16(&config[RESOLUTION_LOC + 2]); + ts->int_trigger_type = config[TRIGGER_LOC] & 0x03; + ts->max_touch_num = config[MAX_CONTACTS_LOC] & 0x0f; + if (!ts->abs_x_max || !ts->abs_y_max || !ts->max_touch_num) { + dev_err(&ts->client->dev, + "Invalid config, using defaults\n"); + ts->abs_x_max = GOODIX_MAX_WIDTH; + ts->abs_y_max = GOODIX_MAX_HEIGHT; + ts->max_touch_num = GOODIX_MAX_CONTACTS; + } +} + +/** + * goodix_read_version - Read goodix touchscreen version + * + * @client: the i2c client + * @version: output buffer containing the version on success + */ +static int goodix_read_version(struct i2c_client *client, u16 *version) +{ + int error; + u8 buf[6]; + + error = goodix_i2c_read(client, GOODIX_REG_VERSION, buf, sizeof(buf)); + if (error) { + dev_err(&client->dev, "read version failed: %d\n", error); + return error; + } + + if (version) + *version = get_unaligned_le16(&buf[4]); + + dev_info(&client->dev, "IC VERSION: %6ph\n", buf); + + 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_CONFIG_DATA, + &test, 1); + if (!error) + return 0; + + dev_err(&client->dev, "i2c test failed attempt %d: %d\n", + retry, error); + msleep(20); + } + + return error; +} + +/** + * goodix_request_input_dev - Allocate, populate and register the input device + * + * @ts: our goodix_ts_data pointer + * + * Must be called during probe + */ +static int goodix_request_input_dev(struct goodix_ts_data *ts) +{ + int error; + + 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->evbit[0] = BIT_MASK(EV_SYN) | + BIT_MASK(EV_KEY) | + BIT_MASK(EV_ABS); + + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, + ts->abs_x_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, + ts->abs_y_max, 0, 0); + 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); + + input_mt_init_slots(ts->input_dev, ts->max_touch_num, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + + 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; + ts->input_dev->id.product = 0x1001; + ts->input_dev->id.version = 10427; + + error = input_register_device(ts->input_dev); + if (error) { + dev_err(&ts->client->dev, + "Failed to register input device: %d", error); + return error; + } + + return 0; +} + +static int goodix_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct goodix_ts_data *ts; + unsigned long irq_flags; + int error; + u16 version_info; + + 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); + + error = goodix_i2c_test(client); + if (error) { + dev_err(&client->dev, "I2C communication failure: %d\n", error); + return error; + } + + error = goodix_read_version(client, &version_info); + if (error) { + dev_err(&client->dev, "Read version failed.\n"); + return error; + } + + goodix_read_config(ts); + + error = goodix_request_input_dev(ts); + if (error) + return error; + + irq_flags = goodix_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT; + error = devm_request_threaded_irq(&ts->client->dev, client->irq, + NULL, goodix_ts_irq_handler, + irq_flags, client->name, ts); + if (error) { + dev_err(&client->dev, "request IRQ failed: %d\n", error); + return error; + } + + return 0; +} + +static const struct i2c_device_id goodix_ts_id[] = { + { "GDIX1001:00", 0 }, + { } +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id goodix_acpi_match[] = { + { "GDIX1001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, goodix_acpi_match); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id goodix_of_match[] = { + { .compatible = "goodix,gt911" }, + { .compatible = "goodix,gt9110" }, + { .compatible = "goodix,gt912" }, + { .compatible = "goodix,gt927" }, + { .compatible = "goodix,gt9271" }, + { .compatible = "goodix,gt928" }, + { .compatible = "goodix,gt967" }, + { } +}; +MODULE_DEVICE_TABLE(of, goodix_of_match); +#endif + +static struct i2c_driver goodix_ts_driver = { + .probe = goodix_ts_probe, + .id_table = goodix_ts_id, + .driver = { + .name = "Goodix-TS", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(goodix_acpi_match), + .of_match_table = of_match_ptr(goodix_of_match), + }, +}; +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/gunze.c b/drivers/input/touchscreen/gunze.c new file mode 100644 index 000000000..e2ee62615 --- /dev/null +++ b/drivers/input/touchscreen/gunze.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2000-2001 Vojtech Pavlik + */ + +/* + * Gunze AHL-51S touchscreen driver for Linux + */ + +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#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 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..ecb1e0e01 --- /dev/null +++ b/drivers/input/touchscreen/hampshire.c @@ -0,0 +1,189 @@ +/* + * Hampshire serial touchscreen driver + * + * Copyright (c) 2010 Adam Bennett + * Based on the dynapro driver (c) Tias Guns + * + */ + +/* + * 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. + */ + +/* + * 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 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/hp680_ts_input.c b/drivers/input/touchscreen/hp680_ts_input.c new file mode 100644 index 000000000..85cf9bee8 --- /dev/null +++ b/drivers/input/touchscreen/hp680_ts_input.c @@ -0,0 +1,127 @@ +#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..92e2243fb --- /dev/null +++ b/drivers/input/touchscreen/htcpen.c @@ -0,0 +1,248 @@ +/* + * HTC Shift touchscreen driver + * + * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> + * + * 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/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 int 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); + + return 0; +} + +#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 struct dmi_system_id htcshift_dmi_table[] __initdata = { + { + .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/ili210x.c b/drivers/input/touchscreen/ili210x.c new file mode 100644 index 000000000..da6dc819c --- /dev/null +++ b/drivers/input/touchscreen/ili210x.c @@ -0,0 +1,358 @@ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/input/ili210x.h> + +#define MAX_TOUCHES 2 +#define DEFAULT_POLL_PERIOD 20 + +/* Touchscreen commands */ +#define REG_TOUCHDATA 0x10 +#define REG_PANEL_INFO 0x20 +#define REG_FIRMWARE_VERSION 0x40 +#define REG_CALIBRATE 0xcc + +struct finger { + u8 x_low; + u8 x_high; + u8 y_low; + u8 y_high; +} __packed; + +struct touchdata { + u8 status; + struct finger finger[MAX_TOUCHES]; +} __packed; + +struct panel_info { + struct finger finger_max; + u8 xchannel_num; + u8 ychannel_num; +} __packed; + +struct firmware_version { + u8 id; + u8 major; + u8 minor; +} __packed; + +struct ili210x { + struct i2c_client *client; + struct input_dev *input; + bool (*get_pendown_state)(void); + unsigned int poll_period; + struct delayed_work dwork; +}; + +static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf, + size_t len) +{ + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + if (i2c_transfer(client->adapter, msg, 2) != 2) { + dev_err(&client->dev, "i2c transfer failed\n"); + return -EIO; + } + + return 0; +} + +static void ili210x_report_events(struct input_dev *input, + const struct touchdata *touchdata) +{ + int i; + bool touch; + unsigned int x, y; + const struct finger *finger; + + for (i = 0; i < MAX_TOUCHES; i++) { + input_mt_slot(input, i); + + finger = &touchdata->finger[i]; + + touch = touchdata->status & (1 << i); + input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); + if (touch) { + x = finger->x_low | (finger->x_high << 8); + y = finger->y_low | (finger->y_high << 8); + + input_report_abs(input, ABS_MT_POSITION_X, x); + input_report_abs(input, ABS_MT_POSITION_Y, y); + } + } + + input_mt_report_pointer_emulation(input, false); + input_sync(input); +} + +static bool get_pendown_state(const struct ili210x *priv) +{ + bool state = false; + + if (priv->get_pendown_state) + state = priv->get_pendown_state(); + + return state; +} + +static void ili210x_work(struct work_struct *work) +{ + struct ili210x *priv = container_of(work, struct ili210x, + dwork.work); + struct i2c_client *client = priv->client; + struct touchdata touchdata; + int error; + + error = ili210x_read_reg(client, REG_TOUCHDATA, + &touchdata, sizeof(touchdata)); + if (error) { + dev_err(&client->dev, + "Unable to get touchdata, err = %d\n", error); + return; + } + + ili210x_report_events(priv->input, &touchdata); + + if ((touchdata.status & 0xf3) || get_pendown_state(priv)) + schedule_delayed_work(&priv->dwork, + msecs_to_jiffies(priv->poll_period)); +} + +static irqreturn_t ili210x_irq(int irq, void *irq_data) +{ + struct ili210x *priv = irq_data; + + schedule_delayed_work(&priv->dwork, 0); + + return IRQ_HANDLED; +} + +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, 0644, NULL, ili210x_calibrate); + +static struct attribute *ili210x_attributes[] = { + &dev_attr_calibrate.attr, + NULL, +}; + +static const struct attribute_group ili210x_attr_group = { + .attrs = ili210x_attributes, +}; + +static int ili210x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + const struct ili210x_platform_data *pdata = dev_get_platdata(dev); + struct ili210x *priv; + struct input_dev *input; + struct panel_info panel; + struct firmware_version firmware; + int xmax, ymax; + int error; + + dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver"); + + if (!pdata) { + dev_err(dev, "No platform data!\n"); + return -EINVAL; + } + + if (client->irq <= 0) { + dev_err(dev, "No IRQ!\n"); + return -EINVAL; + } + + /* Get firmware version */ + error = ili210x_read_reg(client, REG_FIRMWARE_VERSION, + &firmware, sizeof(firmware)); + if (error) { + dev_err(dev, "Failed to get firmware version, err: %d\n", + error); + return error; + } + + /* get panel info */ + error = ili210x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel)); + if (error) { + dev_err(dev, "Failed to get panel informations, err: %d\n", + error); + return error; + } + + xmax = panel.finger_max.x_low | (panel.finger_max.x_high << 8); + ymax = panel.finger_max.y_low | (panel.finger_max.y_high << 8); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + input = input_allocate_device(); + if (!priv || !input) { + error = -ENOMEM; + goto err_free_mem; + } + + priv->client = client; + priv->input = input; + priv->get_pendown_state = pdata->get_pendown_state; + priv->poll_period = pdata->poll_period ? : DEFAULT_POLL_PERIOD; + INIT_DELAYED_WORK(&priv->dwork, ili210x_work); + + /* Setup input device */ + input->name = "ILI210x Touchscreen"; + input->id.bustype = BUS_I2C; + input->dev.parent = dev; + + __set_bit(EV_SYN, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_ABS, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + + /* Single touch */ + input_set_abs_params(input, ABS_X, 0, xmax, 0, 0); + input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0); + + /* Multi touch */ + input_mt_init_slots(input, MAX_TOUCHES, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0); + + input_set_drvdata(input, priv); + i2c_set_clientdata(client, priv); + + error = request_irq(client->irq, ili210x_irq, pdata->irq_flags, + client->name, priv); + if (error) { + dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", + error); + goto err_free_mem; + } + + error = sysfs_create_group(&dev->kobj, &ili210x_attr_group); + if (error) { + dev_err(dev, "Unable to create sysfs attributes, err: %d\n", + error); + goto err_free_irq; + } + + error = input_register_device(priv->input); + if (error) { + dev_err(dev, "Cannot regiser input device, err: %d\n", error); + goto err_remove_sysfs; + } + + device_init_wakeup(&client->dev, 1); + + dev_dbg(dev, + "ILI210x initialized (IRQ: %d), firmware version %d.%d.%d", + client->irq, firmware.id, firmware.major, firmware.minor); + + return 0; + +err_remove_sysfs: + sysfs_remove_group(&dev->kobj, &ili210x_attr_group); +err_free_irq: + free_irq(client->irq, priv); +err_free_mem: + input_free_device(input); + kfree(priv); + return error; +} + +static int ili210x_i2c_remove(struct i2c_client *client) +{ + struct ili210x *priv = i2c_get_clientdata(client); + + sysfs_remove_group(&client->dev.kobj, &ili210x_attr_group); + free_irq(priv->client->irq, priv); + cancel_delayed_work_sync(&priv->dwork); + input_unregister_device(priv->input); + kfree(priv); + + return 0; +} + +static int __maybe_unused ili210x_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (device_may_wakeup(&client->dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int __maybe_unused ili210x_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(ili210x_i2c_pm, + ili210x_i2c_suspend, ili210x_i2c_resume); + +static const struct i2c_device_id ili210x_i2c_id[] = { + { "ili210x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id); + +static struct i2c_driver ili210x_ts_driver = { + .driver = { + .name = "ili210x_i2c", + .owner = THIS_MODULE, + .pm = &ili210x_i2c_pm, + }, + .id_table = ili210x_i2c_id, + .probe = ili210x_i2c_probe, + .remove = ili210x_i2c_remove, +}; + +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/inexio.c b/drivers/input/touchscreen/inexio.c new file mode 100644 index 000000000..adb80b65a --- /dev/null +++ b/drivers/input/touchscreen/inexio.c @@ -0,0 +1,191 @@ +/* + * iNexio serial touchscreen driver + * + * Copyright (c) 2008 Richard Lemon + * Based on the mtouch driver (c) Vojtech Pavlik and Dan Streetman + * + */ + +/* + * 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. + */ + +/* + * 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 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/intel-mid-touch.c b/drivers/input/touchscreen/intel-mid-touch.c new file mode 100644 index 000000000..b4f0725a1 --- /dev/null +++ b/drivers/input/touchscreen/intel-mid-touch.c @@ -0,0 +1,654 @@ +/* + * Intel MID Resistive Touch Screen Driver + * + * Copyright (C) 2008 Intel Corp + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * Questions/Comments/Bug fixes to Sreedhara (sreedhara.ds@intel.com) + * Ramesh Agarwal (ramesh.agarwal@intel.com) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * TODO: + * review conversion of r/m/w sequences + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/param.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <asm/intel_scu_ipc.h> +#include <linux/device.h> + +/* PMIC Interrupt registers */ +#define PMIC_REG_ID1 0x00 /* PMIC ID1 register */ + +/* PMIC Interrupt registers */ +#define PMIC_REG_INT 0x04 /* PMIC interrupt register */ +#define PMIC_REG_MINT 0x05 /* PMIC interrupt mask register */ + +/* ADC Interrupt registers */ +#define PMIC_REG_ADCINT 0x5F /* ADC interrupt register */ +#define PMIC_REG_MADCINT 0x60 /* ADC interrupt mask register */ + +/* ADC Control registers */ +#define PMIC_REG_ADCCNTL1 0x61 /* ADC control register */ + +/* ADC Channel Selection registers */ +#define PMICADDR0 0xA4 +#define END_OF_CHANNEL 0x1F + +/* ADC Result register */ +#define PMIC_REG_ADCSNS0H 0x64 + +/* ADC channels for touch screen */ +#define MRST_TS_CHAN10 0xA /* Touch screen X+ connection */ +#define MRST_TS_CHAN11 0xB /* Touch screen X- connection */ +#define MRST_TS_CHAN12 0xC /* Touch screen Y+ connection */ +#define MRST_TS_CHAN13 0xD /* Touch screen Y- connection */ + +/* Touch screen channel BIAS constants */ +#define MRST_XBIAS 0x20 +#define MRST_YBIAS 0x40 +#define MRST_ZBIAS 0x80 + +/* Touch screen coordinates */ +#define MRST_X_MIN 10 +#define MRST_X_MAX 1024 +#define MRST_X_FUZZ 5 +#define MRST_Y_MIN 10 +#define MRST_Y_MAX 1024 +#define MRST_Y_FUZZ 5 +#define MRST_PRESSURE_MIN 0 +#define MRST_PRESSURE_NOMINAL 50 +#define MRST_PRESSURE_MAX 100 + +#define WAIT_ADC_COMPLETION 10 /* msec */ + +/* PMIC ADC round robin delays */ +#define ADC_LOOP_DELAY0 0x0 /* Continuous loop */ +#define ADC_LOOP_DELAY1 0x1 /* 4.5 ms approximate */ + +/* PMIC Vendor Identifiers */ +#define PMIC_VENDOR_FS 0 /* PMIC vendor FreeScale */ +#define PMIC_VENDOR_MAXIM 1 /* PMIC vendor MAXIM */ +#define PMIC_VENDOR_NEC 2 /* PMIC vendor NEC */ +#define MRSTOUCH_MAX_CHANNELS 32 /* Maximum ADC channels */ + +/* Touch screen device structure */ +struct mrstouch_dev { + struct device *dev; /* device associated with touch screen */ + struct input_dev *input; + char phys[32]; + u16 asr; /* Address selection register */ + int irq; + unsigned int vendor; /* PMIC vendor */ + unsigned int rev; /* PMIC revision */ + + int (*read_prepare)(struct mrstouch_dev *tsdev); + int (*read)(struct mrstouch_dev *tsdev, u16 *x, u16 *y, u16 *z); + int (*read_finish)(struct mrstouch_dev *tsdev); +}; + + +/*************************** NEC and Maxim Interface ************************/ + +static int mrstouch_nec_adc_read_prepare(struct mrstouch_dev *tsdev) +{ + return intel_scu_ipc_update_register(PMIC_REG_MADCINT, 0, 0x20); +} + +/* + * Enables PENDET interrupt. + */ +static int mrstouch_nec_adc_read_finish(struct mrstouch_dev *tsdev) +{ + int err; + + err = intel_scu_ipc_update_register(PMIC_REG_MADCINT, 0x20, 0x20); + if (!err) + err = intel_scu_ipc_update_register(PMIC_REG_ADCCNTL1, 0, 0x05); + + return err; +} + +/* + * Reads PMIC ADC touch screen result + * Reads ADC storage registers for higher 7 and lower 3 bits and + * converts the two readings into a single value and turns off gain bit + */ +static int mrstouch_ts_chan_read(u16 offset, u16 chan, u16 *vp, u16 *vm) +{ + int err; + u16 result; + u32 res; + + result = PMIC_REG_ADCSNS0H + offset; + + if (chan == MRST_TS_CHAN12) + result += 4; + + err = intel_scu_ipc_ioread32(result, &res); + if (err) + return err; + + /* Mash the bits up */ + + *vp = (res & 0xFF) << 3; /* Highest 7 bits */ + *vp |= (res >> 8) & 0x07; /* Lower 3 bits */ + *vp &= 0x3FF; + + res >>= 16; + + *vm = (res & 0xFF) << 3; /* Highest 7 bits */ + *vm |= (res >> 8) & 0x07; /* Lower 3 bits */ + *vm &= 0x3FF; + + return 0; +} + +/* + * Enables X, Y and Z bias values + * Enables YPYM for X channels and XPXM for Y channels + */ +static int mrstouch_ts_bias_set(uint offset, uint bias) +{ + int count; + u16 chan, start; + u16 reg[4]; + u8 data[4]; + + chan = PMICADDR0 + offset; + start = MRST_TS_CHAN10; + + for (count = 0; count <= 3; count++) { + reg[count] = chan++; + data[count] = bias | (start + count); + } + + return intel_scu_ipc_writev(reg, data, 4); +} + +/* To read touch screen channel values */ +static int mrstouch_nec_adc_read(struct mrstouch_dev *tsdev, + u16 *x, u16 *y, u16 *z) +{ + int err; + u16 xm, ym, zm; + + /* configure Y bias for X channels */ + err = mrstouch_ts_bias_set(tsdev->asr, MRST_YBIAS); + if (err) + goto ipc_error; + + msleep(WAIT_ADC_COMPLETION); + + /* read x+ and x- channels */ + err = mrstouch_ts_chan_read(tsdev->asr, MRST_TS_CHAN10, x, &xm); + if (err) + goto ipc_error; + + /* configure x bias for y channels */ + err = mrstouch_ts_bias_set(tsdev->asr, MRST_XBIAS); + if (err) + goto ipc_error; + + msleep(WAIT_ADC_COMPLETION); + + /* read y+ and y- channels */ + err = mrstouch_ts_chan_read(tsdev->asr, MRST_TS_CHAN12, y, &ym); + if (err) + goto ipc_error; + + /* configure z bias for x and y channels */ + err = mrstouch_ts_bias_set(tsdev->asr, MRST_ZBIAS); + if (err) + goto ipc_error; + + msleep(WAIT_ADC_COMPLETION); + + /* read z+ and z- channels */ + err = mrstouch_ts_chan_read(tsdev->asr, MRST_TS_CHAN10, z, &zm); + if (err) + goto ipc_error; + + return 0; + +ipc_error: + dev_err(tsdev->dev, "ipc error during adc read\n"); + return err; +} + + +/*************************** Freescale Interface ************************/ + +static int mrstouch_fs_adc_read_prepare(struct mrstouch_dev *tsdev) +{ + int err, count; + u16 chan; + u16 reg[5]; + u8 data[5]; + + /* Stop the ADC */ + err = intel_scu_ipc_update_register(PMIC_REG_MADCINT, 0x00, 0x02); + if (err) + goto ipc_error; + + chan = PMICADDR0 + tsdev->asr; + + /* Set X BIAS */ + for (count = 0; count <= 3; count++) { + reg[count] = chan++; + data[count] = 0x2A; + } + reg[count] = chan++; /* Dummy */ + data[count] = 0; + + err = intel_scu_ipc_writev(reg, data, 5); + if (err) + goto ipc_error; + + msleep(WAIT_ADC_COMPLETION); + + /* Set Y BIAS */ + for (count = 0; count <= 3; count++) { + reg[count] = chan++; + data[count] = 0x4A; + } + reg[count] = chan++; /* Dummy */ + data[count] = 0; + + err = intel_scu_ipc_writev(reg, data, 5); + if (err) + goto ipc_error; + + msleep(WAIT_ADC_COMPLETION); + + /* Set Z BIAS */ + err = intel_scu_ipc_iowrite32(chan + 2, 0x8A8A8A8A); + if (err) + goto ipc_error; + + msleep(WAIT_ADC_COMPLETION); + + return 0; + +ipc_error: + dev_err(tsdev->dev, "ipc error during %s\n", __func__); + return err; +} + +static int mrstouch_fs_adc_read(struct mrstouch_dev *tsdev, + u16 *x, u16 *y, u16 *z) +{ + int err; + u16 result; + u16 reg[4]; + u8 data[4]; + + result = PMIC_REG_ADCSNS0H + tsdev->asr; + + reg[0] = result + 4; + reg[1] = result + 5; + reg[2] = result + 16; + reg[3] = result + 17; + + err = intel_scu_ipc_readv(reg, data, 4); + if (err) + goto ipc_error; + + *x = data[0] << 3; /* Higher 7 bits */ + *x |= data[1] & 0x7; /* Lower 3 bits */ + *x &= 0x3FF; + + *y = data[2] << 3; /* Higher 7 bits */ + *y |= data[3] & 0x7; /* Lower 3 bits */ + *y &= 0x3FF; + + /* Read Z value */ + reg[0] = result + 28; + reg[1] = result + 29; + + err = intel_scu_ipc_readv(reg, data, 4); + if (err) + goto ipc_error; + + *z = data[0] << 3; /* Higher 7 bits */ + *z |= data[1] & 0x7; /* Lower 3 bits */ + *z &= 0x3FF; + + return 0; + +ipc_error: + dev_err(tsdev->dev, "ipc error during %s\n", __func__); + return err; +} + +static int mrstouch_fs_adc_read_finish(struct mrstouch_dev *tsdev) +{ + int err, count; + u16 chan; + u16 reg[5]; + u8 data[5]; + + /* Clear all TS channels */ + chan = PMICADDR0 + tsdev->asr; + for (count = 0; count <= 4; count++) { + reg[count] = chan++; + data[count] = 0; + } + err = intel_scu_ipc_writev(reg, data, 5); + if (err) + goto ipc_error; + + for (count = 0; count <= 4; count++) { + reg[count] = chan++; + data[count] = 0; + } + err = intel_scu_ipc_writev(reg, data, 5); + if (err) + goto ipc_error; + + err = intel_scu_ipc_iowrite32(chan + 2, 0x00000000); + if (err) + goto ipc_error; + + /* Start ADC */ + err = intel_scu_ipc_update_register(PMIC_REG_MADCINT, 0x02, 0x02); + if (err) + goto ipc_error; + + return 0; + +ipc_error: + dev_err(tsdev->dev, "ipc error during %s\n", __func__); + return err; +} + +static void mrstouch_report_event(struct input_dev *input, + unsigned int x, unsigned int y, unsigned int z) +{ + if (z > MRST_PRESSURE_NOMINAL) { + /* Pen touched, report button touch and coordinates */ + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + } else { + input_report_key(input, BTN_TOUCH, 0); + } + + input_report_abs(input, ABS_PRESSURE, z); + input_sync(input); +} + +/* PENDET interrupt handler */ +static irqreturn_t mrstouch_pendet_irq(int irq, void *dev_id) +{ + struct mrstouch_dev *tsdev = dev_id; + u16 x, y, z; + + /* + * Should we lower thread priority? Probably not, since we are + * not spinning but sleeping... + */ + + if (tsdev->read_prepare(tsdev)) + goto out; + + do { + if (tsdev->read(tsdev, &x, &y, &z)) + break; + + mrstouch_report_event(tsdev->input, x, y, z); + } while (z > MRST_PRESSURE_NOMINAL); + + tsdev->read_finish(tsdev); + +out: + return IRQ_HANDLED; +} + +/* Utility to read PMIC ID */ +static int mrstouch_read_pmic_id(uint *vendor, uint *rev) +{ + int err; + u8 r; + + err = intel_scu_ipc_ioread8(PMIC_REG_ID1, &r); + if (err) + return err; + + *vendor = r & 0x7; + *rev = (r >> 3) & 0x7; + + return 0; +} + +/* + * Parse ADC channels to find end of the channel configured by other ADC user + * NEC and MAXIM requires 4 channels and FreeScale needs 18 channels + */ +static int mrstouch_chan_parse(struct mrstouch_dev *tsdev) +{ + int found = 0; + int err, i; + u8 r8; + + for (i = 0; i < MRSTOUCH_MAX_CHANNELS; i++) { + err = intel_scu_ipc_ioread8(PMICADDR0 + i, &r8); + if (err) + return err; + + if (r8 == END_OF_CHANNEL) { + found = i; + break; + } + } + + if (tsdev->vendor == PMIC_VENDOR_FS) { + if (found > MRSTOUCH_MAX_CHANNELS - 18) + return -ENOSPC; + } else { + if (found > MRSTOUCH_MAX_CHANNELS - 4) + return -ENOSPC; + } + + return found; +} + + +/* + * Writes touch screen channels to ADC address selection registers + */ +static int mrstouch_ts_chan_set(uint offset) +{ + u16 chan; + + int ret, count; + + chan = PMICADDR0 + offset; + for (count = 0; count <= 3; count++) { + ret = intel_scu_ipc_iowrite8(chan++, MRST_TS_CHAN10 + count); + if (ret) + return ret; + } + return intel_scu_ipc_iowrite8(chan++, END_OF_CHANNEL); +} + +/* Initialize ADC */ +static int mrstouch_adc_init(struct mrstouch_dev *tsdev) +{ + int err, start; + u8 ra, rm; + + err = mrstouch_read_pmic_id(&tsdev->vendor, &tsdev->rev); + if (err) { + dev_err(tsdev->dev, "Unable to read PMIC id\n"); + return err; + } + + switch (tsdev->vendor) { + case PMIC_VENDOR_NEC: + case PMIC_VENDOR_MAXIM: + tsdev->read_prepare = mrstouch_nec_adc_read_prepare; + tsdev->read = mrstouch_nec_adc_read; + tsdev->read_finish = mrstouch_nec_adc_read_finish; + break; + + case PMIC_VENDOR_FS: + tsdev->read_prepare = mrstouch_fs_adc_read_prepare; + tsdev->read = mrstouch_fs_adc_read; + tsdev->read_finish = mrstouch_fs_adc_read_finish; + break; + + default: + dev_err(tsdev->dev, + "Unsupported touchscreen: %d\n", tsdev->vendor); + return -ENXIO; + } + + start = mrstouch_chan_parse(tsdev); + if (start < 0) { + dev_err(tsdev->dev, "Unable to parse channels\n"); + return start; + } + + tsdev->asr = start; + + /* + * ADC power on, start, enable PENDET and set loop delay + * ADC loop delay is set to 4.5 ms approximately + * Loop delay more than this results in jitter in adc readings + * Setting loop delay to 0 (continuous loop) in MAXIM stops PENDET + * interrupt generation sometimes. + */ + + if (tsdev->vendor == PMIC_VENDOR_FS) { + ra = 0xE0 | ADC_LOOP_DELAY0; + rm = 0x5; + } else { + /* NEC and MAXIm not consistent with loop delay 0 */ + ra = 0xE0 | ADC_LOOP_DELAY1; + rm = 0x0; + + /* configure touch screen channels */ + err = mrstouch_ts_chan_set(tsdev->asr); + if (err) + return err; + } + + err = intel_scu_ipc_update_register(PMIC_REG_ADCCNTL1, ra, 0xE7); + if (err) + return err; + + err = intel_scu_ipc_update_register(PMIC_REG_MADCINT, rm, 0x03); + if (err) + return err; + + return 0; +} + + +/* Probe function for touch screen driver */ +static int mrstouch_probe(struct platform_device *pdev) +{ + struct mrstouch_dev *tsdev; + struct input_dev *input; + int err; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no interrupt assigned\n"); + return -EINVAL; + } + + tsdev = devm_kzalloc(&pdev->dev, sizeof(struct mrstouch_dev), + GFP_KERNEL); + if (!tsdev) { + dev_err(&pdev->dev, "unable to allocate memory\n"); + return -ENOMEM; + } + + input = devm_input_allocate_device(&pdev->dev); + if (!input) { + dev_err(&pdev->dev, "unable to allocate input device\n"); + return -ENOMEM; + } + + tsdev->dev = &pdev->dev; + tsdev->input = input; + tsdev->irq = irq; + + snprintf(tsdev->phys, sizeof(tsdev->phys), + "%s/input0", dev_name(tsdev->dev)); + + err = mrstouch_adc_init(tsdev); + if (err) { + dev_err(&pdev->dev, "ADC initialization failed\n"); + return err; + } + + input->name = "mrst_touchscreen"; + input->phys = tsdev->phys; + input->dev.parent = tsdev->dev; + + input->id.vendor = tsdev->vendor; + input->id.version = tsdev->rev; + + 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(tsdev->input, ABS_X, + MRST_X_MIN, MRST_X_MAX, MRST_X_FUZZ, 0); + input_set_abs_params(tsdev->input, ABS_Y, + MRST_Y_MIN, MRST_Y_MAX, MRST_Y_FUZZ, 0); + input_set_abs_params(tsdev->input, ABS_PRESSURE, + MRST_PRESSURE_MIN, MRST_PRESSURE_MAX, 0, 0); + + err = devm_request_threaded_irq(&pdev->dev, tsdev->irq, NULL, + mrstouch_pendet_irq, IRQF_ONESHOT, + "mrstouch", tsdev); + if (err) { + dev_err(tsdev->dev, "unable to allocate irq\n"); + return err; + } + + err = input_register_device(tsdev->input); + if (err) { + dev_err(tsdev->dev, "unable to register input device\n"); + return err; + } + + return 0; +} + +static struct platform_driver mrstouch_driver = { + .driver = { + .name = "pmic_touch", + }, + .probe = mrstouch_probe, +}; +module_platform_driver(mrstouch_driver); + +MODULE_AUTHOR("Sreedhara Murthy. D.S, sreedhara.ds@intel.com"); +MODULE_DESCRIPTION("Intel Moorestown Resistive Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/ipaq-micro-ts.c b/drivers/input/touchscreen/ipaq-micro-ts.c new file mode 100644 index 000000000..33c134820 --- /dev/null +++ b/drivers/input/touchscreen/ipaq-micro-ts.c @@ -0,0 +1,164 @@ +/* + * 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. + * + * 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(µ->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->users) + 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 = µ_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/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c new file mode 100644 index 000000000..ea3b6a5b8 --- /dev/null +++ b/drivers/input/touchscreen/jornada720_ts.c @@ -0,0 +1,153 @@ +/* + * 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> + * + * 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. + * + * HP Jornada 710/720/729 Touchscreen Driver + */ + +#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/hardware.h> +#include <mach/jornada720.h> +#include <mach/irqs.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; + 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_GPIO9 is set to high then report pen up */ + if (GPLR & GPIO_GPIO(9)) { + 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; + + 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->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_GPIO9, + 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..24d704cd9 --- /dev/null +++ b/drivers/input/touchscreen/lpc32xx_ts.c @@ -0,0 +1,408 @@ +/* + * LPC32xx built-in touchscreen driver + * + * Copyright (C) 2010 NXP Semiconductors + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#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 (1 << 8) +#define LPC32XX_TSC_STAT_FIFO_EMPTY (1 << 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 (1 << 2) +#define LPC32XX_TSC_ADCCON_AUTO_EN (1 << 0) + +#define LPC32XX_TSC_FIFO_TS_P_LEVEL (1 << 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(tsc->clk); +} + +static void lpc32xx_setup_tsc(struct lpc32xx_tsc *tsc) +{ + u32 tmp; + + clk_enable(tsc->clk); + + 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); +} + +static int lpc32xx_ts_open(struct input_dev *dev) +{ + struct lpc32xx_tsc *tsc = input_get_drvdata(dev); + + lpc32xx_setup_tsc(tsc); + + return 0; +} + +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) { + dev_err(&pdev->dev, "Can't get interrupt resource\n"); + 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; + + device_init_wakeup(&pdev->dev, 0); + 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->users) { + 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->users) { + 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..0786010d7 --- /dev/null +++ b/drivers/input/touchscreen/mainstone-wm97xx.c @@ -0,0 +1,309 @@ +/* + * 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> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * 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/irq.h> +#include <linux/interrupt.h> +#include <linux/wm97xx.h> +#include <linux/io.h> +#include <linux/gpio.h> + +#include <mach/regs-ac97.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 u16 last, tries; +static int 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 */ +#ifdef CONFIG_PXA27x +static void wm97xx_acc_pen_up(struct wm97xx *wm) +{ + schedule_timeout_uninterruptible(1); + + while (MISR & (1 << 2)) + MODR; +} +#else +static void wm97xx_acc_pen_up(struct wm97xx *wm) +{ + unsigned int count; + + schedule_timeout_uninterruptible(1); + + for (count = 0; count < 16; count++) + MODR; +} +#endif + +static int wm97xx_acc_pen_down(struct wm97xx *wm) +{ + u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES; + int reads = 0; + + /* 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. + */ + schedule_timeout_uninterruptible(1); + + if (tries > 5) { + tries = 0; + return RC_PENUP; + } + + x = MODR; + if (x == last) { + tries++; + return RC_AGAIN; + } + last = x; + do { + if (reads) + x = MODR; + y = MODR; + if (pressure) + p = 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; + irq = 27; + /* There is some obscure mutant of WM9712 interbred with WM9713 + * used on Palm HW */ + wm->variant = WM97xx_WM1613; + } else if (machine_is_mainstone() && pen_int) + irq = 4; + + if (irq) { + ret = gpio_request(irq, "Touchscreen IRQ"); + if (ret) + goto out; + + ret = gpio_direction_input(irq); + if (ret) { + gpio_free(irq); + goto out; + } + + wm->pen_irq = gpio_to_irq(irq); + irq_set_irq_type(wm->pen_irq, IRQ_TYPE_EDGE_BOTH); + } else /* pen irq not supported */ + pen_int = 0; + + /* 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; + } + } + +out: + return ret; +} + +static void wm97xx_acc_shutdown(struct wm97xx *wm) +{ + /* codec specific deconfig */ + if (pen_int) { + if (irq) + gpio_free(irq); + wm->pen_irq = 0; + } +} + +static void wm97xx_irq_enable(struct wm97xx *wm, int enable) +{ + if (enable) + enable_irq(wm->pen_irq); + else + disable_irq_nosync(wm->pen_irq); +} + +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_enable = wm97xx_irq_enable, + .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..a68ec142e --- /dev/null +++ b/drivers/input/touchscreen/max11801_ts.c @@ -0,0 +1,242 @@ +/* + * 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 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; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * 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: + /* fall through */ + 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); + input_set_drvdata(input_dev, data); + + 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; + + i2c_set_clientdata(client, data); + return 0; +} + +static const struct i2c_device_id max11801_ts_id[] = { + {"max11801", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, max11801_ts_id); + +static struct i2c_driver max11801_ts_driver = { + .driver = { + .name = "max11801_ts", + .owner = THIS_MODULE, + }, + .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..913e25a99 --- /dev/null +++ b/drivers/input/touchscreen/mc13783_ts.c @@ -0,0 +1,255 @@ +/* + * 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/ + * + * 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/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; + struct workqueue_struct *workq; + 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. + */ + queue_delayed_work(priv->workq, &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); + queue_delayed_work(priv->workq, &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; + } + + /* + * We need separate workqueue because mc13783_adc_do_conversion + * uses keventd and thus would deadlock. + */ + priv->workq = create_singlethread_workqueue("mc13783_ts"); + if (!priv->workq) + 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_destroy_wq; + } + + platform_set_drvdata(pdev, priv); + return 0; + +err_destroy_wq: + destroy_workqueue(priv->workq); +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); + + destroy_workqueue(priv->workq); + 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..8b47e1fec --- /dev/null +++ b/drivers/input/touchscreen/mcs5000_ts.c @@ -0,0 +1,294 @@ +/* + * 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 + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c/mcs.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/irq.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); + + input_set_drvdata(input_dev, data); + 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/migor_ts.c b/drivers/input/touchscreen/migor_ts.c new file mode 100644 index 000000000..c038db93e --- /dev/null +++ b/drivers/input/touchscreen/migor_ts.c @@ -0,0 +1,249 @@ +/* + * 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. + * + * This file 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; either + * version 2 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#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 int 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); + + return 0; +} + +static int 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 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); + +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..36e57deac --- /dev/null +++ b/drivers/input/touchscreen/mk712.c @@ -0,0 +1,219 @@ +/* + * 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 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. + */ + +/* + * This driver supports the ICS MicroClock MK712 TouchScreen controller, + * found in Gateway AOL Connected Touchpad computers. + * + * Documentation for ICS MK712 can be found at: + * http://www.idt.com/products/getDoc.cfm?docID=18713923 + */ + +/* + * 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_named(io, mk712_io, uint, 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_named(irq, mk712_irq, uint, 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..67c0d3161 --- /dev/null +++ b/drivers/input/touchscreen/mms114.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2012 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim <jy0922.shim@samsung.com> + * + * 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/module.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/i2c.h> +#include <linux/i2c/mms114.h> +#include <linux/input/mt.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 (1 << 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_INFOMATION 0x10 +#define MMS114_TSP_REV 0xF0 + +/* 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_PACKET_NUM 8 + +/* Touch type */ +#define MMS114_TYPE_NONE 0 +#define MMS114_TYPE_TOUCHSCREEN 1 +#define MMS114_TYPE_TOUCHKEY 2 + +struct mms114_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *core_reg; + struct regulator *io_reg; + const struct mms114_platform_data *pdata; + + /* 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: use repeated start */ + xfer[0].addr = client->addr; + xfer[0].flags = I2C_M_TEN | I2C_M_NOSTART; + xfer[0].len = 1; + 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; + + 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) +{ + const struct mms114_platform_data *pdata = data->pdata; + 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; + if (x > pdata->x_size || y > pdata->y_size) { + dev_dbg(&client->dev, + "Wrong touch coordinates (%d, %d)\n", x, y); + return; + } + + if (pdata->x_invert) + x = pdata->x_size - x; + if (pdata->y_invert) + y = pdata->y_size - y; + + 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) { + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width); + 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, 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_dev->users) { + 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; + + touch_size = packet_size / MMS114_PACKET_NUM; + + error = __mms114_read_reg(data, MMS114_INFOMATION, 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 error; + + error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); + if (error < 0) + return error; + + dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", + buf[0], buf[1], buf[3]); + + return 0; +} + +static int mms114_setup_regs(struct mms114_data *data) +{ + const struct mms114_platform_data *pdata = data->pdata; + int val; + int error; + + error = mms114_get_version(data); + if (error < 0) + return error; + + error = mms114_set_active(data, true); + if (error < 0) + return error; + + val = (pdata->x_size >> 8) & 0xf; + val |= ((pdata->y_size >> 8) & 0xf) << 4; + error = mms114_write_reg(data, MMS114_XY_RESOLUTION_H, val); + if (error < 0) + return error; + + val = pdata->x_size & 0xff; + error = mms114_write_reg(data, MMS114_X_RESOLUTION, val); + if (error < 0) + return error; + + val = pdata->y_size & 0xff; + error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val); + if (error < 0) + return error; + + if (pdata->contact_threshold) { + error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD, + pdata->contact_threshold); + if (error < 0) + return error; + } + + if (pdata->moving_threshold) { + error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD, + pdata->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; + } + + mdelay(MMS114_POWERON_DELAY); + + error = mms114_setup_regs(data); + if (error < 0) { + regulator_disable(data->io_reg); + regulator_disable(data->core_reg); + return error; + } + + if (data->pdata->cfg_pin) + data->pdata->cfg_pin(true); + + 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); + + if (data->pdata->cfg_pin) + data->pdata->cfg_pin(false); + + 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); +} + +#ifdef CONFIG_OF +static struct mms114_platform_data *mms114_parse_dt(struct device *dev) +{ + struct mms114_platform_data *pdata; + struct device_node *np = dev->of_node; + + if (!np) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "failed to allocate platform data\n"); + return NULL; + } + + if (of_property_read_u32(np, "x-size", &pdata->x_size)) { + dev_err(dev, "failed to get x-size property\n"); + return NULL; + }; + + if (of_property_read_u32(np, "y-size", &pdata->y_size)) { + dev_err(dev, "failed to get y-size property\n"); + return NULL; + }; + + of_property_read_u32(np, "contact-threshold", + &pdata->contact_threshold); + of_property_read_u32(np, "moving-threshold", + &pdata->moving_threshold); + + if (of_find_property(np, "x-invert", NULL)) + pdata->x_invert = true; + if (of_find_property(np, "y-invert", NULL)) + pdata->y_invert = true; + + return pdata; +} +#else +static inline struct mms114_platform_data *mms114_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + +static int mms114_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct mms114_platform_data *pdata; + struct mms114_data *data; + struct input_dev *input_dev; + int error; + + pdata = dev_get_platdata(&client->dev); + if (!pdata) + pdata = mms114_parse_dt(&client->dev); + + if (!pdata) { + dev_err(&client->dev, "Need platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_PROTOCOL_MANGLING)) { + dev_err(&client->dev, + "Need i2c bus that supports protocol mangling\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; + data->pdata = pdata; + + input_dev->name = "MELFAS MMS114 Touchscreen"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->open = mms114_input_open; + input_dev->close = mms114_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, data->pdata->x_size, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, data->pdata->y_size, 0, 0); + + /* For multi touch */ + input_mt_init_slots(input_dev, MMS114_MAX_TOUCH, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, MMS114_MAX_AREA, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, data->pdata->x_size, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, data->pdata->y_size, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + + 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_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&client->dev), data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + return error; + } + disable_irq(client->irq); + + 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_state(input_dev, MT_TOOL_FINGER, false); + } + + input_mt_report_pointer_emulation(input_dev, true); + input_sync(input_dev); + + mutex_lock(&input_dev->mutex); + if (input_dev->users) + 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_dev->users) { + 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" }, + { } +}; +#endif + +static struct i2c_driver mms114_driver = { + .driver = { + .name = "mms114", + .owner = THIS_MODULE, + .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"); diff --git a/drivers/input/touchscreen/mtouch.c b/drivers/input/touchscreen/mtouch.c new file mode 100644 index 000000000..9b5552a26 --- /dev/null +++ b/drivers/input/touchscreen/mtouch.c @@ -0,0 +1,204 @@ +/* + * MicroTouch (3M) serial touchscreen driver + * + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * 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. + */ + +/* + * 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 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/of_touchscreen.c b/drivers/input/touchscreen/of_touchscreen.c new file mode 100644 index 000000000..b82b5207c --- /dev/null +++ b/drivers/input/touchscreen/of_touchscreen.c @@ -0,0 +1,87 @@ +/* + * Generic DT helper functions for touchscreen devices + * + * Copyright (c) 2014 Sebastian Reichel <sre@kernel.org> + * + * 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/of.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> + +static u32 of_get_optional_u32(struct device_node *np, + const char *property) +{ + u32 val = 0; + + of_property_read_u32(np, property, &val); + + return val; +} + +static void touchscreen_set_params(struct input_dev *dev, + unsigned long axis, + int max, int fuzz) +{ + struct input_absinfo *absinfo; + + if (!test_bit(axis, dev->absbit)) { + /* + * Emit a warning only if the axis is not a multitouch + * axis, which might not be set by the driver. + */ + if (!input_is_mt_axis(axis)) + dev_warn(&dev->dev, + "DT specifies parameters but the axis is not set up\n"); + return; + } + + absinfo = &dev->absinfo[axis]; + absinfo->maximum = max; + absinfo->fuzz = fuzz; +} + +/** + * touchscreen_parse_of_params - parse common touchscreen DT properties + * @dev: device that should be parsed + * + * This function parses common DT properties for touchscreens and setups the + * input device accordingly. The function keeps previously setuped default + * values if no value is specified via DT. + */ +void touchscreen_parse_of_params(struct input_dev *dev) +{ + struct device_node *np = dev->dev.parent->of_node; + u32 maximum, fuzz; + + input_alloc_absinfo(dev); + if (!dev->absinfo) + return; + + maximum = of_get_optional_u32(np, "touchscreen-size-x"); + fuzz = of_get_optional_u32(np, "touchscreen-fuzz-x"); + if (maximum || fuzz) { + touchscreen_set_params(dev, ABS_X, maximum, fuzz); + touchscreen_set_params(dev, ABS_MT_POSITION_X, maximum, fuzz); + } + + maximum = of_get_optional_u32(np, "touchscreen-size-y"); + fuzz = of_get_optional_u32(np, "touchscreen-fuzz-y"); + if (maximum || fuzz) { + touchscreen_set_params(dev, ABS_Y, maximum, fuzz); + touchscreen_set_params(dev, ABS_MT_POSITION_Y, maximum, fuzz); + } + + maximum = of_get_optional_u32(np, "touchscreen-max-pressure"); + fuzz = of_get_optional_u32(np, "touchscreen-fuzz-pressure"); + if (maximum || fuzz) { + touchscreen_set_params(dev, ABS_PRESSURE, maximum, fuzz); + touchscreen_set_params(dev, ABS_MT_PRESSURE, maximum, fuzz); + } +} +EXPORT_SYMBOL(touchscreen_parse_of_params); diff --git a/drivers/input/touchscreen/pcap_ts.c b/drivers/input/touchscreen/pcap_ts.c new file mode 100644 index 000000000..23a354a39 --- /dev/null +++ b/drivers/input/touchscreen/pcap_ts.c @@ -0,0 +1,258 @@ +/* + * 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> + * + * 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/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 = container_of(work, struct 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..417d87379 --- /dev/null +++ b/drivers/input/touchscreen/penmount.c @@ -0,0 +1,319 @@ +/* + * 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 + */ + +/* + * 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/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 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..8f3e243a6 --- /dev/null +++ b/drivers/input/touchscreen/pixcir_i2c_ts.c @@ -0,0 +1,619 @@ +/* + * Driver for Pixcir I2C touchscreen controllers. + * + * Copyright (C) 2010-2011 Pixcir, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/input/pixcir_ts.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_device.h> + +#define PIXCIR_MAX_SLOTS 5 /* Max fingers supported by driver */ + +struct pixcir_i2c_ts_data { + struct i2c_client *client; + struct input_dev *input; + const struct pixcir_ts_platform_data *pdata; + bool running; + int max_fingers; /* Max fingers supported in this instance */ +}; + +struct pixcir_touch { + int x; + int y; + int id; +}; + +struct pixcir_report_data { + int num_touches; + struct pixcir_touch touches[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->pdata->chip; + + memset(report, 0, sizeof(struct pixcir_report_data)); + + i = chip->has_hw_ids ? 1 : 0; + readsize = 2 + tsdata->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->max_fingers) + touch = tsdata->max_fingers; + + report->num_touches = touch; + bufptr = &rdbuf[2]; + + for (i = 0; i < touch; i++) { + report->touches[i].x = (bufptr[1] << 8) | bufptr[0]; + report->touches[i].y = (bufptr[3] << 8) | bufptr[2]; + + if (chip->has_hw_ids) { + report->touches[i].id = 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) +{ + struct input_mt_pos pos[PIXCIR_MAX_SLOTS]; + int slots[PIXCIR_MAX_SLOTS]; + struct pixcir_touch *touch; + int n, i, slot; + struct device *dev = &ts->client->dev; + const struct pixcir_i2c_chip_data *chip = &ts->pdata->chip; + + n = report->num_touches; + if (n > PIXCIR_MAX_SLOTS) + n = PIXCIR_MAX_SLOTS; + + if (!chip->has_hw_ids) { + for (i = 0; i < n; i++) { + touch = &report->touches[i]; + pos[i].x = touch->x; + pos[i].y = touch->y; + } + + input_mt_assign_slots(ts->input, slots, pos, n, 0); + } + + for (i = 0; i < n; i++) { + touch = &report->touches[i]; + + if (chip->has_hw_ids) { + slot = input_mt_get_slot_by_key(ts->input, touch->id); + if (slot < 0) { + dev_dbg(dev, "no free slot for id 0x%x\n", + touch->id); + continue; + } + } else { + slot = slots[i]; + } + + input_mt_slot(ts->input, slot); + input_mt_report_slot_state(ts->input, + MT_TOOL_FINGER, true); + + input_event(ts->input, EV_ABS, ABS_MT_POSITION_X, touch->x); + input_event(ts->input, EV_ABS, ABS_MT_POSITION_Y, touch->y); + + dev_dbg(dev, "%d: slot %d, x %d, y %d\n", + i, slot, touch->x, touch->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; + const struct pixcir_ts_platform_data *pdata = tsdata->pdata; + struct pixcir_report_data report; + + while (tsdata->running) { + /* parse packet */ + pixcir_ts_parse(tsdata, &report); + + /* report it */ + pixcir_ts_report(tsdata, &report); + + if (gpio_get_value(pdata->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 int pixcir_set_power_mode(struct pixcir_i2c_ts_data *ts, + enum pixcir_power_mode mode) +{ + struct device *dev = &ts->client->dev; + int ret; + + ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_POWER_MODE); + if (ret < 0) { + dev_err(dev, "%s: can't read reg 0x%x : %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 0x%x : %d\n", + __func__, PIXCIR_REG_POWER_MODE, ret); + return ret; + } + + 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 0x%x : %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 0x%x : %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 0x%x : %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 0x%x : %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; + + /* 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); + + 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->users) { + ret = pixcir_start(ts); + if (ret) { + dev_err(dev, "Failed to start\n"); + goto unlock; + } + } + + enable_irq_wake(client->irq); + } else if (input->users) { + 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)) { + disable_irq_wake(client->irq); + + if (!input->users) { + ret = pixcir_stop(ts); + if (ret) { + dev_err(dev, "Failed to stop\n"); + goto unlock; + } + } + } else if (input->users) { + 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); + +#ifdef CONFIG_OF +static const struct of_device_id pixcir_of_match[]; + +static struct pixcir_ts_platform_data *pixcir_parse_dt(struct device *dev) +{ + struct pixcir_ts_platform_data *pdata; + struct device_node *np = dev->of_node; + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(pixcir_of_match), dev); + if (!match) + return ERR_PTR(-EINVAL); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->chip = *(const struct pixcir_i2c_chip_data *)match->data; + + pdata->gpio_attb = of_get_named_gpio(np, "attb-gpio", 0); + /* gpio_attb validity is checked in probe */ + + if (of_property_read_u32(np, "touchscreen-size-x", &pdata->x_max)) { + dev_err(dev, "Failed to get touchscreen-size-x property\n"); + return ERR_PTR(-EINVAL); + } + pdata->x_max -= 1; + + if (of_property_read_u32(np, "touchscreen-size-y", &pdata->y_max)) { + dev_err(dev, "Failed to get touchscreen-size-y property\n"); + return ERR_PTR(-EINVAL); + } + pdata->y_max -= 1; + + dev_dbg(dev, "%s: x %d, y %d, gpio %d\n", __func__, + pdata->x_max + 1, pdata->y_max + 1, pdata->gpio_attb); + + return pdata; +} +#else +static struct pixcir_ts_platform_data *pixcir_parse_dt(struct device *dev) +{ + return ERR_PTR(-EINVAL); +} +#endif + +static int pixcir_i2c_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct pixcir_ts_platform_data *pdata = + dev_get_platdata(&client->dev); + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct pixcir_i2c_ts_data *tsdata; + struct input_dev *input; + int error; + + if (np && !pdata) { + pdata = pixcir_parse_dt(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + if (!pdata) { + dev_err(&client->dev, "platform data not defined\n"); + return -EINVAL; + } + + if (!gpio_is_valid(pdata->gpio_attb)) { + dev_err(dev, "Invalid gpio_attb in pdata\n"); + return -EINVAL; + } + + if (!pdata->chip.max_fingers) { + dev_err(dev, "Invalid max_fingers in pdata\n"); + return -EINVAL; + } + + tsdata = devm_kzalloc(dev, sizeof(*tsdata), GFP_KERNEL); + if (!tsdata) + return -ENOMEM; + + 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; + tsdata->pdata = pdata; + + input->name = client->name; + input->id.bustype = BUS_I2C; + input->open = pixcir_input_open; + input->close = pixcir_input_close; + input->dev.parent = &client->dev; + + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_ABS, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_X, 0, pdata->x_max, 0, 0); + input_set_abs_params(input, ABS_Y, 0, pdata->y_max, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, pdata->x_max, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, pdata->y_max, 0, 0); + + tsdata->max_fingers = tsdata->pdata->chip.max_fingers; + if (tsdata->max_fingers > PIXCIR_MAX_SLOTS) { + tsdata->max_fingers = PIXCIR_MAX_SLOTS; + dev_info(dev, "Limiting maximum fingers to %d\n", + tsdata->max_fingers); + } + + error = input_mt_init_slots(input, tsdata->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); + + error = devm_gpio_request_one(dev, pdata->gpio_attb, + GPIOF_DIR_IN, "pixcir_i2c_attb"); + if (error) { + dev_err(dev, "Failed to request ATTB gpio\n"); + return error; + } + + 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; + } + + /* 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); + device_init_wakeup(&client->dev, 1); + + return 0; +} + +static int pixcir_i2c_ts_remove(struct i2c_client *client) +{ + device_init_wakeup(&client->dev, 0); + + return 0; +} + +static const struct i2c_device_id pixcir_i2c_ts_id[] = { + { "pixcir_ts", 0 }, + { "pixcir_tangoc", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pixcir_i2c_ts_id); + +#ifdef CONFIG_OF +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 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 = { + .owner = THIS_MODULE, + .name = "pixcir_ts", + .pm = &pixcir_dev_pm_ops, + .of_match_table = of_match_ptr(pixcir_of_match), + }, + .probe = pixcir_i2c_ts_probe, + .remove = pixcir_i2c_ts_remove, + .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/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c new file mode 100644 index 000000000..bdfa27dc0 --- /dev/null +++ b/drivers/input/touchscreen/s3c2410_ts.c @@ -0,0 +1,439 @@ +/* + * Samsung S3C24XX touchscreen driver + * + * This program is free software; you can redistribute it and/or modify + * it under the term of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * 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/gpio.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 <plat/adc.h> +#include <plat/regs-adc.h> +#include <linux/platform_data/touchscreen-s3c2410.h> + +#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(unsigned long data) +{ + 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, 0, 0); + +/** + * 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(&pdev->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; + } + + clk_prepare_enable(ts.clock); + 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: + del_timer_sync(&touch_timer); + 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 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/st1232.c b/drivers/input/touchscreen/st1232.c new file mode 100644 index 000000000..697e26e52 --- /dev/null +++ b/drivers/input/touchscreen/st1232.c @@ -0,0 +1,309 @@ +/* + * 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. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm_qos.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/platform_data/st1232_pdata.h> + +#define ST1232_TS_NAME "st1232-ts" + +#define MIN_X 0x00 +#define MIN_Y 0x00 +#define MAX_X 0x31f /* (800 - 1) */ +#define MAX_Y 0x1df /* (480 - 1) */ +#define MAX_AREA 0xff +#define MAX_FINGERS 2 + +struct st1232_ts_finger { + u16 x; + u16 y; + u8 t; + bool is_valid; +}; + +struct st1232_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct st1232_ts_finger finger[MAX_FINGERS]; + struct dev_pm_qos_request low_latency_req; + int reset_gpio; +}; + +static int st1232_ts_read_data(struct st1232_ts_data *ts) +{ + struct st1232_ts_finger *finger = ts->finger; + struct i2c_client *client = ts->client; + struct i2c_msg msg[2]; + int error; + u8 start_reg; + u8 buf[10]; + + /* read touchscreen data from ST1232 */ + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &start_reg; + start_reg = 0x10; + + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(buf); + msg[1].buf = buf; + + error = i2c_transfer(client->adapter, msg, 2); + if (error < 0) + return error; + + /* get "valid" bits */ + finger[0].is_valid = buf[2] >> 7; + finger[1].is_valid = buf[5] >> 7; + + /* get xy coordinate */ + if (finger[0].is_valid) { + finger[0].x = ((buf[2] & 0x0070) << 4) | buf[3]; + finger[0].y = ((buf[2] & 0x0007) << 8) | buf[4]; + finger[0].t = buf[8]; + } + + if (finger[1].is_valid) { + finger[1].x = ((buf[5] & 0x0070) << 4) | buf[6]; + finger[1].y = ((buf[5] & 0x0007) << 8) | buf[7]; + finger[1].t = buf[9]; + } + + return 0; +} + +static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id) +{ + struct st1232_ts_data *ts = dev_id; + struct st1232_ts_finger *finger = ts->finger; + struct input_dev *input_dev = ts->input_dev; + int count = 0; + int i, ret; + + ret = st1232_ts_read_data(ts); + if (ret < 0) + goto end; + + /* multi touch protocol */ + for (i = 0; i < MAX_FINGERS; i++) { + if (!finger[i].is_valid) + continue; + + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t); + input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y); + input_mt_sync(input_dev); + count++; + } + + /* SYN_MT_REPORT only if no contact */ + if (!count) { + input_mt_sync(input_dev); + 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); + } + + /* SYN_REPORT */ + input_sync(input_dev); + +end: + return IRQ_HANDLED; +} + +static void st1232_ts_power(struct st1232_ts_data *ts, bool poweron) +{ + if (gpio_is_valid(ts->reset_gpio)) + gpio_direction_output(ts->reset_gpio, poweron); +} + +static int st1232_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct st1232_ts_data *ts; + struct st1232_pdata *pdata = dev_get_platdata(&client->dev); + struct input_dev *input_dev; + int error; + + 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; + + input_dev = devm_input_allocate_device(&client->dev); + if (!input_dev) + return -ENOMEM; + + ts->client = client; + ts->input_dev = input_dev; + + if (pdata) + ts->reset_gpio = pdata->reset_gpio; + else if (client->dev.of_node) + ts->reset_gpio = of_get_gpio(client->dev.of_node, 0); + else + ts->reset_gpio = -ENODEV; + + if (gpio_is_valid(ts->reset_gpio)) { + error = devm_gpio_request(&client->dev, ts->reset_gpio, NULL); + if (error) { + dev_err(&client->dev, + "Unable to request GPIO pin %d.\n", + ts->reset_gpio); + return error; + } + } + + st1232_ts_power(ts, true); + + input_dev->name = "st1232-touchscreen"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + + __set_bit(EV_SYN, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_AREA, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, MIN_X, MAX_X, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, MIN_Y, MAX_Y, 0, 0); + + 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); + device_init_wakeup(&client->dev, 1); + + return 0; +} + +static int st1232_ts_remove(struct i2c_client *client) +{ + struct st1232_ts_data *ts = i2c_get_clientdata(client); + + device_init_wakeup(&client->dev, 0); + st1232_ts_power(ts, false); + + 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); + + if (device_may_wakeup(&client->dev)) { + enable_irq_wake(client->irq); + } else { + disable_irq(client->irq); + 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)) { + disable_irq_wake(client->irq); + } else { + 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, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st1232_ts_id); + +#ifdef CONFIG_OF +static const struct of_device_id st1232_ts_dt_ids[] = { + { .compatible = "sitronix,st1232", }, + { } +}; +MODULE_DEVICE_TABLE(of, st1232_ts_dt_ids); +#endif + +static struct i2c_driver st1232_ts_driver = { + .probe = st1232_ts_probe, + .remove = st1232_ts_remove, + .id_table = st1232_ts_id, + .driver = { + .name = ST1232_TS_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(st1232_ts_dt_ids), + .pm = &st1232_ts_pm_ops, + }, +}; + +module_i2c_driver(st1232_ts_driver); + +MODULE_AUTHOR("Tony SIM <chinyeow.sim.xt@renesas.com>"); +MODULE_DESCRIPTION("SITRONIX ST1232 Touchscreen Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c new file mode 100644 index 000000000..e4c31256a --- /dev/null +++ b/drivers/input/touchscreen/stmpe-ts.c @@ -0,0 +1,396 @@ +/* + * STMicroelectronics STMPE811 Touchscreen Driver + * + * (C) 2010 Luotao Fu <l.fu@pengutronix.de> + * All rights reserved. + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#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/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_ADC_CTRL1 0x20 +#define STMPE_REG_ADC_CTRL2 0x21 +#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 SAMPLE_TIME(x) ((x & 0xf) << 4) +#define MOD_12B(x) ((x & 0x1) << 3) +#define REF_SEL(x) ((x & 0x1) << 1) +#define ADC_FREQ(x) (x & 0x3) +#define AVE_CTRL(x) ((x & 0x3) << 6) +#define DET_DELAY(x) ((x & 0x7) << 3) +#define SETTLING(x) (x & 0x7) +#define FRACTION_Z(x) (x & 0x7) +#define I_DRIVE(x) (x & 0x1) +#define OP_MODE(x) ((x & 0x7) << 1) + +#define STMPE_TS_NAME "stmpe-ts" +#define XY_MASK 0xfff + +struct stmpe_touch { + struct stmpe *stmpe; + struct input_dev *idev; + struct delayed_work work; + struct device *dev; + u8 sample_time; + u8 mod_12b; + u8 ref_sel; + u8 adc_freq; + 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]; + + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + 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 adc_ctrl1, adc_ctrl1_mask, 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; + } + + adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) | + REF_SEL(ts->ref_sel); + adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff); + + ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1, + adc_ctrl1_mask, adc_ctrl1); + if (ret) { + dev_err(dev, "Could not setup ADC\n"); + return ret; + } + + ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2, + ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq)); + if (ret) { + dev_err(dev, "Could not setup ADC\n"); + return ret; + } + + tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) | + SETTLING(ts->settling); + tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | 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, + FRACTION_Z(0xff), 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, + I_DRIVE(0xff), 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, + OP_MODE(0xff), 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 stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct device_node *np = pdev->dev.of_node; + struct stmpe_ts_platform_data *ts_pdata = NULL; + + ts->stmpe = stmpe; + + if (stmpe->pdata && stmpe->pdata->ts) { + ts_pdata = stmpe->pdata->ts; + + ts->sample_time = ts_pdata->sample_time; + ts->mod_12b = ts_pdata->mod_12b; + ts->ref_sel = ts_pdata->ref_sel; + ts->adc_freq = ts_pdata->adc_freq; + ts->ave_ctrl = ts_pdata->ave_ctrl; + ts->touch_det_delay = ts_pdata->touch_det_delay; + ts->settling = ts_pdata->settling; + ts->fraction_z = ts_pdata->fraction_z; + ts->i_drive = ts_pdata->i_drive; + } else if (np) { + u32 val; + + if (!of_property_read_u32(np, "st,sample-time", &val)) + ts->sample_time = val; + if (!of_property_read_u32(np, "st,mod-12b", &val)) + ts->mod_12b = val; + if (!of_property_read_u32(np, "st,ref-sel", &val)) + ts->ref_sel = val; + if (!of_property_read_u32(np, "st,adc-freq", &val)) + ts->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_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->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->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + idev->open = stmpe_ts_open; + idev->close = stmpe_ts_close; + + input_set_drvdata(idev, ts); + + 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); + + 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); + +MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>"); +MODULE_DESCRIPTION("STMPEXXX touchscreen driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" STMPE_TS_NAME); diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c new file mode 100644 index 000000000..c01169940 --- /dev/null +++ b/drivers/input/touchscreen/sun4i-ts.c @@ -0,0 +1,426 @@ +/* + * 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> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * 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/README) + * (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; + struct thermal_zone_device *tz; + 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, long *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(void *data, long *temp) +{ + return sun4i_get_temp(data, temp); +} + +static struct thermal_zone_of_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); + long temp; + int error; + + error = sun4i_get_temp(ts, &temp); + if (error) + return error; + + return sprintf(buf, "%ld\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; + 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_ioremap_resource(dev, + platform_get_resource(pdev, IORESOURCE_MEM, 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); + + ts->tz = thermal_zone_of_sensor_register(ts->dev, 0, ts, + &sun4i_ts_tz_ops); + if (IS_ERR(ts->tz)) + ts->tz = NULL; + + 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); + thermal_zone_of_sensor_unregister(ts->dev, ts->tz); + 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); + + thermal_zone_of_sensor_unregister(ts->dev, ts->tz); + + /* 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..a24eba5ea --- /dev/null +++ b/drivers/input/touchscreen/sur40.c @@ -0,0 +1,876 @@ +/* + * 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. + * + * 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; either version 2 of + * the License, or (at your option) any later version. + */ + +#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-polldev.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/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 unknown; /* always 0x01 or 0x02 (no idea what this is?) */ + + __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[32]; + +} __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 10 + +/* 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_UNKNOWN1 0xb3 /* 5 bytes */ +#define SUR40_UNKNOWN2 0xc1 /* 24 bytes */ + +#define SUR40_GET_STATE 0xc5 /* 4 bytes state (?) */ +#define SUR40_GET_SENSORS 0xb1 /* 8 bytes sensors */ + +/* master device state */ +struct sur40_state { + + struct usb_device *usbdev; + struct device *dev; + struct input_polled_dev *input; + + struct v4l2_device v4l2; + struct video_device vdev; + struct mutex lock; + + struct vb2_queue queue; + struct vb2_alloc_ctx *alloc_ctx; + 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; + + char phys[64]; +}; + +struct sur40_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +/* forward declarations */ +static const struct video_device sur40_video_device; +static const struct v4l2_pix_format sur40_video_format; +static const struct vb2_queue sur40_queue; +static void sur40_process_video(struct sur40_state *sur40); + +/* + * 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); +} + +/* Initialization routine, called from sur40_open */ +static int sur40_init(struct sur40_state *dev) +{ + int result; + u8 buffer[24]; + + /* stupidly replay the original MS driver init sequence */ + result = sur40_command(dev, SUR40_GET_VERSION, 0x00, buffer, 12); + if (result < 0) + return result; + + result = sur40_command(dev, SUR40_GET_VERSION, 0x01, buffer, 12); + if (result < 0) + return result; + + result = sur40_command(dev, SUR40_GET_VERSION, 0x02, buffer, 12); + if (result < 0) + return result; + + result = sur40_command(dev, SUR40_UNKNOWN2, 0x00, buffer, 24); + if (result < 0) + return result; + + result = sur40_command(dev, SUR40_UNKNOWN1, 0x00, buffer, 5); + if (result < 0) + return result; + + result = sur40_command(dev, SUR40_GET_VERSION, 0x03, buffer, 12); + + /* + * Discard the result buffer - no known data inside except + * some version strings, maybe extract these sometime... + */ + + return result; +} + +/* + * Callback routines from input_polled_dev + */ + +/* Enable the device, polling will now start. */ +static void sur40_open(struct input_polled_dev *polldev) +{ + struct sur40_state *sur40 = polldev->private; + + dev_dbg(sur40->dev, "open\n"); + sur40_init(sur40); +} + +/* Disable device, polling has stopped. */ +static void sur40_close(struct input_polled_dev *polldev) +{ + struct sur40_state *sur40 = polldev->private; + + 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 = le16_to_cpu(blob->bb_size_x); + int bb_size_y = le16_to_cpu(blob->bb_size_y); + + int pos_x = le16_to_cpu(blob->pos_x); + int pos_y = le16_to_cpu(blob->pos_y); + + int ctr_x = le16_to_cpu(blob->ctr_x); + int ctr_y = le16_to_cpu(blob->ctr_y); + + int slotnum = input_mt_get_slot_by_key(input, blob->blob_id); + if (slotnum < 0 || slotnum >= MAX_CONTACTS) + return; + + 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_polled_dev *polldev) +{ + struct sur40_state *sur40 = polldev->private; + struct input_dev *input = polldev->input; + int result, bulk_read, need_blobs, packet_blobs, i; + u32 uninitialized_var(packet_id); + + 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. + */ + if (packet_id != header->packet_id) + dev_warn(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); + + /* 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_video_format.sizeimage) { + dev_err(sur40->dev, "image size mismatch\n"); + goto err_poll; + } + + sgt = vb2_dma_sg_plane_desc(&new_buf->vb, 0); + + result = usb_sg_init(&sgr, sur40->usbdev, + usb_rcvbulkpipe(sur40->usbdev, VIDEO_ENDPOINT), 0, + sgt->sgl, sgt->nents, sur40_video_format.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; + } + + /* mark as finished */ + v4l2_get_timestamp(&new_buf->vb.v4l2_buf.timestamp); + new_buf->vb.v4l2_buf.sequence = sur40->sequence++; + new_buf->vb.v4l2_buf.field = V4L2_FIELD_NONE; + vb2_buffer_done(&new_buf->vb, VB2_BUF_STATE_DONE); + return; + +err_poll: + vb2_buffer_done(&new_buf->vb, VB2_BUF_STATE_ERROR); +} + +/* Initialize input device parameters. */ +static void sur40_input_setup(struct input_dev *input_dev) +{ + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + + 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); + + input_mt_init_slots(input_dev, MAX_CONTACTS, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); +} + +/* 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_polled_dev *poll_dev; + int error; + + /* Check if we really have the right interface. */ + iface_desc = &interface->altsetting[0]; + if (iface_desc->desc.bInterfaceClass != 0xFF) + 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; + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + 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 polled input device control structure */ + poll_dev->private = sur40; + poll_dev->poll_interval = POLL_INTERVAL; + poll_dev->open = sur40_open; + poll_dev->poll = sur40_poll; + poll_dev->close = sur40_close; + + /* Set up regular input device structure */ + sur40_input_setup(poll_dev->input); + + poll_dev->input->name = DRIVER_LONG; + usb_to_input_id(usbdev, &poll_dev->input->id); + usb_make_path(usbdev, sur40->phys, sizeof(sur40->phys)); + strlcat(sur40->phys, "/input0", sizeof(sur40->phys)); + poll_dev->input->phys = sur40->phys; + poll_dev->input->dev.parent = &interface->dev; + + sur40->usbdev = usbdev; + sur40->dev = &interface->dev; + sur40->input = poll_dev; + + /* 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_polldev; + } + + /* register the polled input device */ + error = input_register_polled_device(poll_dev); + 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; + + /* initialize the queue */ + error = vb2_queue_init(&sur40->queue); + if (error) + goto err_unreg_v4l2; + + sur40->alloc_ctx = vb2_dma_sg_init_ctx(sur40->dev); + if (IS_ERR(sur40->alloc_ctx)) { + dev_err(sur40->dev, "Can't allocate buffer context"); + goto err_unreg_v4l2; + } + + 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); + + error = video_register_device(&sur40->vdev, VFL_TYPE_GRABBER, -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_polldev: + input_free_polled_device(sur40->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); + + video_unregister_device(&sur40->vdev); + v4l2_device_unregister(&sur40->v4l2); + vb2_dma_sg_cleanup_ctx(sur40->alloc_ctx); + + input_unregister_polled_device(sur40->input); + input_free_polled_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, const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct sur40_state *sur40 = vb2_get_drv_priv(q); + + if (q->num_buffers + *nbuffers < 3) + *nbuffers = 3 - q->num_buffers; + + if (fmt && fmt->fmt.pix.sizeimage < sur40_video_format.sizeimage) + return -EINVAL; + + *nplanes = 1; + sizes[0] = fmt ? fmt->fmt.pix.sizeimage : sur40_video_format.sizeimage; + alloc_ctxs[0] = sur40->alloc_ctx; + + 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_video_format.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, 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); + + /* 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); + + strlcpy(cap->driver, DRIVER_SHORT, sizeof(cap->driver)); + strlcpy(cap->card, DRIVER_LONG, sizeof(cap->card)); + usb_make_path(sur40->usbdev, cap->bus_info, sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + 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_CAMERA; + i->std = V4L2_STD_UNKNOWN; + strlcpy(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_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + f->fmt.pix = sur40_video_format; + return 0; +} + +static int sur40_vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + strlcpy(f->description, "8-bit greyscale", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_GREY; + f->flags = 0; + 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_fmt, + .vidioc_s_fmt_vid_cap = sur40_vidioc_fmt, + .vidioc_g_fmt_vid_cap = sur40_vidioc_fmt, + + .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, +}; + +static const struct v4l2_pix_format sur40_video_format = { + .pixelformat = V4L2_PIX_FMT_GREY, + .width = SENSOR_RES_X / 2, + .height = SENSOR_RES_Y / 2, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .bytesperline = SENSOR_RES_X / 2, + .sizeimage = (SENSOR_RES_X/2) * (SENSOR_RES_Y/2), +}; + +/* 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/sx8654.c b/drivers/input/touchscreen/sx8654.c new file mode 100644 index 000000000..642f4a53d --- /dev/null +++ b/drivers/input/touchscreen/sx8654.c @@ -0,0 +1,286 @@ +/* + * 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 + * + * 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/input.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.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 + +/* commands */ +#define CMD_READ_REGISTER 0x40 +#define CMD_MANUAL 0xc0 +#define CMD_PENTRG 0xe0 + +/* value for I2C_REG_SOFTRESET */ +#define SOFTRESET_VALUE 0xde + +/* bits for I2C_REG_IRQSRC */ +#define IRQ_PENTOUCH_TOUCHCONVDONE 0x08 +#define IRQ_PENRELEASE 0x04 + +/* bits for RegTouch1 */ +#define CONDIRQ 0x20 +#define FILT_7SA 0x03 + +/* bits for I2C_REG_CHANMASK */ +#define CONV_X 0x80 +#define CONV_Y 0x40 + +/* 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 + +#define MAX_12BIT ((1 << 12) - 1) + +struct sx8654 { + struct input_dev *input; + struct i2c_client *client; +}; + +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]); + + input_report_abs(sx8654->input, ABS_X, x); + input_report_abs(sx8654->input, ABS_Y, y); + 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_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); + + /* enable manual mode mode */ + error = i2c_smbus_write_byte(client, CMD_MANUAL); + if (error) { + dev_err(&client->dev, "writing command CMD_MANUAL failed"); + return; + } + + error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH0, 0); + 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; + + 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); + + sx8654->client = client; + sx8654->input = input; + + input_set_drvdata(sx8654->input, sx8654); + + error = i2c_smbus_write_byte_data(client, I2C_REG_SOFTRESET, + SOFTRESET_VALUE); + if (error) { + dev_err(&client->dev, "writing softreset value failed"); + return error; + } + + error = i2c_smbus_write_byte_data(client, I2C_REG_CHANMASK, + CONV_X | CONV_Y); + if (error) { + dev_err(&client->dev, "writing to I2C_REG_CHANMASK failed"); + return error; + } + + error = i2c_smbus_write_byte_data(client, I2C_REG_IRQMASK, + IRQ_PENTOUCH_TOUCHCONVDONE | + IRQ_PENRELEASE); + if (error) { + dev_err(&client->dev, "writing to I2C_REG_IRQMASK failed"); + return error; + } + + error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH1, + CONDIRQ | 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_irq, + IRQF_TRIGGER_FALLING | 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; + + i2c_set_clientdata(client, sx8654); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sx8654_of_match[] = { + { .compatible = "semtech,sx8654", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sx8654_of_match); +#endif + +static const struct i2c_device_id sx8654_id_table[] = { + { "semtech_sx8654", 0 }, + { }, +}; +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..191a1b878 --- /dev/null +++ b/drivers/input/touchscreen/ti_am335x_tsc.c @@ -0,0 +1,554 @@ +/* + * 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/mfd/ti_am335x_tscadc.h> + +#define ADCFSM_STEPID 0x10 +#define SEQ_SETTLE 275 +#define MAX_12BIT ((1 << 12) - 1) + +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; + 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; + int end_step, first_step, tsc_steps; + u32 stepenable; + + config = STEPCONFIG_MODE_HWSYNC | + STEPCONFIG_AVG_16 | ts_dev->bit_xp; + 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; + 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), STEPCONFIG_OPENDLY); + } + + 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 | + ts_dev->bit_xn | ts_dev->bit_yp; + 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; + for (i = first_step; i < end_step; i++) { + titsc_writel(ts_dev, REG_STEPCONFIG(i), config); + titsc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY); + } + + /* 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_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; + titsc_writel(ts_dev, REG_IRQWAKEUP, 0x00); + titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN); + irqclr |= IRQENB_HW_PEN; + } + + 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); + } 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 postion/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(struct titsc), 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; + + 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; + } + + 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: + 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; + + 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; +} + +#ifdef CONFIG_PM +static int titsc_suspend(struct device *dev) +{ + struct titsc *ts_dev = dev_get_drvdata(dev); + struct ti_tscadc_dev *tscadc_dev; + unsigned int idle; + + tscadc_dev = ti_tscadc_dev_get(to_platform_device(dev)); + if (device_may_wakeup(tscadc_dev->dev)) { + 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 titsc_resume(struct device *dev) +{ + struct titsc *ts_dev = dev_get_drvdata(dev); + struct ti_tscadc_dev *tscadc_dev; + + tscadc_dev = ti_tscadc_dev_get(to_platform_device(dev)); + if (device_may_wakeup(tscadc_dev->dev)) { + titsc_writel(ts_dev, REG_IRQWAKEUP, + 0x00); + titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN); + } + titsc_step_config(ts_dev); + titsc_writel(ts_dev, REG_FIFO0THR, + ts_dev->coordinate_readouts * 2 + 2 - 1); + return 0; +} + +static const struct dev_pm_ops titsc_pm_ops = { + .suspend = titsc_suspend, + .resume = titsc_resume, +}; +#define TITSC_PM_OPS (&titsc_pm_ops) +#else +#define TITSC_PM_OPS NULL +#endif + +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..c27cf8f3d --- /dev/null +++ b/drivers/input/touchscreen/touchit213.c @@ -0,0 +1,218 @@ +/* + * 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> + */ + +/* + * 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/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 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..4000e5205 --- /dev/null +++ b/drivers/input/touchscreen/touchright.c @@ -0,0 +1,178 @@ +/* + * 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> + */ + +/* + * 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/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 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..ba90f447d --- /dev/null +++ b/drivers/input/touchscreen/touchwin.c @@ -0,0 +1,185 @@ +/* + * 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> + */ + +/* + * 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. + */ + +/* + * 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 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..4ffd829d1 --- /dev/null +++ b/drivers/input/touchscreen/tps6507x-ts.c @@ -0,0 +1,326 @@ +/* + * 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/input-polldev.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_polled_dev *poll_dev; + 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) +{ + int err; + + err = tsc->mfd->read_dev(tsc->mfd, reg, 1, data); + + if (err) + return err; + + return 0; +} + +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_polled_dev *poll_dev) +{ + struct tps6507x_ts *tsc = poll_dev->private; + struct input_dev *input_dev = poll_dev->input; + 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_polled_dev *poll_dev; + 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 = kzalloc(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)); + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + dev_err(tsc->dev, "Failed to allocate polled input device.\n"); + error = -ENOMEM; + goto err_free_mem; + } + + tsc->poll_dev = poll_dev; + + poll_dev->private = tsc; + poll_dev->poll = tps6507x_ts_poll; + poll_dev->poll_interval = init_data ? + init_data->poll_period : TSC_DEFAULT_POLL_PERIOD; + + input_dev = poll_dev->input; + 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_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) + goto err_free_polled_dev; + + error = input_register_polled_device(poll_dev); + if (error) + goto err_free_polled_dev; + + platform_set_drvdata(pdev, tsc); + + return 0; + +err_free_polled_dev: + input_free_polled_device(poll_dev); +err_free_mem: + kfree(tsc); + return error; +} + +static int tps6507x_ts_remove(struct platform_device *pdev) +{ + struct tps6507x_ts *tsc = platform_get_drvdata(pdev); + struct input_polled_dev *poll_dev = tsc->poll_dev; + + input_unregister_polled_device(poll_dev); + input_free_polled_device(poll_dev); + + kfree(tsc); + + return 0; +} + +static struct platform_driver tps6507x_ts_driver = { + .driver = { + .name = "tps6507x-ts", + }, + .probe = tps6507x_ts_probe, + .remove = tps6507x_ts_remove, +}; +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/tsc2005.c b/drivers/input/touchscreen/tsc2005.c new file mode 100644 index 000000000..72657c579 --- /dev/null +++ b/drivers/input/touchscreen/tsc2005.c @@ -0,0 +1,827 @@ +/* + * TSC2005 touchscreen driver + * + * Copyright (C) 2006-2010 Nokia Corporation + * + * Author: Lauri Leukkunen <lauri.leukkunen@nokia.com> + * based on TSC2301 driver by Klaus K. Pedersen <klaus.k.pedersen@nokia.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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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/of_gpio.h> +#include <linux/spi/spi.h> +#include <linux/spi/tsc2005.h> +#include <linux/regulator/consumer.h> + +/* + * The touchscreen interface operates as follows: + * + * 1) Pen is pressed against the touchscreen. + * 2) TSC2005 performs AD conversion. + * 3) After the conversion is done TSC2005 drives DAV line down. + * 4) GPIO IRQ is received and tsc2005_irq_thread() is scheduled. + * 5) tsc2005_irq_thread() queues up an spi transfer to fetch the x, y, z1, z2 + * values. + * 6) tsc2005_irq_thread() reports coordinates to input layer and sets up + * tsc2005_penup_timer() to be called after TSC2005_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 TSC2005 doesn't respond + * after a configurable period (in ms) of activity. If esd_timeout is 0, the + * watchdog is disabled. + */ + +/* control byte 1 */ +#define TSC2005_CMD 0x80 +#define TSC2005_CMD_NORMAL 0x00 +#define TSC2005_CMD_STOP 0x01 +#define TSC2005_CMD_12BIT 0x04 + +/* control byte 0 */ +#define TSC2005_REG_READ 0x0001 +#define TSC2005_REG_PND0 0x0002 +#define TSC2005_REG_X 0x0000 +#define TSC2005_REG_Y 0x0008 +#define TSC2005_REG_Z1 0x0010 +#define TSC2005_REG_Z2 0x0018 +#define TSC2005_REG_TEMP_HIGH 0x0050 +#define TSC2005_REG_CFR0 0x0060 +#define TSC2005_REG_CFR1 0x0068 +#define TSC2005_REG_CFR2 0x0070 + +/* configuration register 0 */ +#define TSC2005_CFR0_PRECHARGE_276US 0x0040 +#define TSC2005_CFR0_STABTIME_1MS 0x0300 +#define TSC2005_CFR0_CLOCK_1MHZ 0x1000 +#define TSC2005_CFR0_RESOLUTION12 0x2000 +#define TSC2005_CFR0_PENMODE 0x8000 +#define TSC2005_CFR0_INITVALUE (TSC2005_CFR0_STABTIME_1MS | \ + TSC2005_CFR0_CLOCK_1MHZ | \ + TSC2005_CFR0_RESOLUTION12 | \ + TSC2005_CFR0_PRECHARGE_276US | \ + TSC2005_CFR0_PENMODE) + +/* bits common to both read and write of configuration register 0 */ +#define TSC2005_CFR0_RW_MASK 0x3fff + +/* configuration register 1 */ +#define TSC2005_CFR1_BATCHDELAY_4MS 0x0003 +#define TSC2005_CFR1_INITVALUE TSC2005_CFR1_BATCHDELAY_4MS + +/* configuration register 2 */ +#define TSC2005_CFR2_MAVE_Z 0x0004 +#define TSC2005_CFR2_MAVE_Y 0x0008 +#define TSC2005_CFR2_MAVE_X 0x0010 +#define TSC2005_CFR2_AVG_7 0x0800 +#define TSC2005_CFR2_MEDIUM_15 0x3000 +#define TSC2005_CFR2_INITVALUE (TSC2005_CFR2_MAVE_X | \ + TSC2005_CFR2_MAVE_Y | \ + TSC2005_CFR2_MAVE_Z | \ + TSC2005_CFR2_MEDIUM_15 | \ + TSC2005_CFR2_AVG_7) + +#define MAX_12BIT 0xfff +#define TSC2005_DEF_X_FUZZ 4 +#define TSC2005_DEF_Y_FUZZ 8 +#define TSC2005_DEF_P_FUZZ 2 +#define TSC2005_DEF_RESISTOR 280 + +#define TSC2005_SPI_MAX_SPEED_HZ 10000000 +#define TSC2005_PENUP_TIME_MS 40 + +struct tsc2005_spi_rd { + struct spi_transfer spi_xfer; + u32 spi_tx; + u32 spi_rx; +}; + +struct tsc2005 { + struct spi_device *spi; + + struct spi_message spi_read_msg; + struct tsc2005_spi_rd spi_x; + struct tsc2005_spi_rd spi_y; + struct tsc2005_spi_rd spi_z1; + struct tsc2005_spi_rd spi_z2; + + 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; + + 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; + + int reset_gpio; + void (*set_reset)(bool enable); +}; + +static int tsc2005_cmd(struct tsc2005 *ts, u8 cmd) +{ + u8 tx = TSC2005_CMD | TSC2005_CMD_12BIT | cmd; + struct spi_transfer xfer = { + .tx_buf = &tx, + .len = 1, + .bits_per_word = 8, + }; + struct spi_message msg; + int error; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + error = spi_sync(ts->spi, &msg); + if (error) { + dev_err(&ts->spi->dev, "%s: failed, command: %x, error: %d\n", + __func__, cmd, error); + return error; + } + + return 0; +} + +static int tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value) +{ + u32 tx = ((reg | TSC2005_REG_PND0) << 16) | value; + struct spi_transfer xfer = { + .tx_buf = &tx, + .len = 4, + .bits_per_word = 24, + }; + struct spi_message msg; + int error; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + error = spi_sync(ts->spi, &msg); + if (error) { + dev_err(&ts->spi->dev, + "%s: failed, register: %x, value: %x, error: %d\n", + __func__, reg, value, error); + return error; + } + + return 0; +} + +static void tsc2005_setup_read(struct tsc2005_spi_rd *rd, u8 reg, bool last) +{ + memset(rd, 0, sizeof(*rd)); + + rd->spi_tx = (reg | TSC2005_REG_READ) << 16; + rd->spi_xfer.tx_buf = &rd->spi_tx; + rd->spi_xfer.rx_buf = &rd->spi_rx; + rd->spi_xfer.len = 4; + rd->spi_xfer.bits_per_word = 24; + rd->spi_xfer.cs_change = !last; +} + +static int tsc2005_read(struct tsc2005 *ts, u8 reg, u16 *value) +{ + struct tsc2005_spi_rd spi_rd; + struct spi_message msg; + int error; + + tsc2005_setup_read(&spi_rd, reg, true); + + spi_message_init(&msg); + spi_message_add_tail(&spi_rd.spi_xfer, &msg); + + error = spi_sync(ts->spi, &msg); + if (error) + return error; + + *value = spi_rd.spi_rx; + return 0; +} + +static void tsc2005_update_pen_state(struct tsc2005 *ts, + int x, int y, int pressure) +{ + if (pressure) { + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + 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->spi->dev, "point(%4d,%4d), pressure (%4d)\n", x, y, + pressure); +} + +static irqreturn_t tsc2005_irq_thread(int irq, void *_ts) +{ + struct tsc2005 *ts = _ts; + unsigned long flags; + unsigned int pressure; + u32 x, y; + u32 z1, z2; + int error; + + /* read the coordinates */ + error = spi_sync(ts->spi, &ts->spi_read_msg); + if (unlikely(error)) + goto out; + + x = ts->spi_x.spi_rx; + y = ts->spi_y.spi_rx; + z1 = ts->spi_z1.spi_rx; + z2 = ts->spi_z2.spi_rx; + + /* validate position */ + if (unlikely(x > MAX_12BIT || y > MAX_12BIT)) + goto out; + + /* Skip reading if the pressure components are out of range */ + if (unlikely(z1 == 0 || z2 > MAX_12BIT || z1 >= 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 == x && ts->in_y == y && + ts->in_z1 == z1 && ts->in_z2 == 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 = x; + ts->in_y = y; + ts->in_z1 = z1; + ts->in_z2 = z2; + + /* Compute touch pressure resistance using equation #1 */ + pressure = x * (z2 - z1) / z1; + pressure = pressure * ts->x_plate_ohm / 4096; + if (unlikely(pressure > MAX_12BIT)) + goto out; + + spin_lock_irqsave(&ts->lock, flags); + + tsc2005_update_pen_state(ts, x, y, pressure); + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2005_PENUP_TIME_MS)); + + spin_unlock_irqrestore(&ts->lock, flags); + + ts->last_valid_interrupt = jiffies; +out: + return IRQ_HANDLED; +} + +static void tsc2005_penup_timer(unsigned long data) +{ + struct tsc2005 *ts = (struct tsc2005 *)data; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + tsc2005_update_pen_state(ts, 0, 0, 0); + spin_unlock_irqrestore(&ts->lock, flags); +} + +static void tsc2005_start_scan(struct tsc2005 *ts) +{ + tsc2005_write(ts, TSC2005_REG_CFR0, TSC2005_CFR0_INITVALUE); + tsc2005_write(ts, TSC2005_REG_CFR1, TSC2005_CFR1_INITVALUE); + tsc2005_write(ts, TSC2005_REG_CFR2, TSC2005_CFR2_INITVALUE); + tsc2005_cmd(ts, TSC2005_CMD_NORMAL); +} + +static void tsc2005_stop_scan(struct tsc2005 *ts) +{ + tsc2005_cmd(ts, TSC2005_CMD_STOP); +} + +static void tsc2005_set_reset(struct tsc2005 *ts, bool enable) +{ + if (ts->reset_gpio >= 0) + gpio_set_value(ts->reset_gpio, enable); + else if (ts->set_reset) + ts->set_reset(enable); +} + +/* must be called with ts->mutex held */ +static void __tsc2005_disable(struct tsc2005 *ts) +{ + tsc2005_stop_scan(ts); + + disable_irq(ts->spi->irq); + del_timer_sync(&ts->penup_timer); + + cancel_delayed_work_sync(&ts->esd_work); + + enable_irq(ts->spi->irq); +} + +/* must be called with ts->mutex held */ +static void __tsc2005_enable(struct tsc2005 *ts) +{ + tsc2005_start_scan(ts); + + if (ts->esd_timeout && (ts->set_reset || 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 tsc2005_selftest_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct tsc2005 *ts = spi_get_drvdata(spi); + u16 temp_high; + u16 temp_high_orig; + u16 temp_high_test; + bool success = true; + int error; + + mutex_lock(&ts->mutex); + + /* + * Test TSC2005 communications via temp high register. + */ + __tsc2005_disable(ts); + + error = tsc2005_read(ts, TSC2005_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 = tsc2005_write(ts, TSC2005_REG_TEMP_HIGH, temp_high_test); + if (error) { + dev_warn(dev, "selftest failed: write error %d\n", error); + success = false; + goto out; + } + + error = tsc2005_read(ts, TSC2005_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 */ + tsc2005_set_reset(ts, false); + usleep_range(100, 500); /* only 10us required */ + tsc2005_set_reset(ts, true); + + if (!success) + goto out; + + /* test that the reset really happened */ + error = tsc2005_read(ts, TSC2005_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: + __tsc2005_enable(ts); + mutex_unlock(&ts->mutex); + + return sprintf(buf, "%d\n", success); +} + +static DEVICE_ATTR(selftest, S_IRUGO, tsc2005_selftest_show, NULL); + +static struct attribute *tsc2005_attrs[] = { + &dev_attr_selftest.attr, + NULL +}; + +static umode_t tsc2005_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct spi_device *spi = to_spi_device(dev); + struct tsc2005 *ts = spi_get_drvdata(spi); + umode_t mode = attr->mode; + + if (attr == &dev_attr_selftest.attr) { + if (!ts->set_reset && !ts->reset_gpio) + mode = 0; + } + + return mode; +} + +static const struct attribute_group tsc2005_attr_group = { + .is_visible = tsc2005_attr_is_visible, + .attrs = tsc2005_attrs, +}; + +static void tsc2005_esd_work(struct work_struct *work) +{ + struct tsc2005 *ts = container_of(work, struct tsc2005, esd_work.work); + int error; + u16 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 = tsc2005_read(ts, TSC2005_REG_CFR0, &r); + if (!error && + !((r ^ TSC2005_CFR0_INITVALUE) & TSC2005_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->spi->dev, "TSC2005 not responding - resetting\n"); + + disable_irq(ts->spi->irq); + del_timer_sync(&ts->penup_timer); + + tsc2005_update_pen_state(ts, 0, 0, 0); + + tsc2005_set_reset(ts, false); + usleep_range(100, 500); /* only 10us required */ + tsc2005_set_reset(ts, true); + + enable_irq(ts->spi->irq); + tsc2005_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 tsc2005_open(struct input_dev *input) +{ + struct tsc2005 *ts = input_get_drvdata(input); + + mutex_lock(&ts->mutex); + + if (!ts->suspended) + __tsc2005_enable(ts); + + ts->opened = true; + + mutex_unlock(&ts->mutex); + + return 0; +} + +static void tsc2005_close(struct input_dev *input) +{ + struct tsc2005 *ts = input_get_drvdata(input); + + mutex_lock(&ts->mutex); + + if (!ts->suspended) + __tsc2005_disable(ts); + + ts->opened = false; + + mutex_unlock(&ts->mutex); +} + +static void tsc2005_setup_spi_xfer(struct tsc2005 *ts) +{ + tsc2005_setup_read(&ts->spi_x, TSC2005_REG_X, false); + tsc2005_setup_read(&ts->spi_y, TSC2005_REG_Y, false); + tsc2005_setup_read(&ts->spi_z1, TSC2005_REG_Z1, false); + tsc2005_setup_read(&ts->spi_z2, TSC2005_REG_Z2, true); + + spi_message_init(&ts->spi_read_msg); + spi_message_add_tail(&ts->spi_x.spi_xfer, &ts->spi_read_msg); + spi_message_add_tail(&ts->spi_y.spi_xfer, &ts->spi_read_msg); + spi_message_add_tail(&ts->spi_z1.spi_xfer, &ts->spi_read_msg); + spi_message_add_tail(&ts->spi_z2.spi_xfer, &ts->spi_read_msg); +} + +static int tsc2005_probe(struct spi_device *spi) +{ + const struct tsc2005_platform_data *pdata = dev_get_platdata(&spi->dev); + struct device_node *np = spi->dev.of_node; + + struct tsc2005 *ts; + struct input_dev *input_dev; + unsigned int max_x = MAX_12BIT; + unsigned int max_y = MAX_12BIT; + unsigned int max_p = MAX_12BIT; + unsigned int fudge_x = TSC2005_DEF_X_FUZZ; + unsigned int fudge_y = TSC2005_DEF_Y_FUZZ; + unsigned int fudge_p = TSC2005_DEF_P_FUZZ; + unsigned int x_plate_ohm = TSC2005_DEF_RESISTOR; + unsigned int esd_timeout; + int error; + + if (!np && !pdata) { + dev_err(&spi->dev, "no platform data\n"); + return -ENODEV; + } + + if (spi->irq <= 0) { + dev_err(&spi->dev, "no irq\n"); + return -ENODEV; + } + + if (pdata) { + fudge_x = pdata->ts_x_fudge; + fudge_y = pdata->ts_y_fudge; + fudge_p = pdata->ts_pressure_fudge; + max_x = pdata->ts_x_max; + max_y = pdata->ts_y_max; + max_p = pdata->ts_pressure_max; + x_plate_ohm = pdata->ts_x_plate_ohm; + esd_timeout = pdata->esd_timeout_ms; + } else { + x_plate_ohm = TSC2005_DEF_RESISTOR; + of_property_read_u32(np, "ti,x-plate-ohms", &x_plate_ohm); + esd_timeout = 0; + of_property_read_u32(np, "ti,esd-recovery-timeout-ms", + &esd_timeout); + } + + 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; + + ts = devm_kzalloc(&spi->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + input_dev = devm_input_allocate_device(&spi->dev); + if (!input_dev) + return -ENOMEM; + + ts->spi = spi; + ts->idev = input_dev; + + ts->x_plate_ohm = x_plate_ohm; + ts->esd_timeout = esd_timeout; + + if (np) { + ts->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0); + if (ts->reset_gpio == -EPROBE_DEFER) + return ts->reset_gpio; + if (ts->reset_gpio < 0) { + dev_err(&spi->dev, "error acquiring reset gpio: %d\n", + ts->reset_gpio); + return ts->reset_gpio; + } + + error = devm_gpio_request_one(&spi->dev, ts->reset_gpio, 0, + "reset-gpios"); + if (error) { + dev_err(&spi->dev, "error requesting reset gpio: %d\n", + error); + return error; + } + + ts->vio = devm_regulator_get(&spi->dev, "vio"); + if (IS_ERR(ts->vio)) { + error = PTR_ERR(ts->vio); + dev_err(&spi->dev, "vio regulator missing (%d)", error); + return error; + } + } else { + ts->reset_gpio = -1; + ts->set_reset = pdata->set_reset; + } + + mutex_init(&ts->mutex); + + spin_lock_init(&ts->lock); + setup_timer(&ts->penup_timer, tsc2005_penup_timer, (unsigned long)ts); + + INIT_DELAYED_WORK(&ts->esd_work, tsc2005_esd_work); + + tsc2005_setup_spi_xfer(ts); + + snprintf(ts->phys, sizeof(ts->phys), + "%s/input-ts", dev_name(&spi->dev)); + + input_dev->name = "TSC2005 touchscreen"; + input_dev->phys = ts->phys; + input_dev->id.bustype = BUS_SPI; + input_dev->dev.parent = &spi->dev; + input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, max_x, fudge_x, 0); + input_set_abs_params(input_dev, ABS_Y, 0, max_y, fudge_y, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, max_p, fudge_p, 0); + + if (np) + touchscreen_parse_of_params(input_dev); + + input_dev->open = tsc2005_open; + input_dev->close = tsc2005_close; + + input_set_drvdata(input_dev, ts); + + /* Ensure the touchscreen is off */ + tsc2005_stop_scan(ts); + + error = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, + tsc2005_irq_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "tsc2005", ts); + if (error) { + dev_err(&spi->dev, "Failed to request irq, err: %d\n", error); + return error; + } + + /* enable regulator for DT */ + if (ts->vio) { + error = regulator_enable(ts->vio); + if (error) + return error; + } + + spi_set_drvdata(spi, ts); + error = sysfs_create_group(&spi->dev.kobj, &tsc2005_attr_group); + if (error) { + dev_err(&spi->dev, + "Failed to create sysfs attributes, err: %d\n", error); + goto disable_regulator; + } + + error = input_register_device(ts->idev); + if (error) { + dev_err(&spi->dev, + "Failed to register input device, err: %d\n", error); + goto err_remove_sysfs; + } + + irq_set_irq_wake(spi->irq, 1); + return 0; + +err_remove_sysfs: + sysfs_remove_group(&spi->dev.kobj, &tsc2005_attr_group); +disable_regulator: + if (ts->vio) + regulator_disable(ts->vio); + return error; +} + +static int tsc2005_remove(struct spi_device *spi) +{ + struct tsc2005 *ts = spi_get_drvdata(spi); + + sysfs_remove_group(&spi->dev.kobj, &tsc2005_attr_group); + + if (ts->vio) + regulator_disable(ts->vio); + + return 0; +} + +static int __maybe_unused tsc2005_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct tsc2005 *ts = spi_get_drvdata(spi); + + mutex_lock(&ts->mutex); + + if (!ts->suspended && ts->opened) + __tsc2005_disable(ts); + + ts->suspended = true; + + mutex_unlock(&ts->mutex); + + return 0; +} + +static int __maybe_unused tsc2005_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct tsc2005 *ts = spi_get_drvdata(spi); + + mutex_lock(&ts->mutex); + + if (ts->suspended && ts->opened) + __tsc2005_enable(ts); + + ts->suspended = false; + + mutex_unlock(&ts->mutex); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(tsc2005_pm_ops, tsc2005_suspend, tsc2005_resume); + +static struct spi_driver tsc2005_driver = { + .driver = { + .name = "tsc2005", + .owner = THIS_MODULE, + .pm = &tsc2005_pm_ops, + }, + .probe = tsc2005_probe, + .remove = tsc2005_remove, +}; + +module_spi_driver(tsc2005_driver); + +MODULE_AUTHOR("Lauri Leukkunen <lauri.leukkunen@nokia.com>"); +MODULE_DESCRIPTION("TSC2005 Touchscreen Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:tsc2005"); diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c new file mode 100644 index 000000000..ccc8aa615 --- /dev/null +++ b/drivers/input/touchscreen/tsc2007.c @@ -0,0 +1,497 @@ +/* + * 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 + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/i2c/tsc2007.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/of_gpio.h> + +#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; + + unsigned gpio; + int irq; + + wait_queue_head_t wait; + bool stopped; + + int (*get_pendown_state)(struct device *); + void (*clear_penirq)(void); +}; + +static inline 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); +} + +static u32 tsc2007_calculate_pressure(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 pressure 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; +} + +static 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 */ + tsc2007_read_values(ts, &tc); + + rt = tsc2007_calculate_pressure(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), pressure (%4u)\n", + tc.x, tc.y, 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); +} + +#ifdef CONFIG_OF +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 !gpio_get_value(ts->gpio); +} + +static int tsc2007_probe_dt(struct i2c_client *client, struct tsc2007 *ts) +{ + struct device_node *np = client->dev.of_node; + u32 val32; + u64 val64; + + if (!np) { + dev_err(&client->dev, "missing device tree data\n"); + return -EINVAL; + } + + if (!of_property_read_u32(np, "ti,max-rt", &val32)) + ts->max_rt = val32; + else + ts->max_rt = MAX_12BIT; + + if (!of_property_read_u32(np, "ti,fuzzx", &val32)) + ts->fuzzx = val32; + + if (!of_property_read_u32(np, "ti,fuzzy", &val32)) + ts->fuzzy = val32; + + if (!of_property_read_u32(np, "ti,fuzzz", &val32)) + ts->fuzzz = val32; + + if (!of_property_read_u64(np, "ti,poll-period", &val64)) + ts->poll_period = msecs_to_jiffies(val64); + else + ts->poll_period = msecs_to_jiffies(1); + + if (!of_property_read_u32(np, "ti,x-plate-ohms", &val32)) { + ts->x_plate_ohms = val32; + } else { + dev_err(&client->dev, "missing ti,x-plate-ohms devicetree property."); + return -EINVAL; + } + + ts->gpio = of_get_gpio(np, 0); + if (gpio_is_valid(ts->gpio)) + ts->get_pendown_state = tsc2007_get_pendown_state_gpio; + else + dev_warn(&client->dev, + "GPIO not specified in DT (of_get_gpio returned %d)\n", + ts->gpio); + + return 0; +} +#else +static int tsc2007_probe_dt(struct i2c_client *client, struct tsc2007 *ts) +{ + dev_err(&client->dev, "platform data is required!\n"); + return -EINVAL; +} +#endif + +static int tsc2007_probe_pdev(struct i2c_client *client, 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(&client->dev, "x_plate_ohms is not set up in platform data"); + 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, ts, pdata, id); + else + err = tsc2007_probe_dt(client, 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); + + 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_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, 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); + + err = input_register_device(input_dev); + if (err) { + dev_err(&client->dev, + "Failed to register input device: %d\n", err); + return err; + } + + return 0; +} + +static const struct i2c_device_id tsc2007_idtable[] = { + { "tsc2007", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, tsc2007_idtable); + +#ifdef CONFIG_OF +static const struct of_device_id tsc2007_of_match[] = { + { .compatible = "ti,tsc2007" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tsc2007_of_match); +#endif + +static struct i2c_driver tsc2007_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "tsc2007", + .of_match_table = of_match_ptr(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/tsc40.c b/drivers/input/touchscreen/tsc40.c new file mode 100644 index 000000000..29687872c --- /dev/null +++ b/drivers/input/touchscreen/tsc40.c @@ -0,0 +1,172 @@ +/* + * TSC-40 serial touchscreen driver. It should be compatible with + * TSC-10 and 25. + * + * Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de> + * License: GPLv2 as published by the FSF. + */ + +#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 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..c1e23cfc6 --- /dev/null +++ b/drivers/input/touchscreen/ucb1400_ts.c @@ -0,0 +1,463 @@ +/* + * 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 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. + * + * 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; + bool penup; + + 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 && !(penup = 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 (idev->users) + ucb1400_ts_start(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 (idev->users) + ucb1400_ts_stop(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..f2c6c352c --- /dev/null +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -0,0 +1,1769 @@ +/****************************************************************************** + * 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) + * + * 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; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * 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> + + +#define DRIVER_VERSION "v0.6" +#define DRIVER_AUTHOR "Daniel Ritz <daniel.ritz@gmx.ch>" +#define DRIVER_DESC "USB Touchscreen Driver" + +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; + 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(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_rcvctrlpipe(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 + +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; +} + +static int mtouch_init(struct usbtouch_usb *usbtouch) +{ + int ret, i; + struct usb_device *udev = interface_to_usbdev(usbtouch->interface); + + ret = usb_control_msg(udev, usb_rcvctrlpipe(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_rcvctrlpipe(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; +} +#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; + } + + /* 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_rcvctrlpipe (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 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 (!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, + .init = mtouch_init, + }, +#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; + + if (!usbtouch->type->irq_always) { + if (usb_submit_urb(usbtouch->irq, GFP_KERNEL)) { + r = -EIO; + goto out_put; + } + } + + usbtouch->interface->needs_remote_wakeup = 1; +out_put: + 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; + + if (!usbtouch->type->irq_always) + usb_kill_urb(usbtouch->irq); + 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); + struct input_dev *input = usbtouch->input; + int result = 0; + + mutex_lock(&input->mutex); + if (input->users || usbtouch->type->irq_always) + result = usb_submit_urb(usbtouch->irq, GFP_NOIO); + mutex_unlock(&input->mutex); + + return result; +} + +static int usbtouch_reset_resume(struct usb_interface *intf) +{ + struct usbtouch_usb *usbtouch = usb_get_intfdata(intf); + struct input_dev *input = usbtouch->input; + 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(&input->mutex); + if (input->users) + err = usb_submit_urb(usbtouch->irq, GFP_NOIO); + mutex_unlock(&input->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; + + 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; + + 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) + strlcpy(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(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("touchkitusb"); +MODULE_ALIAS("itmtouch"); +MODULE_ALIAS("mtouchusb"); diff --git a/drivers/input/touchscreen/w90p910_ts.c b/drivers/input/touchscreen/w90p910_ts.c new file mode 100644 index 000000000..da6004e97 --- /dev/null +++ b/drivers/input/touchscreen/w90p910_ts.c @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2008 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.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 of the License. + * + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +/* ADC controller bit defines */ +#define ADC_DELAY 0xf00 +#define ADC_DOWN 0x01 +#define ADC_TSC_Y (0x01 << 8) +#define ADC_TSC_X (0x00 << 8) +#define TSC_FOURWIRE (~(0x03 << 1)) +#define ADC_CLK_EN (0x01 << 28) /* ADC clock enable */ +#define ADC_READ_CON (0x01 << 12) +#define ADC_CONV (0x01 << 13) +#define ADC_SEMIAUTO (0x01 << 14) +#define ADC_WAITTRIG (0x03 << 14) +#define ADC_RST1 (0x01 << 16) +#define ADC_RST0 (0x00 << 16) +#define ADC_EN (0x01 << 17) +#define ADC_INT (0x01 << 18) +#define WT_INT (0x01 << 20) +#define ADC_INT_EN (0x01 << 21) +#define LVD_INT_EN (0x01 << 22) +#define WT_INT_EN (0x01 << 23) +#define ADC_DIV (0x04 << 1) /* div = 6 */ + +enum ts_state { + TS_WAIT_NEW_PACKET, /* We are waiting next touch report */ + TS_WAIT_X_COORD, /* We are waiting for ADC to report X coord */ + TS_WAIT_Y_COORD, /* We are waiting for ADC to report Y coord */ + TS_IDLE, /* Input device is closed, don't do anything */ +}; + +struct w90p910_ts { + struct input_dev *input; + struct timer_list timer; + struct clk *clk; + int irq_num; + void __iomem *ts_reg; + spinlock_t lock; + enum ts_state state; +}; + +static void w90p910_report_event(struct w90p910_ts *w90p910_ts, bool down) +{ + struct input_dev *dev = w90p910_ts->input; + + if (down) { + input_report_abs(dev, ABS_X, + __raw_readl(w90p910_ts->ts_reg + 0x0c)); + input_report_abs(dev, ABS_Y, + __raw_readl(w90p910_ts->ts_reg + 0x10)); + } + + input_report_key(dev, BTN_TOUCH, down); + input_sync(dev); +} + +static void w90p910_prepare_x_reading(struct w90p910_ts *w90p910_ts) +{ + unsigned long ctlreg; + + __raw_writel(ADC_TSC_X, w90p910_ts->ts_reg + 0x04); + ctlreg = __raw_readl(w90p910_ts->ts_reg); + ctlreg &= ~(ADC_WAITTRIG | WT_INT | WT_INT_EN); + ctlreg |= ADC_SEMIAUTO | ADC_INT_EN | ADC_CONV; + __raw_writel(ctlreg, w90p910_ts->ts_reg); + + w90p910_ts->state = TS_WAIT_X_COORD; +} + +static void w90p910_prepare_y_reading(struct w90p910_ts *w90p910_ts) +{ + unsigned long ctlreg; + + __raw_writel(ADC_TSC_Y, w90p910_ts->ts_reg + 0x04); + ctlreg = __raw_readl(w90p910_ts->ts_reg); + ctlreg &= ~(ADC_WAITTRIG | ADC_INT | WT_INT_EN); + ctlreg |= ADC_SEMIAUTO | ADC_INT_EN | ADC_CONV; + __raw_writel(ctlreg, w90p910_ts->ts_reg); + + w90p910_ts->state = TS_WAIT_Y_COORD; +} + +static void w90p910_prepare_next_packet(struct w90p910_ts *w90p910_ts) +{ + unsigned long ctlreg; + + ctlreg = __raw_readl(w90p910_ts->ts_reg); + ctlreg &= ~(ADC_INT | ADC_INT_EN | ADC_SEMIAUTO | ADC_CONV); + ctlreg |= ADC_WAITTRIG | WT_INT_EN; + __raw_writel(ctlreg, w90p910_ts->ts_reg); + + w90p910_ts->state = TS_WAIT_NEW_PACKET; +} + +static irqreturn_t w90p910_ts_interrupt(int irq, void *dev_id) +{ + struct w90p910_ts *w90p910_ts = dev_id; + unsigned long flags; + + spin_lock_irqsave(&w90p910_ts->lock, flags); + + switch (w90p910_ts->state) { + case TS_WAIT_NEW_PACKET: + /* + * The controller only generates interrupts when pen + * is down. + */ + del_timer(&w90p910_ts->timer); + w90p910_prepare_x_reading(w90p910_ts); + break; + + + case TS_WAIT_X_COORD: + w90p910_prepare_y_reading(w90p910_ts); + break; + + case TS_WAIT_Y_COORD: + w90p910_report_event(w90p910_ts, true); + w90p910_prepare_next_packet(w90p910_ts); + mod_timer(&w90p910_ts->timer, jiffies + msecs_to_jiffies(100)); + break; + + case TS_IDLE: + break; + } + + spin_unlock_irqrestore(&w90p910_ts->lock, flags); + + return IRQ_HANDLED; +} + +static void w90p910_check_pen_up(unsigned long data) +{ + struct w90p910_ts *w90p910_ts = (struct w90p910_ts *) data; + unsigned long flags; + + spin_lock_irqsave(&w90p910_ts->lock, flags); + + if (w90p910_ts->state == TS_WAIT_NEW_PACKET && + !(__raw_readl(w90p910_ts->ts_reg + 0x04) & ADC_DOWN)) { + + w90p910_report_event(w90p910_ts, false); + } + + spin_unlock_irqrestore(&w90p910_ts->lock, flags); +} + +static int w90p910_open(struct input_dev *dev) +{ + struct w90p910_ts *w90p910_ts = input_get_drvdata(dev); + unsigned long val; + + /* enable the ADC clock */ + clk_enable(w90p910_ts->clk); + + __raw_writel(ADC_RST1, w90p910_ts->ts_reg); + msleep(1); + __raw_writel(ADC_RST0, w90p910_ts->ts_reg); + msleep(1); + + /* set delay and screen type */ + val = __raw_readl(w90p910_ts->ts_reg + 0x04); + __raw_writel(val & TSC_FOURWIRE, w90p910_ts->ts_reg + 0x04); + __raw_writel(ADC_DELAY, w90p910_ts->ts_reg + 0x08); + + w90p910_ts->state = TS_WAIT_NEW_PACKET; + wmb(); + + /* set trigger mode */ + val = __raw_readl(w90p910_ts->ts_reg); + val |= ADC_WAITTRIG | ADC_DIV | ADC_EN | WT_INT_EN; + __raw_writel(val, w90p910_ts->ts_reg); + + return 0; +} + +static void w90p910_close(struct input_dev *dev) +{ + struct w90p910_ts *w90p910_ts = input_get_drvdata(dev); + unsigned long val; + + /* disable trigger mode */ + + spin_lock_irq(&w90p910_ts->lock); + + w90p910_ts->state = TS_IDLE; + + val = __raw_readl(w90p910_ts->ts_reg); + val &= ~(ADC_WAITTRIG | ADC_DIV | ADC_EN | WT_INT_EN | ADC_INT_EN); + __raw_writel(val, w90p910_ts->ts_reg); + + spin_unlock_irq(&w90p910_ts->lock); + + /* Now that interrupts are shut off we can safely delete timer */ + del_timer_sync(&w90p910_ts->timer); + + /* stop the ADC clock */ + clk_disable(w90p910_ts->clk); +} + +static int w90x900ts_probe(struct platform_device *pdev) +{ + struct w90p910_ts *w90p910_ts; + struct input_dev *input_dev; + struct resource *res; + int err; + + w90p910_ts = kzalloc(sizeof(struct w90p910_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!w90p910_ts || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + w90p910_ts->input = input_dev; + w90p910_ts->state = TS_IDLE; + spin_lock_init(&w90p910_ts->lock); + setup_timer(&w90p910_ts->timer, w90p910_check_pen_up, + (unsigned long)w90p910_ts); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + err = -ENXIO; + goto fail1; + } + + if (!request_mem_region(res->start, resource_size(res), + pdev->name)) { + err = -EBUSY; + goto fail1; + } + + w90p910_ts->ts_reg = ioremap(res->start, resource_size(res)); + if (!w90p910_ts->ts_reg) { + err = -ENOMEM; + goto fail2; + } + + w90p910_ts->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(w90p910_ts->clk)) { + err = PTR_ERR(w90p910_ts->clk); + goto fail3; + } + + input_dev->name = "W90P910 TouchScreen"; + input_dev->phys = "w90p910ts/event0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0005; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + input_dev->open = w90p910_open; + input_dev->close = w90p910_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, 0, 0x400, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x400, 0, 0); + + input_set_drvdata(input_dev, w90p910_ts); + + w90p910_ts->irq_num = platform_get_irq(pdev, 0); + if (request_irq(w90p910_ts->irq_num, w90p910_ts_interrupt, + 0, "w90p910ts", w90p910_ts)) { + err = -EBUSY; + goto fail4; + } + + err = input_register_device(w90p910_ts->input); + if (err) + goto fail5; + + platform_set_drvdata(pdev, w90p910_ts); + + return 0; + +fail5: free_irq(w90p910_ts->irq_num, w90p910_ts); +fail4: clk_put(w90p910_ts->clk); +fail3: iounmap(w90p910_ts->ts_reg); +fail2: release_mem_region(res->start, resource_size(res)); +fail1: input_free_device(input_dev); + kfree(w90p910_ts); + return err; +} + +static int w90x900ts_remove(struct platform_device *pdev) +{ + struct w90p910_ts *w90p910_ts = platform_get_drvdata(pdev); + struct resource *res; + + free_irq(w90p910_ts->irq_num, w90p910_ts); + del_timer_sync(&w90p910_ts->timer); + iounmap(w90p910_ts->ts_reg); + + clk_put(w90p910_ts->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + input_unregister_device(w90p910_ts->input); + kfree(w90p910_ts); + + return 0; +} + +static struct platform_driver w90x900ts_driver = { + .probe = w90x900ts_probe, + .remove = w90x900ts_remove, + .driver = { + .name = "nuc900-ts", + }, +}; +module_platform_driver(w90x900ts_driver); + +MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); +MODULE_DESCRIPTION("w90p910 touch screen driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nuc900-ts"); diff --git a/drivers/input/touchscreen/wacom_i2c.c b/drivers/input/touchscreen/wacom_i2c.c new file mode 100644 index 000000000..32f8ac003 --- /dev/null +++ b/drivers/input/touchscreen/wacom_i2c.c @@ -0,0 +1,286 @@ +/* + * Wacom Penabled Driver for I2C + * + * Copyright (c) 2011 - 2013 Tatsunosuke Tobita, Wacom. + * <tobita.tatsunosuke@wacom.co.jp> + * + * 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; either version of 2 of the License, + * or (at your option) any later version. + */ + +#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 <linux/gpio.h> +#include <asm/unaligned.h> + +#define WACOM_CMD_QUERY0 0x04 +#define WACOM_CMD_QUERY1 0x00 +#define WACOM_CMD_QUERY2 0x33 +#define WACOM_CMD_QUERY3 0x02 +#define WACOM_CMD_THROW0 0x05 +#define WACOM_CMD_THROW1 0x00 +#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) +{ + int ret; + u8 cmd1[] = { WACOM_CMD_QUERY0, WACOM_CMD_QUERY1, + WACOM_CMD_QUERY2, WACOM_CMD_QUERY3 }; + u8 cmd2[] = { WACOM_CMD_THROW0, WACOM_CMD_THROW1 }; + u8 data[WACOM_QUERY_SIZE]; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(cmd1), + .buf = cmd1, + }, + { + .addr = client->addr, + .flags = 0, + .len = sizeof(cmd2), + .buf = cmd2, + }, + { + .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] & 0x01; + ers = data[3] & 0x04; + f1 = data[3] & 0x02; + f2 = data[3] & 0x10; + 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] & 0x0c) ? + BTN_TOOL_RUBBER : BTN_TOOL_PEN; + + wac_i2c->prox = data[3] & 0x20; + + 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 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(&client->dev, "i2c_check_functionality error\n"); + return -EIO; + } + + error = wacom_query_device(client, &features); + if (error) + return error; + + wac_i2c = kzalloc(sizeof(*wac_i2c), GFP_KERNEL); + input = input_allocate_device(); + if (!wac_i2c || !input) { + error = -ENOMEM; + goto err_free_mem; + } + + wac_i2c->client = client; + 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->dev.parent = &client->dev; + 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 = request_threaded_irq(client->irq, NULL, wacom_i2c_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "wacom_i2c", wac_i2c); + if (error) { + dev_err(&client->dev, + "Failed to enable IRQ, error: %d\n", error); + goto err_free_mem; + } + + /* 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(&client->dev, + "Failed to register input device, error: %d\n", error); + goto err_free_irq; + } + + i2c_set_clientdata(client, wac_i2c); + return 0; + +err_free_irq: + free_irq(client->irq, wac_i2c); +err_free_mem: + input_free_device(input); + kfree(wac_i2c); + + return error; +} + +static int wacom_i2c_remove(struct i2c_client *client) +{ + struct wacom_i2c *wac_i2c = i2c_get_clientdata(client); + + free_irq(client->irq, wac_i2c); + input_unregister_device(wac_i2c->input); + kfree(wac_i2c); + + 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", + .owner = THIS_MODULE, + .pm = &wacom_i2c_pm, + }, + + .probe = wacom_i2c_probe, + .remove = wacom_i2c_remove, + .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..2792ca397 --- /dev/null +++ b/drivers/input/touchscreen/wacom_w8001.c @@ -0,0 +1,596 @@ +/* + * 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_LENGTH 11 +#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 *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[32]; + int type; + unsigned int pktlen; + u16 max_touch_x; + u16 max_touch_y; + u16 max_pen_x; + u16 max_pen_y; + char name[64]; +}; + +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->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->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: + input_report_key(dev, BTN_TOUCH, 0); + input_report_key(dev, BTN_TOOL_FINGER, 0); + input_sync(dev); + /* fall through */ + + 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->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_report_key(dev, BTN_TOOL_FINGER, 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; + } + + 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); + + return w8001_command(w8001, W8001_CMD_START, false); +} + +static void w8001_close(struct input_dev *dev) +{ + struct w8001 *w8001 = input_get_drvdata(dev); + + w8001_command(w8001, W8001_CMD_STOP, false); +} + +static int w8001_setup(struct w8001 *w8001) +{ + struct input_dev *dev = w8001->dev; + struct w8001_coord coord; + struct w8001_touch_query touch; + int error; + + error = w8001_command(w8001, W8001_CMD_STOP, false); + if (error) + return error; + + msleep(250); /* wait 250ms before querying the device */ + + dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + strlcat(w8001->name, "Wacom Serial", sizeof(w8001->name)); + + __set_bit(INPUT_PROP_DIRECT, dev->propbit); + + /* penabled? */ + error = w8001_command(w8001, W8001_CMD_QUERY, true); + if (!error) { + __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); + + 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(w8001->name, " Penabled", sizeof(w8001->name)); + } + + /* Touch enabled? */ + error = w8001_command(w8001, W8001_CMD_TOUCHQUERY, true); + + /* + * Some non-touch devices may reply to the touch query. But their + * second byte is empty, which indicates touch is not supported. + */ + if (!error && w8001->response[1]) { + __set_bit(BTN_TOUCH, dev->keybit); + __set_bit(BTN_TOOL_FINGER, dev->keybit); + + 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(w8001->name, " 1FG", sizeof(w8001->name)); + break; + + case 1: + case 3: + case 4: + w8001->pktlen = W8001_PKTLEN_TOUCH9A; + strlcat(w8001->name, " 1FG", sizeof(w8001->name)); + w8001->id = 0x9a; + break; + + case 5: + w8001->pktlen = W8001_PKTLEN_TOUCH2FG; + + input_mt_init_slots(dev, 2, 0); + 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); + + strlcat(w8001->name, " 2FG", sizeof(w8001->name)); + if (w8001->max_pen_x && w8001->max_pen_y) + w8001->id = 0xE3; + else + w8001->id = 0xE2; + break; + } + } + + strlcat(w8001->name, " Touchscreen", sizeof(w8001->name)); + + return 0; +} + +/* + * 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); + + input_unregister_device(w8001->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; + int err; + + w8001 = kzalloc(sizeof(struct w8001), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!w8001 || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + w8001->serio = serio; + w8001->dev = input_dev; + 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_setup(w8001); + if (err) + goto fail3; + + input_dev->name = w8001->name; + input_dev->phys = w8001->phys; + input_dev->id.product = w8001->id; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = 0x056a; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->open = w8001_open; + input_dev->close = w8001_close; + + input_set_drvdata(input_dev, w8001); + + err = input_register_device(w8001->dev); + if (err) + goto fail3; + + return 0; + +fail3: + serio_close(serio); +fail2: + serio_set_drvdata(serio, NULL); +fail1: + input_free_device(input_dev); + kfree(w8001); + return err; +} + +static 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/wm831x-ts.c b/drivers/input/touchscreen/wm831x-ts.c new file mode 100644 index 000000000..1db0a1410 --- /dev/null +++ b/drivers/input/touchscreen/wm831x-ts.c @@ -0,0 +1,405 @@ +/* + * Touchscreen driver for WM831x PMICs + * + * Copyright 2011 Wolfson Microelectronics plc. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#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); + /* Fall through */ + 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, + "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; + } + disable_irq(wm831x_ts->data_irq); + + 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..adc13a523 --- /dev/null +++ b/drivers/input/touchscreen/wm9705.c @@ -0,0 +1,350 @@ +/* + * 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> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#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..705ffa1e0 --- /dev/null +++ b/drivers/input/touchscreen/wm9712.c @@ -0,0 +1,471 @@ +/* + * 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> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#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..572a5a64f --- /dev/null +++ b/drivers/input/touchscreen/wm9713.c @@ -0,0 +1,481 @@ +/* + * 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> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#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..b1ae77995 --- /dev/null +++ b/drivers/input/touchscreen/wm97xx-core.c @@ -0,0 +1,859 @@ +/* + * 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> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * 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/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.txt 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 + * + * + * 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); + +/* + * Handle a pen down interrupt. + */ +static void wm97xx_pen_irq_worker(struct work_struct *work) +{ + struct wm97xx *wm = container_of(work, struct wm97xx, pen_event_work); + 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); + + wm->mach_ops->irq_enable(wm, 1); +} + +/* + * Codec PENDOWN irq handler + * + * We have to disable the codec interrupt in the handler because it + * can take up to 1ms to clear the interrupt source. We schedule a task + * in a work queue to do the actual interaction with the chip. The + * interrupt is then enabled again in the slow handler when the source + * has been cleared. + */ +static irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id) +{ + struct wm97xx *wm = dev_id; + + if (!work_pending(&wm->pen_event_work)) { + wm->mach_ops->irq_enable(wm, 0); + queue_work(wm->ts_workq, &wm->pen_event_work); + } + + return IRQ_HANDLED; +} + +/* + * initialise pen IRQ handler and workqueue + */ +static int wm97xx_init_pen_irq(struct wm97xx *wm) +{ + u16 reg; + + /* If an interrupt is supplied an IRQ enable operation must also be + * provided. */ + BUG_ON(!wm->mach_ops->irq_enable); + + if (request_irq(wm->pen_irq, wm97xx_pen_interrupt, IRQF_SHARED, + "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 = create_singlethread_workqueue("kwm97xx"); + 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); + INIT_WORK(&wm->pen_event_work, wm97xx_pen_irq_worker); + + 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; + + /* Balance out interrupt disables/enables */ + if (cancel_work_sync(&wm->pen_event_work)) + wm->mach_ops->irq_enable(wm, 1); + + /* 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_probe(struct device *dev) +{ + struct wm97xx *wm; + struct wm97xx_pdata *pdata = dev_get_platdata(dev); + int ret = 0, id = 0; + + wm = kzalloc(sizeof(struct wm97xx), GFP_KERNEL); + if (!wm) + return -ENOMEM; + mutex_init(&wm->codec_mutex); + + wm->dev = dev; + dev_set_drvdata(dev, wm); + wm->ac97 = to_ac97_t(dev); + + /* check that we have a supported codec */ + id = wm97xx_reg_read(wm, AC97_VENDOR_ID1); + if (id != WM97XX_ID1) { + dev_err(dev, "Device with vendor %04x is not a wm97xx\n", id); + ret = -ENODEV; + goto alloc_err; + } + + 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); + ret = -ENODEV; + goto alloc_err; + } + + /* 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); + + wm->input_dev = input_allocate_device(); + if (wm->input_dev == NULL) { + ret = -ENOMEM; + goto alloc_err; + } + + /* 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 = dev; + + ret = input_register_device(wm->input_dev); + if (ret < 0) + goto dev_alloc_err; + + /* register our battery device */ + wm->battery_dev = platform_device_alloc("wm97xx-battery", -1); + if (!wm->battery_dev) { + ret = -ENOMEM; + goto batt_err; + } + platform_set_drvdata(wm->battery_dev, wm); + wm->battery_dev->dev.parent = dev; + wm->battery_dev->dev.platform_data = pdata; + ret = platform_device_add(wm->battery_dev); + if (ret < 0) + goto batt_reg_err; + + /* register our extended touch device (for machine specific + * extensions) */ + wm->touch_dev = platform_device_alloc("wm97xx-touch", -1); + if (!wm->touch_dev) { + ret = -ENOMEM; + goto touch_err; + } + platform_set_drvdata(wm->touch_dev, wm); + wm->touch_dev->dev.parent = dev; + wm->touch_dev->dev.platform_data = pdata; + ret = platform_device_add(wm->touch_dev); + if (ret < 0) + goto touch_reg_err; + + return ret; + + touch_reg_err: + platform_device_put(wm->touch_dev); + touch_err: + platform_device_del(wm->battery_dev); + batt_reg_err: + platform_device_put(wm->battery_dev); + batt_err: + input_unregister_device(wm->input_dev); + wm->input_dev = NULL; + dev_alloc_err: + input_free_device(wm->input_dev); + alloc_err: + kfree(wm); + + return ret; +} + +static int wm97xx_remove(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + platform_device_unregister(wm->battery_dev); + platform_device_unregister(wm->touch_dev); + input_unregister_device(wm->input_dev); + kfree(wm); + + return 0; +} + +#ifdef CONFIG_PM +static int wm97xx_suspend(struct device *dev, pm_message_t state) +{ + 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; + + if (wm->input_dev->users) + 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 (wm->input_dev->users) + 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 && + (!wm->input_dev->users || !suspend_mode)) { + reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) | 0x8000; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); + } + + return 0; +} + +static int wm97xx_resume(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + /* 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 (wm->input_dev->users) { + 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 (wm->input_dev->users && !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); + } + + return 0; +} + +#else +#define wm97xx_suspend NULL +#define wm97xx_resume NULL +#endif + +/* + * 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", + .bus = &ac97_bus_type, + .owner = THIS_MODULE, + .probe = wm97xx_probe, + .remove = wm97xx_remove, + .suspend = wm97xx_suspend, + .resume = wm97xx_resume, +}; + +static int __init wm97xx_init(void) +{ + return driver_register(&wm97xx_driver); +} + +static void __exit wm97xx_exit(void) +{ + driver_unregister(&wm97xx_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/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c new file mode 100644 index 000000000..19880c738 --- /dev/null +++ b/drivers/input/touchscreen/zforce_ts.c @@ -0,0 +1,934 @@ +/* + * 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> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#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.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/delay.h> +#include <linux/of.h> +#include <linux/of_gpio.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; + + 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 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] & 0x03; + point.id = (payload[9 * i + 5] & 0xfc) >> 2; + + /* 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; + const struct zforce_ts_platdata *pdata = ts->pdata; + 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); + + while (!gpio_get_value(pdata->gpio_int)) { + 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; + } + } + + 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); + int ret; + + ret = zforce_start(ts); + if (ret) + return ret; + + return 0; +} + +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->users) { + ret = zforce_start(ts); + if (ret) + goto unlock; + } + + enable_irq_wake(client->irq); + } else if (input->users) { + 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->users) { + ret = zforce_stop(ts); + if (ret) + goto unlock; + } + } else if (input->users) { + 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; + + gpio_set_value(ts->pdata->gpio_rst, 0); + + 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); + } + + pdata->gpio_int = of_get_gpio(np, 0); + if (!gpio_is_valid(pdata->gpio_int)) { + dev_err(dev, "failed to get interrupt gpio\n"); + return ERR_PTR(-EINVAL); + } + + pdata->gpio_rst = of_get_gpio(np, 1); + if (!gpio_is_valid(pdata->gpio_rst)) { + dev_err(dev, "failed to get reset gpio\n"); + return ERR_PTR(-EINVAL); + } + + 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; + + ret = devm_gpio_request_one(&client->dev, pdata->gpio_int, GPIOF_IN, + "zforce_ts_int"); + if (ret) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_int, ret); + return ret; + } + + ret = devm_gpio_request_one(&client->dev, pdata->gpio_rst, + GPIOF_OUT_INIT_LOW, "zforce_ts_rst"); + if (ret) { + dev_err(&client->dev, "request of gpio %d failed, %d\n", + pdata->gpio_rst, 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 */ + gpio_set_value(pdata->gpio_rst, 1); + + 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 = { + .owner = THIS_MODULE, + .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/zylonite-wm97xx.c b/drivers/input/touchscreen/zylonite-wm97xx.c new file mode 100644 index 000000000..e2ccd683d --- /dev/null +++ b/drivers/input/touchscreen/zylonite-wm97xx.c @@ -0,0 +1,231 @@ +/* + * 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> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * 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.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/wm97xx.h> + +#include <mach/hardware.h> +#include <mach/mfp.h> +#include <mach/regs-ac97.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++) + 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 = MODR; + if (x == last) { + tries++; + return RC_AGAIN; + } + last = x; + do { + if (reads) + x = MODR; + y = MODR; + if (pressure) + p = 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 void wm97xx_irq_enable(struct wm97xx *wm, int enable) +{ + if (enable) + enable_irq(wm->pen_irq); + else + disable_irq_nosync(wm->pen_irq); +} + +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_enable = wm97xx_irq_enable, + .irq_gpio = WM97XX_GPIO_2, +}; + +static int zylonite_wm97xx_probe(struct platform_device *pdev) +{ + struct wm97xx *wm = platform_get_drvdata(pdev); + int gpio_touch_irq; + + if (cpu_is_pxa320()) + gpio_touch_irq = mfp_to_gpio(MFP_PIN_GPIO15); + else + gpio_touch_irq = mfp_to_gpio(MFP_PIN_GPIO26); + + wm->pen_irq = gpio_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"); |