diff mbox

[2/2] input: gt801_2plus1 - Add initial support for Goodix GT801 2+1

Message ID 1449473161-3535-3-git-send-email-plaes@plaes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Priit Laes Dec. 7, 2015, 7:26 a.m. UTC
This patch adds Goodix GT801 2+1 touchscreen controller support.

GT801 2+1 is a 10-finger touch controller consisting of
ARM controller interfacing two GT801 5-finger controllers.

Signed-off-by: Priit Laes <plaes@plaes.org>
---
 .../bindings/input/touchscreen/gt801_2plus1.txt    |  23 ++
 MAINTAINERS                                        |   6 +
 drivers/input/touchscreen/Kconfig                  |  19 +-
 drivers/input/touchscreen/Makefile                 |   1 +
 drivers/input/touchscreen/gt801_2plus1.c           | 375 +++++++++++++++++++++
 5 files changed, 421 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt
 create mode 100644 drivers/input/touchscreen/gt801_2plus1.c

Comments

Bastien Nocera Dec. 8, 2015, 12:14 p.m. UTC | #1
Hey Priit,

On Mon, 2015-12-07 at 09:26 +0200, Priit Laes wrote:
> This patch adds Goodix GT801 2+1 touchscreen controller support.
> 
> GT801 2+1 is a 10-finger touch controller consisting of
> ARM controller interfacing two GT801 5-finger controllers.

