[PATCH 3/6] [TSC2301] Fix touchscreen jitter

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Excecutice summery:
- faster response time (improved virtual kb typing)
- less lag (your doodles follow the pen in Sketch)
- much less jitter (nicer doodles, and no jerky panning)

Reading the touch-pad converters asynchronously to DAV interrupt
results in even more subtle errors. The chip works by measuring
each channel multiple times and the average of those values is
stored in the corresponding register before the chip moves on
to measure the next channel.

If you are not synchronized to the DAV interrupt - then instead of
drawing diagonal lines in Sketch you will draw "staircases".
Given enough filtering you can straighten them - but surely that
is not optimal.

To get good touchscreen results filtering are essential. Use the
HW-filter to remove high frequency noise (from backlight).
Using 8 sample average is very efficient. Another source of noise
is the screen, but that generates very low freq noise and thus
require a SW filter. Using 4 coordinate average is efficient.

Signed-off-by: Klaus Pedersen <klaus.k.pedersen@xxxxxxxxx>
---
 arch/arm/mach-omap2/board-n800.c       |    6 +-
 drivers/input/touchscreen/tsc2301_ts.c |  265 +++++++++++++++-----------------
 include/linux/spi/tsc2301.h            |    1 -
 3 files changed, 125 insertions(+), 147 deletions(-)

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 40136d9..41550cd 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -330,8 +330,7 @@ static void __init n800_ts_set_config(void)
 	if (conf != NULL) {
 		if (strcmp(conf->panel_name, "lph8923") == 0) {
 			tsc2301_config.ts_x_plate_ohm	= 180;
-			tsc2301_config.ts_hw_avg	= 4;
-			tsc2301_config.ts_ignore_last	= 1;
+			tsc2301_config.ts_hw_avg	= 8;
 			tsc2301_config.ts_touch_pressure = 400;
 			tsc2301_config.ts_stab_time	= 100;
 			tsc2301_config.ts_pressure_max	= 2048;
@@ -342,8 +341,7 @@ static void __init n800_ts_set_config(void)
 			tsc2301_config.ts_y_fudge	= 7;
 		} else if (strcmp(conf->panel_name, "ls041y3") == 0) {
 			tsc2301_config.ts_x_plate_ohm	= 280;
-			tsc2301_config.ts_hw_avg	= 16;
-			tsc2301_config.ts_ignore_last	= 1;
+			tsc2301_config.ts_hw_avg	= 8;
 			tsc2301_config.ts_touch_pressure = 400;
 			tsc2301_config.ts_stab_time	= 1000;
 			tsc2301_config.ts_pressure_max	= 2048;
diff --git a/drivers/input/touchscreen/tsc2301_ts.c b/drivers/input/touchscreen/tsc2301_ts.c
index 27ff8ea..58fab66 100644
--- a/drivers/input/touchscreen/tsc2301_ts.c
+++ b/drivers/input/touchscreen/tsc2301_ts.c
@@ -1,7 +1,7 @@
 /*
  * TSC2301 touchscreen driver
  *
- * Copyright (C) 2005-2006 Nokia Corporation
+ * Copyright (C) 2005-2008 Nokia Corporation
  *
  * Written by Jarkko Oikarinen, Imre Deak and Juha Yrjola
  *
@@ -45,21 +45,18 @@
  *  2) TSC2301 performs AD conversion
  *  3) After the conversion is done TSC2301 drives DAV line down
  *  4) GPIO IRQ is received and tsc2301_ts_irq_handler is called
- *  5) tsc2301_ts_irq_handler sets up tsc2301_ts_timer in TSC2301_TS_SCAN_TIME
- *  6) tsc2301_ts_timer disables the irq and requests spi driver
- *     to read X, Y, Z1 and Z2
- *  7) SPI framework calls tsc2301_ts_rx after the coordinates are read
- *  8) tsc2301_ts_rx reports coordinates to input layer and
- *     sets up tsc2301_ts_timer to be called after TSC2301_TS_SCAN_TIME
- *  9) if tsc2301_tx_timer notices that the pen has been lifted, the lift event
- *     is sent, and irq is again enabled.
+ *  5) tsc2301_ts_irq_handler queues up an spi transfer to fetch
+ *     the x, y, z1, z2 values
+ *  6) SPI framework calls tsc2301_ts_rx after the coordinates are read
+ *  7) When the penup_timer expires, there have not been DAV interrupts
+ *     during the last 20ms which means the pen has been lifted.
  */
 
 
 #define TSC2301_TOUCHSCREEN_PRODUCT_ID      		0x0052
 #define TSC2301_TOUCHSCREEN_PRODUCT_VERSION 		0x0001
 
-#define TSC2301_TS_SCAN_TIME		     		1
+#define TSC2301_TS_PENUP_TIME		     		20
 
 #define TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301	0x8000
 #define TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST		0x0000
@@ -102,36 +99,40 @@
 #define TSC2301_ADCREG_STOP_CONVERSION			0x4000
 
 #define MAX_12BIT					((1 << 12) - 1)
+#define TS_SAMPLES					4
+#define TS_RECT_SIZE					8
 
 struct tsc2301_ts {
 	struct input_dev	*idev;
 	char			phys[32];
-	struct timer_list	timer;
-	spinlock_t		lock;
+	struct timer_list	penup_timer;
+	struct mutex		mutex;
 
 	struct spi_transfer	read_xfer[2];
 	struct spi_message	read_msg;
 	u16                     data[4];
 
+	int 			avg_x;
+	int 			avg_y;
+	int 			avg_z1;
+	int 			avg_z2;
+
 	int			hw_avg_max;
 	u16			x;
 	u16			y;
 	u16			p;
 	int			sample_cnt;
 
-	int			ignore_last : 1;
 	u16			x_plate_ohm;
 	int			stab_time;
 	int			p_max;
 	int			touch_pressure;
-	int			pressure_limit;
-
-	u16			irq_enabled:1;
-	u16			pen_down:1;
-	u16			disabled:1;
-	u16			pending:1;
 
 	int			hw_flags;
+	u8			sample_sent;
+	u8			pen_down;
+	u8			disabled;
+	u8			disable_depth;
 
 	s16			dav_gpio;
 	int			irq;
@@ -271,33 +272,24 @@ static void tsc2301_ts_stop_scan(struct tsc2301 *tsc)
 	tsc2301_write_reg(tsc, TSC2301_REG_ADC, TSC2301_ADCREG_STOP_CONVERSION);
 }
 
-static int device_suspended(struct device *dev)
-{
-	struct tsc2301 *tsc = dev_get_drvdata(dev);
-	return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled;
-}
-
 static void update_pen_state(struct tsc2301_ts *ts, int x, int y, int pressure)
 {
-	int sync = 0;
-
 	if (pressure) {
 		input_report_abs(ts->idev, ABS_X, x);
 		input_report_abs(ts->idev, ABS_Y, y);
 		input_report_abs(ts->idev, ABS_PRESSURE, pressure);
 		if (!ts->pen_down)
 			input_report_key(ts->idev, BTN_TOUCH, 1);
-		sync = 1;
-	} else if (ts->pen_down) {
+		ts->pen_down = 1;
+	} else {
 		input_report_abs(ts->idev, ABS_PRESSURE, 0);
-		input_report_key(ts->idev, BTN_TOUCH, 0);
-		sync = 1;
+		if (ts->pen_down)
+			input_report_key(ts->idev, BTN_TOUCH, 0);
+		ts->pen_down = 0;
 	}
 
-	if (sync)
-		input_sync(ts->idev);
+	input_sync(ts->idev);
 
-	ts->pen_down = pressure ? 1 : 0;
 #ifdef VERBOSE
 	dev_dbg(&tsc->spi->dev, "x %4d y %4d p %4d\n", x, y, pressure);
 #endif
@@ -311,151 +303,143 @@ static void tsc2301_ts_rx(void *arg)
 {
 	struct tsc2301 *tsc = arg;
 	struct tsc2301_ts *ts = tsc->ts;
-	unsigned int x, y, z1, z2, pressure;
+	int inside_rect, pressure_limit;
+	int x, y, z1, z2, pressure;
 
 	x  = ts->data[0];
 	y  = ts->data[1];
 	z1 = ts->data[2];
 	z2 = ts->data[3];
 
-	if (z1) {
-		pressure = ts->x_plate_ohm * x;
-		pressure /= 4096;
-		pressure *= z2 - z1;
-		pressure /= z1;
-	} else
-		pressure = 0;
-
-	/* If pressure value is above a preset limit (pen is barely
-	 * touching the screen) we can't trust the coordinate values.
-	 */
-	if (pressure < ts->pressure_limit && x < MAX_12BIT && y < MAX_12BIT) {
-		ts->pressure_limit = ts->p_max;
-		if (ts->ignore_last) {
-			if (ts->sample_cnt)
-				update_pen_state(ts, ts->x, ts->y, ts->p);
-			ts->x = x;
-			ts->y = y;
-			ts->p = pressure;
-		} else
-			update_pen_state(ts, x, y, pressure);
-		ts->sample_cnt++;
+	/* validate pressure and position */
+	if (x > MAX_12BIT || y > MAX_12BIT)
+		goto exit;
+
+	/* skip coords if the pressure-components are out of range */
+	if (z1 < 100 || z2 > 4000)
+		goto exit;
+
+	/* don't run average on the "pen down" event */
+	if (ts->sample_sent) {
+		ts->avg_x  += x;
+		ts->avg_y  += y;
+		ts->avg_z1 += z1;
+		ts->avg_z2 += z2;
+
+		if (++ts->sample_cnt < TS_SAMPLES)
+			goto exit;
+		x = ts->avg_x / TS_SAMPLES;
+		y = ts->avg_y / TS_SAMPLES;
+		z1 = ts->avg_z1 / TS_SAMPLES;
+		z2 = ts->avg_z2 / TS_SAMPLES;
 	}
 
-	mod_timer(&ts->timer,
-		  jiffies + msecs_to_jiffies(TSC2301_TS_SCAN_TIME));
-}
+	ts->sample_cnt = 0;
+	ts->avg_x  = 0;
+	ts->avg_y  = 0;
+	ts->avg_z1 = 0;
+	ts->avg_z2 = 0;
 
-static int is_pen_down(struct tsc2301_ts *ts)
-{
-	return ts->pen_down;
+	if (z1) {
+		pressure = x * (z2 - z1) / z1;
+		pressure = pressure * ts->x_plate_ohm / 4096;
+	} else
+		goto exit;
+
+	pressure_limit = ts->sample_sent? ts->p_max: ts->touch_pressure;
+	if (pressure > pressure_limit)
+		goto exit;
+
+	/* discard the event if it still is within the previous rect - unless
+	 * if the pressure is harder, but then use previous x,y position */
+	inside_rect = (ts->sample_sent &&
+	    x > (int)ts->x - TS_RECT_SIZE && x < (int)ts->x + TS_RECT_SIZE &&
+	    y > (int)ts->y - TS_RECT_SIZE && y < (int)ts->y + TS_RECT_SIZE);
+
+	if (inside_rect)
+		x = ts->x, y = ts->y;
+
+	if (!inside_rect || pressure < ts->p) {
+		update_pen_state(ts, x, y, pressure);
+		ts->sample_sent = 1;
+		ts->x  = x;
+		ts->y  = y;
+		ts->p  = pressure;
+	}
+exit:
+	/* kick pen up timer - to make sure it expires again(!) */
+	if (ts->sample_sent)
+		mod_timer(&ts->penup_timer,
+			  jiffies + msecs_to_jiffies(TSC2301_TS_PENUP_TIME));
 }
 
 /*
- * Timer is called every TSC2301_TS_SCAN_TIME when the pen is down
+ * Timer is called TSC2301_TS_PENUP_TIME after pen is up
  */
-static void tsc2301_ts_timer(unsigned long arg)
+static void tsc2301_ts_timer_handler(unsigned long data)
 {
-	struct tsc2301 *tsc = (void *) arg;
+	struct tsc2301 *tsc = (struct tsc2301 *)data;
 	struct tsc2301_ts *ts = tsc->ts;
-	unsigned long flags;
-	int ndav;
-	int r;
 
-	spin_lock_irqsave(&ts->lock, flags);
-	ndav = omap_get_gpio_datain(ts->dav_gpio);
-	if (ndav || device_suspended(&tsc->spi->dev)) {
-		/* Pen has been lifted */
-		if (!device_suspended(&tsc->spi->dev)) {
-			ts->irq_enabled = 1;
-			enable_irq(ts->irq);
-		}
+	if (ts->sample_sent) {
+		ts->sample_sent = 0;
 		update_pen_state(ts, 0, 0, 0);
-		ts->pending = 0;
-		spin_unlock_irqrestore(&ts->lock, flags);
-
-	} else {
-		ts->pen_down = 1;
-		spin_unlock_irqrestore(&ts->lock, flags);
-
-		r = spi_async(tsc->spi, &ts->read_msg);
-		if (r)
-			dev_err(&tsc->spi->dev, "ts: spi_async() failed");
 	}
 }
 
 /*
- * This interrupt is called when pen is down and first coordinates are
- * available. That is indicated by a falling edge on DEV line.  IRQ is
- * disabled here because while the pen is down the coordinates are
- * read by a timer.
+ * This interrupt is called when pen is down and coordinates are
+ * available. That is indicated by a falling edge on DEV line.
  */
 static irqreturn_t tsc2301_ts_irq_handler(int irq, void *dev_id)
 {
 	struct tsc2301 *tsc = dev_id;
 	struct tsc2301_ts *ts = tsc->ts;
-	unsigned long flags;
-
-	spin_lock_irqsave(&ts->lock, flags);
-	if (ts->irq_enabled) {
-		ts->irq_enabled = 0;
-		disable_irq(ts->irq);
-		ts->pending = 1;
-		ts->pressure_limit = ts->touch_pressure;
-		ts->sample_cnt = 0;
-		mod_timer(&ts->timer,
-			  jiffies + msecs_to_jiffies(TSC2301_TS_SCAN_TIME));
-	}
-	spin_unlock_irqrestore(&ts->lock, flags);
+	int r;
+
+	r = spi_async(tsc->spi, &ts->read_msg);
+	if (r)
+		dev_err(&tsc->spi->dev, "ts: spi_async() failed");
+
+	mod_timer(&ts->penup_timer,
+		  jiffies + msecs_to_jiffies(TSC2301_TS_PENUP_TIME));
 
 	return IRQ_HANDLED;
 }
 
-/* Must be called with ts->lock held */
 static void tsc2301_ts_disable(struct tsc2301 *tsc)
 {
 	struct tsc2301_ts *ts = tsc->ts;
 
-	if (ts->disabled)
+	if (ts->disable_depth++ != 0)
 		return;
 
-	ts->disabled = 1;
-	if (!ts->pending) {
-		ts->irq_enabled = 0;
-		disable_irq(ts->irq);
-	} else {
-		while (ts->pending) {
-			spin_unlock_irq(&ts->lock);
-			msleep(1);
-			spin_lock_irq(&ts->lock);
-		}
-	}
+	disable_irq(ts->irq);
+
+	/* wait until penup timer expire normally */
+	do {
+		msleep(4);
+	} while (ts->sample_sent);
 
-	spin_unlock_irq(&ts->lock);
 	tsc2301_ts_stop_scan(tsc);
 	/* Workaround a bug where turning on / off touchscreen scanner
 	 * can get the keypad scanner stuck.
 	 */
 	tsc2301_kp_restart(tsc);
-	spin_lock_irq(&ts->lock);
 }
 
 static void tsc2301_ts_enable(struct tsc2301 *tsc)
 {
 	struct tsc2301_ts *ts = tsc->ts;
 
-	if (!ts->disabled)
+	if (--ts->disable_depth != 0)
 		return;
 
-	ts->disabled = 0;
-	ts->irq_enabled = 1;
 	enable_irq(ts->irq);
 
-	spin_unlock_irq(&ts->lock);
 	tsc2301_ts_start_scan(tsc);
 	/* Same workaround as above. */
 	tsc2301_kp_restart(tsc);
-	spin_lock_irq(&ts->lock);
 }
 
 #ifdef CONFIG_PM
@@ -463,9 +447,9 @@ int tsc2301_ts_suspend(struct tsc2301 *tsc)
 {
 	struct tsc2301_ts *ts = tsc->ts;
 
-	spin_lock_irq(&ts->lock);
+	mutex_lock(&ts->mutex);
 	tsc2301_ts_disable(tsc);
-	spin_unlock_irq(&ts->lock);
+	mutex_unlock(&ts->mutex);
 
 	return 0;
 }
@@ -474,9 +458,9 @@ void tsc2301_ts_resume(struct tsc2301 *tsc)
 {
 	struct tsc2301_ts *ts = tsc->ts;
 
-	spin_lock_irq(&ts->lock);
+	mutex_lock(&ts->mutex);
 	tsc2301_ts_enable(tsc);
-	spin_unlock_irq(&ts->lock);
+	mutex_unlock(&ts->mutex);
 }
 #endif
 
@@ -507,7 +491,7 @@ static ssize_t tsc2301_ts_pen_down_show(struct device *dev,
 {
 	struct tsc2301 *tsc = dev_get_drvdata(dev);
 
-	return sprintf(buf, "%u\n", is_pen_down(tsc->ts));
+	return sprintf(buf, "%u\n", tsc->ts->pen_down);
 }
 
 static DEVICE_ATTR(pen_down, S_IRUGO, tsc2301_ts_pen_down_show, NULL);
@@ -531,15 +515,17 @@ static ssize_t tsc2301_ts_disable_store(struct device *dev,
 	int i;
 
 	i = simple_strtoul(buf, &endp, 10);
-	spin_lock_irq(&ts->lock);
+	i = i ? 1 : 0;
+	mutex_lock(&ts->mutex);
+	if (i == ts->disabled) goto out;
+	ts->disabled = i;
 
 	if (i)
 		tsc2301_ts_disable(tsc);
 	else
 		tsc2301_ts_enable(tsc);
-
-	spin_unlock_irq(&ts->lock);
-
+out:
+	mutex_unlock(&ts->mutex);
 	return count;
 }
 
@@ -576,18 +562,17 @@ int __devinit tsc2301_ts_init(struct tsc2301 *tsc,
 	omap_set_gpio_direction(dav_gpio, 1);
 	ts->irq = OMAP_GPIO_IRQ(dav_gpio);
 #endif
-	init_timer(&ts->timer);
-	ts->timer.data = (unsigned long) tsc;
-	ts->timer.function = tsc2301_ts_timer;
+	init_timer(&ts->penup_timer);
+	setup_timer(&ts->penup_timer, tsc2301_ts_timer_handler,
+			(unsigned long)tsc);
 
-	spin_lock_init(&ts->lock);
+	mutex_init(&ts->mutex);
 
 	ts->x_plate_ohm		= pdata->ts_x_plate_ohm ? : 280;
 	ts->hw_avg_max		= pdata->ts_hw_avg;
 	ts->stab_time		= pdata->ts_stab_time;
 	ts->p_max		= pdata->ts_pressure_max ? : MAX_12BIT;
 	ts->touch_pressure	= pdata->ts_touch_pressure ? : ts->p_max;
-	ts->ignore_last		= pdata->ts_ignore_last;
 
 	x_max		= pdata->ts_x_max ? : 4096;
 	y_max		= pdata->ts_y_max ? : 4096;
@@ -623,7 +608,6 @@ int __devinit tsc2301_ts_init(struct tsc2301 *tsc,
 
 	tsc2301_ts_start_scan(tsc);
 
-	ts->irq_enabled = 1;
 	r = request_irq(ts->irq, tsc2301_ts_irq_handler,
 			IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING,
 			"tsc2301-ts", tsc);
@@ -666,11 +650,8 @@ err1:
 void __devexit tsc2301_ts_exit(struct tsc2301 *tsc)
 {
 	struct tsc2301_ts *ts = tsc->ts;
-	unsigned long flags;
 
-	spin_lock_irqsave(&ts->lock, flags);
 	tsc2301_ts_disable(tsc);
-	spin_unlock_irqrestore(&ts->lock, flags);
 
 	device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts);
 	device_remove_file(&tsc->spi->dev, &dev_attr_pen_down);
diff --git a/include/linux/spi/tsc2301.h b/include/linux/spi/tsc2301.h
index 34dffc7..a29e947 100644
--- a/include/linux/spi/tsc2301.h
+++ b/include/linux/spi/tsc2301.h
@@ -27,7 +27,6 @@ struct tsc2301_platform_data {
 	u32	ts_touch_pressure;	/* Pressure limit until we report a
 					   touch event. After that we switch
 					   to ts_max_pressure. */
-	unsigned ts_ignore_last : 1;
 	u32	ts_pressure_fudge;
 	u32	ts_x_max;
 	u32	ts_x_fudge;
-- 
1.5.3.3

-
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux