diff mbox

[2/2] input: sirfsoc_rs - add sirfsoc internal ADC-based touchscreen driver

Message ID 1403516581-12560-3-git-send-email-21cnbao@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Barry Song June 23, 2014, 9:43 a.m. UTC
From: Guoying Zhang <Guoying.Zhang@csr.com>

The Touch Screen (TS) Controller is used for driving the touch screen to
perform coordinates and pressure measurements. It supports a 4-wire
resistive touch screen interface.

in this driver, we support both single and dual touch. The dual touch is
actually based on a software algorithm.

The idea is to recover original dual touch coordinates from AD samples.

- Equivalent schematic.
                            touch1         touch2
                               |              |
                               v              v
                       RX1     A      RX2     D      RX3
Upper Plane:  XP ----/\/\/\----o----/\/\/\----o----/\/\/\---- XN
                               |              |
                               /              /
                       Rtouch1 \      Rtouch2 \
                               /              /
                               \              \
                               |              |
Lower Plane:  YP ----/\/\/\----o----/\/\/\----o----/\/\/\---- YN
                       RY1     B      RY2     C      RY3

  Touch points(touch1/2) introduce two resisters(Rtouch1/2) between
  upper plane and lower plane, as well as cutting each plane into
  three consecutive resisters(RX1/2/3, RY1/2/3).

- Eight AD samples can be got from sirf touchscreen controller, which
  serve as known conditions to solve the above resister network.
  ---------------------------------
  |   Power   |     AD samples    |
  ---------------------------------
  | Vcc | GND | Sample1 | Sample2 |
  ---------------------------------
  | XP  | XN  |   YP    |   YN    | \
  ---------------------------------  > Recover coordinates
  | YP  | YN  |   XP    |   XN    | /
  ---------------------------------
  | XP  | YN  |   YP    |   XN    | \
  ---------------------------------  > Calculate Rtouch
  | YP  | XN  |   XP    |   YN    | /
  ---------------------------------

- Non-trivial calculation is required to recover dual touch coordinates
  as we need to resolve some second order equations and deal with very
  big or small float numbers. Some tricks are necessary to make it work.
  * float numbers are represented by enlarged integers.
    ex. (float)1.03 -> *1024 -> (u32)1054
  * Sequence of multiply and division is important as it may result in
    overflow or underflow if switched, though in theory it's nonsense.
  * Single touch dominates dual touch. Single touch detection must be
    kept fast and accurate.

Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Guoying Zhang <Guoying.Zhang@csr.com>
Signed-off-by: Yibo Cai <Yibo.Cai@csr.com>
Signed-off-by: Zhiwu Song <Zhiwu.Song@csr.com>
Signed-off-by: Barry Song <Baohua.Song@csr.com>
---
 drivers/input/touchscreen/Kconfig      |  10 +
 drivers/input/touchscreen/Makefile     |   1 +
 drivers/input/touchscreen/sirfsoc_ts.c | 658 +++++++++++++++++++++++++++++++++
 3 files changed, 669 insertions(+)
 create mode 100644 drivers/input/touchscreen/sirfsoc_ts.c
diff mbox

Patch

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index a23a94b..8312b7c 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -868,6 +868,16 @@  config TOUCHSCREEN_PCAP
 	  To compile this driver as a module, choose M here: the
 	  module will be called pcap_ts.
 
+config TOUCHSCREEN_SIRFSOC
+	tristate "SiRFSoC touchscreen"
+	depends on ARCH_SIRF
+	select IIO
+	help
+	  Say Y here if you have a SiRFSoC internal ADC based touchscreen.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sirfsoc_ts.
+
 config TOUCHSCREEN_ST1232
 	tristate "Sitronix ST1232 touchscreen controllers"
 	depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 126479d..ef438f7 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -52,6 +52,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_SIRFSOC)       += sirfsoc_ts.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/sirfsoc_ts.c b/drivers/input/touchscreen/sirfsoc_ts.c