This would implement support for the "Guitar" protocol used by a few
more variants of the GT80x devices. Can you rename the driver/docs to that effect?
<snip>
> +
> +static const struct i2c_device_id gt801x_ts_id[] = {
> +	{ "GDIX1001:00", 0 },

This clashes with the ID used in the goodix.c driver.

Cheers
Rob Herring (Arm) Dec. 8, 2015, 3:18 p.m. UTC | #2
On Mon, Dec 07, 2015 at 09:26:01AM +0200, Priit Laes wrote:
> This patch adds Goodix GT801 2+1 touchscreen controller support.
> 
> GT801 2+1 is a 10-finger touch controller consisting of
> ARM controller interfacing two GT801 5-finger controllers.
> 
> Signed-off-by: Priit Laes <plaes@plaes.org>
> ---
>  .../bindings/input/touchscreen/gt801_2plus1.txt    |  23 ++

For the binding:

Acked-by: Rob Herring <robh@kernel.org>

>  MAINTAINERS                                        |   6 +
>  drivers/input/touchscreen/Kconfig                  |  19 +-
>  drivers/input/touchscreen/Makefile                 |   1 +
>  drivers/input/touchscreen/gt801_2plus1.c           | 375 +++++++++++++++++++++
>  5 files changed, 421 insertions(+), 3 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt
>  create mode 100644 drivers/input/touchscreen/gt801_2plus1.c
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt b/Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt
> new file mode 100644
> index 0000000..070ff5b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt
> @@ -0,0 +1,23 @@
> +Device tree bindings for Goodix GT801 2+1 series touchscreen controller
> +
> +Required properties:
> +
> + - compatible		: Should be "goodix,gt801_2plus1"
> + - reg			: I2C address of the chip. Should be 0x55
> + - interrupt-parent	: Interrupt controller to which the chip is connected
> + - interrupts		: Interrupt to which the chip is connected
> +
> +Example:
> +
> +	i2c@00000000 {
> +		/* ... */
> +
> +		touchscreen@55 {
> +			compatible = "goodix,gt801_2plus1";
> +			reg = <0x55>;
> +			interrupt-parent = <&gpio>;
> +			interrupts = <0 0>;
> +		};
> +
> +		/* ... */
> +	};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cba790b..e292126 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4743,6 +4743,12 @@ L:	linux-input@vger.kernel.org
>  S:	Maintained
>  F:	drivers/input/touchscreen/goodix.c
>  
> +GOODIX GT801 2PLUS1 TOUCHSCREEN
> +M:	Priit Laes <plaes@plaes.org>
> +L:	linux-input@vger.kernel.org
> +S:	Maintained
> +F:	drivers/input/touchscreen/gt801_2plus1.c
> +
>  GPIO SUBSYSTEM
>  M:	Linus Walleij <linus.walleij@linaro.org>
>  M:	Alexandre Courbot <gnurou@gmail.com>
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index ae33da7..c7c3324 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -322,11 +322,11 @@ config TOUCHSCREEN_FUJITSU
>  	  module will be called fujitsu-ts.
>  
>  config TOUCHSCREEN_GOODIX
> -	tristate "Goodix I2C touchscreen"
> +	tristate "Goodix GT9xx 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
> +	  Say Y here if you have one of the Goodix GT9xx touchscreens
> +	  (such as one installed in Onda v975w tablet) connected to your
>  	  system. It also supports 5-finger chip models, which can be
>  	  found on ARM tablets, like Wexler TAB7200 and MSI Primo73.
>  
> @@ -335,6 +335,19 @@ config TOUCHSCREEN_GOODIX
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called goodix.
>  
> +config TOUCHSCREEN_GT801_2PLUS1
> +	tristate "Goodix GT801 2+1 I2C touchscreen"
> +	depends on I2C
> +	help
> +	  Say Y here if you have the Goodix GT801 2+1 touchscreen.
> +	  This controller is found on some older ARM tablets like
> +	  (Gemei G9 and Zareason Zatab).
> +
> +	  If unsure, say N.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called gt801_2plus1.
> +
>  config TOUCHSCREEN_ILI210X
>  	tristate "Ilitek ILI210X based touchscreen"
>  	depends on I2C
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index cbaa6ab..ff25d1b4 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -38,6 +38,7 @@ obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_FT6236)	+= ft6236.o
>  obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix.o
> +obj-$(CONFIG_TOUCHSCREEN_GT801_2PLUS1)	+= gt801_2plus1.o
>  obj-$(CONFIG_TOUCHSCREEN_ILI210X)	+= ili210x.o
>  obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC)	+= imx6ul_tsc.o
>  obj-$(CONFIG_TOUCHSCREEN_INEXIO)	+= inexio.o
> diff --git a/drivers/input/touchscreen/gt801_2plus1.c b/drivers/input/touchscreen/gt801_2plus1.c
> new file mode 100644
> index 0000000..e2be479
> --- /dev/null
> +++ b/drivers/input/touchscreen/gt801_2plus1.c
> @@ -0,0 +1,375 @@
> +/*
> + *  Driver for Goodix GT801 2+1 ARM touchscreen controllers
> + *
> + *  Copyright (c) 2015 Priit Laes <plaes@plaes.org>.
> + *
> + *  This code is based on goodix.c driver (c) 2014 Red Hat Inc,
> + *  various Android codedumps (c) 2010 - 2012 Goodix Technology
> + *  and cleanups done by Emilio López (turl) for linux-sunxi.
> + *
> + * 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 <asm/unaligned.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/input/mt.h>
> +
> +struct gt801x_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_MAX_CONTACTS		10
> +#define MAX_CONTACTS_LOC	5
> +#define RESOLUTION_LOC		1
> +#define TRIGGER_LOC		6
> +
> +/* Register defines */
> +#define GT801X_COOR_ADDR		0x01
> +#define GT801X_CONFIG_DATA	0x65
> +#define GT801X_REG_ID			0xf0
> +
> +/* Device specific defines */
> +#define GT801X_CONFIG_MAX_LENGTH	7
> +#define GT801X_CONTACT_SIZE		5
> +
> +static const unsigned long goodix_irq_flags[] = {
> +	IRQ_TYPE_EDGE_RISING,
> +	IRQ_TYPE_EDGE_FALLING,
> +	IRQ_TYPE_LEVEL_LOW,
> +	IRQ_TYPE_LEVEL_HIGH,
> +};
> +
> +/**
> + * gt801x_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 gt801x_i2c_read(struct i2c_client *client,
> +			   u8 reg, u8 *buf, int len)
> +{
> +	struct i2c_msg msgs[2];
> +	int ret;
> +
> +	msgs[0].flags = 0;
> +	msgs[0].addr  = client->addr;
> +	msgs[0].len   = 1;
> +	msgs[0].buf   = &reg;
> +
> +	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);
> +}
> +
> +/**
> + * gt801x_process_events - Process incoming events
> + *
> + * @ts: our gt801x_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 gt801x_process_events(struct gt801x_ts_data *ts)
> +{
> +	u8 point_data[3 + GT801X_CONTACT_SIZE * GOODIX_MAX_CONTACTS];
> +	u8 touch_map[GOODIX_MAX_CONTACTS] = {0};
> +	int input_x, input_y, input_w;
> +	u8 checksum = 0;
> +	u16 touch_raw;
> +	u8 touch_num;
> +	int error;
> +	int loc;
> +	int i;
> +
> +	error = gt801x_i2c_read(ts->client, GT801X_COOR_ADDR,
> +				point_data, sizeof(point_data));
> +	if (error) {
> +		dev_err(&ts->client->dev, "I2C transfer error: %d\n", error);
> +		return;
> +	}
> +
> +	/* Fetch touch mapping bits */
> +	touch_raw = get_unaligned_le16(&point_data[0]);
> +	if (!touch_raw)
> +		return;
> +
> +	/* Build touch map */
> +	touch_num = 0;
> +	for (i = 0; (touch_raw != 0) && (i < ts->max_touch_num); i++) {
> +		if (touch_raw & 1)
> +			touch_map[touch_num++] = i;
> +			touch_raw >>= 1;
> +	}
> +
> +	/* Calculate checksum */
> +	for (i = 0; i < (touch_num*GT801X_CONTACT_SIZE + 3); i++)
> +		checksum += point_data[i];
> +	if (checksum != 0)
> +		return;
> +
> +	/* Report touches */
> +	for (i = 0; i < touch_num; i++) {
> +		loc = 2 + GT801X_CONTACT_SIZE * i;
> +		input_x = get_unaligned_be16(&point_data[loc]);
> +		input_y = get_unaligned_be16(&point_data[loc + 2]);
> +		input_w = point_data[loc + 4];
> +
> +		input_mt_slot(ts->input_dev, i);
> +		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);
> +	}
> +
> +	input_mt_sync_frame(ts->input_dev);
> +	input_sync(ts->input_dev);
> +}
> +
> +/**
> + * gt801x_ts_irq_handler - The IRQ handler
> + *
> + * @irq: interrupt number.
> + * @dev_id: private data pointer.
> + */
> +static irqreturn_t gt801x_ts_irq_handler(int irq, void *dev_id)
> +{
> +	gt801x_process_events((struct gt801x_ts_data *)dev_id);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/**
> + * gt801x_read_config - Read the embedded configuration of the panel
> + *
> + * @ts: our gt801x_ts_data pointer
> + *
> + * Must be called during probe
> + */
> +static void gt801x_read_config(struct gt801x_ts_data *ts)
> +{
> +	u8 config[GT801X_CONFIG_MAX_LENGTH];
> +	int error;
> +
> +	error = gt801x_i2c_read(ts->client, GT801X_CONFIG_DATA,
> +				config,
> +				GT801X_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_be16(&config[RESOLUTION_LOC]);
> +	ts->abs_y_max = get_unaligned_be16(&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;
> +	}
> +}
> +
> +/**
> + * gt801x_read_version - Read GT801 2+1 touchscreen version
> + *
> + * @client: the i2c client
> + * @version: output buffer containing the version on success
> + * @id: output buffer containing the id on success
> + */
> +static int gt801x_read_version(struct i2c_client *client, u16 *version, u16 *id)
> +{
> +	int error;
> +	u8 buf[16];
> +
> +	error = gt801x_i2c_read(client, GT801X_REG_ID, buf, sizeof(buf));
> +	if (error) {
> +		dev_err(&client->dev, "read version failed: %d\n", error);
> +		return error;
> +	}
> +	/* TODO: version info contains 'GT801NI_3R15_1AV' */
> +	print_hex_dump_bytes("", DUMP_PREFIX_NONE, buf, ARRAY_SIZE(buf));
> +	*id = 0x802;
> +	*version = 0x15;
> +	dev_info(&client->dev, "ID %d, version: %04x\n", *id, *version);
> +	return 0;
> +}
> +
> +/**
> + * gt801x_i2c_test - I2C test function to check if the device answers.
> + *
> + * @client: the i2c client
> + */
> +static int gt801x_i2c_test(struct i2c_client *client)
> +{
> +	int retry = 0;
> +	int error;
> +	u8 test;
> +
> +	while (retry++ < 2) {
> +		error = gt801x_i2c_read(client, GT801X_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;
> +}
> +
> +/**
> + * gt801x_request_input_dev - Allocate, populate and register the input device
> + *
> + * @ts: our gt801x_ts_data pointer
> + * @version: device firmware version
> + * @id: device ID
> + *
> + * Must be called during probe
> + */
> +static int gt801x_request_input_dev(struct gt801x_ts_data *ts,
> +					u16 version, u16 id)
> +{
> +	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;
> +	}
> +
> +	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 (GT801 2+1)";
> +	ts->input_dev->phys = "input/ts";
> +	ts->input_dev->id.bustype = BUS_I2C;
> +	ts->input_dev->id.vendor = 0x0416;
> +	ts->input_dev->id.product = id;
> +	ts->input_dev->id.version = version;
> +
> +	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 gt801x_ts_probe(struct i2c_client *client,
> +			   const struct i2c_device_id *id)
> +{
> +	struct gt801x_ts_data *ts;
> +	unsigned long irq_flags;
> +	int error;
> +	u16 version_info, id_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 = gt801x_i2c_test(client);
> +	if (error) {
> +		dev_err(&client->dev, "I2C communication failure: %d\n", error);
> +		return error;
> +	}
> +
> +	error = gt801x_read_version(client, &version_info, &id_info);
> +	if (error) {
> +		dev_err(&client->dev, "Read version failed.\n");
> +		return error;
> +	}
> +
> +	gt801x_read_config(ts);
> +
> +	error = gt801x_request_input_dev(ts, version_info, id_info);
> +	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, gt801x_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 gt801x_ts_id[] = {
> +	{ "GDIX1001:00", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, gt801x_ts_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id gt801x_of_match[] = {
> +	{ .compatible = "goodix,gt801_2plus1" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, gt801x_of_match);
> +#endif
> +
> +static struct i2c_driver gt801x_ts_driver = {
> +	.probe = gt801x_ts_probe,
> +	.id_table = gt801x_ts_id,
> +	.driver = {
> +		.name = "Goodix-TS",
> +		.of_match_table = of_match_ptr(gt801x_of_match),
> +	},
> +};
> +module_i2c_driver(gt801x_ts_driver);
> +
> +MODULE_AUTHOR("Priit Laes <plaes@plaes.org>");
> +MODULE_DESCRIPTION("Goodix GT801 2+1 touchscreen driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.6.3
>
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt b/Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt
new file mode 100644
index 0000000..070ff5b
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/gt801_2plus1.txt
@@ -0,0 +1,23 @@ 
+Device tree bindings for Goodix GT801 2+1 series touchscreen controller
+
+Required properties:
+
+ - compatible		: Should be "goodix,gt801_2plus1"
+ - reg			: I2C address of the chip. Should be 0x55
+ - interrupt-parent	: Interrupt controller to which the chip is connected
+ - interrupts		: Interrupt to which the chip is connected
+
+Example:
+
+	i2c@00000000 {
+		/* ... */
+
+		touchscreen@55 {
+			compatible = "goodix,gt801_2plus1";
+			reg = <0x55>;
+			interrupt-parent = <&gpio>;
+			interrupts = <0 0>;
+		};
+
+		/* ... */
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index cba790b..e292126 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4743,6 +4743,12 @@  L:	linux-input@vger.kernel.org
 S:	Maintained
 F:	drivers/input/touchscreen/goodix.c
 
+GOODIX GT801 2PLUS1 TOUCHSCREEN
+M:	Priit Laes <plaes@plaes.org>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/input/touchscreen/gt801_2plus1.c
+
 GPIO SUBSYSTEM
 M:	Linus Walleij <linus.walleij@linaro.org>
 M:	Alexandre Courbot <gnurou@gmail.com>
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index ae33da7..c7c3324 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -322,11 +322,11 @@  config TOUCHSCREEN_FUJITSU
 	  module will be called fujitsu-ts.
 
 config TOUCHSCREEN_GOODIX
-	tristate "Goodix I2C touchscreen"
+	tristate "Goodix GT9xx 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
+	  Say Y here if you have one of the Goodix GT9xx touchscreens
+	  (such as one installed in Onda v975w tablet) connected to your
 	  system. It also supports 5-finger chip models, which can be
 	  found on ARM tablets, like Wexler TAB7200 and MSI Primo73.
 
@@ -335,6 +335,19 @@  config TOUCHSCREEN_GOODIX
 	  To compile this driver as a module, choose M here: the
 	  module will be called goodix.
 
+config TOUCHSCREEN_GT801_2PLUS1
+	tristate "Goodix GT801 2+1 I2C touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have the Goodix GT801 2+1 touchscreen.
+	  This controller is found on some older ARM tablets like
+	  (Gemei G9 and Zareason Zatab).
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gt801_2plus1.
+
 config TOUCHSCREEN_ILI210X
 	tristate "Ilitek ILI210X based touchscreen"
 	depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index cbaa6ab..ff25d1b4 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -38,6 +38,7 @@  obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FT6236)	+= ft6236.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
 obj-$(CONFIG_TOUCHSCREEN_GOODIX)	+= goodix.o
+obj-$(CONFIG_TOUCHSCREEN_GT801_2PLUS1)	+= gt801_2plus1.o
 obj-$(CONFIG_TOUCHSCREEN_ILI210X)	+= ili210x.o
 obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC)	+= imx6ul_tsc.o
 obj-$(CONFIG_TOUCHSCREEN_INEXIO)	+= inexio.o
diff --git a/drivers/input/touchscreen/gt801_2plus1.c b/drivers/input/touchscreen/gt801_2plus1.c
new file mode 100644
index 0000000..e2be479
--- /dev/null
+++ b/drivers/input/touchscreen/gt801_2plus1.c
@@ -0,0 +1,375 @@ 
+/*
+ *  Driver for Goodix GT801 2+1 ARM touchscreen controllers
+ *
+ *  Copyright (c) 2015 Priit Laes <plaes@plaes.org>.
+ *
+ *  This code is based on goodix.c driver (c) 2014 Red Hat Inc,
+ *  various Android codedumps (c) 2010 - 2012 Goodix Technology
+ *  and cleanups done by Emilio López (turl) for linux-sunxi.
+ *
+ * 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 <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/input/mt.h>
+
+struct gt801x_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_MAX_CONTACTS		10
+#define MAX_CONTACTS_LOC	5
+#define RESOLUTION_LOC		1
+#define TRIGGER_LOC		6
+
+/* Register defines */
+#define GT801X_COOR_ADDR		0x01
+#define GT801X_CONFIG_DATA	0x65
+#define GT801X_REG_ID			0xf0
+
+/* Device specific defines */
+#define GT801X_CONFIG_MAX_LENGTH	7
+#define GT801X_CONTACT_SIZE		5
+
+static const unsigned long goodix_irq_flags[] = {
+	IRQ_TYPE_EDGE_RISING,
+	IRQ_TYPE_EDGE_FALLING,
+	IRQ_TYPE_LEVEL_LOW,
+	IRQ_TYPE_LEVEL_HIGH,
+};
+
+/**
+ * gt801x_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 gt801x_i2c_read(struct i2c_client *client,
+			   u8 reg, u8 *buf, int len)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+
+	msgs[0].flags = 0;
+	msgs[0].addr  = client->addr;
+	msgs[0].len   = 1;
+	msgs[0].buf   = &reg;
+
+	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);
+}
+
+/**
+ * gt801x_process_events - Process incoming events
+ *
+ * @ts: our gt801x_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 gt801x_process_events(struct gt801x_ts_data *ts)
+{
+	u8 point_data[3 + GT801X_CONTACT_SIZE * GOODIX_MAX_CONTACTS];
+	u8 touch_map[GOODIX_MAX_CONTACTS] = {0};
+	int input_x, input_y, input_w;
+	u8 checksum = 0;
+	u16 touch_raw;
+	u8 touch_num;
+	int error;
+	int loc;
+	int i;
+
+	error = gt801x_i2c_read(ts->client, GT801X_COOR_ADDR,
+				point_data, sizeof(point_data));
+	if (error) {
+		dev_err(&ts->client->dev, "I2C transfer error: %d\n", error);
+		return;
+	}
+
+	/* Fetch touch mapping bits */
+	touch_raw = get_unaligned_le16(&point_data[0]);
+	if (!touch_raw)
+		return;
+
+	/* Build touch map */
+	touch_num = 0;
+	for (i = 0; (touch_raw != 0) && (i < ts->max_touch_num); i++) {
+		if (touch_raw & 1)
+			touch_map[touch_num++] = i;
+			touch_raw >>= 1;
+	}
+
+	/* Calculate checksum */
+	for (i = 0; i < (touch_num*GT801X_CONTACT_SIZE + 3); i++)
+		checksum += point_data[i];
+	if (checksum != 0)
+		return;
+
+	/* Report touches */
+	for (i = 0; i < touch_num; i++) {
+		loc = 2 + GT801X_CONTACT_SIZE * i;
+		input_x = get_unaligned_be16(&point_data[loc]);
+		input_y = get_unaligned_be16(&point_data[loc + 2]);
+		input_w = point_data[loc + 4];
+
+		input_mt_slot(ts->input_dev, i);
+		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);
+	}
+
+	input_mt_sync_frame(ts->input_dev);
+	input_sync(ts->input_dev);
+}
+
+/**
+ * gt801x_ts_irq_handler - The IRQ handler
+ *
+ * @irq: interrupt number.
+ * @dev_id: private data pointer.
+ */
+static irqreturn_t gt801x_ts_irq_handler(int irq, void *dev_id)
+{
+	gt801x_process_events((struct gt801x_ts_data *)dev_id);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * gt801x_read_config - Read the embedded configuration of the panel
+ *
+ * @ts: our gt801x_ts_data pointer
+ *
+ * Must be called during probe
+ */
+static void gt801x_read_config(struct gt801x_ts_data *ts)
+{
+	u8 config[GT801X_CONFIG_MAX_LENGTH];
+	int error;
+
+	error = gt801x_i2c_read(ts->client, GT801X_CONFIG_DATA,
+				config,
+				GT801X_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_be16(&config[RESOLUTION_LOC]);
+	ts->abs_y_max = get_unaligned_be16(&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;
+	}
+}
+
+/**
+ * gt801x_read_version - Read GT801 2+1 touchscreen version
+ *
+ * @client: the i2c client
+ * @version: output buffer containing the version on success
+ * @id: output buffer containing the id on success
+ */
+static int gt801x_read_version(struct i2c_client *client, u16 *version, u16 *id)
+{
+	int error;
+	u8 buf[16];
+
+	error = gt801x_i2c_read(client, GT801X_REG_ID, buf, sizeof(buf));
+	if (error) {
+		dev_err(&client->dev, "read version failed: %d\n", error);
+		return error;
+	}
+	/* TODO: version info contains 'GT801NI_3R15_1AV' */
+	print_hex_dump_bytes("", DUMP_PREFIX_NONE, buf, ARRAY_SIZE(buf));
+	*id = 0x802;
+	*version = 0x15;
+	dev_info(&client->dev, "ID %d, version: %04x\n", *id, *version);
+	return 0;
+}
+
+/**
+ * gt801x_i2c_test - I2C test function to check if the device answers.
+ *
+ * @client: the i2c client
+ */
+static int gt801x_i2c_test(struct i2c_client *client)
+{
+	int retry = 0;
+	int error;
+	u8 test;
+
+	while (retry++ < 2) {
+		error = gt801x_i2c_read(client, GT801X_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;
+}
+
+/**
+ * gt801x_request_input_dev - Allocate, populate and register the input device
+ *
+ * @ts: our gt801x_ts_data pointer
+ * @version: device firmware version
+ * @id: device ID
+ *
+ * Must be called during probe
+ */
+static int gt801x_request_input_dev(struct gt801x_ts_data *ts,
+					u16 version, u16 id)
+{
+	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;
+	}
+
+	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 (GT801 2+1)";
+	ts->input_dev->phys = "input/ts";
+	ts->input_dev->id.bustype = BUS_I2C;
+	ts->input_dev->id.vendor = 0x0416;
+	ts->input_dev->id.product = id;
+	ts->input_dev->id.version = version;
+
+	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 gt801x_ts_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	struct gt801x_ts_data *ts;
+	unsigned long irq_flags;
+	int error;
+	u16 version_info, id_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 = gt801x_i2c_test(client);
+	if (error) {
+		dev_err(&client->dev, "I2C communication failure: %d\n", error);
+		return error;
+	}
+
+	error = gt801x_read_version(client, &version_info, &id_info);
+	if (error) {
+		dev_err(&client->dev, "Read version failed.\n");
+		return error;
+	}
+
+	gt801x_read_config(ts);
+
+	error = gt801x_request_input_dev(ts, version_info, id_info);
+	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, gt801x_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 gt801x_ts_id[] = {
+	{ "GDIX1001:00", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, gt801x_ts_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id gt801x_of_match[] = {
+	{ .compatible = "goodix,gt801_2plus1" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gt801x_of_match);
+#endif
+
+static struct i2c_driver gt801x_ts_driver = {
+	.probe = gt801x_ts_probe,
+	.id_table = gt801x_ts_id,
+	.driver = {
+		.name = "Goodix-TS",
+		.of_match_table = of_match_ptr(gt801x_of_match),
+	},
+};
+module_i2c_driver(gt801x_ts_driver);
+
+MODULE_AUTHOR("Priit Laes <plaes@plaes.org>");
+MODULE_DESCRIPTION("Goodix GT801 2+1 touchscreen driver");
+MODULE_LICENSE("GPL v2");