From 174882a675bf36b766f697958813ff0f8028a6ee Mon Sep 17 00:00:00 2001
From: Simon Thum <simon.thum@gmx.de>
Date: Sun, 6 Feb 2011 17:57:17 +0100
Subject: [PATCH] Add hysteresis-based noise reduction

This introduces hysteresis into the driver's processing. It significantly
reduces noise motion, i.e. now the pad does no longer generate a stream of
sub-pixel events when just holding the position with the finger down.
Also, taking off the finger no longer generates additional motion,
scrolling becomes flicker-free etc.

The code makes use of "fuzz" from the kernel, if available. This has not
been tested extensively, as an overwhelming majority of evdev touchpad
drivers view 0 (zero) as a good value for fuzz, forcing userland into
assuming "zero fuzz" means "make zero assumptions about fuzz", not
"there is no fuzz". Until things improve, this is what we do.

Anyway, the fuzz a.k.a. hysteresis can be set/overridden with options
and properties, as documented.

Signed-off-by: Simon Thum <simon.thum@gmx.de>
---
 include/synaptics-properties.h |    3 ++
 man/synaptics.man              |   18 +++++++++++++
 src/eventcomm.c                |    9 ++++++
 src/properties.c               |   17 ++++++++++++
 src/synaptics.c                |   55 ++++++++++++++++++++++++++++++++++------
 src/synapticsstr.h             |    3 ++
 6 files changed, 97 insertions(+), 8 deletions(-)

diff --git a/include/synaptics-properties.h b/include/synaptics-properties.h
index 9c6a2ee..bdb2112 100644
--- a/include/synaptics-properties.h
+++ b/include/synaptics-properties.h
@@ -155,4 +155,7 @@
 /* 32 bit, 4 values, left, right, top, bottom */
 #define SYNAPTICS_PROP_AREA "Synaptics Area"
 
+/* 32 Bit Integer, 2 values, horizontal hysteresis, vertical hysteresis */
+#define SYNAPTICS_PROP_NOISE_CANCELLATION "Synaptics Noise Cancellation"
+
 #endif /* _SYNAPTICS_PROPERTIES_H_ */
diff --git a/man/synaptics.man b/man/synaptics.man
index 3f1ca9d..16ae7f6 100644
--- a/man/synaptics.man
+++ b/man/synaptics.man
@@ -222,6 +222,17 @@ Motion Factor"
 Greatest setting for pressure motion factor. Property: "Synaptics Pressure
 Motion Factor"
 .TP
+.BI "Option \*qHorizHysteresis\*q \*q" integer \*q
+The minimum horizontal HW distance required to generate motion events. Can be
+specified as a percentage. Increase if noise motion is a problem for you. Zero
+is disabled.
+Default: 0.5 percent of the diagonal or (in case of evdev) the appropriate
+"fuzz" as advertised by the device.
+.TP
+.BI "Option \*qVertHysteresis\*q \*q" integer \*q
+The minimum vertical HW distance required to generate motion events. See
+\fBHorizHysteresis\fR.
+.TP
 .BI "Option \*qUpDownScrolling\*q \*q" boolean \*q
 If on, the up/down buttons generate button 4/5 events.
 .
@@ -707,6 +718,13 @@ scrolling to circular scrolling.  That is, if CornerCoasting is
 active, scrolling will stop, and circular scrolling will not start,
 when the finger leaves the corner.
 
+.SS Noise cancellation
+The synaptics has a built-in nose canellation based on hysteresis. This means
+that incoming coordinates actually shift a box of predefined dimensions such
+that it covers the incoming coordinate, and only the boxes own center is used
+as input. Obviously, the smaller the box the better, but the likelyhood of
+noise motion coming through also increases.
+
 .SS Trackstick mode
 Trackstick emulation mode is entered when pressing the finger hard on
 the touchpad.
diff --git a/src/eventcomm.c b/src/eventcomm.c
index 4593bba..9357a26 100644
--- a/src/eventcomm.c
+++ b/src/eventcomm.c
@@ -184,6 +184,12 @@ event_query_axis_ranges(InputInfoPtr pInfo)
 		abs.minimum, abs.maximum);
 	priv->minx = abs.minimum;
 	priv->maxx = abs.maximum;
+	/*
+	 * The kernel's fuzziness concept seems a bit weird, but it can more or
+	 * less be applied as hysteresis directly, i.e. no factor here. Though,
+	/* we don't trust a zero fuzz as it probably is just a lazy value. */
+	if (abs.fuzz > 0)
+	    priv->synpara.hyst_x = abs.fuzz;
 #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)
 	priv->resx = abs.resolution;
 #endif
@@ -198,6 +204,9 @@ event_query_axis_ranges(InputInfoPtr pInfo)
 		abs.minimum, abs.maximum);
 	priv->miny = abs.minimum;
 	priv->maxy = abs.maximum;
+	/* don't trust a zero fuzz */
+	if (abs.fuzz > 0)
+	    priv->synpara.hyst_y = abs.fuzz;
 #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)
 	priv->resy = abs.resolution;
 #endif
diff --git a/src/properties.c b/src/properties.c
index 5400928..23b5a6a 100644
--- a/src/properties.c
+++ b/src/properties.c
@@ -82,6 +82,7 @@ Atom prop_gestures              = 0;
 Atom prop_capabilities          = 0;
 Atom prop_resolution            = 0;
 Atom prop_area                  = 0;
+Atom prop_noise_cancellation    = 0;
 
 static Atom
 InitAtom(DeviceIntPtr dev, char *name, int format, int nvalues, int *values)
