diff mbox

[v5,1/2] Input: Driver for SiS 9200 family I2C touchscreen controller.

Message ID 20160702001719.GA36469@dtor-ws (mailing list archive)
State New, archived
Headers show

Commit Message

Dmitry Torokhov July 2, 2016, 12:17 a.m. UTC
Hi Mika,

On Fri, May 06, 2016 at 08:22:16AM +0300, mika.penttila@nextfour.com wrote:
> From: Mika Penttilä <mika.penttila@nextfour.com>
> 
> Multitouch protocol B support.
> 
> v5:
>   - rebased to 4.6.0-rc6
> 
> v4:
>   - cleanups and fixes according to review feedback
>   - irq and reset gpios are now optional,
>     if not spesified it is expected the firmware / hw
>     takes care of reset states and irq flow
>   - irq and reset gpio polarities come from dts/firmware
>   - report parsing and reporting are done at once
>   - error handling improvements
>   - misc cleanups
>   - added comments
>     
> v3:
>   - cleanup unused defines
>   - added acked-bys from SiS
> 
> v2:
>   - use gpio descriptor api
>   - probe cleanups
>   - error handling cleanups
> 
> Signed-off-by: Mika Penttilä <mika.penttila@nextfour.com>
> Acked-by: Tammy Tseng <tammy_tseng@sis.com>
> Acked-by: Yuger Yu <yuger_yu@sis.com>
> ---
>  drivers/input/touchscreen/Kconfig   |  12 +
>  drivers/input/touchscreen/Makefile  |   1 +
>  drivers/input/touchscreen/sis_i2c.c | 461 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 474 insertions(+)
>  create mode 100644 drivers/input/touchscreen/sis_i2c.c
> 
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 8ecdc38..8f06ae7 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -1155,4 +1155,16 @@ config TOUCHSCREEN_ROHM_BU21023
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called bu21023_ts.
>  
> +config TOUCHSCREEN_SIS_I2C
> +	tristate "SiS 9200 family I2C touchscreen driver"
> +	depends on I2C
> +	depends on GPIOLIB
> +	help
> +	  This enables support for SiS 9200 family over I2C based touchscreens.
> +
> +	  If unsure, say N.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called sis_i2c.
> +
>  endif
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index f42975e..0839e99 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -63,6 +63,7 @@ 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_SIS_I2C)       += sis_i2c.o
>  obj-$(CONFIG_TOUCHSCREEN_ST1232)	+= st1232.o
>  obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
>  obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
> diff --git a/drivers/input/touchscreen/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c
> new file mode 100644
> index 0000000..f1b66fa
> --- /dev/null
> +++ b/drivers/input/touchscreen/sis_i2c.c
> @@ -0,0 +1,461 @@
> +/* drivers/input/touchscreen/sis_i2c.c
> + *  - I2C Touch panel driver for SiS 9200 family
> + *
> + * Copyright (C) 2011 SiS, Inc.
> + * Copyright (C) 2015 Nextfour Group
> + *
> + * 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/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>

Not needed I think.

> +#include <linux/platform_device.h>

Not needed.

> +#include <linux/linkage.h>

Not needed.

> +#include <linux/slab.h>
> +#include <linux/of_gpio.h>

Not needed (but gpio/consumer.h is)

> +#include <linux/uaccess.h>
> +#include <linux/irq.h>

Not needed I think.

> +#include <asm/unaligned.h>
> +#include <linux/crc-itu-t.h>
> +
> +#define SIS_I2C_NAME "sis_i2c_ts"
> +#define MAX_FINGERS					10
> +
> +#define SIS_MAX_X					4095
> +#define SIS_MAX_Y					4095
> +
> +#define PACKET_BUFFER_SIZE				128
> +
> +#define SIS_CMD_NORMAL					0x0
> +
> +#define TOUCHDOWN                                       0x3
> +#define TOUCHUP                                         0x0
> +#define MAX_BYTE					 64
> +#define PRESSURE_MAX                                    255
> +
> +/* Resolution diagonal */
> +#define AREA_LENGTH_LONGER				5792
> +/*((SIS_MAX_X^2) + (SIS_MAX_Y^2))^0.5*/
> +#define AREA_LENGTH_SHORT				5792
> +#define AREA_UNIT					(5792/32)
> +
> +#define P_BYTECOUNT					0
> +#define ALL_IN_ONE_PACKAGE				0x10
> +#define IS_TOUCH(x)					((x) & 0x1)
> +#define IS_HIDI2C(x)					(((x) & 0xF) == 0x06)
> +#define IS_AREA(x)					(((x) >> 4) & 0x1)
> +#define IS_PRESSURE(x)				        (((x) >> 5) & 0x1)
> +#define IS_SCANTIME(x)			                (((x) >> 6) & 0x1)
> +
> +#define NORMAL_LEN_PER_POINT			        6
> +#define AREA_LEN_PER_POINT				2
> +#define PRESSURE_LEN_PER_POINT			        1
> +
> +#define TOUCH_FORMAT					0x1
> +#define HIDI2C_FORMAT					0x6
> +#define P_REPORT_ID					2
> +#define BYTE_BYTECOUNT					2
> +#define BYTE_REPORTID					1
> +#define BYTE_CRC_HIDI2C                                 0
> +#define BYTE_CRC_I2C					2
> +#define BYTE_SCANTIME					2
> +
> +struct _touchpoint {
> +	u8 id;
> +	u16 x, y;
> +	u16 pressure;
> +	u16 width;
> +	u16 height;
> +};
> +
> +struct sistp_driver_data {
> +	int fingers;
> +	struct _touchpoint pt[MAX_FINGERS];
> +};
> +
> +struct sis_ts_data {
> +	struct gpio_desc *irq_gpiod;
> +	struct gpio_desc *reset_gpiod;
> +	struct i2c_client *client;
> +	struct input_dev *input_dev;
> +struct sistp_driver_data tpinfo;


There is actually no need to have storage for contact info in the driver
object, you can report them as you parse them.

> +};
> +
> +static int sis_cul_unit(u8 report_id)
> +{
> +	int ret = NORMAL_LEN_PER_POINT;
> +
> +	if (report_id != ALL_IN_ONE_PACKAGE) {
> +
> +		if (IS_AREA(report_id) /*&& IS_TOUCH(report_id)*/)
> +			ret += AREA_LEN_PER_POINT;
> +
> +		if (IS_PRESSURE(report_id))
> +			ret += PRESSURE_LEN_PER_POINT;
> +	}
> +
> +	return ret;
> +}
> +
> +static int sis_readpacket(struct i2c_client *client, u8 cmd, u8 *buf)
> +{
> +	u8 tmpbuf[MAX_BYTE] = {0};
> +	int ret = -1;
> +	int touchnum = 0;
> +	int p_count = 0;
> +	int touch_format_id = 0;
> +	int location = 0;
> +	bool read_first = true;
> +
> +/*
> + * I2C touch report format
> + *
> + * The controller sends one or two
> + * 64 byte reports (depending on how many
> + * contacts down etc). We read first 64 bytes
> + * and then the second chunk if needed.
> + * The packets are individually CRC
> + * checksummed.
> + *
> + * buf[0] = Low 8 bits of byte count value
> + * buf[1] = High 8 bits of byte counte value
> + * buf[2] = Report ID
> + * buf[touch num * 6 + 2 ] = Touch information
> + * 1 touch point has 6 bytes, it could be none if no touch
> + * buf[touch num * 6 + 3] = Touch numbers
> + *
> + * One touch point information include 6 bytes, the order is
> + *
> + * 1. status = touch down or touch up
> + * 2. id = finger id
> + * 3. x axis low 8 bits
> + * 4. x axis high 8 bits
> + * 5. y axis low 8 bits
> + * 6. y axis high 8 bits
> + */
> +	do {
> +		if (location >= PACKET_BUFFER_SIZE) {
> +			dev_err(&client->dev, "sis_readpacket: buffer overflow\n");
> +			return -1;
> +		}
> +
> +		ret = i2c_master_recv(client, tmpbuf, MAX_BYTE);
> +
> +		if (ret <= 0) {
> +			return touchnum;
> +		} else if (tmpbuf[P_BYTECOUNT] > MAX_BYTE) {
> +			dev_err(&client->dev, "sis_readpacket: invalid bytecout\n");
> +			return -1;
> +		}
> +
> +		if (tmpbuf[P_BYTECOUNT] < 10)
> +			return touchnum;
> +
> +		if (read_first)
> +			if (tmpbuf[P_BYTECOUNT] == 0)
> +				return 0;	/* touchnum is 0 */
> +
> +		touch_format_id = tmpbuf[P_REPORT_ID] & 0xf;
> +
> +		if ((touch_format_id != TOUCH_FORMAT)
> +			&& (touch_format_id != HIDI2C_FORMAT)
> +			&& (tmpbuf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE)) {
> +			dev_err(&client->dev, "sis_readpacket: invalid reportid\n");
> +			return -1;
> +		}
> +
> +		p_count = (int) tmpbuf[P_BYTECOUNT] - 1; /* start from 0 */
> +		if (tmpbuf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE) {
> +			if (IS_TOUCH(tmpbuf[P_REPORT_ID])) {
> +				p_count -= BYTE_CRC_I2C; /* delete 2 byte crc */
> +			} else if (IS_HIDI2C(tmpbuf[P_REPORT_ID])) {
> +				p_count -= BYTE_CRC_HIDI2C;
> +			} else {
> +				dev_err(&client->dev, "sis_readpacket: delete crc error\n");
> +				return -1;
> +			}
> +			if (IS_SCANTIME(tmpbuf[P_REPORT_ID]))
> +				p_count -= BYTE_SCANTIME;
> +		}
> +
> +		if (read_first)
> +			touchnum = tmpbuf[p_count];
> +		else {
> +			if (tmpbuf[p_count] != 0) {
> +				dev_err(&client->dev, "sis_readpacket: nonzero point count in tail packet\n");
> +				return -1;
> +			}
> +		}
> +
> +		if ((touch_format_id != HIDI2C_FORMAT) &&
> +			(tmpbuf[P_BYTECOUNT] > 3)) {
> +			int crc_end = p_count +
> +				(IS_SCANTIME(tmpbuf[P_REPORT_ID]) * 2);
> +			u16 buf_crc =
> +				crc_itu_t(0, tmpbuf + 2, crc_end - 1);
> +			int l_package_crc =
> +				(IS_SCANTIME(tmpbuf[P_REPORT_ID]) * 2) +
> +				p_count + 1;
> +			u16 package_crc =
> +				get_unaligned_le16(&tmpbuf[l_package_crc]);
> +			if (buf_crc != package_crc) {
> +				dev_err(&client->dev, "sis_readpacket: CRC Error\n");
> +				return -1;
> +			}
> +		}
> +
> +		memcpy(&buf[location], &tmpbuf[0], 64);
> +		/* Buf_Data [0~63] [64~128] */
> +		location += MAX_BYTE;
> +		read_first = false;
> +	} while (tmpbuf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE &&
> +		tmpbuf[p_count] > 5);
> +
> +	return touchnum;

I must say that I find this logic of combining 2 packets into one larger
confusing. I tried to rework it so that we fetch, parse and report
contacts as we go. Please see below.

> +}
> +
> +static irqreturn_t sis_ts_irq_handler(int irq, void *dev_id)
> +{
> +	struct sis_ts_data *ts = dev_id;
> +	struct sistp_driver_data *tpinfo = &ts->tpinfo;
> +
> +	int ret = -1;
> +	int point_unit;
> +	u8 buf[PACKET_BUFFER_SIZE] = {0};
> +	u8 i = 0, fingers = 0;
> +	u8 px = 0, py = 0, pstatus = 0;
> +	u8 p_area = 0;
> +	u8 p_preasure = 0;
> +	int slot;
> +
> +redo:
> +	/* I2C or SMBUS block data read */
> +	ret = sis_readpacket(ts->client, SIS_CMD_NORMAL, buf);
> +
> +	if (ret < 0)
> +		goto recheck_irq;
> +
> +	else if (ret == 0) {
> +		fingers = 0;
> +		goto label_sync_input;
> +	}
> +
> +	point_unit = sis_cul_unit(buf[P_REPORT_ID]);
> +	fingers = ret;
> +
> +	tpinfo->fingers = fingers = (fingers > MAX_FINGERS ? 0 : fingers);
> +
> +	for (i = 0; i < fingers; i++) {
> +		if ((buf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE) && (i >= 5)) {
> +			pstatus = BYTE_BYTECOUNT + BYTE_REPORTID
> +				+ ((i - 5) * point_unit);
> +			pstatus += 64;
> +		} else {
> +			pstatus = BYTE_BYTECOUNT + BYTE_REPORTID
> +				+ (i * point_unit);
> +		}
> +		/* X and Y coordinate locations */
> +		px = pstatus + 2;
> +		py = px + 2;
> +
> +		if ((buf[pstatus]) == TOUCHUP) {
> +			tpinfo->pt[i].width    = 0;
> +			tpinfo->pt[i].height   = 0;
> +			tpinfo->pt[i].pressure = 0;
> +		} else if (buf[P_REPORT_ID] == ALL_IN_ONE_PACKAGE
> +			&& (buf[pstatus]) == TOUCHDOWN) {
> +			tpinfo->pt[i].width    = 1;
> +			tpinfo->pt[i].height   = 1;
> +			tpinfo->pt[i].pressure = 1;
> +		} else if ((buf[pstatus]) == TOUCHDOWN) {
> +			p_area = py + 2;
> +			p_preasure = py + 2 + (IS_AREA(buf[P_REPORT_ID]) * 2);
> +
> +			if (IS_AREA(buf[P_REPORT_ID])) {
> +				tpinfo->pt[i].width = buf[p_area];
> +				tpinfo->pt[i].height = buf[p_area + 1];
> +			} else {
> +				tpinfo->pt[i].width = 1;
> +				tpinfo->pt[i].height = 1;
> +			}
> +
> +			if (IS_PRESSURE(buf[P_REPORT_ID]))
> +				tpinfo->pt[i].pressure = (buf[p_preasure]);
> +			else
> +				tpinfo->pt[i].pressure = 1;
> +		} else {
> +			dev_err(&ts->client->dev, "Touch status error\n");
> +			goto recheck_irq;
> +		}
> +		tpinfo->pt[i].id = (buf[pstatus + 1]);
> +		tpinfo->pt[i].x = le16_to_cpu(get_unaligned_le16(&buf[px]));
> +		tpinfo->pt[i].y = le16_to_cpu(get_unaligned_le16(&buf[py]));

get_unaligned_le16() already converts to native CPU endianness, calling
le16_to_cpu on it is a mistake.

> +
> +		slot = input_mt_get_slot_by_key(
> +			ts->input_dev, tpinfo->pt[i].id);
> +
> +		if (slot < 0)
> +			continue;
> +
> +		input_mt_slot(ts->input_dev, slot);
> +		input_mt_report_slot_state(ts->input_dev,
> +					MT_TOOL_FINGER, tpinfo->pt[i].pressure);
> +
> +		if (tpinfo->pt[i].pressure) {
> +
> +			tpinfo->pt[i].width *= AREA_UNIT;
> +			input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR,
> +					tpinfo->pt[i].width);
> +			tpinfo->pt[i].height *= AREA_UNIT;
> +			input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR,
> +					tpinfo->pt[i].height);
> +			input_report_abs(ts->input_dev, ABS_MT_PRESSURE,
> +					tpinfo->pt[i].pressure);
> +			input_report_abs(ts->input_dev, ABS_MT_POSITION_X,
> +					tpinfo->pt[i].x);
> +			input_report_abs(ts->input_dev, ABS_MT_POSITION_Y,
> +					tpinfo->pt[i].y);
> +		}
> +	}
> +
> +label_sync_input:
> +	input_mt_sync_frame(ts->input_dev);
> +	input_sync(ts->input_dev);
> +
> +recheck_irq:
> +	if (ts->irq_gpiod) {
> +		/*
> +		 * If provided and interrupt gpio and
> +		 * irq is still asserted,
> +		 * read data until interrupt is deasserted.
> +		 */
> +		ret = gpiod_get_value_cansleep(ts->irq_gpiod);
> +		if (ret == 1)
> +			goto redo;
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void sis_ts_reset(struct i2c_client *client, struct sis_ts_data *ts)
> +{
> +
> +	ts->irq_gpiod   = devm_gpiod_get_optional(&client->dev,
> +						"irq", GPIOD_IN);
> +	ts->reset_gpiod = devm_gpiod_get_optional(&client->dev,
> +						"reset", GPIOD_OUT_LOW);
> +
> +	if (ts->reset_gpiod) {
> +		/* Get out of reset */
> +		msleep(1);

msleep(1) will not work well with low HZ values, you want to use
usleep_range().

> +		gpiod_set_value(ts->reset_gpiod, 1);
> +		msleep(1);
> +		gpiod_set_value(ts->reset_gpiod, 0);
> +		msleep(100);
> +	}
> +}
> +
> +static int sis_ts_probe(
> +	struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	int error = 0;
> +	struct sis_ts_data *ts = NULL;

No need to initialize to NULL.

> +
> +	ts = devm_kzalloc(&client->dev, sizeof(struct sis_ts_data), GFP_KERNEL);
> +	if (!ts)
> +		return -ENOMEM;
> +
> +	sis_ts_reset(client, ts);
> +
> +	ts->client = client;
> +	i2c_set_clientdata(client, ts);
> +
> +	ts->input_dev = devm_input_allocate_device(&client->dev);
> +	if (!ts->input_dev) {
> +		dev_err(&client->dev, "sis_ts_probe: Failed to allocate input device\n");
> +		return -ENOMEM;
> +	}
> +
> +	ts->input_dev->name = "sis_touch";
> +	ts->input_dev->id.bustype = BUS_I2C;
> +
> +	input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE,
> +			0, PRESSURE_MAX, 0, 0);
> +	input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR,
> +			0, AREA_LENGTH_LONGER, 0, 0);
> +	input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MINOR,
> +			0, AREA_LENGTH_SHORT, 0, 0);
> +	input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X,
> +			0, SIS_MAX_X, 0, 0);
> +	input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y,
> +			0, SIS_MAX_Y, 0, 0);
> +
> +	error = input_mt_init_slots(ts->input_dev, MAX_FINGERS,
> +			INPUT_MT_DROP_UNUSED | INPUT_MT_DIRECT);

