input: ft5x06_ts: Add driver for ft5x06 series

Ft5x06 controllers are single chip capacitive
touch panel controller ICs with a built-in 8 bit
Micro Controller Unit. It supports multi-touch
capability and can detect up to five touches.

Change-Id: I39eb1175d473d1f2c463e1c4a0a1606307da9dc0
Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org>
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 87cdb02..eef0891 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -817,4 +817,18 @@
 
          To compile this driver as a module, choose M here: the
          module will be called cyttsp-i2c.
+
+config TOUCHSCREEN_FT5X06
+       tristate "FocalTech touchscreens"
+       depends on I2C
+       help
+         Say Y here if you have a ft5X06 touchscreen.
+	 Ft5x06 controllers are multi touch controllers which can
+	 report 5 touches at a time.
+
+         If unsure, say N.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ft5x06_ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 1d67427..616b3a4 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -26,6 +26,7 @@
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
 obj-$(CONFIG_TOUCHSCREEN_ELAN_I2C_8232) += elan8232_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
+obj-$(CONFIG_TOUCHSCREEN_FT5X06)	+= ft5x06_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
 obj-$(CONFIG_TOUCHSCREEN_INEXIO)	+= inexio.o
 obj-$(CONFIG_TOUCHSCREEN_INTEL_MID)	+= intel-mid-touch.o