@@ -278,6 +279,12 @@ InitDeviceProperties(InputInfoPtr pInfo)
     values[2] = para->area_top_edge;
     values[3] = para->area_bottom_edge;
     prop_area = InitAtom(pInfo->dev, SYNAPTICS_PROP_AREA, 32, 4, values);
+
+    values[0] = para->hyst_x;
+    values[1] = para->hyst_y;
+    prop_noise_cancellation = InitAtom(pInfo->dev,
+            SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 2, values);
+
 }
 
 int
@@ -649,6 +656,16 @@ SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
         para->area_right_edge  = area[1];
         para->area_top_edge    = area[2];
         para->area_bottom_edge = area[3];
+    } else if (property == prop_noise_cancellation) {
+        INT32 *hyst;
+        if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        hyst = (INT32*)prop->data;
+        if (hyst[0] < 0 || hyst[1] < 0)
+            return BadValue;
+        para->hyst_x = hyst[0];
+        para->hyst_y = hyst[1];
     }
 
     return Success;
diff --git a/src/synaptics.c b/src/synaptics.c
index 783bd64..d57a75a 100644
--- a/src/synaptics.c
+++ b/src/synaptics.c
@@ -387,18 +387,19 @@ calculate_edge_widths(SynapticsPrivate *priv, int *l, int *r, int *t, int *b)
  * the log message.
  */
 static int set_percent_option(pointer options, const char* optname,
-                              const int range, const int offset)
+                              const int range, const int offset,
+                              const int default_value)
 {
     int result;
 #if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 11
-    int percent = xf86CheckPercentOption(options, optname, -1);
+    double percent = xf86CheckPercentOption(options, optname, -1);
 
-    if (percent != -1) {
+    if (percent >= 0.0) {
         percent = xf86SetPercentOption(options, optname, -1);
         result = percent/100.0 * range + offset;
     } else
 #endif
-        result = xf86SetIntOption(options, optname, 0);
+        result = xf86SetIntOption(options, optname, default_value);
 
     return result;
 }
@@ -427,6 +428,7 @@ static void set_default_parameters(InputInfoPtr pInfo)
     int horizResolution = 1;
     int vertResolution = 1;
     int width, height, diag, range;
+    int horizHyst, vertHyst;
 
     /* read the parameters */
     if (priv->synshm)
@@ -458,6 +460,10 @@ static void set_default_parameters(InputInfoPtr pInfo)
     edgeMotionMaxSpeed = diag * .080;
     accelFactor = 200.0 / diag; /* trial-and-error */
 
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
     range = priv->maxp - priv->minp;
 
     /* scaling based on defaults and a pressure of 256 */
@@ -513,10 +519,13 @@ static void set_default_parameters(InputInfoPtr pInfo)
     pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
     pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
 
-    pars->area_top_edge = set_percent_option(opts, "AreaTopEdge", height, priv->miny);
-    pars->area_bottom_edge = set_percent_option(opts, "AreaBottomEdge", height, priv->miny);
-    pars->area_left_edge = set_percent_option(opts, "AreaLeftEdge", width, priv->minx);
-    pars->area_right_edge = set_percent_option(opts, "AreaRightEdge", width, priv->minx);
+    pars->area_top_edge = set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge = set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge = set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge = set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x = set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y = set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
 
     pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
     pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
@@ -722,6 +731,8 @@ SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
     priv->tap_button = 0;
     priv->tap_button_state = TBS_BUTTON_UP;
     priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
 
     /* read hardware dimensions */
     ReadDevDimensions(pInfo);
@@ -1715,6 +1726,26 @@ estimate_delta(double x0, double x1, double x2, double x3)
     return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
 }
 
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int hysteresis(int in, int center, int margin) {
+    int diff = in - center;
+    if (abs(diff) <= margin) {
+	diff = 0;
+    } else if (diff > margin) {
+	diff -= margin;
+    } else if (diff < -margin) {
+	diff += margin;
+    }
+    return center + diff;
+}
+
 static int
 ComputeDeltas(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
 	      edge_type edge, int *dxP, int *dyP, Bool inside_area)
@@ -2366,6 +2397,14 @@ HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
     if (para->touchpad_off == 1)
 	return delay;
 
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    priv->hyst_center_x = hysteresis(hw->x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(hw->y, priv->hyst_center_y, para->hyst_y);
+    hw->x = priv->hyst_center_x;
+    hw->y = priv->hyst_center_y;
+
     inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
 
     /* now we know that these _coordinates_ aren't in the area.
diff --git a/src/synapticsstr.h b/src/synapticsstr.h
index 9ad8638..066b3f3 100644
--- a/src/synapticsstr.h
+++ b/src/synapticsstr.h
@@ -160,6 +160,7 @@ typedef struct _SynapticsParameters
     unsigned int resolution_horiz;          /* horizontal resolution of touchpad in units/mm */
     unsigned int resolution_vert;           /* vertical resolution of touchpad in units/mm */
     int area_left_edge, area_right_edge, area_top_edge, area_bottom_edge; /* area coordinates absolute */
+    int hyst_x, hyst_y;                     /* x and y width of hysteresis box */
 } SynapticsParameters;
 
 
@@ -183,6 +184,8 @@ typedef struct _SynapticsPrivateRec
     Bool absolute_events;               /* post absolute motion events instead of relative */
     SynapticsMoveHistRec move_hist[SYNAPTICS_MOVE_HISTORY]; /* movement history */
     int hist_index;			/* Last added entry in move_hist[] */
+    int hyst_center_x;			/* center x of hysteresis*/
+    int hyst_center_y;			/* center y of hysteresis*/
     int scroll_y;			/* last y-scroll position */
     int scroll_x;			/* last x-scroll position */
     double scroll_a;			/* last angle-scroll position */
-- 
1.7.3.4
