Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 14 Oct 2008 17:48:36 +0000 (UTC)
From:      Jean-Sebastien Pedron <dumbbell@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r183888 - head/sys/dev/atkbdc
Message-ID:  <200810141748.m9EHma9d012354@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: dumbbell
Date: Tue Oct 14 17:48:36 2008
New Revision: 183888
URL: http://svn.freebsd.org/changeset/base/183888

Log:
  Rewrite Synaptics touchpads support with the following goals in mind:
      o  better quality of the movement smoothing
      o  more features such as tap-hold and virtual scrolling
  
  Support must still be enabled with this line in your /boot/loader.conf:
      hw.psm.synaptics_support="1"
  
  The following sysctls were removed:
      hw.psm.synaptics.low_speed_threshold
      hw.psm.synaptics.min_movement
      hw.psm.synaptics.squelch_level
  
  An overview of this new driver and a short documentation about the added
  sysctls is available on the wiki:
  http://wiki.freebsd.org/SynapticsTouchpad

Modified:
  head/sys/dev/atkbdc/psm.c

Modified: head/sys/dev/atkbdc/psm.c
==============================================================================
--- head/sys/dev/atkbdc/psm.c	Tue Oct 14 17:47:29 2008	(r183887)
+++ head/sys/dev/atkbdc/psm.c	Tue Oct 14 17:48:36 2008	(r183888)
@@ -163,15 +163,101 @@ typedef struct packetbuf {
 #define	PSM_PACKETQUEUE	128
 #endif
 
+enum {
+	SYNAPTICS_SYSCTL_MIN_PRESSURE,
+	SYNAPTICS_SYSCTL_MAX_PRESSURE,
+	SYNAPTICS_SYSCTL_MAX_WIDTH,
+	SYNAPTICS_SYSCTL_MARGIN_TOP,
+	SYNAPTICS_SYSCTL_MARGIN_RIGHT,
+	SYNAPTICS_SYSCTL_MARGIN_BOTTOM,
+	SYNAPTICS_SYSCTL_MARGIN_LEFT,
+	SYNAPTICS_SYSCTL_NA_TOP,
+	SYNAPTICS_SYSCTL_NA_RIGHT,
+	SYNAPTICS_SYSCTL_NA_BOTTOM,
+	SYNAPTICS_SYSCTL_NA_LEFT,
+	SYNAPTICS_SYSCTL_WINDOW_MIN,
+	SYNAPTICS_SYSCTL_WINDOW_MAX,
+	SYNAPTICS_SYSCTL_MULTIPLICATOR,
+	SYNAPTICS_SYSCTL_WEIGHT_CURRENT,
+	SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS,
+	SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA,
+	SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED,
+	SYNAPTICS_SYSCTL_DIV_MIN,
+	SYNAPTICS_SYSCTL_DIV_MAX,
+	SYNAPTICS_SYSCTL_DIV_MAX_NA,
+	SYNAPTICS_SYSCTL_DIV_LEN,
+	SYNAPTICS_SYSCTL_TAP_MAX_DELTA,
+	SYNAPTICS_SYSCTL_TAP_MIN_QUEUE,
+	SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT,
+	SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA,
+	SYNAPTICS_SYSCTL_VSCROLL_VER_AREA,
+	SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA,
+	SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN,
+	SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX
+};
+
 typedef struct synapticsinfo {
-	struct sysctl_ctx_list	sysctl_ctx;
+	struct sysctl_ctx_list	 sysctl_ctx;
 	struct sysctl_oid	*sysctl_tree;
-	int			directional_scrolls;
-	int			low_speed_threshold;
-	int			min_movement;
-	int			squelch_level;
+	int			 directional_scrolls;
+	int			 min_pressure;
+	int			 max_pressure;
+	int			 max_width;
+	int			 margin_top;
+	int			 margin_right;
+	int			 margin_bottom;
+	int			 margin_left;
+	int			 na_top;
+	int			 na_right;
+	int			 na_bottom;
+	int			 na_left;
+	int			 window_min;
+	int			 window_max;
+	int			 multiplicator;
+	int			 weight_current;
+	int			 weight_previous;
+	int			 weight_previous_na;
+	int			 weight_len_squared;
+	int			 div_min;
+	int			 div_max;
+	int			 div_max_na;
+	int			 div_len;
+	int			 tap_max_delta;
+	int			 tap_min_queue;
+	int			 taphold_timeout;
+	int			 vscroll_ver_area;
+	int			 vscroll_hor_area;
+	int			 vscroll_min_delta;
+	int			 vscroll_div_min;
+	int			 vscroll_div_max;
 } synapticsinfo_t;
 
+typedef struct synapticspacket {
+	int			x;
+	int			y;
+} synapticspacket_t;
+
+#define	SYNAPTICS_PACKETQUEUE 10
+#define SYNAPTICS_QUEUE_CURSOR(x)					\
+	(x + SYNAPTICS_PACKETQUEUE) % SYNAPTICS_PACKETQUEUE
+
+typedef struct synapticsaction {
+	synapticspacket_t	queue[SYNAPTICS_PACKETQUEUE];
+	int			queue_len;
+	int			queue_cursor;
+	int			window_min;
+	int			start_x;
+	int			start_y;
+	int			avg_dx;
+	int			avg_dy;
+	int			squelch_x;
+	int			squelch_y;
+	int			fingers_nb;
+	int			tap_button;
+	int			in_taphold;
+	int			in_vscroll;
+} synapticsaction_t;
+
 /* driver control block */
 struct psm_softc {		/* Driver status information */
 	int		unit;
@@ -185,6 +271,7 @@ struct psm_softc {		/* Driver status inf
 	mousehw_t	hw;		/* hardware information */
 	synapticshw_t	synhw;		/* Synaptics hardware information */
 	synapticsinfo_t	syninfo;	/* Synaptics configuration */
+	synapticsaction_t synaction;	/* Synaptics action context */
 	mousemode_t	mode;		/* operation mode */
 	mousemode_t	dflt_mode;	/* default operation mode */
 	mousestatus_t	status;		/* accumulated mouse movement */
@@ -781,6 +868,35 @@ doopen(struct psm_softc *sc, int command
 {
 	int stat[3];
 
+	/*
+	 * FIXME: Synaptics TouchPad seems to go back to Relative Mode with
+	 * no obvious reason. Thus we check the current mode and restore the
+	 * Absolute Mode if it was cleared.
+	 *
+	 * The previous hack at the end of psmprobe() wasn't efficient when
+	 * moused(8) was restarted.
+	 *
+	 * A Reset (FF) or Set Defaults (F6) command would clear the
+	 * Absolute Mode bit. But a verbose boot or debug.psm.loglevel=5
+	 * doesn't show any evidence of such a command.
+	 */
+	if (sc->hw.model == MOUSE_MODEL_SYNAPTICS) {
+		mouse_ext_command(sc->kbdc, 1);
+		get_mouse_status(sc->kbdc, stat, 0, 3);
+		if (stat[1] == 0x47 && stat[2] == 0x40) {
+			/* Set the mode byte -- request wmode where
+			 * available */
+			if (sc->synhw.capExtended)
+				mouse_ext_command(sc->kbdc, 0xc1);
+			else
+				mouse_ext_command(sc->kbdc, 0xc0);
+			set_mouse_sampling_rate(sc->kbdc, 20);
+			VLOG(5, (LOG_DEBUG, "psm%d: Synaptis Absolute Mode "
+			    "hopefully restored\n",
+			    sc->unit));
+		}
+	}
+
 	/* enable the mouse device */
 	if (!enable_aux_dev(sc->kbdc)) {
 		/* MOUSE ERROR: failed to enable the mouse because:
@@ -1272,15 +1388,6 @@ psmprobe(device_t dev)
 		endprobe(ENXIO);
 	}
 
-	/*
-	 * Synaptics TouchPad seems to go back to Relative Mode after
-	 * the previous set_controller_command_byte() call; by issueing
-	 * a Read Mode Byte command, the touchpad is in Absolute Mode
-	 * again.
-	 */
-	if (sc->hw.model == MOUSE_MODEL_SYNAPTICS)
-		mouse_ext_command(sc->kbdc, 1);
-
 	/* done */
 	kbdc_set_device_mask(sc->kbdc, mask | KBD_AUX_CONTROL_BITS);
 	kbdc_lock(sc->kbdc, FALSE);
@@ -2398,7 +2505,7 @@ proc_synaptics(struct psm_softc *sc, pac
 {
 	static int touchpad_buttons;
 	static int guest_buttons;
-	int w, x0, y0, xavg, yavg, xsensitivity, ysensitivity, sensitivity = 0;
+	int w, x0, y0;
 
 	/* TouchPad PS/2 absolute mode message format
 	 *
@@ -2418,7 +2525,7 @@ proc_synaptics(struct psm_softc *sc, pac
 	 *  U: up button
 	 *  W: "wrist" value
 	 *  X: x position
-	 *  Y: x position
+	 *  Y: y position
 	 *  Z: pressure
 	 *
 	 * Absolute reportable limits:    0 - 6143.
@@ -2438,22 +2545,44 @@ proc_synaptics(struct psm_softc *sc, pac
 	    (pb->ipacket[3] & 0xc8) != 0xc0)
 		return (-1);
 
-	*x = *y = x0 = y0 = 0;
+	*x = *y = 0;
 
-	/* Pressure value. */
+	/*
+	 * Pressure value.
+	 * Interpretation:
+	 *   z = 0      No finger contact
+	 *   z = 10     Finger hovering near the pad
+	 *   z = 30     Very light finger contact
+	 *   z = 80     Normal finger contact
+	 *   z = 110    Very heavy finger contact
+	 *   z = 200    Finger lying flat on pad surface
+	 *   z = 255    Maximum reportable Z
+	 */
 	*z = pb->ipacket[2];
 
-	/* Finger width value */
+	/*
+	 * Finger width value
+	 * Interpretation:
+	 *   w = 0      Two finger on the pad (capMultiFinger needed)
+	 *   w = 1      Three or more fingers (capMultiFinger needed)
+	 *   w = 2      Pen (instead of finger) (capPen needed)
+	 *   w = 3      Reserved (passthrough?)
+	 *   w = 4-7    Finger of normal width (capPalmDetect needed)
+	 *   w = 8-14   Very wide finger or palm (capPalmDetect needed)
+	 *   w = 15     Maximum reportable width (capPalmDetect needed)
+	 */
+	/* XXX Is checking capExtended enough? */
 	if (sc->synhw.capExtended)
 		w = ((pb->ipacket[0] & 0x30) >> 2) |
 		    ((pb->ipacket[0] & 0x04) >> 1) |
 		    ((pb->ipacket[3] & 0x04) >> 2);
 	else {
-		/* Assume a finger of regular width */
+		/* Assume a finger of regular width. */
 		w = 4;
 	}
 
 	/* Handle packets from the guest device */
+	/* XXX Documentation? */
 	if (w == 3 && sc->synhw.capPassthrough) {
 		*x = ((pb->ipacket[1] & 0x10) ?
 		    pb->ipacket[4] - 256 : pb->ipacket[4]);
@@ -2470,7 +2599,7 @@ proc_synaptics(struct psm_softc *sc, pac
 			guest_buttons |= MOUSE_BUTTON3DOWN;
 
 		ms->button = touchpad_buttons | guest_buttons;
-		return (0);
+		goto SYNAPTICS_END;
 	}
 
 	/* Button presses */
@@ -2492,6 +2621,7 @@ proc_synaptics(struct psm_softc *sc, pac
 	 * the packet indicates that we have an extended
 	 * button press.
 	 */
+	/* XXX Documentation? */
 	if (pb->ipacket[3] & 0x02) {
 		/*
 		 * if directional_scrolls is not 1, we treat any of
@@ -2507,151 +2637,495 @@ proc_synaptics(struct psm_softc *sc, pac
 			if (pb->ipacket[5] & 0x02)
 				touchpad_buttons |= MOUSE_BUTTON7DOWN;
 		} else {
-			if ((pb->ipacket[4] & 0x0F) || (pb->ipacket[5] & 0x0F))
+			if ((pb->ipacket[4] & 0x0F) ||
+			    (pb->ipacket[5] & 0x0F))
 				touchpad_buttons |= MOUSE_BUTTON2DOWN;
 		}
 	}
 
 	ms->button = touchpad_buttons | guest_buttons;
 
-	/* There is a finger on the pad. */
-	if ((w >= 4 && w <= 7) && (*z >= 16 && *z < 200)) {
+	/* Check pressure to detect a real wanted action on the
+	 * touchpad. */
+	if (*z >= sc->syninfo.min_pressure) {
+		synapticsaction_t *synaction;
+		int cursor, peer, window;
+		int dx, dy, dxp, dyp;
+		int max_width, max_pressure;
+		int margin_top, margin_right, margin_bottom, margin_left;
+		int na_top, na_right, na_bottom, na_left;
+		int window_min, window_max;
+		int multiplicator;
+		int weight_current, weight_previous, weight_len_squared;
+		int div_min, div_max, div_len;
+		int vscroll_hor_area, vscroll_ver_area;
+
+		int len, weight_prev_x, weight_prev_y;
+		int div_max_x, div_max_y, div_x, div_y;
+
+		/* Read sysctl. */
+		/* XXX Verify values? */
+		max_width = sc->syninfo.max_width;
+		max_pressure = sc->syninfo.max_pressure;
+		margin_top = sc->syninfo.margin_top;
+		margin_right = sc->syninfo.margin_right;
+		margin_bottom = sc->syninfo.margin_bottom;
+		margin_left = sc->syninfo.margin_left;
+		na_top = sc->syninfo.na_top;
+		na_right = sc->syninfo.na_right;
+		na_bottom = sc->syninfo.na_bottom;
+		na_left = sc->syninfo.na_left;
+		window_min = sc->syninfo.window_min;
+		window_max = sc->syninfo.window_max;
+		multiplicator = sc->syninfo.multiplicator;
+		weight_current = sc->syninfo.weight_current;
+		weight_previous = sc->syninfo.weight_previous;
+		weight_len_squared = sc->syninfo.weight_len_squared;
+		div_min = sc->syninfo.div_min;
+		div_max = sc->syninfo.div_max;
+		div_len = sc->syninfo.div_len;
+		vscroll_hor_area = sc->syninfo.vscroll_hor_area;
+		vscroll_ver_area = sc->syninfo.vscroll_ver_area;
+
+		/* Palm detection. */
+		if (!(
+		    (sc->synhw.capMultiFinger && (w == 0 || w == 1)) ||
+		    (sc->synhw.capPalmDetect && w >= 4 && w <= max_width) ||
+		    (!sc->synhw.capPalmDetect && *z <= max_pressure) ||
+		    (sc->synhw.capPen && w == 2))) {
+			/*
+			 * We consider the packet irrelevant for the current
+			 * action when:
+			 *  - the width isn't comprised in:
+			 *    [4; max_width]
+			 *  - the pressure isn't comprised in:
+			 *    [min_pressure; max_pressure]
+			 *  - pen aren't supported but w is 2
+			 *
+			 *  Note that this doesn't terminate the current action.
+			 */
+			VLOG(2, (LOG_DEBUG,
+			    "synaptics: palm detected! (%d)\n", w));
+			goto SYNAPTICS_END;
+		}
+
+		/* Read current absolute position. */
 		x0 = ((pb->ipacket[3] & 0x10) << 8) |
-		    ((pb->ipacket[1] & 0x0f) << 8) | pb->ipacket[4];
+		    ((pb->ipacket[1] & 0x0f) << 8) |
+		    pb->ipacket[4];
 		y0 = ((pb->ipacket[3] & 0x20) << 7) |
-		    ((pb->ipacket[1] & 0xf0) << 4) | pb->ipacket[5];
+		    ((pb->ipacket[1] & 0xf0) << 4) |
+		    pb->ipacket[5];
 
-		if (sc->flags & PSM_FLAGS_FINGERDOWN) {
-			*x = x0 - sc->xold;
-			*y = y0 - sc->yold;
+		synaction = &(sc->synaction);
 
-			/*
-			 * we compute averages of x and y
-			 * movement
-			 */
-			if (sc->xaverage == 0)
-				sc->xaverage = *x;
+		/*
+		 * If the action is just beginning, init the structure and
+		 * compute tap timeout.
+		 */
+		if (!(sc->flags & PSM_FLAGS_FINGERDOWN)) {
+			VLOG(3, (LOG_DEBUG, "synaptics: ----\n"));
 
-			if (sc->yaverage == 0)
-				sc->yaverage = *y;
+			/* Store the first point of this action. */
+			synaction->start_x = x0;
+			synaction->start_y = y0;
+			dx = dy = 0;
+
+			/* Initialize queue. */
+			synaction->queue_cursor = SYNAPTICS_PACKETQUEUE;
+			synaction->queue_len = 0;
+			synaction->window_min = window_min;
+
+			/* Reset average. */
+			synaction->avg_dx = 0;
+			synaction->avg_dy = 0;
+
+			/* Reset squelch. */
+			synaction->squelch_x = 0;
+			synaction->squelch_y = 0;
+
+			/* Reset pressure peak. */
+			sc->zmax = 0;
+
+			/* Reset fingers count. */
+			synaction->fingers_nb = 0;
+
+			/* Reset virtual scrolling state. */
+			synaction->in_vscroll = 0;
+
+			/* Compute tap timeout. */
+			sc->taptimeout.tv_sec  = tap_timeout / 1000000;
+			sc->taptimeout.tv_usec = tap_timeout % 1000000;
+			timevaladd(&sc->taptimeout, &sc->lastsoftintr);
 
-			xavg = sc->xaverage;
-			yavg = sc->yaverage;
+			sc->flags |= PSM_FLAGS_FINGERDOWN;
+		} else {
+			/* Calculate the current delta. */
+			cursor = synaction->queue_cursor;
+			dx = x0 - synaction->queue[cursor].x;
+			dy = y0 - synaction->queue[cursor].y;
+		}
 
-			sc->xaverage = (xavg + *x) >> 1;
-			sc->yaverage = (yavg + *y) >> 1;
+		/* If in tap-hold, add the recorded button. */
+		if (synaction->in_taphold)
+			ms->button |= synaction->tap_button;
 
-			/*
-			 * then use the averages to compute
-			 * a sensitivity level in each dimension
-			 */
-			xsensitivity = (sc->xaverage - xavg);
-			if (xsensitivity < 0)
-				xsensitivity = -xsensitivity;
-
-			ysensitivity = (sc->yaverage - yavg);
-			if (ysensitivity < 0)
-				ysensitivity = -ysensitivity;
+		/*
+		 * From now on, we can use the SYNAPTICS_END label to skip
+		 * the current packet.
+		 */
+
+		/*
+		 * Limit the coordinates to the specified margins because
+		 * this area isn't very reliable.
+		 */
+		if (x0 <= margin_left)
+			x0 = margin_left;
+		else if (x0 >= 6143 - margin_right)
+			x0 = 6143 - margin_right;
+		if (y0 <= margin_bottom)
+			y0 = margin_bottom;
+		else if (y0 >= 6143 - margin_top)
+			y0 = 6143 - margin_top;
+
+		VLOG(3, (LOG_DEBUG, "synaptics: ipacket: [%d, %d], %d, %d\n",
+		    x0, y0, *z, w));
+
+		/* Queue this new packet. */
+		cursor = SYNAPTICS_QUEUE_CURSOR(synaction->queue_cursor - 1);
+		synaction->queue[cursor].x = x0;
+		synaction->queue[cursor].y = y0;
+		synaction->queue_cursor = cursor;
+		if (synaction->queue_len < SYNAPTICS_PACKETQUEUE)
+			synaction->queue_len++;
+		VLOG(5, (LOG_DEBUG,
+		    "synaptics: cursor[%d]: x=%d, y=%d, dx=%d, dy=%d\n",
+		    cursor, x0, y0, dx, dy));
 
+		/*
+		 * For tap, we keep the maximum number of fingers and the
+		 * pressure peak. Also with multiple fingers, we increase
+		 * the minimum window.
+		 */
+		switch (w) {
+		case 1: /* Three or more fingers. */
+			synaction->fingers_nb = imax(3, synaction->fingers_nb);
+			synaction->window_min = window_max;
+			break;
+		case 0: /* Two fingers. */
+			synaction->fingers_nb = imax(2, synaction->fingers_nb);
+			synaction->window_min = window_max;
+			break;
+		default: /* One finger or undetectable. */
+			synaction->fingers_nb = imax(1, synaction->fingers_nb);
+		}
+		sc->zmax = imax(*z, sc->zmax);
+
+		/* Do we have enough packets to consider this a movement? */
+		if (synaction->queue_len < synaction->window_min)
+			goto SYNAPTICS_END;
+
+		/* Is a scrolling action occuring? */
+		if (!synaction->in_taphold && !synaction->in_vscroll) {
 			/*
-			 * The sensitivity level is higher the faster
-			 * the finger is moving.  It also tends to be
-			 * higher in the middle of a touchpad motion
-			 * than on either end
-			 * Note - sensitivity gets to 0 when moving slowly -
-			 * so we add 1 to it to give it a meaningful value
-			 * in that case.
+			 * A scrolling action must not conflict with a tap
+			 * action. Here are the conditions to consider a
+			 * scrolling action:
+			 *  - the action in a configurable area
+			 *  - one of the following:
+			 *     . the distance between the last packet and the
+			 *       first should be above a configurable minimum
+			 *     . tap timed out
 			 */
-			sensitivity = (xsensitivity & ysensitivity) + 1;
+			dxp = abs(synaction->queue[synaction->queue_cursor].x -
+			    synaction->start_x);
+			dyp = abs(synaction->queue[synaction->queue_cursor].y -
+			    synaction->start_y);
+
+			if (timevalcmp(&sc->lastsoftintr, &sc->taptimeout, >) ||
+			    dxp >= sc->syninfo.vscroll_min_delta ||
+			    dyp >= sc->syninfo.vscroll_min_delta) {
+				/* Check for horizontal scrolling. */
+				if ((vscroll_hor_area > 0 &&
+				    synaction->start_y <= vscroll_hor_area) ||
+				    (vscroll_hor_area < 0 &&
+				     synaction->start_y >=
+				     6143 + vscroll_hor_area))
+					synaction->in_vscroll += 2;
+
+				/* Check for vertical scrolling. */
+				if ((vscroll_ver_area > 0 &&
+				    synaction->start_x <= vscroll_ver_area) ||
+				    (vscroll_ver_area < 0 &&
+				     synaction->start_x >=
+				     6143 + vscroll_ver_area))
+					synaction->in_vscroll += 1;
+
+				/* Avoid conflicts if area overlaps. */
+				if (synaction->in_vscroll == 3)
+					synaction->in_vscroll =
+					    (dxp > dyp) ? 2 : 1;
+			}
+			VLOG(5, (LOG_DEBUG,
+			    "synaptics: virtual scrolling: %s "
+			    "(direction=%d, dxp=%d, dyp=%d)\n",
+			    synaction->in_vscroll ? "YES" : "NO",
+			    synaction->in_vscroll, dxp, dyp));
+		}
 
+		weight_prev_x = weight_prev_y = weight_previous;
+		div_max_x = div_max_y = div_max;
+
+		if (synaction->in_vscroll) {
+			/* Dividers are different with virtual scrolling. */
+			div_min = sc->syninfo.vscroll_div_min;
+			div_max_x = div_max_y = sc->syninfo.vscroll_div_max;
+		} else {
 			/*
-			 * If either our x or y change is greater than
-			 * our hi/low speed threshold - we do the high-speed
-			 * absolute to relative calculation otherwise
-			 * we do the low-speed calculation.
+			 * There's a lot of noise in coordinates when
+			 * the finger is on the touchpad's borders. When
+			 * using this area, we apply a special weight and
+			 * div.
 			 */
-			if ((*x > sc->syninfo.low_speed_threshold ||
-			    *x < -sc->syninfo.low_speed_threshold) ||
-			    (*y > sc->syninfo.low_speed_threshold ||
-			    *y < -sc->syninfo.low_speed_threshold)) {
-				x0 = (x0 + sc->xold * 3) / 4;
-				y0 = (y0 + sc->yold * 3) / 4;
-				*x = (x0 - sc->xold) * 10 / 85;
-				*y = (y0 - sc->yold) * 10 / 85;
-			} else {
-				/*
-				 * This is the low speed calculation.
-				 * We simply check to see if our movement is
-				 * more than our minimum movement threshold
-				 * and if it is - set the movement to 1
-				 * in the correct direction.
-				 * NOTE - Normally this would result
-				 * in pointer movement that was WAY too fast.
-				 * This works due to the movement squelch
-				 * we do later.
-				 */
-				if (*x < -sc->syninfo.min_movement)
-					*x = -1;
-				else if (*x > sc->syninfo.min_movement)
-					*x = 1;
-				else
-					*x = 0;
-				if (*y < -sc->syninfo.min_movement)
-					*y = -1;
-				else if (*y > sc->syninfo.min_movement)
-					*y = 1;
-				else
-					*y = 0;
+			if (x0 <= na_left || x0 >= 6143 - na_right) {
+				weight_prev_x = sc->syninfo.weight_previous_na;
+				div_max_x = sc->syninfo.div_max_na;
+			}
 
+			if (y0 <= na_bottom || y0 >= 6143 - na_top) {
+				weight_prev_y = sc->syninfo.weight_previous_na;
+				div_max_y = sc->syninfo.div_max_na;
 			}
-		} else
-			sc->flags |= PSM_FLAGS_FINGERDOWN;
+		}
 
 		/*
-		 * The squelch process.  Take our sensitivity value and
-		 * add it to the current squelch value - if squelch is
-		 * less than our squelch threshold we kill the movement,
-		 * otherwise we reset squelch and pass the movement through.
-		 * Since squelch is cumulative - when mouse movement is slow
-		 * (around sensitivity 1) the net result is that only 1
-		 * out of every squelch_level packets is delivered,
-		 * effectively slowing down the movement.
-		 */
-		sc->squelch += sensitivity;
-		if (sc->squelch < sc->syninfo.squelch_level) {
-			*x = 0;
-			*y = 0;
-		} else
-			sc->squelch = 0;
+		 * Calculate weights for the average operands and
+		 * the divisor. Both depend on the distance between
+		 * the current packet and a previous one (based on the
+		 * window width).
+		 */
+		window = imin(synaction->queue_len, window_max);
+		peer = SYNAPTICS_QUEUE_CURSOR(cursor + window - 1);
+		dxp = abs(x0 - synaction->queue[peer].x) + 1;
+		dyp = abs(y0 - synaction->queue[peer].y) + 1;
+		len = (dxp * dxp) + (dyp * dyp);
+		weight_prev_x = imin(weight_prev_x,
+		    weight_len_squared * weight_prev_x / len);
+		weight_prev_y = imin(weight_prev_y,
+		    weight_len_squared * weight_prev_y / len);
+
+		len = (dxp + dyp) / 2;
+		div_x = div_len * div_max_x / len;
+		div_x = imin(div_max_x, div_x);
+		div_x = imax(div_min, div_x);
+		div_y = div_len * div_max_y / len;
+		div_y = imin(div_max_y, div_y);
+		div_y = imax(div_min, div_y);
+
+		VLOG(3, (LOG_DEBUG,
+		    "synaptics: peer=%d, len=%d, weight=%d/%d, div=%d/%d\n",
+		    peer, len, weight_prev_x, weight_prev_y, div_x, div_y));
+
+		/* Compute averages. */
+		synaction->avg_dx =
+		    (weight_current * dx * multiplicator +
+		     weight_prev_x * synaction->avg_dx) /
+		    (weight_current + weight_prev_x);
+
+		synaction->avg_dy =
+		    (weight_current * dy * multiplicator +
+		     weight_prev_y * synaction->avg_dy) /
+		    (weight_current + weight_prev_y);
+
+		VLOG(5, (LOG_DEBUG,
+		    "synaptics: avg_dx~=%d, avg_dy~=%d\n",
+		    synaction->avg_dx / multiplicator,
+		    synaction->avg_dy / multiplicator));
+
+		/* Use these averages to calculate x & y. */
+		synaction->squelch_x += synaction->avg_dx;
+		*x = synaction->squelch_x / (div_x * multiplicator);
+		synaction->squelch_x = synaction->squelch_x %
+		    (div_x * multiplicator);
+
+		synaction->squelch_y += synaction->avg_dy;
+		*y = synaction->squelch_y / (div_y * multiplicator);
+		synaction->squelch_y = synaction->squelch_y %
+		    (div_y * multiplicator);
+
+		if (synaction->in_vscroll) {
+			switch(synaction->in_vscroll) {
+			case 1: /* Vertical scrolling. */
+				if (*y != 0)
+					ms->button |= (*y > 0) ?
+					    MOUSE_BUTTON4DOWN :
+					    MOUSE_BUTTON5DOWN;
+				break;
+			case 2: /* Horizontal scrolling. */
+				if (*x != 0)
+					ms->button |= (*x > 0) ?
+					    MOUSE_BUTTON7DOWN :
+					    MOUSE_BUTTON6DOWN;
+				break;
+			}
+
+			/* The pointer is not moved. */
+			*x = *y = 0;
+		} else {
+			VLOG(3, (LOG_DEBUG, "synaptics: [%d, %d] -> [%d, %d]\n",
+			    dx, dy, *x, *y));
+		}
+	} else if (sc->flags & PSM_FLAGS_FINGERDOWN) {
+		/*
+		 * An action is currently taking place but the pressure
+		 * dropped under the minimum, putting an end to it.
+		 */
+		synapticsaction_t *synaction;
+		int taphold_timeout, dx, dy, tap_max_delta;
+
+		synaction = &(sc->synaction);
+		dx = abs(synaction->queue[synaction->queue_cursor].x -
+		    synaction->start_x);
+		dy = abs(synaction->queue[synaction->queue_cursor].y -
+		    synaction->start_y);
+
+		/* Max delta is disabled for multi-fingers tap. */
+		if (synaction->fingers_nb > 1)
+			tap_max_delta = imax(dx, dy);
+		else
+			tap_max_delta = sc->syninfo.tap_max_delta;
 
-		sc->xold = x0;
-		sc->yold = y0;
-		sc->zmax = imax(*z, sc->zmax);
-	} else {
 		sc->flags &= ~PSM_FLAGS_FINGERDOWN;
 
-		if (sc->zmax > tap_threshold &&
-		    timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=)) {
-			if (w == 0)
-				ms->button |= MOUSE_BUTTON3DOWN;
-			else if (w == 1)
-				ms->button |= MOUSE_BUTTON2DOWN;
-			else
-				ms->button |= MOUSE_BUTTON1DOWN;
-		}
+		/* Check for tap. */
+		VLOG(3, (LOG_DEBUG,
+		    "synaptics: zmax=%d, dx=%d, dy=%d, "
+		    "delta=%d, fingers=%d, queue=%d\n",
+		    sc->zmax, dx, dy, tap_max_delta, synaction->fingers_nb,
+		    synaction->queue_len));
+		if (!synaction->in_vscroll && sc->zmax >= tap_threshold &&
+		    timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=) &&
+		    dx <= tap_max_delta && dy <= tap_max_delta &&
+		    synaction->queue_len >= sc->syninfo.tap_min_queue) {
+			/*
+			 * We have a tap if:
+			 *   - the maximum pressure went over tap_threshold
+			 *   - the action ended before tap_timeout
+			 *
+			 * To handle tap-hold, we must delay any button push to
+			 * the next action.
+			 */
+			if (synaction->in_taphold) {
+				/*
+				 * This is the second and last tap of a
+				 * double tap action, not a tap-hold.
+				 */
+				synaction->in_taphold = 0;
 
-		sc->zmax = 0;
-		sc->taptimeout.tv_sec = tap_timeout / 1000000;
-		sc->taptimeout.tv_usec = tap_timeout % 1000000;
-		timevaladd(&sc->taptimeout, &sc->lastsoftintr);
+				/*
+				 * For double-tap to work:
+				 *   - no button press is emitted (to
+				 *     simulate a button release)
+				 *   - PSM_FLAGS_FINGERDOWN is set to
+				 *     force the next packet to emit a
+				 *     button press)
+				 */
+				VLOG(2, (LOG_DEBUG,
+				    "synaptics: button RELEASE: %d\n",
+				    synaction->tap_button));
+				sc->flags |= PSM_FLAGS_FINGERDOWN;
+			} else {
+				/*
+				 * This is the first tap: we set the
+				 * tap-hold state and notify the button
+				 * down event.
+				 */
+				synaction->in_taphold = 1;
+				taphold_timeout = sc->syninfo.taphold_timeout;
+				sc->taptimeout.tv_sec  = taphold_timeout /
+				    1000000;
+				sc->taptimeout.tv_usec = taphold_timeout %
+				    1000000;
+				timevaladd(&sc->taptimeout, &sc->lastsoftintr);
+
+				switch (synaction->fingers_nb) {
+				case 3:
+					synaction->tap_button =
+					    MOUSE_BUTTON2DOWN;
+					break;
+				case 2:
+					synaction->tap_button =
+					    MOUSE_BUTTON3DOWN;
+					break;
+				default:
+					synaction->tap_button =
+					    MOUSE_BUTTON1DOWN;
+				}
+				VLOG(2, (LOG_DEBUG,
+				    "synaptics: button PRESS: %d\n",
+				    synaction->tap_button));
+				ms->button |= synaction->tap_button;
+			}
+		} else {
+			/*
+			 * Not enough pressure or timeout: reset
+			 * tap-hold state.
+			 */
+			if (synaction->in_taphold) {
+				VLOG(2, (LOG_DEBUG,
+				    "synaptics: button RELEASE: %d\n",
+				    synaction->tap_button));
+				synaction->in_taphold = 0;
+			} else {
+				VLOG(2, (LOG_DEBUG,
+				    "synaptics: not a tap-hold\n"));
+			}
+		}
+	} else if (!(sc->flags & PSM_FLAGS_FINGERDOWN) &&
+	    sc->synaction.in_taphold) {
+		/*
+		 * For a tap-hold to work, the button must remain down at
+		 * least until timeout (where the in_taphold flags will be
+		 * cleared) or during the next action.
+		 */
+		if (timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=)) {
+			ms->button |= sc->synaction.tap_button;
+		} else {
+			VLOG(2, (LOG_DEBUG,
+			    "synaptics: button RELEASE: %d\n",
+			    sc->synaction.tap_button));
+			sc->synaction.in_taphold = 0;
+		}
 	}
 
-	/* Use the extra buttons as a scrollwheel */
-	if (ms->button & MOUSE_BUTTON4DOWN)
+SYNAPTICS_END:
+	/*
+	 * Use the extra buttons as a scrollwheel
+	 *
+	 * XXX X.Org uses the Z axis for vertical wheel only,
+	 * whereas moused(8) understands special values to differ
+	 * vertical and horizontal wheels.
+	 *
+	 * xf86-input-mouse needs therefore a small patch to
+	 * understand these special values. Without it, the
+	 * horizontal wheel acts as a vertical wheel in X.Org.
+	 *
+	 * That's why the horizontal wheel is disabled by
+	 * default for now.
+	 */
+	if (ms->button & MOUSE_BUTTON4DOWN) {
 		*z = -1;
-	else if (ms->button & MOUSE_BUTTON5DOWN)
+		ms->button &= ~MOUSE_BUTTON4DOWN;
+	} else if (ms->button & MOUSE_BUTTON5DOWN) {
 		*z = 1;
-	else
+		ms->button &= ~MOUSE_BUTTON5DOWN;
+	} else if (ms->button & MOUSE_BUTTON6DOWN) {
+		*z = -2;
+		ms->button &= ~MOUSE_BUTTON6DOWN;
+	} else if (ms->button & MOUSE_BUTTON7DOWN) {
+		*z = 2;
+		ms->button &= ~MOUSE_BUTTON7DOWN;
+	} else
 		*z = 0;
 
 	return (0);
@@ -3400,13 +3874,87 @@ enable_4dplus(struct psm_softc *sc)
 
 /* Synaptics Touchpad */
 static int
-enable_synaptics(struct psm_softc *sc)
+synaptics_sysctl(SYSCTL_HANDLER_ARGS)
 {
-	int status[3];
-	KBDC kbdc;
+	int error, arg;
 
-	if (!synaptics_support)
-		return (FALSE);
+	/* Read the current value. */
+	arg = *(int *)oidp->oid_arg1;
+	error = sysctl_handle_int(oidp, &arg, 0, req);
+
+	/* Sanity check. */
+	if (error || !req->newptr)
+		return (error);
+
+	/*
+	 * Check that the new value is in the concerned node's range
+	 * of values.
+	 */
+	switch (oidp->oid_arg2) {
+	case SYNAPTICS_SYSCTL_MIN_PRESSURE:
+	case SYNAPTICS_SYSCTL_MAX_PRESSURE:
+		if (arg < 0 || arg > 255)
+			return (EINVAL);
+		break;
+	case SYNAPTICS_SYSCTL_MAX_WIDTH:
+		if (arg < 4 || arg > 15)
+			return (EINVAL);
+		break;
+	case SYNAPTICS_SYSCTL_MARGIN_TOP:
+	case SYNAPTICS_SYSCTL_MARGIN_RIGHT:
+	case SYNAPTICS_SYSCTL_MARGIN_BOTTOM:
+	case SYNAPTICS_SYSCTL_MARGIN_LEFT:
+	case SYNAPTICS_SYSCTL_NA_TOP:
+	case SYNAPTICS_SYSCTL_NA_RIGHT:
+	case SYNAPTICS_SYSCTL_NA_BOTTOM:
+	case SYNAPTICS_SYSCTL_NA_LEFT:
+		if (arg < 0 || arg > 6143)
+			return (EINVAL);
+		break;
+	case SYNAPTICS_SYSCTL_WINDOW_MIN:
+	case SYNAPTICS_SYSCTL_WINDOW_MAX:
+	case SYNAPTICS_SYSCTL_TAP_MIN_QUEUE:
+		if (arg < 1 || arg > SYNAPTICS_PACKETQUEUE)
+			return (EINVAL);
+		break;
+	case SYNAPTICS_SYSCTL_MULTIPLICATOR:
+	case SYNAPTICS_SYSCTL_WEIGHT_CURRENT:
+	case SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS:
+	case SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA:
+	case SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED:
+	case SYNAPTICS_SYSCTL_DIV_MIN:
+	case SYNAPTICS_SYSCTL_DIV_MAX:
+	case SYNAPTICS_SYSCTL_DIV_MAX_NA:
+	case SYNAPTICS_SYSCTL_DIV_LEN:
+	case SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN:
+	case SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX:
+		if (arg < 1)
+			return (EINVAL);
+		break;
+	case SYNAPTICS_SYSCTL_TAP_MAX_DELTA:
+	case SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT:
+	case SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA:
+		if (arg < 0)
+			return (EINVAL);
+		break;
+	case SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA:
+	case SYNAPTICS_SYSCTL_VSCROLL_VER_AREA:
+		if (arg < -6143 || arg > 6143)
+			return (EINVAL);
+		break;
+	default:
+		return (EINVAL);
+	}
+
+	/* Update. */
+	*(int *)oidp->oid_arg1 = arg;
+
+	return (error);
+}
+
+static void
+synaptics_sysctl_create_tree(struct psm_softc *sc)
+{
 
 	/* Attach extra synaptics sysctl nodes under hw.psm.synaptics */
 	sysctl_ctx_init(&sc->syninfo.sysctl_ctx);
@@ -3414,54 +3962,307 @@ enable_synaptics(struct psm_softc *sc)
 	    SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, "synaptics", CTLFLAG_RD,
 	    0, "Synaptics TouchPad");
 
-	/*
-	 * synaptics_directional_scrolls - if non-zero, the directional
-	 * pad scrolls, otherwise it registers as a middle-click.
-	 */
+	/* hw.psm.synaptics.directional_scrolls. */
 	sc->syninfo.directional_scrolls = 1;
 	SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "directional_scrolls", CTLFLAG_RW,
+	    "directional_scrolls", CTLFLAG_RW|CTLFLAG_ANYBODY,
 	    &sc->syninfo.directional_scrolls, 0,
-	    "directional pad scrolls (1=yes  0=3rd button)");
+	    "Enable hardware scrolling pad (if non-zero) or register it as "
+	    "a middle-click (if 0)");
 
-	/*
-	 * Synaptics_low_speed_threshold - the number of touchpad units
-	 * below-which we go into low-speed tracking mode.
-	 */
-	sc->syninfo.low_speed_threshold = 20;
-	SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx,
+	/* hw.psm.synaptics.min_pressure. */
+	sc->syninfo.min_pressure = 16;
+	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "low_speed_threshold", CTLFLAG_RW,
-	    &sc->syninfo.low_speed_threshold, 0,
-	    "threshold between low and hi speed positioning");
-
-	/*
-	 * Synaptics_min_movement - the number of touchpad units below
-	 * which we ignore altogether.
-	 */
-	sc->syninfo.min_movement = 2;
-	SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx,
+	    "min_pressure", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    &sc->syninfo.min_pressure, SYNAPTICS_SYSCTL_MIN_PRESSURE,
+	    synaptics_sysctl, "I",
+	    "Minimum pressure required to start an action");
+
+	/* hw.psm.synaptics.max_pressure. */

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200810141748.m9EHma9d012354>