diff --git a/drivers/input/touchscreen/ft5x06_ts.c b/drivers/input/touchscreen/ft5x06_ts.c
new file mode 100644
index 0000000..c9905a4
--- /dev/null
+++ b/drivers/input/touchscreen/ft5x06_ts.c
@@ -0,0 +1,654 @@
+/*
+ *
+ * FocalTech ft5x06 TouchScreen driver.
+ *
+ * Copyright (c) 2010  Focal tech Ltd.
+ * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * 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/i2c.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/input/ft5x06_ts.h>
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+#include <linux/earlysuspend.h>
+/* Early-suspend level */
+#define FT5X06_SUSPEND_LEVEL 1
+#endif
+
+#define CFG_MAX_TOUCH_POINTS	5
+
+#define FT_STARTUP_DLY		150
+#define FT_RESET_DLY		20
+
+#define FT_PRESS		0x7F
+#define FT_MAX_ID		0x0F
+#define FT_TOUCH_STEP		6
+#define FT_TOUCH_X_H_POS	3
+#define FT_TOUCH_X_L_POS	4
+#define FT_TOUCH_Y_H_POS	5
+#define FT_TOUCH_Y_L_POS	6
+#define FT_TOUCH_EVENT_POS	3
+#define FT_TOUCH_ID_POS		5
+
+#define POINT_READ_BUF	(3 + FT_TOUCH_STEP * CFG_MAX_TOUCH_POINTS)
+
+/*register address*/
+#define FT5X06_REG_PMODE	0xA5
+#define FT5X06_REG_FW_VER	0xA6
+#define FT5X06_REG_POINT_RATE	0x88
+#define FT5X06_REG_THGROUP	0x80
+
+/* power register bits*/
+#define FT5X06_PMODE_ACTIVE		0x00
+#define FT5X06_PMODE_MONITOR		0x01
+#define FT5X06_PMODE_STANDBY		0x02
+#define FT5X06_PMODE_HIBERNATE		0x03
+
+#define FT5X06_VTG_MIN_UV	2600000
+#define FT5X06_VTG_MAX_UV	3300000
+#define FT5X06_I2C_VTG_MIN_UV	1800000
+#define FT5X06_I2C_VTG_MAX_UV	1800000
+
+struct ts_event {
+	u16 x[CFG_MAX_TOUCH_POINTS];	/*x coordinate */
+	u16 y[CFG_MAX_TOUCH_POINTS];	/*y coordinate */
+	/* touch event: 0 -- down; 1-- contact; 2 -- contact */
+	u8 touch_event[CFG_MAX_TOUCH_POINTS];
+	u8 finger_id[CFG_MAX_TOUCH_POINTS];	/*touch ID */
+	u16 pressure;
+	u8 touch_point;
+};
+
+struct ft5x06_ts_data {
+	struct i2c_client *client;
+	struct input_dev *input_dev;
+	struct ts_event event;
+	const struct ft5x06_ts_platform_data *pdata;
+	struct regulator *vdd;
+	struct regulator *vcc_i2c;
+#ifdef CONFIG_HAS_EARLYSUSPEND
+	struct early_suspend early_suspend;
+#endif
+};
+
+static int ft5x06_i2c_read(struct i2c_client *client, char *writebuf,
+			   int writelen, char *readbuf, int readlen)
+{
+	int ret;
+
+	if (writelen > 0) {
+		struct i2c_msg msgs[] = {
+			{
+				 .addr = client->addr,
+				 .flags = 0,
+				 .len = writelen,
+				 .buf = writebuf,
+			 },
+			{
+				 .addr = client->addr,
+				 .flags = I2C_M_RD,
+				 .len = readlen,
+				 .buf = readbuf,
+			 },
+		};
+		ret = i2c_transfer(client->adapter, msgs, 2);
+		if (ret < 0)
+			dev_err(&client->dev, "%s: i2c read error.\n",
+				__func__);
+	} else {
+		struct i2c_msg msgs[] = {
+			{
+				 .addr = client->addr,
+				 .flags = I2C_M_RD,
+				 .len = readlen,
+				 .buf = readbuf,
+			 },
+		};
+		ret = i2c_transfer(client->adapter, msgs, 1);
+		if (ret < 0)
+			dev_err(&client->dev, "%s:i2c read error.\n", __func__);
+	}
+	return ret;
+}
+
+static int ft5x06_i2c_write(struct i2c_client *client, char *writebuf,
+			    int writelen)
+{
+	int ret;
+
+	struct i2c_msg msgs[] = {
+		{
+			 .addr = client->addr,
+			 .flags = 0,
+			 .len = writelen,
+			 .buf = writebuf,
+		 },
+	};
+	ret = i2c_transfer(client->adapter, msgs, 1);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: i2c write error.\n", __func__);
+
+	return ret;
+}
+
+static void ft5x06_report_value(struct ft5x06_ts_data *data)
+{
+	struct ts_event *event = &data->event;
+	int i;
+	int fingerdown = 0;
+
+	for (i = 0; i < event->touch_point; i++) {
+		if (event->touch_event[i] == 0 || event->touch_event[i] == 2) {
+			event->pressure = FT_PRESS;
+			fingerdown++;
+		} else {
+			event->pressure = 0;
+		}
+
+		input_report_abs(data->input_dev, ABS_MT_POSITION_X,
+				 event->x[i]);
+		input_report_abs(data->input_dev, ABS_MT_POSITION_Y,
+				 event->y[i]);
+		input_report_abs(data->input_dev, ABS_MT_PRESSURE,
+				 event->pressure);
+		input_report_abs(data->input_dev, ABS_MT_TRACKING_ID,
+				 event->finger_id[i]);
+		input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR,
+				 event->pressure);
+		input_mt_sync(data->input_dev);
+	}
+
+	input_report_key(data->input_dev, BTN_TOUCH, !!fingerdown);
+	input_sync(data->input_dev);
+}
+
+static int ft5x06_handle_touchdata(struct ft5x06_ts_data *data)
+{
+	struct ts_event *event = &data->event;
+	int ret, i;
+	u8 buf[POINT_READ_BUF] = { 0 };
+	u8 pointid = FT_MAX_ID;
+
+	ret = ft5x06_i2c_read(data->client, buf, 1, buf, POINT_READ_BUF);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "%s read touchdata failed.\n",
+			__func__);
+		return ret;
+	}
+	memset(event, 0, sizeof(struct ts_event));
+
+	event->touch_point = 0;
+	for (i = 0; i < CFG_MAX_TOUCH_POINTS; i++) {
+		pointid = (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4;
+		if (pointid >= FT_MAX_ID)
+			break;
+		else
+			event->touch_point++;
+		event->x[i] =
+		    (s16) (buf[FT_TOUCH_X_H_POS + FT_TOUCH_STEP * i] & 0x0F) <<
+		    8 | (s16) buf[FT_TOUCH_X_L_POS + FT_TOUCH_STEP * i];
+		event->y[i] =
+		    (s16) (buf[FT_TOUCH_Y_H_POS + FT_TOUCH_STEP * i] & 0x0F) <<
+		    8 | (s16) buf[FT_TOUCH_Y_L_POS + FT_TOUCH_STEP * i];
+		event->touch_event[i] =
+		    buf[FT_TOUCH_EVENT_POS + FT_TOUCH_STEP * i] >> 6;
+		event->finger_id[i] =
+		    (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4;
+	}
+
+	ft5x06_report_value(data);
+
+	return 0;
+}
+
+static irqreturn_t ft5x06_ts_interrupt(int irq, void *dev_id)
+{
+	struct ft5x06_ts_data *data = dev_id;
+	int rc;
+
+	rc = ft5x06_handle_touchdata(data);
+	if (rc)
+		pr_err("%s: handling touchdata failed\n", __func__);
+
+	return IRQ_HANDLED;
+}
+
+static int ft5x06_power_on(struct ft5x06_ts_data *data, bool on)
+{
+	int rc;
+
+	if (!on)
+		goto power_off;
+
+	rc = regulator_enable(data->vdd);
+	if (rc) {
+		dev_err(&data->client->dev,
+			"Regulator vdd enable failed rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = regulator_enable(data->vcc_i2c);
+	if (rc) {
+		dev_err(&data->client->dev,
+			"Regulator vcc_i2c enable failed rc=%d\n", rc);
+		regulator_disable(data->vdd);
+	}
+
+	return rc;
+
+power_off:
+	rc = regulator_disable(data->vdd);
+	if (rc) {
+		dev_err(&data->client->dev,
+			"Regulator vdd disable failed rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = regulator_disable(data->vcc_i2c);
+	if (rc) {
+		dev_err(&data->client->dev,
+			"Regulator vcc_i2c disable failed rc=%d\n", rc);
+		regulator_enable(data->vdd);
+	}
+
+	return rc;
+}
+
+static int ft5x06_power_init(struct ft5x06_ts_data *data, bool on)
+{
+	int rc;
+
+	if (!on)
+		goto pwr_deinit;
+
+	data->vdd = regulator_get(&data->client->dev, "vdd");
+	if (IS_ERR(data->vdd)) {
+		rc = PTR_ERR(data->vdd);
+		dev_err(&data->client->dev,
+			"Regulator get failed vdd rc=%d\n", rc);
+		return rc;
+	}
+
+	if (regulator_count_voltages(data->vdd) > 0) {
+		rc = regulator_set_voltage(data->vdd, FT5X06_VTG_MIN_UV,
+					   FT5X06_VTG_MAX_UV);
+		if (rc) {
+			dev_err(&data->client->dev,
+				"Regulator set_vtg failed vdd rc=%d\n", rc);
+			goto reg_vdd_put;
+		}
+	}
+
+	data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c");
+	if (IS_ERR(data->vcc_i2c)) {
+		rc = PTR_ERR(data->vcc_i2c);
+		dev_err(&data->client->dev,
+			"Regulator get failed vcc_i2c rc=%d\n", rc);
+		goto reg_vdd_set_vtg;
+	}
+
+	if (regulator_count_voltages(data->vcc_i2c) > 0) {
+		rc = regulator_set_voltage(data->vcc_i2c, FT5X06_I2C_VTG_MIN_UV,
+					   FT5X06_I2C_VTG_MAX_UV);
+		if (rc) {
+			dev_err(&data->client->dev,
+			"Regulator set_vtg failed vcc_i2c rc=%d\n", rc);
+			goto reg_vcc_i2c_put;
+		}
+	}
+
+	return 0;
+
+reg_vcc_i2c_put:
+	regulator_put(data->vcc_i2c);
+reg_vdd_set_vtg:
+	if (regulator_count_voltages(data->vdd) > 0)
+		regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV);
+reg_vdd_put:
+	regulator_put(data->vdd);
+	return rc;
+
+pwr_deinit:
+	if (regulator_count_voltages(data->vdd) > 0)
+		regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV);
+
+	regulator_put(data->vdd);
+
+	if (regulator_count_voltages(data->vcc_i2c) > 0)
+		regulator_set_voltage(data->vcc_i2c, 0, FT5X06_I2C_VTG_MAX_UV);
+
+	regulator_put(data->vcc_i2c);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ft5x06_ts_suspend(struct device *dev)
+{
+	struct ft5x06_ts_data *data = dev_get_drvdata(dev);
+	char txbuf[2];
+
+	disable_irq(data->client->irq);
+
+	if (gpio_is_valid(data->pdata->reset_gpio)) {
+		txbuf[0] = FT5X06_REG_PMODE;
+		txbuf[1] = FT5X06_PMODE_HIBERNATE;
+		ft5x06_i2c_write(data->client, txbuf, sizeof(txbuf));
+	}
+
+	return 0;
+}
+
+static int ft5x06_ts_resume(struct device *dev)
+{
+	struct ft5x06_ts_data *data = dev_get_drvdata(dev);
+
+	if (gpio_is_valid(data->pdata->reset_gpio)) {
+		gpio_set_value_cansleep(data->pdata->reset_gpio, 0);
+		msleep(FT_RESET_DLY);
+		gpio_set_value_cansleep(data->pdata->reset_gpio, 1);
+	}
+	enable_irq(data->client->irq);
+
+	return 0;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void ft5x06_ts_early_suspend(struct early_suspend *handler)
+{
+	struct ft5x06_ts_data *data = container_of(handler,
+						   struct ft5x06_ts_data,
+						   early_suspend);
+
+	ft5x06_ts_suspend(&data->client->dev);
+}
+
+static void ft5x06_ts_late_resume(struct early_suspend *handler)
+{
+	struct ft5x06_ts_data *data = container_of(handler,
+						   struct ft5x06_ts_data,
+						   early_suspend);
+
+	ft5x06_ts_resume(&data->client->dev);
+}
+#endif
+
+static const struct dev_pm_ops ft5x06_ts_pm_ops = {
+#ifndef CONFIG_HAS_EARLYSUSPEND
+	.suspend = ft5x06_ts_suspend,
+	.resume = ft5x06_ts_resume,
+#endif
+};
+#endif
+
+static int ft5x06_ts_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	const struct ft5x06_ts_platform_data *pdata = client->dev.platform_data;
+	struct ft5x06_ts_data *data;
+	struct input_dev *input_dev;
+	u8 reg_value;
+	u8 reg_addr;
+	int err;
+
+	if (!pdata) {
+		dev_err(&client->dev, "Invalid pdata\n");
+		return -EINVAL;
+	}
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev, "I2C not supported\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(struct ft5x06_ts_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&client->dev, "Not enough memory\n");
+		return -ENOMEM;
+	}
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		err = -ENOMEM;
+		dev_err(&client->dev, "failed to allocate input device\n");
+		goto free_mem;
+	}
+
+	data->input_dev = input_dev;
+	data->client = client;
+	data->pdata = pdata;
+
+	input_dev->name = "ft5x06_ts";
+	input_dev->id.bustype = BUS_I2C;
+	input_dev->dev.parent = &client->dev;
+
+	input_set_drvdata(input_dev, data);
+	i2c_set_clientdata(client, data);
+
+	__set_bit(EV_KEY, input_dev->evbit);
+	__set_bit(EV_ABS, input_dev->evbit);
+	__set_bit(BTN_TOUCH, input_dev->keybit);
+
+	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_TRACKING_ID, 0,
+			     CFG_MAX_TOUCH_POINTS, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, FT_PRESS, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, FT_PRESS, 0, 0);
+
+	err = input_register_device(input_dev);
+	if (err) {
+		dev_err(&client->dev, "Input device registration failed\n");
+		goto free_inputdev;
+	}
+
+	if (pdata->power_init) {
+		err = pdata->power_init(true);
+		if (err) {
+			dev_err(&client->dev, "power init failed");
+			goto unreg_inputdev;
+		}
+	} else {
+		err = ft5x06_power_init(data, true);
+		if (err) {
+			dev_err(&client->dev, "power init failed");
+			goto unreg_inputdev;
+		}
+	}
+
+	if (pdata->power_on) {
+		err = pdata->power_on(true);
+		if (err) {
+			dev_err(&client->dev, "power on failed");
+			goto pwr_deinit;
+		}
+	} else {
+		err = ft5x06_power_on(data, true);
+		if (err) {
+			dev_err(&client->dev, "power on failed");
+			goto pwr_deinit;
+		}
+	}
+
+	if (gpio_is_valid(pdata->irq_gpio)) {
+		err = gpio_request(pdata->irq_gpio, "ft5x06_irq_gpio");
+		if (err) {
+			dev_err(&client->dev, "irq gpio request failed");
+			goto pwr_off;
+		}
+		err = gpio_direction_input(pdata->irq_gpio);
+		if (err) {
+			dev_err(&client->dev,
+				"set_direction for irq gpio failed\n");
+			goto free_irq_gpio;
+		}
+	}
+
+	if (gpio_is_valid(pdata->reset_gpio)) {
+		err = gpio_request(pdata->reset_gpio, "ft5x06_reset_gpio");
+		if (err) {
+			dev_err(&client->dev, "reset gpio request failed");
+			goto free_irq_gpio;
+		}
+
+		err = gpio_direction_output(pdata->reset_gpio, 0);
+		if (err) {
+			dev_err(&client->dev,
+				"set_direction for reset gpio failed\n");
+			goto free_reset_gpio;
+		}
+		msleep(FT_RESET_DLY);
+		gpio_set_value_cansleep(data->pdata->reset_gpio, 1);
+	}
+
+	/* make sure CTP already finish startup process */
+	msleep(FT_STARTUP_DLY);
+
+	/*get some register information */
+	reg_addr = FT5X06_REG_FW_VER;
+	err = ft5x06_i2c_read(client, &reg_addr, 1, &reg_value, 1);
+	if (err)
+		dev_err(&client->dev, "version read failed");
+
+	dev_info(&client->dev, "[FTS] Firmware version = 0x%x\n", reg_value);
+
+	reg_addr = FT5X06_REG_POINT_RATE;
+	ft5x06_i2c_read(client, &reg_addr, 1, &reg_value, 1);
+	if (err)
+		dev_err(&client->dev, "report rate read failed");
+	dev_info(&client->dev, "[FTS] report rate is %dHz.\n", reg_value * 10);
+
+	reg_addr = FT5X06_REG_THGROUP;
+	err = ft5x06_i2c_read(client, &reg_addr, 1, &reg_value, 1);
+	if (err)
+		dev_err(&client->dev, "threshold read failed");
+	dev_dbg(&client->dev, "[FTS] touch threshold is %d.\n", reg_value * 4);
+
+	err = request_threaded_irq(client->irq, NULL,
+				   ft5x06_ts_interrupt, pdata->irqflags,
+				   client->dev.driver->name, data);
+	if (err) {
+		dev_err(&client->dev, "request irq failed\n");
+		goto free_reset_gpio;
+	}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+	data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
+	    FT5X06_SUSPEND_LEVEL;
+	data->early_suspend.suspend = ft5x06_ts_early_suspend;
+	data->early_suspend.resume = ft5x06_ts_late_resume;
+	register_early_suspend(&data->early_suspend);
+#endif
+
+	return 0;
+
+free_reset_gpio:
+	if (gpio_is_valid(pdata->reset_gpio))
+		gpio_free(pdata->reset_gpio);
+free_irq_gpio:
+	if (gpio_is_valid(pdata->irq_gpio))
+		gpio_free(pdata->reset_gpio);
+pwr_off:
+	if (pdata->power_on)
+		pdata->power_on(false);
+	else
+		ft5x06_power_on(data, false);
+pwr_deinit:
+	if (pdata->power_init)
+		pdata->power_init(false);
+	else
+		ft5x06_power_init(data, false);
+unreg_inputdev:
+	input_unregister_device(input_dev);
+	input_dev = NULL;
+free_inputdev:
+	input_free_device(input_dev);
+free_mem:
+	kfree(data);
+	return err;
+}
+
+static int __devexit ft5x06_ts_remove(struct i2c_client *client)
+{
+	struct ft5x06_ts_data *data = i2c_get_clientdata(client);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+	unregister_early_suspend(&data->early_suspend);
+#endif
+	free_irq(client->irq, data);
+
+	if (gpio_is_valid(data->pdata->reset_gpio))
+		gpio_free(data->pdata->reset_gpio);
+
+	if (gpio_is_valid(data->pdata->irq_gpio))
+		gpio_free(data->pdata->reset_gpio);
+
+	if (data->pdata->power_on)
+		data->pdata->power_on(false);
+	else
+		ft5x06_power_on(data, false);
+
+	if (data->pdata->power_init)
+		data->pdata->power_init(false);
+	else
+		ft5x06_power_init(data, false);
+
+	input_unregister_device(data->input_dev);
+	kfree(data);
+
+	return 0;
+}
+
+static const struct i2c_device_id ft5x06_ts_id[] = {
+	{"ft5x06_ts", 0},
+	{},
+};
+
+MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id);
+
+static struct i2c_driver ft5x06_ts_driver = {
+	.probe = ft5x06_ts_probe,
+	.remove = __devexit_p(ft5x06_ts_remove),
+	.driver = {
+		   .name = "ft5x06_ts",
+		   .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		   .pm = &ft5x06_ts_pm_ops,
+#endif
+		   },
+	.id_table = ft5x06_ts_id,
+};
+
+static int __init ft5x06_ts_init(void)
+{
+	return i2c_add_driver(&ft5x06_ts_driver);
+}
+module_init(ft5x06_ts_init);
+
+static void __exit ft5x06_ts_exit(void)
+{
+	i2c_del_driver(&ft5x06_ts_driver);
+}
+module_exit(ft5x06_ts_exit);
+
+MODULE_DESCRIPTION("FocalTech ft5x06 TouchScreen driver");
+MODULE_LICENSE("GPL v2");