new file mode 100644
index 0000000..77cde19
--- /dev/null
+++ b/drivers/input/touchscreen/sirfsoc_ts.c
@@ -0,0 +1,658 @@ 
+/*
+ * sirfsoc touch controller Driver
+ *
+ * Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company.
+ *
+ * Licensed under GPLv2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/reset.h>
+#include <linux/iio/consumer.h>
+
+#define DRIVER_NAME		"sirfsoc_tsc"
+
+#define DATA_SHIFT_BITS		14
+#define DATA_XMASK		(0x3FFF << 0)
+#define DATA_YMASK		(0x3FFF << DATA_SHIFT_BITS)
+#define GETX(val)		((val) & DATA_XMASK)
+#define GETY(val)		(((val) & DATA_YMASK) >> DATA_SHIFT_BITS)
+
+/*
+ * If new coordinates are close enough to last ones, last values
+ * should be used to suppress glitches.
+ */
+#define TS_GLITCH_GAP		60
+
+/* Dual touch: Configurable parameters */
+#define TS_PREC_BITS		10	/* Precision, must >= 2 */
+#define TS_DUAL_MIN		15	/* Min UAD/UBC of dual touch points */
+/* Dual touch: Fixed parameter */
+#define TS_V_MAX		(1 << DATA_SHIFT_BITS)	/* Full scale AD */
+/* Dual touch: Screen dependent parameters */
+#define TS_RX			694	/* X-plane resister */
+#define TS_RY			228	/* Y-plane resister */
+#define TS_V			10800	/* Max AD (LeftUpper corner) */
+#define TS_COEF_MIN		((u32)(0.100f * (1 << TS_PREC_BITS)))
+#define TS_COEF_MAX		((u32)(100.0f * (1 << TS_PREC_BITS)))
+#define TS_COEF_DEFAULT		((u32)(2.000f * (1 << TS_PREC_BITS)))
+#define TS_RTOUCH_NORMAL	700	/* Normal touch resister */
+#define TS_RTOUCH_MIN		(70 << TS_PREC_BITS)	/* 70 Ohm */
+#define TS_RTOUCH_MAX		(1700 << TS_PREC_BITS)	/* 1700 Ohm */
+#define TS_RTOUCH_DUAL_UP	350	/* Upper bound of dual touch */
+#define TS_RTOUCH_SINGLE_LOW	450	/* Lower bound of single touch */
+#define TS_SINGLE_MAXGAP_X	100	/* Max UAD of single touch point */
+#define TS_SINGLE_MAXGAP_Y	50	/* Max UBC of single touch point */
+
+/* Select AD samples to read (SEL bits in ADC_CONTROL1 register) */
+#define SIRFSOC_TS_SEL_X	0x01	/* x sample */
+#define SIRFSOC_TS_SEL_Y	0x02	/* y sample */
+#define SIRFSOC_TS_SEL_DUAL	0x0F	/* eight samples for dual touch */
+#define SIRFSOC_TS_CTL1(sel)	(ADC_POLL | ADC_SEL(sel) | ADC_DEL_SET(6) \
+		| ADC_FREQ_6K | ADC_TP_TIME(0) | ADC_SGAIN(0) \
+		| ADC_EXTCM(0) | ADC_RBAT_DISABLE | ADC_MORE_CTL1)
+
+/* AD sample indexes (single touch) */
+enum {
+	X,
+	Y,
+	AD_SAMPLE_COUNT_SINGLE
+};
+
+/* AD sample indexes (dual touch) */
+enum {
+	XPXN_YP,
+	YPYN_XP,
+	XPXN_YN,
+	YPYN_XN,
+	XPYN_YP,
+	XPYN_XN,
+	YPXN_XP,
+	YPXN_YN,
+	AD_SAMPLE_COUNT_DUAL
+};
+
+#define AD_REG_COUNT		(AD_SAMPLE_COUNT_DUAL / 2)
+
+struct sirfsoc_ts {
+	char			phys[32];
+
+	/* AD samples buffer of current detection */
+	u32			samples[AD_SAMPLE_COUNT_DUAL];
+
+	/* Calculated coordinates of current detection */
+	int			sampled_x[2], sampled_y[2];
+	/* Last successfully calculated and issued coordinates */
+	int			issued_x[2], issued_y[2];
+	bool			press_down;
+
+	/* Fingers detected pressing on the screen
+	 * always 1 for single touch driver
+	 * maybe 1 or 2 for dual touch driver, depends on calculation
+	 */
+	int			fingers;
+
+	/* Last touching event is a dual touch */
+	bool			last_is_dual;
+
+	struct input_dev	*input;
+
+	/* Points to touch specific data */
+	const struct sirfsoc_ts_of_data_touch	*touch;
+
+	/* ADC data channel for ts*/
+	struct iio_channel	*chan;
+};
+
+/* Single/dual touch specific data and routines */
+struct sirfsoc_ts_of_data_touch {
+	/* Number of continuous stable samples for debouncing */
+	int	debounce_rep;
+	/* Max deviation of AD values for stable samples */
+	int	debounce_dev;
+	/* Callback routine to read and calculate coordinates */
+	int	(*get_coord_and_pen)(struct sirfsoc_ts *);
+	/* Callback routine to read AD samples */
+	int	(*read_samples)(struct sirfsoc_ts *, u32 *);
+};
+
+/*
+ * Debounce routine shared by single and dual touch
+ * sample_count: how many AD samples needs to take care
+ */
+static int sirfsoc_ts_debounce(struct sirfsoc_ts *ts, int sample_count)
+{
+	int i, j;
+	u32 samples[AD_SAMPLE_COUNT_DUAL];	/* Max of single/dual samples */
+
+	/* First sample */
+	if ((*(ts->touch->read_samples))(ts, ts->samples))
+		return -EBUSY;
+
+	/* Debouncing
+	 * - Condition for a stable reading: Three (debounce_rep) continuous
+	 *   reading with AD samples deviation in bound (debounce_dev)
+	 * - ts->samples[] stores sum of previous readings
+	 * - samples[] stored current reading
+	 */
+	for (i = 1; i < ts->touch->debounce_rep; i++) {
+		/* Get raw AD samples */
+		if ((*(ts->touch->read_samples))(ts, samples))
+			return -EBUSY;
+		/* Verify adjacent samples deviation */
+		for (j = 0; j < sample_count; j++) {
+			if (abs(ts->samples[j] / i - samples[j]) >
+					ts->touch->debounce_dev)
+				return -EINVAL;
+			ts->samples[j] += samples[j];
+		}
+	}
+
+	/* Average samples, store to ts->samples */
+	for (i = 0; i < ts->touch->debounce_rep; i++)
+		ts->samples[i] /= ts->touch->debounce_rep;
+
+	return 0;
+}
+
+static int sirfsoc_ts_read_samples_single(struct sirfsoc_ts *ts, u32 *samples)
+{
+	int raw_sample;
+	int ret;
+
+	ret = iio_read_channel_raw(ts->chan, &raw_sample);
+	if (ret < 0) {
+		ts->press_down = false;
+		return ret;
+	}
+
+	/* x sample */
+	samples[X] = GETX(raw_sample);
+	/* y sample */
+	samples[Y] = GETY(raw_sample);
+
+	ts->press_down = true;
+	return 0;
+}
+
+/*
+ * Calculate single touch coordinate
+ * coord: (ts->sampled_x[0], y[0])
+ */
+static int sirfsoc_ts_get_coord_and_pen_single(struct sirfsoc_ts *ts)
+{
+	int ret;
+
+	ret = sirfsoc_ts_debounce(ts, AD_SAMPLE_COUNT_SINGLE);
+	if (ret < 0)
+		return ret;
+
+	ts->sampled_x[0] = ts->samples[X];
+	ts->sampled_y[0] = ts->samples[Y];
+
+	return 0;
+}
+
+/* Read eight AD samples from adc */
+static int sirfsoc_ts_read_samples_dual(struct sirfsoc_ts *ts, u32 *samples)
+{
+	int i, ret;
+	int raw_samples[AD_REG_COUNT];
+
+	ret = iio_read_channel_raw(ts->chan, raw_samples);
+	if (ret < 0) {
+		ts->press_down = false;
+		return ret;
+	}
+
+	for (i = 0; i < AD_REG_COUNT; i++) {
+		*samples++ = GETX(raw_samples[i]);
+		*samples++ = GETY(raw_samples[i]);
+	}
+
+	ts->press_down = true;
+	return 0;
+}
+
+/*
+ * Calculate dual touch coordinates
+ *
+ * - Schematic
+ *
+ *             RX1     A      RX2     D      RX3
+ *    XP ----/\/\/\----o----/\/\/\----o----/\/\/\---- XN
+ *                     |              |
+ *                     /              /
+ *              Rtouch \       Rtouch \
+ *                     /              /
+ *                     \              \
+ *                     |              |
+ *    YP ----/\/\/\----o----/\/\/\----o----/\/\/\---- YN
+ *             RY1     B      RY2     C      RY3
+ *
+ * coord1: (ts->sampled_x[0], y[0]); coord2: (ts->sampled_x[1], y[1])
+ */
+static int sirfsoc_ts_calculate_dual(struct sirfsoc_ts *ts)
+{
+	u32 tmp;
+	int ubc, uad;		/* |UB-UC|, |UA-UD| */
+	u32 x, y, rx2, ry2;	/* Intermediate results */
+	u64 a64, b64, c64;	/* Equation coefficients */
+	u32 rtouch;		/* Touch resister between X & Y planes */
+	bool neg_slope;		/* Slope of the line connecting dual points,
+				   negative if LeftUpper and RightDown */
+	u32 *samples = ts->samples;
+
+	/*
+	 * Step 1: Calculate Rtouch
+	 *
+	 * Rtouch depends on pressure and single/dual touch,
+	 * Dual touch halves the value approximately.
+	 * Two equivalent approaches:
+	 * 1. TS_RY * ypyn_xp * (xpyn_xn/xpyn_yp - 1) / TS_V
+	 * 2. TS_RX * xpxn_yp * (ypxn_yn/ypxn_xp - 1) / TS_V
+	 *
+	 * CAUTION:
+	 * rtouch is multiplied by 1024 to keep enough precision bits
+	 */
+	a64 = TS_RY * samples[YPYN_XP];
+	a64 <<= TS_PREC_BITS;	/* *1024 */
+	do_div(a64, TS_V);
+	b64 = a64 * samples[XPYN_XN];
+	do_div(b64, samples[XPYN_YP]);
+	rtouch = (u32)(b64 - a64);
+	if (rtouch < TS_RTOUCH_MIN || rtouch > TS_RTOUCH_MAX)
+		return -EINVAL;	/* Unstable reading */
+
+	/*
+	 * Step2: Check single touch
+	 *
+	 * As single touch is the dominant case, it should be handled first.
+	 * Chance is prominent that we may skip all dual touch related messes.
+	 */
+	ubc = samples[XPXN_YP] - samples[XPXN_YN];
+	uad = samples[YPYN_XP] - samples[YPYN_XN];
+	neg_slope = (ubc < 0);
+	ubc = abs(ubc);
+
+	/* UA-UD should follow the same sign as UB-UC */
+	if ((uad < 0) != neg_slope)
+		goto single_touch;
+	uad = abs(uad);
+	/* Very closed samples mean single touch or vertical/horizontal */
+	if (ubc <= TS_DUAL_MIN || uad <= TS_DUAL_MIN)
+		goto single_touch;
+	/* Fast scratching on the screen may cause misdetection */
+	if (rtouch > (TS_RTOUCH_SINGLE_LOW<<TS_PREC_BITS))
+		goto single_touch;
+
+	/*
+	 * Step 3: Calculate intermediate value x (slope)
+	 *
+	 * CAUTION:
+	 * x is multiplied by 1024 to keep enough precision bits
+	 */
+	x = TS_COEF_DEFAULT;	/* Fixed slope at about +/-45 degree */
+
+	/*
+	 * Step4: Calculate RX2 & RY2
+	 *
+	 * Solve equation a*y^2 - b*y - c = 0, where:
+	 * a = |UA-UD| + x * TS_V
+	 * b = (1+x) * |UA-UD| * TS_RY
+	 * c = 2 * |UA-UD| * TS_RY * RTouch
+	 *
+	 * CAUTION:
+	 * RX2,RY2 is multiplied by 256 to keep enough precision bits
+	 */
+	a64 = x;
+	a64 *= TS_V;
+	a64 >>= TS_PREC_BITS;
+	a64 += uad;
+	b64 = uad;
+	b64 *= TS_RY;
+	b64 *= ((1<<(TS_PREC_BITS-1)) + (x>>1));	/* *512 */
+	c64 = 2 * uad;
+	c64 *= TS_RY;
+	c64 *= TS_RTOUCH_NORMAL;	/* Fixed RTouch */
+
+	/* a: normal; b: *512; c: normal */
+	do_div(b64, a64);	/* (b/2a)*1024 */
+	do_div(c64, a64);	/* (c/a) */
+
+	c64 <<= (2*TS_PREC_BITS);
+	a64 = int64_sqrt(b64*b64 + c64);
+	a64 += b64;
+	a64 >>= 2;		/* *256 */
+	y = (u32)a64;
+
+	ry2 = y;
+	rx2 = (x * y) >> TS_PREC_BITS;
+	/* rx2, ry2: Left shifted 8 bits */
+
+	/* Step5: Calculate coordinates
+	 *
+	 * center: x0 = (UB+UC)/2, y0 = (UA+UD)/2
+	 * delta : dx = 0.5 * TS_V * RX2 / TS_RX
+	 *         dy = 0.5 * TS_V * RY2 / TS_RY
+	 * -------------------------
+	 * |points  |x     | y     |
+	 * -------------------------
+	 * |(x1,y1) |x0-dx | y0-dy |
+	 * |(x2,y2) |x0+dx | y0+dy |
+	 * -------------------------
+	 */
+
+	/* x1, x2 */
+	x = (samples[XPXN_YP] + samples[XPXN_YN]) << (TS_PREC_BITS-2);
+	tmp = (TS_V * rx2) / TS_RX;
+	/* x = x0 * 512, tmp = dx * 512 */
+	if (unlikely(x <= tmp))
+		ts->sampled_x[0] = 1;
+	else
+		ts->sampled_x[0] = (x - tmp) >> (TS_PREC_BITS-1);
+	ts->sampled_x[1] = (x + tmp) >> (TS_PREC_BITS-1);
+
+	/* y1, y2 */
+	y = (samples[YPYN_XP] + samples[YPYN_XN]) << (TS_PREC_BITS-2);
+	tmp = (TS_V * ry2) / TS_RY;
+	/* y = y0 * 512, tmp = dy * 512 */
+	if (unlikely(y <= tmp))
+		ts->sampled_y[0] = 1;
+	else
+		ts->sampled_y[0] = (y - tmp) >> (TS_PREC_BITS-1);
+	ts->sampled_y[1] = (y + tmp) >> (TS_PREC_BITS-1);
+
+	/* Verify data */
+	if (unlikely(ts->sampled_x[0] > ts->sampled_x[1] ||
+		     ts->sampled_y[0] > ts->sampled_y[1] ||
+		     ts->sampled_x[1] >= TS_V_MAX ||
+		     ts->sampled_y[1] >= TS_V_MAX))
+		return -EINVAL;	/* Illegal data */
+	if (ts->sampled_x[1] >= TS_V)
+		ts->sampled_x[1] = TS_V - 1;
+	if (ts->sampled_y[1] >= TS_V)
+		ts->sampled_y[1] = TS_V - 1;
+
+	/* Swap x coordinates if negative slope */
+	if (neg_slope) {
+		tmp = ts->sampled_x[0];
+		ts->sampled_x[0] = ts->sampled_x[1];
+		ts->sampled_x[1] = tmp;
+	}
+
+	/* Dual touch detected */
+	ts->fingers = 2;
+	return 0;
+
+	/* Fall back to single touch */
+single_touch:
+	/*
+	 * Make sure it's not a misdetected dual touch:
+	 * - Touch resister must be above upper bound of dual touch
+	 * - |UB-UC| and |UA-UD| are below upper bound of single touch
+	 */
+	if (rtouch >= (TS_RTOUCH_DUAL_UP<<TS_PREC_BITS)
+			&& ubc <= TS_SINGLE_MAXGAP_Y
+			&& uad <= TS_SINGLE_MAXGAP_X) {
+		/* Get single touch coordinate */
+		ts->sampled_x[0] = (samples[XPXN_YP] + samples[XPXN_YN]) / 2;
+		ts->sampled_y[0] = (samples[YPYN_XP] + samples[YPYN_XN]) / 2;
+		/* Single touch detected */
+		ts->fingers = 1;
+		return 0;
+	} else {
+		return -EINVAL;	/* Drop uncertainty */
+	}
+}
+
+static int sirfsoc_ts_get_coord_and_pen_dual(struct sirfsoc_ts *ts)
+{
+	int ret;
+	bool is_dual;
+
+	/* Read samples and do debouncing */
+	ret = sirfsoc_ts_debounce(ts, AD_SAMPLE_COUNT_DUAL);
+	if (ret < 0)
+		return ret;
+
+	/* Calculate coordinates */
+	ret = sirfsoc_ts_calculate_dual(ts);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * First switching from single to dual or dual to single are
+	 * sometimes unstable, drop it.
+	 */
+	is_dual = (ts->fingers == 2);	/* Current is dual touch? */
+	if (is_dual != ts->last_is_dual) {
+		ts->last_is_dual = is_dual;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Report touch events to event driver */
+static void sirfsoc_ts_report_coord(struct sirfsoc_ts *ts)
+{
+	int i;
+
+	input_report_abs(ts->input, ABS_PRESSURE, 1);
+	input_report_key(ts->input, BTN_TOUCH, 1);
+
+	for (i = 0; i < ts->fingers; i++) {
+		/* Filter small glitches */
+		if (abs(ts->sampled_x[i] - ts->issued_x[i]) < TS_GLITCH_GAP &&
+		    abs(ts->sampled_y[i] - ts->issued_y[i]) < TS_GLITCH_GAP) {
+			/*
+			 * New coord is very close to last checking,
+			 * adopt old coord instead
+			 */
+			ts->sampled_x[i] = ts->issued_x[i];
+			ts->sampled_y[i] = ts->issued_y[i];
+		} else {
+			/* Save new coord for next time comparison */
+			ts->issued_x[i] = ts->sampled_x[i];
+			ts->issued_y[i] = ts->sampled_y[i];
+		}
+
+		input_report_abs(ts->input, ABS_MT_POSITION_X,
+				ts->sampled_x[i]);
+		input_report_abs(ts->input, ABS_MT_POSITION_Y,
+				ts->sampled_y[i]);
+		input_mt_sync(ts->input);
+	}
+
+	input_sync(ts->input);
+}
+
+static irqreturn_t sirfsoc_ts_thread_irq(int irq, void *handle)
+{
+	struct sirfsoc_ts *ts = (struct sirfsoc_ts *)handle;
+	struct input_dev *input = ts->input;
+	int ret;
+
+	/*
+	 * we have pen down interrupt, but before pen up, no more will come
+	 */
+	do {
+		ret = (*(ts->touch->get_coord_and_pen))(ts);
+		if (!ret)
+			sirfsoc_ts_report_coord(ts);
+
+		usleep_range(5000, 8000);
+	} while (ts->press_down);
+
+	input_report_key(input, BTN_TOUCH, 0);
+	input_report_abs(input, ABS_PRESSURE, 0);
+	input_sync(input);
+
+	return IRQ_HANDLED;
+}
+
+static const struct sirfsoc_ts_of_data_touch sirfsoc_ts_of_data_single = {
+	.debounce_rep		= 3,
+	.debounce_dev		= 200,
+	.get_coord_and_pen	= sirfsoc_ts_get_coord_and_pen_single,
+	.read_samples		= sirfsoc_ts_read_samples_single,
+};
+
+static const struct sirfsoc_ts_of_data_touch sirfsoc_ts_of_data_dual = {
+	.debounce_rep		= 4,
+	.debounce_dev		= 500,
+	.get_coord_and_pen	= sirfsoc_ts_get_coord_and_pen_dual,
+	.read_samples		= sirfsoc_ts_read_samples_dual,
+};
+
+static const struct of_device_id sirfsoc_ts_of_match[] = {
+	{ .compatible = "sirf,prima2-tsc",
+	  .data = &sirfsoc_ts_of_data_single },
+	{ .compatible = "sirf,dualtouch-tsc",
+	  .data = &sirfsoc_ts_of_data_dual },
+	{}
+};
+
+static int sirfsoc_ts_probe(struct platform_device *pdev)
+{
+	struct input_dev		*input_dev;
+	struct sirfsoc_ts		*ts;
+	int				ret;
+	int				i;
+	int				irq;
+	const struct of_device_id	*match;
+
+	const unsigned int codes[] = {
+		KEY_HOME, KEY_MENU, KEY_BACK, KEY_SEARCH,
+	};
+
+	ts = devm_kzalloc(&pdev->dev, sizeof(struct sirfsoc_ts), GFP_KERNEL);
+	if (!ts) {
+		dev_err(&pdev->dev,
+			"sirfsoc ts: Cant allocate driver private data\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, ts);
+
+	ts->chan = iio_channel_get(&pdev->dev, NULL);
+	if (IS_ERR(ts->chan)) {
+		dev_err(&pdev->dev, "sirfsoc ts: Unable to get the adc channel\n");
+		ret = PTR_ERR(ts->chan);
+		goto out0;
+	}
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		dev_err(&pdev->dev,
+			"sirfsoc ts: Unable to allocate input device\n");
+		ret = -ENOMEM;
+		goto out1;
+	}
+
+	snprintf(ts->phys, sizeof("SIRFSOC-TS"), "SIRFSOC-TS");
+	input_dev->name = "sirfsoc_touchscreen";
+	input_dev->phys = ts->phys;
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS)
+				| BIT_MASK(EV_SYN);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+
+	input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1, 0, 0);
+	input_set_abs_params(input_dev, ABS_TOOL_WIDTH, 0, 15, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 1, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0);
+
+	for (i = 0; i < ARRAY_SIZE(codes); i++)
+		input_set_capability(input_dev, EV_KEY, codes[i]);
+
+	ret = input_register_device(input_dev);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"sirfsoc ts: Unable to register input device\n");
+		goto out2;
+	}
+	ts->input = input_dev;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "sirfsoc tsc: get irq failed!\n");
+		ret = -ENOMEM;
+		goto out3;
+	}
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+		sirfsoc_ts_thread_irq, IRQF_ONESHOT, DRIVER_NAME, ts);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "sirfsoc ts: regist irq handler failed!\n");
+		ret = -ENODEV;
+		goto out3;
+	}
+
+	input_set_abs_params(input_dev, ABS_X, 0, 0x3FFF, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, 0, 0x3FFF, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 0x3FFF, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 0x3FFF, 0, 0);
+
+	/* Default to single touch */
+	ts->fingers = 1;
+
+	/* Touch specific data */
+	match = of_match_device(of_match_ptr(sirfsoc_ts_of_match), &pdev->dev);
+	ts->touch = match->data;
+
+	return 0;
+out3:
+	input_unregister_device(input_dev);
+out2:
+	input_free_device(input_dev);
+out1:
+	iio_channel_release(ts->chan);
+out0:
+	return ret;
+}
+
+static int sirfsoc_ts_remove(struct platform_device *pdev)
+{
+	struct sirfsoc_ts *ts = platform_get_drvdata(pdev);
+
+	input_unregister_device(ts->input);
+	iio_channel_release(ts->chan);
+
+	return 0;
+}
+
+static void sirfsoc_ts_shutdown(struct platform_device *dev)
+{
+	sirfsoc_ts_remove(dev);
+}
+
+static struct platform_driver tsc_sirfsoc_driver = {
+	.driver	 = {
+		.name   = DRIVER_NAME,
+		.of_match_table = sirfsoc_ts_of_match,
+	},
+	.probe	  = sirfsoc_ts_probe,
+	.remove	 = sirfsoc_ts_remove,
+	.shutdown       = sirfsoc_ts_shutdown,
+};
+
+module_platform_driver(tsc_sirfsoc_driver);
+
+MODULE_AUTHOR("Guoying Zhang <Guoying.Zhang@csr.com>");
+MODULE_AUTHOR("Yibo Cai <Yibo.Cai@csr.com>");
+MODULE_AUTHOR("Sober Song <Zhiwu.Song@csr.com>");
+MODULE_DESCRIPTION("SiRF SoC On-chip Touch screen driver");
+MODULE_LICENSE("GPL v2");