It seems that the hardware keeps track of the contact identity and
reports the release events, so I do not think we need
INPUT_MT_DROP_UNUSED.

> +
> +	if (error) {
> +		dev_err(&client->dev,
> +			"failed to initialize MT slots: %d\n", error);
> +		return error;
> +	}
> +
> +	error = input_register_device(ts->input_dev);
> +	if (error) {
> +		dev_err(&client->dev,
> +			"unable to register input device: %d\n", error);
> +		return error;
> +	}
> +
> +	error = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> +					sis_ts_irq_handler,
> +					IRQF_ONESHOT,
> +					client->name, ts);
> +
> +	if (error) {
> +		dev_err(&client->dev, "request irq failed\n");
> +		return error;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id sis_ts_id[] = {
> +	{ SIS_I2C_NAME, 0 },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, sis_ts_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id sis_ts_dt_ids[] = {
> +	{ .compatible = "sis,9200_ts" },

DT folks object to using underscores in properties, this should be
"sis,9200-ts".

> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, sis_ts_dt_ids);
> +#endif
> +
> +static struct i2c_driver sis_ts_driver = {
> +	.probe		= sis_ts_probe,
> +	.id_table	= sis_ts_id,
> +	.driver = {
> +		.name	= SIS_I2C_NAME,
> +		.of_match_table = of_match_ptr(sis_ts_dt_ids),
> +	},
> +};
> +
> +module_i2c_driver(sis_ts_driver);
> +MODULE_DESCRIPTION("SiS 9200 Family Touchscreen Driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.9.1
> 

Below is the version of the patch with reworked parsing and reporting
code. Please let me know if it is messed up or if it works for you.

Thanks.
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt b/Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt
new file mode 100644
index 0000000..6b06b20
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt
@@ -0,0 +1,33 @@ 
+* SiS I2C Multiple Touch Controller
+
+Required properties:
+- compatible: must be "sis,9200-ts"
+- reg: i2c slave address
+- interrupt-parent: the phandle for the interrupt controller
+  (see interrupt binding [0])
+- interrupts: touch controller interrupt (see interrupt
+  binding [0])
+
+Optional properties:
+- pinctrl-names: should be "default" (see pinctrl binding [1]).
+- pinctrl-0: a phandle pointing to the pin settings for the
+  device (see pinctrl binding [1]).
+- attn-gpio: the gpio pin used as attention line
+- reset-gpio: the gpio pin used to reset the controller
+- wakeup-source: touchscreen can be used as a wakeup source
+
+[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+
+	sis9255@5c  {
+		compatible = "sis,9200-ts";
+		reg = <0x5c>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_sis>;
+		interrupt-parent = <&gpio3>;
+		interrupts = <19 IRQ_TYPE_EDGE_FALLING>;
+		attn-gpio = <&gpio3 19 GPIO_ACTIVE_LOW>;
+		reset-gpio = <&gpio2 30 GPIO_ACTIVE_LOW>;
+	};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 80fdbe2..99029cf 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -221,6 +221,7 @@  simtek
 sii	Seiko Instruments, Inc.
 silergy	Silergy Corp.
 sirf	SiRF Technology, Inc.
+sis	Silicon Integrated Systems Corp.
 sitronix	Sitronix Technology Corporation
 skyworks	Skyworks Solutions, Inc.
 smsc	Standard Microsystems Corporation
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index ee02dc7..9a29ad1 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -1181,4 +1181,16 @@  config TOUCHSCREEN_ROHM_BU21023
 	  To compile this driver as a module, choose M here: the
 	  module will be called bu21023_ts.
 
+config TOUCHSCREEN_SIS_I2C
+	tristate "SiS 9200 family I2C touchscreen driver"
+	depends on I2C
+	depends on GPIOLIB || COMPILE_TEST
+	help
+	  This enables support for SiS 9200 family over I2C based touchscreens.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sis_i2c.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 3315882..e547399 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -64,6 +64,7 @@  obj-$(CONFIG_TOUCHSCREEN_PENMOUNT)	+= penmount.o
 obj-$(CONFIG_TOUCHSCREEN_PIXCIR)	+= pixcir_i2c_ts.o
 obj-$(CONFIG_TOUCHSCREEN_RM_TS)		+= raydium_i2c_ts.o
 obj-$(CONFIG_TOUCHSCREEN_S3C2410)	+= s3c2410_ts.o
+obj-$(CONFIG_TOUCHSCREEN_SIS_I2C)       += sis_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ST1232)	+= st1232.o
 obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
 obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
diff --git a/drivers/input/touchscreen/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c
new file mode 100644
index 0000000..bc96c9d
--- /dev/null
+++ b/drivers/input/touchscreen/sis_i2c.c
@@ -0,0 +1,409 @@ 
+/*
+ * Touch Screen driver for SiS 9200 family I2C Touch panels
+ *
+ * Copyright (C) 2015 SiS, Inc.
+ * Copyright (C) 2015 Nextfour Group
+ *
+ * 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/crc-itu-t.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#define SIS_I2C_NAME		"sis_i2c_ts"
+
+/*
+ * The I2C packet format:
+ * le16		byte count
+ * u8		Report ID
+ * <contact data - variable length>
+ * u8		Number of contacts
+ * le16		Scan Time (optional?)
+ * le16		CRC
+ *
+ * One touch point information consists of 6+ bytes, the order is:
+ * u8		contact state
+ * u8		finger id
+ * le16		x axis
+ * le16		y axis
+ * u8		contact width (optional)
+ * u8		contact height (optional)
+ * u8		pressure (optional)
+ *
+ * Maximum amount of data transmitted in one shot is 64 bytes, if controller
+ * needs to report more contacts than fit in one packet it will send true number
+ * of contacts in first packet and 0 as number of contacts in second packet.
+ */
+
+#define SIS_MAX_PACKET_SIZE		64
+
+#define SIS_PKT_LEN_OFFSET		0
+#define SIS_PKT_REPORT_OFFSET		2 /* Report ID/type */
+#define SIS_PKT_CONTACT_OFFSET		3 /* First contact */
+
+#define SIS_SCAN_TIME_LEN		2
+
+/* Supported report types */
+#define SIS_ALL_IN_ONE_PACKAGE		0x10
+#define SIS_PKT_IS_TOUCH(x)		(((x) & 0x0f) == 0x01)
+#define SIS_PKT_IS_HIDI2C(x)		(((x) & 0x0f) == 0x06)
+
+/* Contact properties within report */
+#define SIS_PKT_HAS_AREA(x)		((x) & BIT(4))
+#define SIS_PKT_HAS_PRESSURE(x)		((x) & BIT(5))
+#define SIS_PKT_HAS_SCANTIME(x)		((x) & BIT(6))
+
+/* Contact size */
+#define SIS_BASE_LEN_PER_CONTACT	6
+#define SIS_AREA_LEN_PER_CONTACT	2
+#define SIS_PRESSURE_LEN_PER_CONTACT	1
+
+/* Offsets within contact data */
+#define SIS_PKT_STATUS_OFFSET		0
+#define SIS_PKT_ID_OFFSET		1 /* Contact ID */
+#define SIS_PKT_X_OFFSET		2
+#define SIS_PKT_Y_OFFSET		4
+#define SIS_PKT_WIDTH_OFFSET		6
+#define SIS_PKT_HEIGHT_OFFSET		7
+#define SIS_PKT_PRESSURE_OFFSET(id)	(SIS_PKT_HAS_AREA(id) ? 8 : 6)
+
+/* Individual contact state */
+#define SIS_STATUS_UP			0x3
+#define SIS_STATUS_DOWN			0x0
+
+/* Touchscreen parameters */
+#define SIS_MAX_FINGERS			10
+#define SIS_MAX_X			4095
+#define SIS_MAX_Y			4095
+#define SIS_MAX_PRESSURE		255
+
+/* Resolution diagonal */
+#define SIS_AREA_LENGTH_LONGER		5792
+/*((SIS_MAX_X^2) + (SIS_MAX_Y^2))^0.5*/
+#define SIS_AREA_LENGTH_SHORT		5792
+#define SIS_AREA_UNIT			(5792/32)
+
+struct sis_ts_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+
+	struct gpio_desc *attn_gpio;
+	struct gpio_desc *reset_gpio;
+
+	u8 packet[SIS_MAX_PACKET_SIZE];
+};
+
+static int sis_read_packet(struct i2c_client *client, u8 *buf,
+			   unsigned int *num_contacts,
+			   unsigned int *contact_size)
+{
+	int count_idx;
+	int ret;
+	u16 len;
+	u16 crc, pkg_crc;
+	u8 report_id;
+
+	ret = i2c_master_recv(client, buf, SIS_MAX_PACKET_SIZE);
+	if (ret <= 0)
+		return -EIO;
+
+	len = get_unaligned_le16(&buf[SIS_PKT_LEN_OFFSET]);
+	if (len > SIS_MAX_PACKET_SIZE) {
+		dev_err(&client->dev,
+			"%s: invalid packet length (%d vs %d)\n",
+			__func__, len, SIS_MAX_PACKET_SIZE);
+		return -E2BIG;
+	}
+
+	if (len < 10)
+		return -EINVAL;
+
+	report_id = buf[SIS_PKT_ID_OFFSET];
+	count_idx  = len - 1;
+	*contact_size = SIS_BASE_LEN_PER_CONTACT;
+
+	if (report_id != SIS_ALL_IN_ONE_PACKAGE) {
+		if (SIS_PKT_IS_TOUCH(report_id)) {
+			/*
+			 * Calculate CRC ignoring packet length
+			 * in the beginning and CRC transmitted
+			 * at the end of the packet.
+			 */
+			crc = crc_itu_t(0, buf + SIS_PKT_LEN_OFFSET,
+					len - SIS_PKT_LEN_OFFSET - 2);
+			pkg_crc = get_unaligned_le16(&buf[len - 2]);
+
+			if (crc != pkg_crc) {
+				dev_err(&client->dev,
+					"%s: CRC Error (%d vs %d)\n",
+					__func__, crc, pkg_crc);
+				return -EINVAL;
+			}
+
+			count_idx -= 2;
+
+		} else if (!SIS_PKT_IS_HIDI2C(report_id)) {
+			dev_err(&client->dev,
+				"%s: invalid packet ID %#02x\n",
+				__func__, report_id);
+			return -EINVAL;
+		}
+
+		if (SIS_PKT_HAS_SCANTIME(report_id))
+			count_idx -= SIS_SCAN_TIME_LEN;
+
+		if (SIS_PKT_HAS_AREA(report_id))
+			*contact_size += SIS_AREA_LEN_PER_CONTACT;
+		if (SIS_PKT_HAS_PRESSURE(report_id))
+			*contact_size += SIS_PRESSURE_LEN_PER_CONTACT;
+	}
+
+	*num_contacts = buf[count_idx];
+	return 0;
+}
+
+static int sis_ts_report_contact(struct sis_ts_data *ts, const u8 *data, u8 id)
+{
+	struct input_dev *input = ts->input;
+	int slot;
+	u8 status = data[SIS_PKT_STATUS_OFFSET];
+	u8 pressure;
+	u8 height, width;
+
+	if (status != SIS_STATUS_DOWN && status != SIS_STATUS_UP) {
+		dev_err(&ts->client->dev, "Unexpected touch status: %#02x\n",
+			data[SIS_PKT_STATUS_OFFSET]);
+		return -EINVAL;
+	}
+
+	slot = input_mt_get_slot_by_key(input, data[SIS_PKT_ID_OFFSET]);
+	if (slot < 0)
+		return -ENOENT;
+
+	input_mt_slot(input, slot);
+	input_mt_report_slot_state(input, MT_TOOL_FINGER,
+				   status == SIS_STATUS_DOWN);
+
+	if (status == SIS_STATUS_DOWN) {
+		pressure = height = width = 1;
+		if (id != SIS_ALL_IN_ONE_PACKAGE) {
+			if (SIS_PKT_HAS_AREA(id)) {
+				width = data[SIS_PKT_WIDTH_OFFSET];
+				height = data[SIS_PKT_HEIGHT_OFFSET];
+			}
+
+			if (SIS_PKT_HAS_PRESSURE(id))
+				pressure = data[SIS_PKT_PRESSURE_OFFSET(id)];
+		}
+
+		input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+				 width * SIS_AREA_UNIT);
+		input_report_abs(input, ABS_MT_TOUCH_MINOR,
+				 height * SIS_AREA_UNIT);
+		input_report_abs(input, ABS_MT_PRESSURE, pressure);
+		input_report_abs(input, ABS_MT_POSITION_X,
+				 get_unaligned_le16(&data[SIS_PKT_X_OFFSET]));
+		input_report_abs(input, ABS_MT_POSITION_Y,
+				 get_unaligned_le16(&data[SIS_PKT_Y_OFFSET]));
+	}
+
+	return 0;
+}
+
+static void sis_ts_handle_packet(struct sis_ts_data *ts)
+{
+	const u8 *contact;
+	unsigned int num_to_report = 0;
+	unsigned int num_contacts;
+	unsigned int num_reported;
+	unsigned int contact_size;
+	int error;
+	u8 report_id;
+
+	do {
+		error = sis_read_packet(ts->client, ts->packet,
+					&num_contacts, &contact_size);
+		if (error)
+			break;
+
+		if (num_to_report == 0) {
+			num_to_report = num_contacts;
+		} else if (num_contacts != 0) {
+			dev_err(&ts->client->dev,
+				"%s: nonzero (%d) point count in tail packet\n",
+				__func__, num_contacts);
+			break;
+		}
+
+		report_id = ts->packet[SIS_PKT_REPORT_OFFSET];
+		contact = &ts->packet[SIS_PKT_CONTACT_OFFSET];
+		num_reported = 0;
+
+		while (num_to_report > 0) {
+			error = sis_ts_report_contact(ts, contact, report_id);
+			if (error)
+				break;
+
+			contact += contact_size;
+			num_to_report--;
+			num_reported++;
+
+			if (report_id != SIS_ALL_IN_ONE_PACKAGE &&
+			    num_reported >= 5) {
+				/*
+				 * The remainder of contacts is sent
+				 * in the 2nd packet.
+				 */
+				break;
+			}
+		}
+	} while (num_to_report > 0);
+
+	input_mt_sync_frame(ts->input);
+	input_sync(ts->input);
+}
+
+static irqreturn_t sis_ts_irq_handler(int irq, void *dev_id)
+{
+	struct sis_ts_data *ts = dev_id;
+
+	do {
+		sis_ts_handle_packet(ts);
+	} while (ts->attn_gpio && gpiod_get_value_cansleep(ts->attn_gpio));
+
+	return IRQ_HANDLED;
+}
+
+static void sis_ts_reset(struct sis_ts_data *ts)
+{
+	if (ts->reset_gpio) {
+		/* Get out of reset */
+		usleep_range(1000, 2000);
+		gpiod_set_value(ts->reset_gpio, 1);
+		usleep_range(1000, 2000);
+		gpiod_set_value(ts->reset_gpio, 0);
+		msleep(100);
+	}
+}
+
+static int sis_ts_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct sis_ts_data *ts;
+	struct input_dev *input;
+	int error;
+
+	ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	ts->attn_gpio = devm_gpiod_get_optional(&client->dev,
+						"attn", GPIOD_IN);
+	if (IS_ERR(ts->attn_gpio)) {
+		error = PTR_ERR(ts->attn_gpio);
+		if (error != -EPROBE_DEFER)
+			dev_err(&client->dev,
+				"Failed to get attention GPIO: %d\n", error);
+		return error;
+	}
+
+	ts->reset_gpio = devm_gpiod_get_optional(&client->dev,
+						 "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ts->reset_gpio)) {
+		error = PTR_ERR(ts->reset_gpio);
+		if (error != -EPROBE_DEFER)
+			dev_err(&client->dev,
+				"Failed to get reset GPIO: %d\n", error);
+		return error;
+	}
+
+	sis_ts_reset(ts);
+
+	ts->input = input = devm_input_allocate_device(&client->dev);
+	if (!input) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	input->name = "SiS Touchscreen";
+	input->id.bustype = BUS_I2C;
+
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, SIS_MAX_X, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, SIS_MAX_Y, 0, 0);
+	input_set_abs_params(input, ABS_MT_PRESSURE, 0, SIS_MAX_PRESSURE, 0, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR,
+			     0, SIS_AREA_LENGTH_LONGER, 0, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MINOR,
+			     0, SIS_AREA_LENGTH_SHORT, 0, 0);
+
+	error = input_mt_init_slots(input, SIS_MAX_FINGERS, INPUT_MT_DIRECT);
+	if (error) {
+		dev_err(&client->dev,
+			"Failed to initialize MT slots: %d\n", error);
+		return error;
+	}
+
+	error = devm_request_threaded_irq(&client->dev, client->irq,
+					  NULL, sis_ts_irq_handler,
+					  IRQF_ONESHOT,
+					  client->name, ts);
+	if (error) {
+		dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+		return error;
+	}
+
+	error = input_register_device(ts->input);
+	if (error) {
+		dev_err(&client->dev,
+			"Failed to register input device: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sis_ts_dt_ids[] = {
+	{ .compatible = "sis,9200-ts" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sis_ts_dt_ids);
+#endif
+
+static const struct i2c_device_id sis_ts_id[] = {
+	{ SIS_I2C_NAME,	0 },
+	{ "9200-ts",	0 },
+	{ /* sentinel */  }
+};
+MODULE_DEVICE_TABLE(i2c, sis_ts_id);
+
+static struct i2c_driver sis_ts_driver = {
+	.driver = {
+		.name	= SIS_I2C_NAME,
+		.of_match_table = of_match_ptr(sis_ts_dt_ids),
+	},
+	.probe		= sis_ts_probe,
+	.id_table	= sis_ts_id,
+};
+module_i2c_driver(sis_ts_driver);
+
+MODULE_DESCRIPTION("SiS 9200 Family Touchscreen Driver");
+MODULE_LICENSE("GPL v2");