From da0a78625ad803413e1e6789ef61d40ea345fa61 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Thu, 4 May 2017 08:48:09 +0200
Subject: [PATCH] WIP atp(4) port

---
 sys/bus/u4b/input/Makefile     |    2 +-
 sys/bus/u4b/input/atp.c        | 2712 +++++++++++++++++++++++-----------------
 sys/bus/u4b/input/atp/Makefile |    9 +
 sys/bus/u4b/usbdevs            |   52 +
 4 files changed, 1617 insertions(+), 1158 deletions(-)
 create mode 100644 sys/bus/u4b/input/atp/Makefile

diff --git a/sys/bus/u4b/input/Makefile b/sys/bus/u4b/input/Makefile
index 288bacfa55..228f07cffe 100644
--- a/sys/bus/u4b/input/Makefile
+++ b/sys/bus/u4b/input/Makefile
@@ -1,3 +1,3 @@
-SUBDIR=	uep uhid ukbd ums
+SUBDIR=	atp uep uhid ukbd ums
 
 .include <bsd.subdir.mk>
diff --git a/sys/bus/u4b/input/atp.c b/sys/bus/u4b/input/atp.c
index de02b38151..b456bf97f1 100644
--- a/sys/bus/u4b/input/atp.c
+++ b/sys/bus/u4b/input/atp.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2009 Rohit Grover
+ * Copyright (c) 2014 Rohit Grover
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -22,35 +22,65 @@
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/sys/dev/usb/input/atp.c 292080 2015-12-11 05:28:00Z imp $
  */
 
-#include <sys/cdefs.h>
-__FBSDID("$FreeBSD$");
+/*
+ * Some tables, structures, definitions and constant values for the
+ * touchpad protocol has been copied from Linux's
+ * "drivers/input/mouse/bcm5974.c" which has the following copyright
+ * holders under GPLv2. All device specific code in this driver has
+ * been written from scratch. The decoding algorithm is based on
+ * output from FreeBSD's usbdump.
+ *
+ * Copyright (C) 2008      Henrik Rydberg (rydberg@euromail.se)
+ * Copyright (C) 2008      Scott Shawcroft (scott.shawcroft@gmail.com)
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2005      Johannes Berg (johannes@sipsolutions.net)
+ * Copyright (C) 2005      Stelian Pop (stelian@popies.net)
+ * Copyright (C) 2005      Frank Arnold (frank@scirocco-5v-turbo.de)
+ * Copyright (C) 2005      Peter Osterlund (petero2@telia.com)
+ * Copyright (C) 2005      Michael Hanselmann (linux-kernel@hansmi.ch)
+ * Copyright (C) 2006      Nicolas Boichat (nicolas@boichat.ch)
+ */
 
+/*
+ * Author's note: 'atp' supports two distinct families of Apple trackpad
+ * products: the older Fountain/Geyser and the latest Wellspring trackpads.
+ * The first version made its appearance with FreeBSD 8 and worked only with
+ * the Fountain/Geyser hardware. A fork of this driver for Wellspring was
+ * contributed by Huang Wen Hui. This driver unifies the Wellspring effort
+ * and also improves upon the original work.
+ *
+ * I'm grateful to Stephan Scheunig, Angela Naegele, and Nokia IT-support
+ * for helping me with access to hardware. Thanks also go to Nokia for
+ * giving me an opportunity to do this work.
+ */
+
+#include <sys/stdint.h>
 #include <sys/param.h>
+#include <sys/types.h>
 #include <sys/systm.h>
 #include <sys/kernel.h>
-#include <sys/malloc.h>
+#include <sys/bus.h>
 #include <sys/module.h>
 #include <sys/lock.h>
-#include <sys/mutex.h>
-#include <sys/bus.h>
+#include <sys/sysctl.h>
+#include <sys/malloc.h>
 #include <sys/conf.h>
 #include <sys/fcntl.h>
 #include <sys/file.h>
-#include <sys/selinfo.h>
-#include <sys/poll.h>
-#include <sys/sysctl.h>
-#include <sys/uio.h>
 
-#include <dev/usb/usb.h>
-#include <dev/usb/usbdi.h>
-#include <dev/usb/usbdi_util.h>
-#include <dev/usb/usbhid.h>
+#include <bus/u4b/usb.h>
+#include <bus/u4b/usbdi.h>
+#include <bus/u4b/usbdi_util.h>
+#include <bus/u4b/usbhid.h>
+
 #include "usbdevs.h"
 
 #define USB_DEBUG_VAR atp_debug
-#include <dev/usb/usb_debug.h>
+#include <bus/u4b/usb_debug.h>
 
 #include <sys/mouse.h>
 
@@ -61,17 +91,35 @@ __FBSDID("$FreeBSD$");
  * `options' statements in the kernel configuration file.
  */
 
-/* The multiplier used to translate sensor reported positions to mickeys. */
+/* The divisor used to translate sensor reported positions to mickeys. */
 #ifndef ATP_SCALE_FACTOR
-#define ATP_SCALE_FACTOR 48
+#define ATP_SCALE_FACTOR                  16
+#endif
+
+/* Threshold for small movement noise (in mickeys) */
+#ifndef ATP_SMALL_MOVEMENT_THRESHOLD
+#define ATP_SMALL_MOVEMENT_THRESHOLD      30
+#endif
+
+/* Threshold of instantaneous deltas beyond which movement is considered fast.*/
+#ifndef ATP_FAST_MOVEMENT_TRESHOLD
+#define ATP_FAST_MOVEMENT_TRESHOLD        150
 #endif
 
 /*
- * This is the age (in microseconds) beyond which a touch is
- * considered to be a slide; and therefore a tap event isn't registered.
+ * This is the age in microseconds beyond which a touch is considered
+ * to be a slide; and therefore a tap event isn't registered.
  */
 #ifndef ATP_TOUCH_TIMEOUT
-#define ATP_TOUCH_TIMEOUT 125000
+#define ATP_TOUCH_TIMEOUT                 125000
+#endif
+
+#ifndef ATP_IDLENESS_THRESHOLD
+#define	ATP_IDLENESS_THRESHOLD 10
+#endif
+
+#ifndef FG_SENSOR_NOISE_THRESHOLD
+#define FG_SENSOR_NOISE_THRESHOLD 2
 #endif
 
 /*
@@ -82,39 +130,40 @@ __FBSDID("$FreeBSD$");
  * tap events preceding the slide for such a gesture.
  */
 #ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD
-#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000
+#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD   200000
 #endif
 
 /*
- * The device provides us only with pressure readings from an array of
- * X and Y sensors; for our algorithms, we need to interpret groups
- * (typically pairs) of X and Y readings as being related to a single
- * finger stroke. We can relate X and Y readings based on their times
- * of incidence. The coincidence window should be at least 10000us
- * since it is used against values from getmicrotime(), which has a
- * precision of around 10ms.
+ * The wait duration in ticks after losing a touch contact before
+ * zombied strokes are reaped and turned into button events.
  */
-#ifndef ATP_COINCIDENCE_THRESHOLD
-#define ATP_COINCIDENCE_THRESHOLD  40000 /* unit: microseconds */
-#if ATP_COINCIDENCE_THRESHOLD > 100000
-#error "ATP_COINCIDENCE_THRESHOLD too large"
-#endif
-#endif /* #ifndef ATP_COINCIDENCE_THRESHOLD */
+#define ATP_ZOMBIE_STROKE_REAP_INTERVAL   (hz / 20)	/* 50 ms */
+
+/* The multiplier used to translate sensor reported positions to mickeys. */
+#define FG_SCALE_FACTOR                   380
 
 /*
- * The wait duration (in microseconds) after losing a touch contact
- * before zombied strokes are reaped and turned into button events.
+ * The movement threshold for a stroke; this is the maximum difference
+ * in position which will be resolved as a continuation of a stroke
+ * component.
  */
-#define ATP_ZOMBIE_STROKE_REAP_WINDOW   50000
-#if ATP_ZOMBIE_STROKE_REAP_WINDOW > 100000
-#error "ATP_ZOMBIE_STROKE_REAP_WINDOW too large"
+#define FG_MAX_DELTA_MICKEYS             ((3 * (FG_SCALE_FACTOR)) >> 1)
+
+/* Distance-squared threshold for matching a finger with a known stroke */
+#ifndef WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ
+#define WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ 1000000
 #endif
 
-/* end of driver specific options */
+/* Ignore pressure spans with cumulative press. below this value. */
+#define FG_PSPAN_MIN_CUM_PRESSURE         10
 
+/* Maximum allowed width for pressure-spans.*/
+#define FG_PSPAN_MAX_WIDTH                4
+
+/* end of driver specific options */
 
 /* Tunables */
-static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB atp");
+static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB ATP");
 
 #ifdef USB_DEBUG
 enum atp_log_level {
@@ -126,290 +175,557 @@ enum atp_log_level {
 static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */
 SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RW,
     &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
-TUNABLE_INT("hw.usb.atp.debug", &atp_debug);
 #endif /* USB_DEBUG */
 
 static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
 SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RW,
-    &atp_touch_timeout, 125000, "age threshold (in micros) for a touch");
-TUNABLE_INT("hw.usb.atp.touch_timeout", &atp_touch_timeout);
+    &atp_touch_timeout, 125000, "age threshold in microseconds for a touch");
 
 static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD;
 SYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RW,
     &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD,
-    "maximum time (in micros) between a double-tap");
-TUNABLE_INT("hw.usb.atp.double_tap_threshold", &atp_double_tap_threshold);
+    "maximum time in microseconds to allow association between a double-tap and "
+    "drag gesture");
 
 static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR;
 static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS);
 SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RW,
     &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor),
     atp_sysctl_scale_factor_handler, "IU", "movement scale factor");
-TUNABLE_INT("hw.usb.atp.scale_factor", &atp_mickeys_scale_factor);
 
-static u_int atp_small_movement_threshold = ATP_SCALE_FACTOR >> 3;
+static u_int atp_small_movement_threshold = ATP_SMALL_MOVEMENT_THRESHOLD;
 SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RW,
-    &atp_small_movement_threshold, ATP_SCALE_FACTOR >> 3,
+    &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD,
     "the small movement black-hole for filtering noise");
-TUNABLE_INT("hw.usb.atp.small_movement", &atp_small_movement_threshold);
 
-/*
- * The movement threshold for a stroke; this is the maximum difference
- * in position which will be resolved as a continuation of a stroke
- * component.
- */
-static u_int atp_max_delta_mickeys = ((3 * ATP_SCALE_FACTOR) >> 1);
-SYSCTL_UINT(_hw_usb_atp, OID_AUTO, max_delta_mickeys, CTLFLAG_RW,
-    &atp_max_delta_mickeys, ((3 * ATP_SCALE_FACTOR) >> 1),
-    "max. mickeys-delta which will match against an existing stroke");
+static u_int atp_tap_minimum = 1;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RW,
+    &atp_tap_minimum, 1, "Minimum number of taps before detection");
+
 /*
  * Strokes which accumulate at least this amount of absolute movement
  * from the aggregate of their components are considered as
  * slides. Unit: mickeys.
  */
-static u_int atp_slide_min_movement = (ATP_SCALE_FACTOR >> 3);
+static u_int atp_slide_min_movement = 2 * ATP_SMALL_MOVEMENT_THRESHOLD;
 SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RW,
-    &atp_slide_min_movement, (ATP_SCALE_FACTOR >> 3),
+    &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD,
     "strokes with at least this amt. of movement are considered slides");
-TUNABLE_INT("hw.usb.atp.slide_min_movement", &atp_slide_min_movement);
 
 /*
  * The minimum age of a stroke for it to be considered mature; this
  * helps filter movements (noise) from immature strokes. Units: interrupts.
  */
-static u_int atp_stroke_maturity_threshold = 2;
+static u_int atp_stroke_maturity_threshold = 4;
 SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RW,
-    &atp_stroke_maturity_threshold, 2,
+    &atp_stroke_maturity_threshold, 4,
     "the minimum age of a stroke for it to be considered mature");
-TUNABLE_INT("hw.usb.atp.stroke_maturity_threshold",
-		&atp_stroke_maturity_threshold);
 
-/* Accept pressure readings from sensors only if above this value. */
-static u_int atp_sensor_noise_threshold = 2;
-SYSCTL_UINT(_hw_usb_atp, OID_AUTO, sensor_noise_threshold, CTLFLAG_RW,
-    &atp_sensor_noise_threshold, 2,
-    "accept pressure readings from sensors only if above this value");
+typedef enum atp_trackpad_family {
+	TRACKPAD_FAMILY_FOUNTAIN_GEYSER,
+	TRACKPAD_FAMILY_WELLSPRING,
+	TRACKPAD_FAMILY_MAX /* keep this at the tail end of the enumeration */
+} trackpad_family_t;
+
+enum fountain_geyser_product {
+	FOUNTAIN,
+	GEYSER1,
+	GEYSER1_17inch,
+	GEYSER2,
+	GEYSER3,
+	GEYSER4,
+	FOUNTAIN_GEYSER_PRODUCT_MAX /* keep this at the end */
+};
 
-/* Ignore pressure spans with cumulative press. below this value. */
-static u_int atp_pspan_min_cum_pressure = 10;
-SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_min_cum_pressure, CTLFLAG_RW,
-    &atp_pspan_min_cum_pressure, 10,
-    "ignore pressure spans with cumulative press. below this value");
+enum wellspring_product {
+	WELLSPRING1,
+	WELLSPRING2,
+	WELLSPRING3,
+	WELLSPRING4,
+	WELLSPRING4A,
+	WELLSPRING5,
+	WELLSPRING6A,
+	WELLSPRING6,
+	WELLSPRING5A,
+	WELLSPRING7,
+	WELLSPRING7A,
+	WELLSPRING8,
+	WELLSPRING_PRODUCT_MAX /* keep this at the end of the enumeration */
+};
 
-/* Maximum allowed width for pressure-spans.*/
-static u_int atp_pspan_max_width = 4;
-SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_max_width, CTLFLAG_RW,
-    &atp_pspan_max_width, 4,
-    "maximum allowed width (in sensors) for pressure-spans");
-
-/* We support three payload protocols */
-typedef enum {
-	ATP_PROT_GEYSER1,
-	ATP_PROT_GEYSER2,
-	ATP_PROT_GEYSER3,
-} atp_protocol;
-
-/* Define the various flavours of devices supported by this driver. */
-enum {
-	ATP_DEV_PARAMS_0,
-	ATP_DEV_PARAMS_PBOOK,
-	ATP_DEV_PARAMS_PBOOK_15A,
-	ATP_DEV_PARAMS_PBOOK_17,
-	ATP_N_DEV_PARAMS
+/* trackpad header types */
+enum fountain_geyser_trackpad_type {
+	FG_TRACKPAD_TYPE_GEYSER1,
+	FG_TRACKPAD_TYPE_GEYSER2,
+	FG_TRACKPAD_TYPE_GEYSER3,
+	FG_TRACKPAD_TYPE_GEYSER4,
 };
-struct atp_dev_params {
-	u_int            data_len;   /* for sensor data */
-	u_int            n_xsensors;
-	u_int            n_ysensors;
-	atp_protocol     prot;
-} atp_dev_params[ATP_N_DEV_PARAMS] = {
-	[ATP_DEV_PARAMS_0] = {
-		.data_len   = 64,
-		.n_xsensors = 20,
-		.n_ysensors = 10,
-		.prot       = ATP_PROT_GEYSER3
+enum wellspring_trackpad_type {
+	WSP_TRACKPAD_TYPE1,      /* plain trackpad */
+	WSP_TRACKPAD_TYPE2,      /* button integrated in trackpad */
+	WSP_TRACKPAD_TYPE3       /* additional header fields since June 2013 */
+};
+
+/*
+ * Trackpad family and product and family are encoded together in the
+ * driver_info value associated with a trackpad product.
+ */
+#define N_PROD_BITS 8  /* Number of bits used to encode product */
+#define ENCODE_DRIVER_INFO(FAMILY, PROD)      \
+    (((FAMILY) << N_PROD_BITS) | (PROD))
+#define DECODE_FAMILY_FROM_DRIVER_INFO(INFO)  ((INFO) >> N_PROD_BITS)
+#define DECODE_PRODUCT_FROM_DRIVER_INFO(INFO) \
+    ((INFO) & ((1 << N_PROD_BITS) - 1))
+
+#define FG_DRIVER_INFO(PRODUCT)               \
+    ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_FOUNTAIN_GEYSER, PRODUCT)
+#define WELLSPRING_DRIVER_INFO(PRODUCT)       \
+    ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_WELLSPRING, PRODUCT)
+
+/*
+ * The following structure captures the state of a pressure span along
+ * an axis. Each contact with the touchpad results in separate
+ * pressure spans along the two axes.
+ */
+typedef struct fg_pspan {
+	u_int width;       /* in units of sensors */
+	u_int cum;         /* cumulative compression (from all sensors) */
+	u_int cog;         /* center of gravity */
+	u_int loc;         /* location (scaled using the mickeys factor) */
+	boolean_t matched; /* to track pspans as they match against strokes. */
+} fg_pspan;
+
+#define FG_MAX_PSPANS_PER_AXIS 3
+#define FG_MAX_STROKES         (2 * FG_MAX_PSPANS_PER_AXIS)
+
+#define WELLSPRING_INTERFACE_INDEX 1
+
+/* trackpad finger data offsets, le16-aligned */
+#define WSP_TYPE1_FINGER_DATA_OFFSET  (13 * 2)
+#define WSP_TYPE2_FINGER_DATA_OFFSET  (15 * 2)
+#define WSP_TYPE3_FINGER_DATA_OFFSET  (19 * 2)
+
+/* trackpad button data offsets */
+#define WSP_TYPE2_BUTTON_DATA_OFFSET   15
+#define WSP_TYPE3_BUTTON_DATA_OFFSET   23
+
+/* list of device capability bits */
+#define HAS_INTEGRATED_BUTTON   1
+
+/* trackpad finger structure - little endian */
+struct wsp_finger_sensor_data {
+	int16_t origin;       /* zero when switching track finger */
+	int16_t abs_x;        /* absolute x coordinate */
+	int16_t abs_y;        /* absolute y coordinate */
+	int16_t rel_x;        /* relative x coordinate */
+	int16_t rel_y;        /* relative y coordinate */
+	int16_t tool_major;   /* tool area, major axis */
+	int16_t tool_minor;   /* tool area, minor axis */
+	int16_t orientation;  /* 16384 when point, else 15 bit angle */
+	int16_t touch_major;  /* touch area, major axis */
+	int16_t touch_minor;  /* touch area, minor axis */
+	int16_t unused[3];    /* zeros */
+	int16_t multi;        /* one finger: varies, more fingers: constant */
+} __packed;
+
+typedef struct wsp_finger {
+	/* to track fingers as they match against strokes. */
+	boolean_t matched;
+
+	/* location (scaled using the mickeys factor) */
+	int x;
+	int y;
+} wsp_finger_t;
+
+#define WSP_MAX_FINGERS               16
+#define WSP_SIZEOF_FINGER_SENSOR_DATA sizeof(struct wsp_finger_sensor_data)
+#define WSP_SIZEOF_ALL_FINGER_DATA    (WSP_MAX_FINGERS * \
+				       WSP_SIZEOF_FINGER_SENSOR_DATA)
+#define WSP_MAX_FINGER_ORIENTATION    16384
+
+#define ATP_SENSOR_DATA_BUF_MAX       1024
+#if (ATP_SENSOR_DATA_BUF_MAX < ((WSP_MAX_FINGERS * 14 * 2) + \
+				WSP_TYPE3_FINGER_DATA_OFFSET))
+/* note: 14 * 2 in the above is based on sizeof(struct wsp_finger_sensor_data)*/
+#error "ATP_SENSOR_DATA_BUF_MAX is too small"
+#endif
+
+#define ATP_MAX_STROKES               MAX(WSP_MAX_FINGERS, FG_MAX_STROKES)
+
+#define FG_MAX_XSENSORS 26
+#define FG_MAX_YSENSORS 16
+
+/* device-specific configuration */
+struct fg_dev_params {
+	u_int                              data_len;   /* for sensor data */
+	u_int                              n_xsensors;
+	u_int                              n_ysensors;
+	enum fountain_geyser_trackpad_type prot;
+};
+struct wsp_dev_params {
+	uint8_t  caps;               /* device capability bitmask */
+	uint8_t  tp_type;            /* type of trackpad interface */
+	uint8_t  finger_data_offset; /* offset to trackpad finger data */
+};
+
+static const struct fg_dev_params fg_dev_params[FOUNTAIN_GEYSER_PRODUCT_MAX] = {
+	[FOUNTAIN] = {
+		.data_len   = 81,
+		.n_xsensors = 16,
+		.n_ysensors = 16,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER1
 	},
-	[ATP_DEV_PARAMS_PBOOK] = {
+	[GEYSER1] = {
 		.data_len   = 81,
 		.n_xsensors = 16,
 		.n_ysensors = 16,
-		.prot       = ATP_PROT_GEYSER1
+		.prot       = FG_TRACKPAD_TYPE_GEYSER1
 	},
-	[ATP_DEV_PARAMS_PBOOK_15A] = {
+	[GEYSER1_17inch] = {
+		.data_len   = 81,
+		.n_xsensors = 26,
+		.n_ysensors = 16,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER1
+	},
+	[GEYSER2] = {
 		.data_len   = 64,
 		.n_xsensors = 15,
 		.n_ysensors = 9,
-		.prot       = ATP_PROT_GEYSER2
+		.prot       = FG_TRACKPAD_TYPE_GEYSER2
 	},
-	[ATP_DEV_PARAMS_PBOOK_17] = {
-		.data_len   = 81,
-		.n_xsensors = 26,
-		.n_ysensors = 16,
-		.prot       = ATP_PROT_GEYSER1
+	[GEYSER3] = {
+		.data_len   = 64,
+		.n_xsensors = 20,
+		.n_ysensors = 10,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER3
 	},
+	[GEYSER4] = {
+		.data_len   = 64,
+		.n_xsensors = 20,
+		.n_ysensors = 10,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER4
+	}
 };
 
-static const STRUCT_USB_HOST_ID atp_devs[] = {
+static const STRUCT_USB_HOST_ID fg_devs[] = {
+	/* PowerBooks Feb 2005, iBooks G4 */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x020e, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x020f, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0210, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x030a, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x030b, FG_DRIVER_INFO(GEYSER1)) },
+
+	/* PowerBooks Oct 2005 */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0214, FG_DRIVER_INFO(GEYSER2)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0215, FG_DRIVER_INFO(GEYSER2)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0216, FG_DRIVER_INFO(GEYSER2)) },
+
 	/* Core Duo MacBook & MacBook Pro */
-	{ USB_VPI(USB_VENDOR_APPLE, 0x0217, ATP_DEV_PARAMS_0) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x0218, ATP_DEV_PARAMS_0) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x0219, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0217, FG_DRIVER_INFO(GEYSER3)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0218, FG_DRIVER_INFO(GEYSER3)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0219, FG_DRIVER_INFO(GEYSER3)) },
 
 	/* Core2 Duo MacBook & MacBook Pro */
-	{ USB_VPI(USB_VENDOR_APPLE, 0x021a, ATP_DEV_PARAMS_0) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x021b, ATP_DEV_PARAMS_0) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x021c, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021a, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021b, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021c, FG_DRIVER_INFO(GEYSER4)) },
 
 	/* Core2 Duo MacBook3,1 */
-	{ USB_VPI(USB_VENDOR_APPLE, 0x0229, ATP_DEV_PARAMS_0) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x022a, ATP_DEV_PARAMS_0) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x022b, ATP_DEV_PARAMS_0) },
-
-	/* 12 inch PowerBook and iBook */
-	{ USB_VPI(USB_VENDOR_APPLE, 0x030a, ATP_DEV_PARAMS_PBOOK) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x030b, ATP_DEV_PARAMS_PBOOK) },
-
-	/* 15 inch PowerBook */
-	{ USB_VPI(USB_VENDOR_APPLE, 0x020e, ATP_DEV_PARAMS_PBOOK) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x020f, ATP_DEV_PARAMS_PBOOK) },
-	{ USB_VPI(USB_VENDOR_APPLE, 0x0215, ATP_DEV_PARAMS_PBOOK_15A) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0229, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x022a, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x022b, FG_DRIVER_INFO(GEYSER4)) },
 
 	/* 17 inch PowerBook */
-	{ USB_VPI(USB_VENDOR_APPLE, 0x020d, ATP_DEV_PARAMS_PBOOK_17) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x020d, FG_DRIVER_INFO(GEYSER1_17inch)) },
+};
 
+static const struct wsp_dev_params wsp_dev_params[WELLSPRING_PRODUCT_MAX] = {
+	[WELLSPRING1] = {
+		.caps       = 0,
+		.tp_type    = WSP_TRACKPAD_TYPE1,
+		.finger_data_offset  = WSP_TYPE1_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING2] = {
+		.caps       = 0,
+		.tp_type    = WSP_TRACKPAD_TYPE1,
+		.finger_data_offset  = WSP_TYPE1_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING3] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING4] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING4A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING5] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING6] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING5A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING6A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING7] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING7A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING8] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE3,
+		.finger_data_offset  = WSP_TYPE3_FINGER_DATA_OFFSET,
+	},
 };
 
-/*
- * The following structure captures the state of a pressure span along
- * an axis. Each contact with the touchpad results in separate
- * pressure spans along the two axes.
- */
-typedef struct atp_pspan {
-	u_int width;   /* in units of sensors */
-	u_int cum;     /* cumulative compression (from all sensors) */
-	u_int cog;     /* center of gravity */
-	u_int loc;     /* location (scaled using the mickeys factor) */
-	boolean_t matched; /* to track pspans as they match against strokes. */
-} atp_pspan;
+#define ATP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+
+/* TODO: STRUCT_USB_HOST_ID */
+static const struct usb_device_id wsp_devs[] = {
+	/* MacbookAir1.1 */
+	ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+	ATP_DEV(APPLE, WELLSPRING_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+	ATP_DEV(APPLE, WELLSPRING_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+
+	/* MacbookProPenryn, aka wellspring2 */
+	ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+	ATP_DEV(APPLE, WELLSPRING2_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+	ATP_DEV(APPLE, WELLSPRING2_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+
+	/* Macbook5,1 (unibody), aka wellspring3 */
+	ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+	ATP_DEV(APPLE, WELLSPRING3_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+	ATP_DEV(APPLE, WELLSPRING3_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+
+	/* MacbookAir3,2 (unibody), aka wellspring4 */
+	ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+	ATP_DEV(APPLE, WELLSPRING4_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+	ATP_DEV(APPLE, WELLSPRING4_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+
+	/* MacbookAir3,1 (unibody), aka wellspring4 */
+	ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+	ATP_DEV(APPLE, WELLSPRING4A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+	ATP_DEV(APPLE, WELLSPRING4A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+
+	/* Macbook8 (unibody, March 2011) */
+	ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+	ATP_DEV(APPLE, WELLSPRING5_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+	ATP_DEV(APPLE, WELLSPRING5_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+
+	/* MacbookAir4,1 (unibody, July 2011) */
+	ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+	ATP_DEV(APPLE, WELLSPRING6A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+	ATP_DEV(APPLE, WELLSPRING6A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+
+	/* MacbookAir4,2 (unibody, July 2011) */
+	ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+	ATP_DEV(APPLE, WELLSPRING6_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+	ATP_DEV(APPLE, WELLSPRING6_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+
+	/* Macbook8,2 (unibody) */
+	ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+	ATP_DEV(APPLE, WELLSPRING5A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+	ATP_DEV(APPLE, WELLSPRING5A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+
+	/* MacbookPro10,1 (unibody, June 2012) */
+	/* MacbookPro11,? (unibody, June 2013) */
+	ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+	ATP_DEV(APPLE, WELLSPRING7_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+	ATP_DEV(APPLE, WELLSPRING7_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+
+	/* MacbookPro10,2 (unibody, October 2012) */
+	ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+	ATP_DEV(APPLE, WELLSPRING7A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+	ATP_DEV(APPLE, WELLSPRING7A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+
+	/* MacbookAir6,2 (unibody, June 2013) */
+	ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+	ATP_DEV(APPLE, WELLSPRING8_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+	ATP_DEV(APPLE, WELLSPRING8_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+};
 
 typedef enum atp_stroke_type {
 	ATP_STROKE_TOUCH,
 	ATP_STROKE_SLIDE,
 } atp_stroke_type;
 
-#define ATP_MAX_PSPANS_PER_AXIS 3
+typedef enum atp_axis {
+	X = 0,
+	Y = 1,
+	NUM_AXES
+} atp_axis;
+
+#define ATP_FIFO_BUF_SIZE        8 /* bytes */
+#define ATP_FIFO_QUEUE_MAXLEN   50 /* units */
+
+enum {
+	ATP_INTR_DT,
+	ATP_RESET,
+	ATP_N_TRANSFER,
+};
 
-typedef struct atp_stroke_component {
+typedef struct fg_stroke_component {
 	/* Fields encapsulating the pressure-span. */
 	u_int loc;              /* location (scaled) */
 	u_int cum_pressure;     /* cumulative compression */
 	u_int max_cum_pressure; /* max cumulative compression */
 	boolean_t matched; /*to track components as they match against pspans.*/
 
-	/* Fields containing information about movement. */
 	int   delta_mickeys;    /* change in location (un-smoothened movement)*/
-	int   pending;          /* cum. of pending short movements */
-	int   movement;         /* current smoothened movement */
-} atp_stroke_component;
-
-typedef enum atp_axis {
-	X = 0,
-	Y = 1
-} atp_axis;
-
-#define ATP_MAX_STROKES         (2 * ATP_MAX_PSPANS_PER_AXIS)
+} fg_stroke_component_t;
 
 /*
  * The following structure captures a finger contact with the
  * touchpad. A stroke comprises two p-span components and some state.
  */
 typedef struct atp_stroke {
-	atp_stroke_type      type;
-	struct timeval       ctime; /* create time; for coincident siblings. */
-	u_int                age;   /*
-				     * Unit: interrupts; we maintain
-				     * this value in addition to
-				     * 'ctime' in order to avoid the
-				     * expensive call to microtime()
-				     * at every interrupt.
-				     */
-
-	atp_stroke_component components[2];
-	u_int                velocity_squared; /*
-						* Average magnitude (squared)
-						* of recent velocity.
-						*/
-	u_int                cum_movement; /* cum. absolute movement so far */
-
-	uint32_t             flags;  /* the state of this stroke */
-#define ATSF_ZOMBIE          0x1
-} atp_stroke;
+	TAILQ_ENTRY(atp_stroke) entry;
 
-#define ATP_FIFO_BUF_SIZE        8 /* bytes */
-#define ATP_FIFO_QUEUE_MAXLEN   50 /* units */
+	atp_stroke_type type;
+	uint32_t        flags; /* the state of this stroke */
+#define ATSF_ZOMBIE 0x1
+	boolean_t       matched;          /* to track match against fingers.*/
 
-enum {
-	ATP_INTR_DT,
-	ATP_RESET,
-	ATP_N_TRANSFER,
-};
+	struct timeval  ctime; /* create time; for coincident siblings. */
+
+	/*
+	 * Unit: interrupts; we maintain this value in
+	 * addition to 'ctime' in order to avoid the
+	 * expensive call to microtime() at every
+	 * interrupt.
+	 */
+	uint32_t age;
+
+	/* Location */
+	int x;
+	int y;
+
+	/* Fields containing information about movement. */
+	int   instantaneous_dx; /* curr. change in X location (un-smoothened) */
+	int   instantaneous_dy; /* curr. change in Y location (un-smoothened) */
+	int   pending_dx;       /* cum. of pending short movements */
+	int   pending_dy;       /* cum. of pending short movements */
+	int   movement_dx;      /* interpreted smoothened movement */
+	int   movement_dy;      /* interpreted smoothened movement */
+	int   cum_movement_x;   /* cum. horizontal movement */
+	int   cum_movement_y;   /* cum. vertical movement */
+
+	/*
+	 * The following member is relevant only for fountain-geyser trackpads.
+	 * For these, there is the need to track pressure-spans and cumulative
+	 * pressures for stroke components.
+	 */
+	fg_stroke_component_t components[NUM_AXES];
+} atp_stroke_t;
+
+struct atp_softc; /* forward declaration */
+typedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len);
 
 struct atp_softc {
-	device_t               sc_dev;
-	struct usb_device     *sc_usb_device;
-#define MODE_LENGTH 8
-	char                   sc_mode_bytes[MODE_LENGTH]; /* device mode */
-	struct mtx             sc_mutex; /* for synchronization */
-	struct usb_xfer       *sc_xfer[ATP_N_TRANSFER];
-	struct usb_fifo_sc     sc_fifo;
-
-	struct atp_dev_params *sc_params;
-
-	mousehw_t              sc_hw;
-	mousemode_t            sc_mode;
-	u_int                  sc_pollrate;
-	mousestatus_t          sc_status;
-	u_int                  sc_state;
-#define ATP_ENABLED            0x01
-#define ATP_ZOMBIES_EXIST      0x02
-#define ATP_DOUBLE_TAP_DRAG    0x04
-#define ATP_VALID              0x08
-
-	u_int                  sc_left_margin;
-	u_int                  sc_right_margin;
-
-	atp_stroke             sc_strokes[ATP_MAX_STROKES];
-	u_int                  sc_n_strokes;
-
-	int8_t                *sensor_data; /* from interrupt packet */
-	int                   *base_x;      /* base sensor readings */
-	int                   *base_y;
-	int                   *cur_x;       /* current sensor readings */
-	int                   *cur_y;
-	int                   *pressure_x;  /* computed pressures */
-	int                   *pressure_y;
-
-	u_int                  sc_idlecount; /* preceding idle interrupts */
-#define ATP_IDLENESS_THRESHOLD 10
-
-	struct timeval         sc_reap_time;
-	struct timeval         sc_reap_ctime; /*ctime of siblings to be reaped*/
+	device_t            sc_dev;
+	struct usb_device  *sc_usb_device;
+	struct lock         sc_lock; /* for synchronization */
+	struct usb_fifo_sc  sc_fifo;
+
+#define	MODE_LENGTH 8
+	char                sc_mode_bytes[MODE_LENGTH]; /* device mode */
+
+	trackpad_family_t   sc_family;
+	const void         *sc_params; /* device configuration */
+	sensor_data_interpreter_t sensor_data_interpreter;
+
+	mousehw_t           sc_hw;
+	mousemode_t         sc_mode;
+	mousestatus_t       sc_status;
+
+	u_int               sc_state;
+#define ATP_ENABLED          0x01
+#define ATP_ZOMBIES_EXIST    0x02
+#define ATP_DOUBLE_TAP_DRAG  0x04
+#define ATP_VALID            0x08
+
+	struct usb_xfer    *sc_xfer[ATP_N_TRANSFER];
+
+	u_int               sc_pollrate;
+	int                 sc_fflags;
+
+	atp_stroke_t        sc_strokes_data[ATP_MAX_STROKES];
+	TAILQ_HEAD(,atp_stroke) sc_stroke_free;
+	TAILQ_HEAD(,atp_stroke) sc_stroke_used;
+	u_int               sc_n_strokes;
+
+	struct usb_callout  sc_callout;
+
+	/*
+	 * button status. Set to non-zero if the mouse-button is physically
+	 * pressed. This state variable is exposed through softc to allow
+	 * reap_sibling_zombies to avoid registering taps while the trackpad
+	 * button is pressed.
+         */
+	uint8_t             sc_ibtn;
+
+	/*
+	 * Time when touch zombies were last reaped; useful for detecting
+	 * double-touch-n-drag.
+	 */
+	struct timeval      sc_touch_reap_time;
+
+	u_int	            sc_idlecount;
+
+	/* Regarding the data transferred from t-pad in USB INTR packets. */
+	u_int   sc_expected_sensor_data_len;
+	uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4);
+
+	int      sc_cur_x[FG_MAX_XSENSORS];      /* current sensor readings */
+	int      sc_cur_y[FG_MAX_YSENSORS];
+	int      sc_base_x[FG_MAX_XSENSORS];     /* base sensor readings */
+	int      sc_base_y[FG_MAX_YSENSORS];
+	int      sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */
+	int      sc_pressure_y[FG_MAX_YSENSORS];
+	fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS];
+	fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS];
 };
 
 /*
- * The last byte of the sensor data contains status bits; the
+ * The last byte of the fountain-geyser sensor data contains status bits; the
  * following values define the meanings of these bits.
+ * (only Geyser 3/4)
  */
-enum atp_status_bits {
-	ATP_STATUS_BUTTON      = (uint8_t)0x01, /* The button was pressed */
-	ATP_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
+enum geyser34_status_bits {
+	FG_STATUS_BUTTON      = (uint8_t)0x01, /* The button was pressed */
+	FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
 };
 
 typedef enum interface_mode {
-	RAW_SENSOR_MODE = (uint8_t)0x04,
+	RAW_SENSOR_MODE = (uint8_t)0x01,
 	HID_MODE        = (uint8_t)0x08
 } interface_mode;
 
+
 /*
  * function prototypes
  */
@@ -429,100 +745,183 @@ static struct usb_fifo_methods atp_fifo_methods = {
 };
 
 /* device initialization and shutdown */
-static usb_error_t   atp_req_get_report(struct usb_device *udev, void *data);
-static int           atp_set_device_mode(device_t dev, interface_mode mode);
-static void          atp_reset_callback(struct usb_xfer *, usb_error_t);
-static int           atp_enable(struct atp_softc *sc);
-static void          atp_disable(struct atp_softc *sc);
-static int           atp_softc_populate(struct atp_softc *);
-static void          atp_softc_unpopulate(struct atp_softc *);
+static usb_error_t   atp_set_device_mode(struct atp_softc *, interface_mode);
+static void	     atp_reset_callback(struct usb_xfer *, usb_error_t);
+static int	     atp_enable(struct atp_softc *);
+static void	     atp_disable(struct atp_softc *);
 
 /* sensor interpretation */
-static __inline void atp_interpret_sensor_data(const int8_t *, u_int, atp_axis,
-			 int *, atp_protocol);
-static __inline void atp_get_pressures(int *, const int *, const int *, int);
-static void          atp_detect_pspans(int *, u_int, u_int, atp_pspan *,
-			 u_int *);
+static void	     fg_interpret_sensor_data(struct atp_softc *, u_int);
+static void	     fg_extract_sensor_data(const int8_t *, u_int, atp_axis,
+    int *, enum fountain_geyser_trackpad_type);
+static void	     fg_get_pressures(int *, const int *, const int *, int);
+static void	     fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *);
+static void	     wsp_interpret_sensor_data(struct atp_softc *, u_int);
 
 /* movement detection */
-static boolean_t     atp_match_stroke_component(atp_stroke_component *,
-                         const atp_pspan *, atp_stroke_type);
-static void          atp_match_strokes_against_pspans(struct atp_softc *,
-			 atp_axis, atp_pspan *, u_int, u_int);
-static boolean_t     atp_update_strokes(struct atp_softc *,
-			 atp_pspan *, u_int, atp_pspan *, u_int);
-static __inline void atp_add_stroke(struct atp_softc *, const atp_pspan *,
-			 const atp_pspan *);
-static void          atp_add_new_strokes(struct atp_softc *, atp_pspan *,
-			 u_int, atp_pspan *, u_int);
-static void          atp_advance_stroke_state(struct atp_softc *,
-			 atp_stroke *, boolean_t *);
-static void          atp_terminate_stroke(struct atp_softc *, u_int);
-static __inline boolean_t atp_stroke_has_small_movement(const atp_stroke *);
-static __inline void atp_update_pending_mickeys(atp_stroke_component *);
-static void          atp_compute_smoothening_scale_ratio(atp_stroke *, int *,
-			 int *);
-static boolean_t     atp_compute_stroke_movement(atp_stroke *);
+static boolean_t     fg_match_stroke_component(fg_stroke_component_t *,
+    const fg_pspan *, atp_stroke_type);
+static void	     fg_match_strokes_against_pspans(struct atp_softc *,
+    atp_axis, fg_pspan *, u_int, u_int);
+static boolean_t     wsp_match_strokes_against_fingers(struct atp_softc *,
+    wsp_finger_t *, u_int);
+static boolean_t     fg_update_strokes(struct atp_softc *, fg_pspan *, u_int,
+    fg_pspan *, u_int);
+static boolean_t     wsp_update_strokes(struct atp_softc *,
+    wsp_finger_t [WSP_MAX_FINGERS], u_int);
+static void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *);
+static void	     fg_add_new_strokes(struct atp_softc *, fg_pspan *,
+    u_int, fg_pspan *, u_int);
+static void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *);
+static void	     atp_advance_stroke_state(struct atp_softc *,
+    atp_stroke_t *, boolean_t *);
+static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *);
+static void	     atp_update_pending_mickeys(atp_stroke_t *);
+static boolean_t     atp_compute_stroke_movement(atp_stroke_t *);
+static void	     atp_terminate_stroke(struct atp_softc *, atp_stroke_t *);
 
 /* tap detection */
-static __inline void atp_setup_reap_time(struct atp_softc *, struct timeval *);
-static void          atp_reap_zombies(struct atp_softc *, u_int *, u_int *);
-static void          atp_convert_to_slide(struct atp_softc *, atp_stroke *);
+static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *);
+static boolean_t atp_is_vertical_scroll(const atp_stroke_t *);
+static void	     atp_reap_sibling_zombies(void *);
+static void	     atp_convert_to_slide(struct atp_softc *, atp_stroke_t *);
 
 /* updating fifo */
-static void          atp_reset_buf(struct atp_softc *sc);
-static void          atp_add_to_queue(struct atp_softc *, int, int, uint32_t);
+static void	     atp_reset_buf(struct atp_softc *);
+static void	     atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t);
+
+/* Device methods. */
+static device_probe_t  atp_probe;
+static device_attach_t atp_attach;
+static device_detach_t atp_detach;
+static usb_callback_t  atp_intr;
+
+static const struct usb_config atp_xfer_config[ATP_N_TRANSFER] = {
+	[ATP_INTR_DT] = {
+		.type      = UE_INTERRUPT,
+		.endpoint  = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {
+			.pipe_bof = 1, /* block pipe on failure */
+			.short_xfer_ok = 1,
+		},
+		.bufsize   = ATP_SENSOR_DATA_BUF_MAX,
+		.callback  = &atp_intr,
+	},
+	[ATP_RESET] = {
+		.type      = UE_CONTROL,
+		.endpoint  = 0, /* Control pipe */
+		.direction = UE_DIR_ANY,
+		.bufsize   = sizeof(struct usb_device_request) + MODE_LENGTH,
+		.callback  = &atp_reset_callback,
+		.interval  = 0,  /* no pre-delay */
+	},
+};
+
+static atp_stroke_t *
+atp_alloc_stroke(struct atp_softc *sc)
+{
+	atp_stroke_t *pstroke;
+
+	pstroke = TAILQ_FIRST(&sc->sc_stroke_free);
+	if (pstroke == NULL)
+		goto done;
 
+	TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry);
+	memset(pstroke, 0, sizeof(*pstroke));
+	TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry);
 
-usb_error_t
-atp_req_get_report(struct usb_device *udev, void *data)
+	sc->sc_n_strokes++;
+done:
+	return (pstroke);
+}
+
+static void
+atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke)
 {
-	struct usb_device_request req;
+	if (pstroke == NULL)
+		return;
 
-	req.bmRequestType = UT_READ_CLASS_INTERFACE;
-	req.bRequest = UR_GET_REPORT;
-	USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
-	USETW(req.wIndex, 0);
-	USETW(req.wLength, MODE_LENGTH);
+	sc->sc_n_strokes--;
 
-	return (usbd_do_request(udev, NULL /* mutex */, &req, data));
+	TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry);
+	TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry);
 }
 
-static int
-atp_set_device_mode(device_t dev, interface_mode mode)
+static void
+atp_init_stroke_pool(struct atp_softc *sc)
 {
-	struct atp_softc     *sc;
-	usb_device_request_t  req;
-	usb_error_t           err;
+	u_int x;
 
-	if ((mode != RAW_SENSOR_MODE) && (mode != HID_MODE))
-		return (ENXIO);
+	TAILQ_INIT(&sc->sc_stroke_free);
+	TAILQ_INIT(&sc->sc_stroke_used);
 
-	sc = device_get_softc(dev);
+	sc->sc_n_strokes = 0;
 
-	sc->sc_mode_bytes[0] = mode;
-	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
-	req.bRequest = UR_SET_REPORT;
-	USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
-	USETW(req.wIndex, 0);
-	USETW(req.wLength, MODE_LENGTH);
-	err = usbd_do_request(sc->sc_usb_device, NULL, &req, sc->sc_mode_bytes);
-	if (err != USB_ERR_NORMAL_COMPLETION)
-		return (ENXIO);
+	memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data));
 
-	return (0);
+	for (x = 0; x != ATP_MAX_STROKES; x++) {
+		TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x],
+		    entry);
+	}
 }
 
-void
+static usb_error_t
+atp_set_device_mode(struct atp_softc *sc, interface_mode newMode)
+{
+	uint8_t mode_value;
+	usb_error_t err;
+
+	if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE))
+		return (USB_ERR_INVAL);
+
+	if ((newMode == RAW_SENSOR_MODE) &&
+	    (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER))
+		mode_value = (uint8_t)0x04;
+	else
+		mode_value = newMode;
+
+	err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */,
+	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
+	    0x03 /* type */, 0x00 /* id */);
+	if (err != USB_ERR_NORMAL_COMPLETION) {
+		DPRINTF("Failed to read device mode (%d)\n", err);
+		return (err);
+	}
+
+	if (sc->sc_mode_bytes[0] == mode_value)
+		return (err);
+
+	/*
+	 * XXX Need to wait at least 250ms for hardware to get
+	 * ready. The device mode handling appears to be handled
+	 * asynchronously and we should not issue these commands too
+	 * quickly.
+	 */
+	tsleep(&atp_set_device_mode, 0, "WHW", hz / 4);
+
+	sc->sc_mode_bytes[0] = mode_value;
+	return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */,
+	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
+	    0x03 /* type */, 0x00 /* id */));
+}
+
+static void
 atp_reset_callback(struct usb_xfer *xfer, usb_error_t error)
 {
 	usb_device_request_t   req;
 	struct usb_page_cache *pc;
 	struct atp_softc      *sc = usbd_xfer_softc(xfer);
 
+	uint8_t mode_value;
+	if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)
+		mode_value = 0x04;
+	else
+		mode_value = RAW_SENSOR_MODE;
+
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_SETUP:
-		sc->sc_mode_bytes[0] = RAW_SENSOR_MODE;
+		sc->sc_mode_bytes[0] = mode_value;
 		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
 		req.bRequest = UR_SET_REPORT;
 		USETW2(req.wValue,
@@ -550,17 +949,14 @@ atp_reset_callback(struct usb_xfer *xfer, usb_error_t error)
 static int
 atp_enable(struct atp_softc *sc)
 {
-	/* Allocate the dynamic buffers */
-	if (atp_softc_populate(sc) != 0) {
-		atp_softc_unpopulate(sc);
-		return (ENOMEM);
-	}
+	if (sc->sc_state & ATP_ENABLED)
+		return (0);
 
 	/* reset status */
-	memset(sc->sc_strokes, 0, sizeof(sc->sc_strokes));
-	sc->sc_n_strokes = 0;
 	memset(&sc->sc_status, 0, sizeof(sc->sc_status));
-	sc->sc_idlecount = 0;
+
+	atp_init_stroke_pool(sc);
+
 	sc->sc_state |= ATP_ENABLED;
 
 	DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
@@ -570,126 +966,94 @@ atp_enable(struct atp_softc *sc)
 static void
 atp_disable(struct atp_softc *sc)
 {
-	atp_softc_unpopulate(sc);
-
 	sc->sc_state &= ~(ATP_ENABLED | ATP_VALID);
 	DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
 }
 
-/* Allocate dynamic memory for some fields in softc. */
-static int
-atp_softc_populate(struct atp_softc *sc)
+static void
+fg_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
 {
-	const struct atp_dev_params *params = sc->sc_params;
-
-	if (params == NULL) {
-		DPRINTF("params uninitialized!\n");
-		return (ENXIO);
-	}
-	if (params->data_len) {
-		sc->sensor_data = malloc(params->data_len * sizeof(int8_t),
-		    M_USB, M_WAITOK);
-		if (sc->sensor_data == NULL) {
-			DPRINTF("mem for sensor_data\n");
-			return (ENXIO);
-		}
-	}
-
-	if (params->n_xsensors != 0) {
-		sc->base_x = malloc(params->n_xsensors * sizeof(*(sc->base_x)),
-		    M_USB, M_WAITOK);
-		if (sc->base_x == NULL) {
-			DPRINTF("mem for sc->base_x\n");
-			return (ENXIO);
-		}
-
-		sc->cur_x = malloc(params->n_xsensors * sizeof(*(sc->cur_x)),
-		    M_USB, M_WAITOK);
-		if (sc->cur_x == NULL) {
-			DPRINTF("mem for sc->cur_x\n");
-			return (ENXIO);
-		}
-
-		sc->pressure_x =
-			malloc(params->n_xsensors * sizeof(*(sc->pressure_x)),
-			    M_USB, M_WAITOK);
-		if (sc->pressure_x == NULL) {
-			DPRINTF("mem. for pressure_x\n");
-			return (ENXIO);
-		}
-	}
+	u_int n_xpspans = 0;
+	u_int n_ypspans = 0;
+	uint8_t status_bits;
 
-	if (params->n_ysensors != 0) {
-		sc->base_y = malloc(params->n_ysensors * sizeof(*(sc->base_y)),
-		    M_USB, M_WAITOK);
-		if (sc->base_y == NULL) {
-			DPRINTF("mem for base_y\n");
-			return (ENXIO);
-		}
+	const struct fg_dev_params *params =
+	    (const struct fg_dev_params *)sc->sc_params;
 
-		sc->cur_y = malloc(params->n_ysensors * sizeof(*(sc->cur_y)),
-		    M_USB, M_WAITOK);
-		if (sc->cur_y == NULL) {
-			DPRINTF("mem for cur_y\n");
-			return (ENXIO);
-		}
+	fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X,
+	    sc->sc_cur_x, params->prot);
+	fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y,
+	    sc->sc_cur_y, params->prot);
 
-		sc->pressure_y =
-			malloc(params->n_ysensors * sizeof(*(sc->pressure_y)),
-			    M_USB, M_WAITOK);
-		if (sc->pressure_y == NULL) {
-			DPRINTF("mem. for pressure_y\n");
-			return (ENXIO);
+	/*
+	 * If this is the initial update (from an untouched
+	 * pad), we should set the base values for the sensor
+	 * data; deltas with respect to these base values can
+	 * be used as pressure readings subsequently.
+	 */
+	status_bits = sc->sc_sensor_data[params->data_len - 1];
+	if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) ||
+	     (params->prot == FG_TRACKPAD_TYPE_GEYSER4))  &&
+	    ((sc->sc_state & ATP_VALID) == 0)) {
+		if (status_bits & FG_STATUS_BASE_UPDATE) {
+			memcpy(sc->sc_base_x, sc->sc_cur_x,
+			    params->n_xsensors * sizeof(*sc->sc_base_x));
+			memcpy(sc->sc_base_y, sc->sc_cur_y,
+			    params->n_ysensors * sizeof(*sc->sc_base_y));
+			sc->sc_state |= ATP_VALID;
+			return;
 		}
 	}
 
-	return (0);
-}
+	/* Get pressure readings and detect p-spans for both axes. */
+	fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x,
+	    params->n_xsensors);
+	fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors,
+	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans);
+	fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y,
+	    params->n_ysensors);
+	fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors,
+	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans);
 
-/* Free dynamic memory allocated for some fields in softc. */
-static void
-atp_softc_unpopulate(struct atp_softc *sc)
-{
-	const struct atp_dev_params *params = sc->sc_params;
+	/* Update strokes with new pspans to detect movements. */
+	if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans))
+		sc->sc_status.flags |= MOUSE_POSCHANGED;
 
-	if (params == NULL) {
-		return;
-	}
-	if (params->n_xsensors != 0) {
-		if (sc->base_x != NULL) {
-			free(sc->base_x, M_USB);
-			sc->base_x = NULL;
-		}
+	sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0;
+	sc->sc_status.button = sc->sc_ibtn;
 
-		if (sc->cur_x != NULL) {
-			free(sc->cur_x, M_USB);
-			sc->cur_x = NULL;
-		}
+	/*
+	 * The Fountain/Geyser device continues to trigger interrupts
+	 * at a fast rate even after touchpad activity has
+	 * stopped. Upon detecting that the device has remained idle
+	 * beyond a threshold, we reinitialize it to silence the
+	 * interrupts.
+	 */
+	if ((sc->sc_status.flags  == 0) && (sc->sc_n_strokes == 0)) {
+		sc->sc_idlecount++;
+		if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) {
+			/*
+			 * Use the last frame before we go idle for
+			 * calibration on pads which do not send
+			 * calibration frames.
+			 */
+			const struct fg_dev_params *params =
+			    (const struct fg_dev_params *)sc->sc_params;
 
-		if (sc->pressure_x != NULL) {
-			free(sc->pressure_x, M_USB);
-			sc->pressure_x = NULL;
-		}
-	}
-	if (params->n_ysensors != 0) {
-		if (sc->base_y != NULL) {
-			free(sc->base_y, M_USB);
-			sc->base_y = NULL;
-		}
+			DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
 
-		if (sc->cur_y != NULL) {
-			free(sc->cur_y, M_USB);
-			sc->cur_y = NULL;
-		}
+			if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) {
+				memcpy(sc->sc_base_x, sc->sc_cur_x,
+				    params->n_xsensors * sizeof(*(sc->sc_base_x)));
+				memcpy(sc->sc_base_y, sc->sc_cur_y,
+				    params->n_ysensors * sizeof(*(sc->sc_base_y)));
+			}
 
-		if (sc->pressure_y != NULL) {
-			free(sc->pressure_y, M_USB);
-			sc->pressure_y = NULL;
+			sc->sc_idlecount = 0;
+			usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
 		}
-	}
-	if (sc->sensor_data != NULL) {
-		free(sc->sensor_data, M_USB);
-		sc->sensor_data = NULL;
+	} else {
+		sc->sc_idlecount = 0;
 	}
 }
 
@@ -719,15 +1083,15 @@ atp_softc_unpopulate(struct atp_softc *sc)
  *   prot
  *       The protocol to use to interpret the data
  */
-static __inline void
-atp_interpret_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
-    int	*arr, atp_protocol prot)
+static void
+fg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
+    int	*arr, enum fountain_geyser_trackpad_type prot)
 {
 	u_int i;
 	u_int di;   /* index into sensor data */
 
 	switch (prot) {
-	case ATP_PROT_GEYSER1:
+	case FG_TRACKPAD_TYPE_GEYSER1:
 		/*
 		 * For Geyser 1, the sensors are laid out in pairs
 		 * every 5 bytes.
@@ -735,24 +1099,33 @@ atp_interpret_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
 		for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) {
 			arr[i] = sensor_data[di];
 			arr[i+8] = sensor_data[di+2];
-			if (axis == X && num > 16)
+			if ((axis == X) && (num > 16))
 				arr[i+16] = sensor_data[di+40];
 		}
 
 		break;
-	case ATP_PROT_GEYSER2:
-	case ATP_PROT_GEYSER3:
+	case FG_TRACKPAD_TYPE_GEYSER2:
+		for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) {
+			arr[i++] = sensor_data[di++];
+			arr[i++] = sensor_data[di++];
+			di++;
+		}
+		break;
+	case FG_TRACKPAD_TYPE_GEYSER3:
+	case FG_TRACKPAD_TYPE_GEYSER4:
 		for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) {
 			arr[i++] = sensor_data[di++];
 			arr[i++] = sensor_data[di++];
 			di++;
 		}
 		break;
+	default:
+		break;
 	}
 }
 
-static __inline void
-atp_get_pressures(int *p, const int *cur, const int *base, int n)
+static void
+fg_get_pressures(int *p, const int *cur, const int *base, int n)
 {
 	int i;
 
@@ -770,24 +1143,24 @@ atp_get_pressures(int *p, const int *cur, const int *base, int n)
 		 * threshold; this will reduce the contribution from
 		 * lower pressure readings.
 		 */
-		if (p[i] <= atp_sensor_noise_threshold)
+		if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD)
 			p[i] = 0; /* filter away noise */
 		else
-			p[i] -= atp_sensor_noise_threshold;
+			p[i] -= FG_SENSOR_NOISE_THRESHOLD;
 	}
 }
 
 static void
-atp_detect_pspans(int *p, u_int num_sensors,
-    u_int       max_spans, /* max # of pspans permitted */
-    atp_pspan  *spans,     /* finger spans */
-    u_int      *nspans_p)  /* num spans detected */
+fg_detect_pspans(int *p, u_int num_sensors,
+    u_int      max_spans, /* max # of pspans permitted */
+    fg_pspan  *spans,     /* finger spans */
+    u_int     *nspans_p)  /* num spans detected */
 {
 	u_int i;
 	int   maxp;             /* max pressure seen within a span */
 	u_int num_spans = 0;
 
-	enum atp_pspan_state {
+	enum fg_pspan_state {
 		ATP_PSPAN_INACTIVE,
 		ATP_PSPAN_INCREASING,
 		ATP_PSPAN_DECREASING,
@@ -797,7 +1170,7 @@ atp_detect_pspans(int *p, u_int num_sensors,
 	 * The following is a simple state machine to track
 	 * the phase of the pressure span.
 	 */
-	memset(spans, 0, max_spans * sizeof(atp_pspan));
+	memset(spans, 0, max_spans * sizeof(fg_pspan));
 	maxp = 0;
 	state = ATP_PSPAN_INACTIVE;
 	for (i = 0; i < num_sensors; i++) {
@@ -862,11 +1235,11 @@ atp_detect_pspans(int *p, u_int num_sensors,
 	/* post-process the spans */
 	for (i = 0; i < num_spans; i++) {
 		/* filter away unwanted pressure spans */
-		if ((spans[i].cum < atp_pspan_min_cum_pressure) ||
-		    (spans[i].width > atp_pspan_max_width)) {
+		if ((spans[i].cum < FG_PSPAN_MIN_CUM_PRESSURE) ||
+		    (spans[i].width > FG_PSPAN_MAX_WIDTH)) {
 			if ((i + 1) < num_spans) {
 				memcpy(&spans[i], &spans[i + 1],
-				    (num_spans - i - 1) * sizeof(atp_pspan));
+				    (num_spans - i - 1) * sizeof(fg_pspan));
 				i--;
 			}
 			num_spans--;
@@ -874,30 +1247,104 @@ atp_detect_pspans(int *p, u_int num_sensors,
 		}
 
 		/* compute this span's representative location */
-		spans[i].loc = spans[i].cog * atp_mickeys_scale_factor /
+		spans[i].loc = spans[i].cog * FG_SCALE_FACTOR /
 			spans[i].cum;
 
-		spans[i].matched = FALSE; /* not yet matched against a stroke */
+		spans[i].matched = false; /* not yet matched against a stroke */
 	}
 
 	*nspans_p = num_spans;
 }
 
+static void
+wsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
+{
+	const struct wsp_dev_params *params = sc->sc_params;
+	wsp_finger_t fingers[WSP_MAX_FINGERS];
+	struct wsp_finger_sensor_data *source_fingerp;
+	u_int n_source_fingers;
+	u_int n_fingers;
+	u_int i;
+
+	/* validate sensor data length */
+	if ((data_len < params->finger_data_offset) ||
+	    ((data_len - params->finger_data_offset) %
+	     WSP_SIZEOF_FINGER_SENSOR_DATA) != 0)
+		return;
+
+	/* compute number of source fingers */
+	n_source_fingers = (data_len - params->finger_data_offset) /
+	    WSP_SIZEOF_FINGER_SENSOR_DATA;
+
+	if (n_source_fingers > WSP_MAX_FINGERS)
+		n_source_fingers = WSP_MAX_FINGERS;
+
+	/* iterate over the source data collecting useful fingers */
+	n_fingers = 0;
+	source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data +
+	     params->finger_data_offset);
+
+	for (i = 0; i < n_source_fingers; i++, source_fingerp++) {
+		/* swap endianness, if any */
+		if (le16toh(0x1234) != 0x1234) {
+			source_fingerp->origin      = le16toh((uint16_t)source_fingerp->origin);
+			source_fingerp->abs_x       = le16toh((uint16_t)source_fingerp->abs_x);
+			source_fingerp->abs_y       = le16toh((uint16_t)source_fingerp->abs_y);
+			source_fingerp->rel_x       = le16toh((uint16_t)source_fingerp->rel_x);
+			source_fingerp->rel_y       = le16toh((uint16_t)source_fingerp->rel_y);
+			source_fingerp->tool_major  = le16toh((uint16_t)source_fingerp->tool_major);
+			source_fingerp->tool_minor  = le16toh((uint16_t)source_fingerp->tool_minor);
+			source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation);
+			source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major);
+			source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor);
+			source_fingerp->multi       = le16toh((uint16_t)source_fingerp->multi);
+		}
+
+		/* check for minium threshold */
+		if (source_fingerp->touch_major == 0)
+			continue;
+
+		fingers[n_fingers].matched = false;
+		fingers[n_fingers].x       = source_fingerp->abs_x;
+		fingers[n_fingers].y       = -source_fingerp->abs_y;
+
+		n_fingers++;
+	}
+
+	if ((sc->sc_n_strokes == 0) && (n_fingers == 0))
+		return;
+
+	if (wsp_update_strokes(sc, fingers, n_fingers))
+		sc->sc_status.flags |= MOUSE_POSCHANGED;
+
+	switch(params->tp_type) {
+	case WSP_TRACKPAD_TYPE2:
+		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET];
+		break;
+	case WSP_TRACKPAD_TYPE3:
+		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET];
+		break;
+	default:
+		break;
+	}
+	sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0;
+}
+
 /*
  * Match a pressure-span against a stroke-component. If there is a
- * match, update the component's state and return TRUE.
+ * match, update the component's state and return true.
  */
 static boolean_t
-atp_match_stroke_component(atp_stroke_component *component,
-    const atp_pspan *pspan, atp_stroke_type stroke_type)
+fg_match_stroke_component(fg_stroke_component_t *component,
+    const fg_pspan *pspan, atp_stroke_type stroke_type)
 {
 	int   delta_mickeys;
 	u_int min_pressure;
 
 	delta_mickeys = pspan->loc - component->loc;
 
-	if (abs(delta_mickeys) > atp_max_delta_mickeys)
-		return (FALSE); /* the finger span is too far out; no match */
+	if (abs(delta_mickeys) > (int)FG_MAX_DELTA_MICKEYS)
+		return (false); /* the finger span is too far out; no match */
 
 	component->loc = pspan->loc;
 
@@ -929,72 +1376,126 @@ atp_match_stroke_component(atp_stroke_component *component,
 		delta_mickeys = 0;
 
 	component->delta_mickeys = delta_mickeys;
-	return (TRUE);
+	return (true);
 }
 
 static void
-atp_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
-    atp_pspan *pspans, u_int n_pspans, u_int repeat_count)
+fg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
+    fg_pspan *pspans, u_int n_pspans, u_int repeat_count)
 {
-	u_int i, j;
+	atp_stroke_t *strokep;
 	u_int repeat_index = 0;
+	u_int i;
 
 	/* Determine the index of the multi-span. */
 	if (repeat_count) {
-		u_int cum = 0;
 		for (i = 0; i < n_pspans; i++) {
-			if (pspans[i].cum > cum) {
+			if (pspans[i].cum > pspans[repeat_index].cum)
 				repeat_index = i;
-				cum = pspans[i].cum;
-			}
 		}
 	}
 
-	for (i = 0; i < sc->sc_n_strokes; i++) {
-		atp_stroke *stroke  = &sc->sc_strokes[i];
-		if (stroke->components[axis].matched)
+	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+		if (strokep->components[axis].matched)
 			continue; /* skip matched components */
 
-		for (j = 0; j < n_pspans; j++) {
-			if (pspans[j].matched)
+		for (i = 0; i < n_pspans; i++) {
+			if (pspans[i].matched)
 				continue; /* skip matched pspans */
 
-			if (atp_match_stroke_component(
-				    &stroke->components[axis], &pspans[j],
-				    stroke->type)) {
+			if (fg_match_stroke_component(
+			    &strokep->components[axis], &pspans[i],
+			    strokep->type)) {
+
 				/* There is a match. */
-				stroke->components[axis].matched = TRUE;
+				strokep->components[axis].matched = true;
 
 				/* Take care to repeat at the multi-span. */
-				if ((repeat_count > 0) && (j == repeat_index))
+				if ((repeat_count > 0) && (i == repeat_index))
 					repeat_count--;
 				else
-					pspans[j].matched = TRUE;
+					pspans[i].matched = true;
 
-				break; /* skip to the next stroke */
+				break; /* skip to the next strokep */
 			}
 		} /* loop over pspans */
 	} /* loop over strokes */
 }
 
+static boolean_t
+wsp_match_strokes_against_fingers(struct atp_softc *sc,
+    wsp_finger_t *fingers, u_int n_fingers)
+{
+	boolean_t movement = false;
+	atp_stroke_t *strokep;
+	u_int i;
+
+	/* reset the matched status for all strokes */
+	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry)
+		strokep->matched = false;
+
+	for (i = 0; i != n_fingers; i++) {
+		u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ;
+		atp_stroke_t *strokep_best = NULL;
+
+		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+			int instantaneous_dx;
+			int instantaneous_dy;
+			u_int d_squared;
+
+			if (strokep->matched)
+				continue;
+
+			instantaneous_dx = fingers[i].x - strokep->x;
+			instantaneous_dy = fingers[i].y - strokep->y;
+
+			/* skip strokes which are far away */
+			d_squared =
+			    (instantaneous_dx * instantaneous_dx) +
+			    (instantaneous_dy * instantaneous_dy);
+
+			if (d_squared < least_distance_sq) {
+				least_distance_sq = d_squared;
+				strokep_best = strokep;
+			}
+		}
+
+		strokep = strokep_best;
+
+		if (strokep != NULL) {
+			fingers[i].matched = true;
+
+			strokep->matched          = true;
+			strokep->instantaneous_dx = fingers[i].x - strokep->x;
+			strokep->instantaneous_dy = fingers[i].y - strokep->y;
+			strokep->x                = fingers[i].x;
+			strokep->y                = fingers[i].y;
+
+			atp_advance_stroke_state(sc, strokep, &movement);
+		}
+	}
+	return (movement);
+}
+
 /*
  * Update strokes by matching against current pressure-spans.
- * Return TRUE if any movement is detected.
+ * Return true if any movement is detected.
  */
 static boolean_t
-atp_update_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
-    u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans)
+fg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
 {
-	u_int       i, j;
-	atp_stroke *stroke;
-	boolean_t   movement = FALSE;
-	u_int       repeat_count = 0;
+	atp_stroke_t *strokep;
+	atp_stroke_t *strokep_next;
+	boolean_t movement = false;
+	u_int repeat_count = 0;
+	u_int i;
+	u_int j;
 
 	/* Reset X and Y components of all strokes as unmatched. */
-	for (i = 0; i < sc->sc_n_strokes; i++) {
-		stroke = &sc->sc_strokes[i];
-		stroke->components[X].matched = FALSE;
-		stroke->components[Y].matched = FALSE;
+	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+		strokep->components[X].matched = false;
+		strokep->components[Y].matched = false;
 	}
 
 	/*
@@ -1033,35 +1534,40 @@ atp_update_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
 	 */
 	repeat_count = abs(n_xpspans - n_ypspans);
 
-	atp_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
+	fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
 	    (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
 		repeat_count : 0));
-	atp_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
+	fg_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
 	    (((repeat_count != 0) && (n_ypspans < n_xpspans)) ?
 		repeat_count : 0));
 
 	/* Update the state of strokes based on the above pspan matches. */
-	for (i = 0; i < sc->sc_n_strokes; i++) {
-		stroke = &sc->sc_strokes[i];
-		if (stroke->components[X].matched &&
-		    stroke->components[Y].matched) {
-			atp_advance_stroke_state(sc, stroke, &movement);
+	TAILQ_FOREACH_MUTABLE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+
+		if (strokep->components[X].matched &&
+		    strokep->components[Y].matched) {
+			strokep->matched = true;
+			strokep->instantaneous_dx =
+			    strokep->components[X].delta_mickeys;
+			strokep->instantaneous_dy =
+			    strokep->components[Y].delta_mickeys;
+			atp_advance_stroke_state(sc, strokep, &movement);
 		} else {
 			/*
 			 * At least one component of this stroke
 			 * didn't match against current pspans;
 			 * terminate it.
 			 */
-			atp_terminate_stroke(sc, i);
+			atp_terminate_stroke(sc, strokep);
 		}
 	}
 
 	/* Add new strokes for pairs of unmatched pspans */
 	for (i = 0; i < n_xpspans; i++) {
-		if (pspans_x[i].matched == FALSE) break;
+		if (pspans_x[i].matched == false) break;
 	}
 	for (j = 0; j < n_ypspans; j++) {
-		if (pspans_y[j].matched == FALSE) break;
+		if (pspans_y[j].matched == false) break;
 	}
 	if ((i < n_xpspans) && (j < n_ypspans)) {
 #ifdef USB_DEBUG
@@ -1084,108 +1590,135 @@ atp_update_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
 #endif /* USB_DEBUG */
 		if ((n_xpspans == 1) && (n_ypspans == 1))
 			/* The common case of a single pair of new pspans. */
-			atp_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
+			fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
 		else
-			atp_add_new_strokes(sc,
-			    pspans_x, n_xpspans,
+			fg_add_new_strokes(sc, pspans_x, n_xpspans,
 			    pspans_y, n_ypspans);
 	}
 
 #ifdef USB_DEBUG
 	if (atp_debug >= ATP_LLEVEL_INFO) {
-		for (i = 0; i < sc->sc_n_strokes; i++) {
-			atp_stroke *stroke = &sc->sc_strokes[i];
-
-			printf(" %s%clc:%u,dm:%d,pnd:%d,cum:%d,max:%d,mv:%d%c"
-			    ",%clc:%u,dm:%d,pnd:%d,cum:%d,max:%d,mv:%d%c",
-			    (stroke->flags & ATSF_ZOMBIE) ? "zomb:" : "",
-			    (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<',
-			    stroke->components[X].loc,
-			    stroke->components[X].delta_mickeys,
-			    stroke->components[X].pending,
-			    stroke->components[X].cum_pressure,
-			    stroke->components[X].max_cum_pressure,
-			    stroke->components[X].movement,
-			    (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>',
-			    (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<',
-			    stroke->components[Y].loc,
-			    stroke->components[Y].delta_mickeys,
-			    stroke->components[Y].pending,
-			    stroke->components[Y].cum_pressure,
-			    stroke->components[Y].max_cum_pressure,
-			    stroke->components[Y].movement,
-			    (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>');
+		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+			printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c"
+			    ",%clc:%u,dm:%d,cum:%d,max:%d,%c",
+			    (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "",
+			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
+			    strokep->components[X].loc,
+			    strokep->components[X].delta_mickeys,
+			    strokep->components[X].cum_pressure,
+			    strokep->components[X].max_cum_pressure,
+			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>',
+			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
+			    strokep->components[Y].loc,
+			    strokep->components[Y].delta_mickeys,
+			    strokep->components[Y].cum_pressure,
+			    strokep->components[Y].max_cum_pressure,
+			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>');
 		}
-		if (sc->sc_n_strokes)
+		if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL)
 			printf("\n");
 	}
 #endif /* USB_DEBUG */
+	return (movement);
+}
+
+/*
+ * Update strokes by matching against current pressure-spans.
+ * Return true if any movement is detected.
+ */
+static boolean_t
+wsp_update_strokes(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers)
+{
+	boolean_t movement = false;
+	atp_stroke_t *strokep_next;
+	atp_stroke_t *strokep;
+	u_int i;
+
+	if (sc->sc_n_strokes > 0) {
+		movement = wsp_match_strokes_against_fingers(
+		    sc, fingers, n_fingers);
 
+		/* handle zombie strokes */
+		TAILQ_FOREACH_MUTABLE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+			if (strokep->matched)
+				continue;
+			atp_terminate_stroke(sc, strokep);
+		}
+	}
+
+	/* initialize unmatched fingers as strokes */
+	for (i = 0; i != n_fingers; i++) {
+		if (fingers[i].matched)
+			continue;
+
+		wsp_add_stroke(sc, fingers + i);
+	}
 	return (movement);
 }
 
 /* Initialize a stroke using a pressure-span. */
-static __inline void
-atp_add_stroke(struct atp_softc *sc, const atp_pspan *pspan_x,
-    const atp_pspan *pspan_y)
+static void
+fg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x,
+    const fg_pspan *pspan_y)
 {
-	atp_stroke *stroke;
+	atp_stroke_t *strokep;
 
-	if (sc->sc_n_strokes >= ATP_MAX_STROKES)
+	strokep = atp_alloc_stroke(sc);
+	if (strokep == NULL)
 		return;
-	stroke = &sc->sc_strokes[sc->sc_n_strokes];
-
-	memset(stroke, 0, sizeof(atp_stroke));
 
 	/*
 	 * Strokes begin as potential touches. If a stroke survives
 	 * longer than a threshold, or if it records significant
 	 * cumulative movement, then it is considered a 'slide'.
 	 */
-	stroke->type = ATP_STROKE_TOUCH;
-	microtime(&stroke->ctime);
-	stroke->age  = 1;       /* Unit: interrupts */
+	strokep->type    = ATP_STROKE_TOUCH;
+	strokep->matched = false;
+	microtime(&strokep->ctime);
+	strokep->age     = 1;		/* number of interrupts */
+	strokep->x       = pspan_x->loc;
+	strokep->y       = pspan_y->loc;
+
+	strokep->components[X].loc              = pspan_x->loc;
+	strokep->components[X].cum_pressure     = pspan_x->cum;
+	strokep->components[X].max_cum_pressure = pspan_x->cum;
+	strokep->components[X].matched          = true;
+
+	strokep->components[Y].loc              = pspan_y->loc;
+	strokep->components[Y].cum_pressure     = pspan_y->cum;
+	strokep->components[Y].max_cum_pressure = pspan_y->cum;
+	strokep->components[Y].matched          = true;
 
-	stroke->components[X].loc              = pspan_x->loc;
-	stroke->components[X].cum_pressure     = pspan_x->cum;
-	stroke->components[X].max_cum_pressure = pspan_x->cum;
-	stroke->components[X].matched          = TRUE;
-
-	stroke->components[Y].loc              = pspan_y->loc;
-	stroke->components[Y].cum_pressure     = pspan_y->cum;
-	stroke->components[Y].max_cum_pressure = pspan_y->cum;
-	stroke->components[Y].matched          = TRUE;
-
-	sc->sc_n_strokes++;
 	if (sc->sc_n_strokes > 1) {
 		/* Reset double-tap-n-drag if we have more than one strokes. */
 		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
 	}
 
 	DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n",
-	    stroke->components[X].loc,
-	    stroke->components[Y].loc,
-	    (unsigned int)stroke->ctime.tv_sec,
-	    (unsigned long int)stroke->ctime.tv_usec);
+	    strokep->components[X].loc,
+	    strokep->components[Y].loc,
+	    (u_int)strokep->ctime.tv_sec,
+	    (unsigned long int)strokep->ctime.tv_usec);
 }
 
 static void
-atp_add_new_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
-    u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans)
+fg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
 {
-	int       i, j;
-	atp_pspan spans[2][ATP_MAX_PSPANS_PER_AXIS];
-	u_int     nspans[2];
+	fg_pspan spans[2][FG_MAX_PSPANS_PER_AXIS];
+	u_int nspans[2];
+	u_int i;
+	u_int j;
 
 	/* Copy unmatched pspans into the local arrays. */
 	for (i = 0, nspans[X] = 0; i < n_xpspans; i++) {
-		if (pspans_x[i].matched == FALSE) {
+		if (pspans_x[i].matched == false) {
 			spans[X][nspans[X]] = pspans_x[i];
 			nspans[X]++;
 		}
 	}
 	for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) {
-		if (pspans_y[j].matched == FALSE) {
+		if (pspans_y[j].matched == false) {
 			spans[Y][nspans[Y]] = pspans_y[j];
 			nspans[Y]++;
 		}
@@ -1194,7 +1727,7 @@ atp_add_new_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
 	if (nspans[X] == nspans[Y]) {
 		/* Create new strokes from pairs of unmatched pspans */
 		for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++)
-			atp_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
 	} else {
 		u_int    cum = 0;
 		atp_axis repeat_axis;      /* axis with multi-pspans */
@@ -1213,7 +1746,7 @@ atp_add_new_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
 		/* Create new strokes from pairs of unmatched pspans */
 		i = 0, j = 0;
 		for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) {
-			atp_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
 
 			/* Take care to repeat at the multi-pspan. */
 			if (repeat_count > 0) {
@@ -1231,413 +1764,390 @@ atp_add_new_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
 	}
 }
 
-/*
- * Advance the state of this stroke--and update the out-parameter
- * 'movement' as a side-effect.
- */
-void
-atp_advance_stroke_state(struct atp_softc *sc, atp_stroke *stroke,
-    boolean_t *movement)
+/* Initialize a stroke from an unmatched finger. */
+static void
+wsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp)
 {
-	stroke->age++;
-	if (stroke->age <= atp_stroke_maturity_threshold) {
-		/* Avoid noise from immature strokes. */
-		stroke->components[X].delta_mickeys = 0;
-		stroke->components[Y].delta_mickeys = 0;
-	}
-
-	/* Revitalize stroke if it had previously been marked as a zombie. */
-	if (stroke->flags & ATSF_ZOMBIE)
-		stroke->flags &= ~ATSF_ZOMBIE;
-
-	if (atp_compute_stroke_movement(stroke))
-		*movement = TRUE;
+	atp_stroke_t *strokep;
 
-	if (stroke->type != ATP_STROKE_TOUCH)
+	strokep = atp_alloc_stroke(sc);
+	if (strokep == NULL)
 		return;
 
-	/* Convert touch strokes to slides upon detecting movement or age. */
-	if (stroke->cum_movement >= atp_slide_min_movement) {
-		atp_convert_to_slide(sc, stroke);
-	} else {
-		/* If a touch stroke is found to be older than the
-		 * touch-timeout threshold, it should be converted to
-		 * a slide; except if there is a co-incident sibling
-		 * with a later creation time.
-		 *
-		 * When multiple fingers make contact with the
-		 * touchpad, they are likely to be separated in their
-		 * times of incidence.  During a multi-finger tap,
-		 * therefore, the last finger to make
-		 * contact--i.e. the one with the latest
-		 * 'ctime'--should be used to determine how the
-		 * touch-siblings get treated; otherwise older
-		 * siblings may lapse the touch-timeout and get
-		 * converted into slides prematurely.  The following
-		 * loop determines if there exists another touch
-		 * stroke with a larger 'ctime' than the current
-		 * stroke (NOTE: zombies with a larger 'ctime' are
-		 * also considered) .
-		 */
-
-		u_int i;
-		for (i = 0; i < sc->sc_n_strokes; i++) {
-			if ((&sc->sc_strokes[i] == stroke) ||
-			    (sc->sc_strokes[i].type != ATP_STROKE_TOUCH))
-				continue;
-
-			if (timevalcmp(&sc->sc_strokes[i].ctime,
-				&stroke->ctime, >))
-				break;
-		}
-		if (i == sc->sc_n_strokes) {
-			/* Found no other touch stroke with a larger 'ctime'. */
-			struct timeval tdiff;
-
-			/* Compute the stroke's age. */
-			getmicrotime(&tdiff);
-			if (timevalcmp(&tdiff, &stroke->ctime, >))
-				timevalsub(&tdiff, &stroke->ctime);
-			else {
-				/*
-				 * If we are here, it is because getmicrotime
-				 * reported the current time as being behind
-				 * the stroke's start time; getmicrotime can
-				 * be imprecise.
-				 */
-				tdiff.tv_sec  = 0;
-				tdiff.tv_usec = 0;
-			}
+	/*
+	 * Strokes begin as potential touches. If a stroke survives
+	 * longer than a threshold, or if it records significant
+	 * cumulative movement, then it is considered a 'slide'.
+	 */
+	strokep->type    = ATP_STROKE_TOUCH;
+	strokep->matched = true;
+	microtime(&strokep->ctime);
+	strokep->age = 1;	/* number of interrupts */
+	strokep->x = fingerp->x;
+	strokep->y = fingerp->y;
+
+	/* Reset double-tap-n-drag if we have more than one strokes. */
+	if (sc->sc_n_strokes > 1)
+		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
 
-			if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) ||
-			    ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) &&
-				(tdiff.tv_usec >=
-				    (atp_touch_timeout % 1000000))))
-				atp_convert_to_slide(sc, stroke);
-		}
-	}
+	DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y);
 }
 
-/* Switch a given touch stroke to being a slide. */
-void
-atp_convert_to_slide(struct atp_softc *sc, atp_stroke *stroke)
+static void
+atp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep,
+    boolean_t *movementp)
 {
-	stroke->type = ATP_STROKE_SLIDE;
-
-	/* Are we at the beginning of a double-click-n-drag? */
-	if ((sc->sc_n_strokes == 1) &&
-	    ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) &&
-	    timevalcmp(&stroke->ctime, &sc->sc_reap_time, >)) {
-		struct timeval delta;
-		struct timeval window = {
-			atp_double_tap_threshold / 1000000,
-			atp_double_tap_threshold % 1000000
-		};
+	/* Revitalize stroke if it had previously been marked as a zombie. */
+	if (strokep->flags & ATSF_ZOMBIE)
+		strokep->flags &= ~ATSF_ZOMBIE;
 
-		delta = stroke->ctime;
-		timevalsub(&delta, &sc->sc_reap_time);
-		if (timevalcmp(&delta, &window, <=))
-			sc->sc_state |= ATP_DOUBLE_TAP_DRAG;
+	strokep->age++;
+	if (strokep->age <= atp_stroke_maturity_threshold) {
+		/* Avoid noise from immature strokes. */
+		strokep->instantaneous_dx = 0;
+		strokep->instantaneous_dy = 0;
 	}
-}
 
-/*
- * Terminate a stroke. While SLIDE strokes are dropped, TOUCH strokes
- * are retained as zombies so as to reap all their siblings together;
- * this helps establish the number of fingers involved in the tap.
- */
-static void
-atp_terminate_stroke(struct atp_softc *sc,
-    u_int index) /* index of the stroke to be terminated */
-{
-	atp_stroke *s = &sc->sc_strokes[index];
+	if (atp_compute_stroke_movement(strokep))
+		*movementp = true;
 
-	if (s->flags & ATSF_ZOMBIE) {
+	if (strokep->type != ATP_STROKE_TOUCH)
 		return;
-	}
 
-	if ((s->type == ATP_STROKE_TOUCH) &&
-	    (s->age > atp_stroke_maturity_threshold)) {
-		s->flags |= ATSF_ZOMBIE;
+	/* Convert touch strokes to slides upon detecting movement or age. */
+	if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) ||
+	    (abs(strokep->cum_movement_y) > atp_slide_min_movement))
+		atp_convert_to_slide(sc, strokep);
+	else {
+		/* Compute the stroke's age. */
+		struct timeval tdiff;
+		getmicrotime(&tdiff);
+		if (timevalcmp(&tdiff, &strokep->ctime, >)) {
+			timevalsub(&tdiff, &strokep->ctime);
 
-		/* If no zombies exist, then prepare to reap zombies later. */
-		if ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) {
-			atp_setup_reap_time(sc, &s->ctime);
-			sc->sc_state |= ATP_ZOMBIES_EXIST;
+			if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) ||
+			    ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) &&
+			     (tdiff.tv_usec >= (atp_touch_timeout % 1000000))))
+				atp_convert_to_slide(sc, strokep);
 		}
-	} else {
-		/* Drop this stroke. */
-		memcpy(&sc->sc_strokes[index], &sc->sc_strokes[index + 1],
-		    (sc->sc_n_strokes - index - 1) * sizeof(atp_stroke));
-		sc->sc_n_strokes--;
-
-		/*
-		 * Reset the double-click-n-drag at the termination of
-		 * any slide stroke.
-		 */
-		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
 	}
 }
 
-static __inline boolean_t
-atp_stroke_has_small_movement(const atp_stroke *stroke)
+static boolean_t
+atp_stroke_has_small_movement(const atp_stroke_t *strokep)
 {
-	return ((abs(stroke->components[X].delta_mickeys) <=
-		atp_small_movement_threshold) &&
-	    (abs(stroke->components[Y].delta_mickeys) <=
-		atp_small_movement_threshold));
+	return (((u_int)abs(strokep->instantaneous_dx) <=
+		 atp_small_movement_threshold) &&
+		((u_int)abs(strokep->instantaneous_dy) <=
+		 atp_small_movement_threshold));
 }
 
 /*
- * Accumulate delta_mickeys into the component's 'pending' bucket; if
+ * Accumulate instantaneous changes into the stroke's 'pending' bucket; if
  * the aggregate exceeds the small_movement_threshold, then retain
- * delta_mickeys for later.
+ * instantaneous changes for later.
  */
-static __inline void
-atp_update_pending_mickeys(atp_stroke_component *component)
-{
-	component->pending += component->delta_mickeys;
-	if (abs(component->pending) <= atp_small_movement_threshold)
-		component->delta_mickeys = 0;
-	else {
-		/*
-		 * Penalise pending mickeys for having accumulated
-		 * over short deltas. This operation has the effect of
-		 * scaling down the cumulative contribution of short
-		 * movements.
-		 */
-		component->pending -= (component->delta_mickeys << 1);
-	}
-}
-
-
 static void
-atp_compute_smoothening_scale_ratio(atp_stroke *stroke, int *numerator,
-    int *denominator)
+atp_update_pending_mickeys(atp_stroke_t *strokep)
 {
-	int   dxdt;
-	int   dydt;
-	u_int vel_squared; /* Square of the velocity vector's magnitude. */
-	u_int vel_squared_smooth;
-
-	/* Table holding (10 * sqrt(x)) for x between 1 and 256. */
-	static uint8_t sqrt_table[256] = {
-		10, 14, 17, 20, 22, 24, 26, 28,
-		30, 31, 33, 34, 36, 37, 38, 40,
-		41, 42, 43, 44, 45, 46, 47, 48,
-		50, 50, 51, 52, 53, 54, 55, 56,
-		57, 58, 59, 60, 60, 61, 62, 63,
-		64, 64, 65, 66, 67, 67, 68, 69,
-		70, 70, 71, 72, 72, 73, 74, 74,
-		75, 76, 76, 77, 78, 78, 79, 80,
-		80, 81, 81, 82, 83, 83, 84, 84,
-		85, 86, 86, 87, 87, 88, 88, 89,
-		90, 90, 91, 91, 92, 92, 93, 93,
-		94, 94, 95, 95, 96, 96, 97, 97,
-		98, 98, 99, 100, 100, 100, 101, 101,
-		102, 102, 103, 103, 104, 104, 105, 105,
-		106, 106, 107, 107, 108, 108, 109, 109,
-		110, 110, 110, 111, 111, 112, 112, 113,
-		113, 114, 114, 114, 115, 115, 116, 116,
-		117, 117, 117, 118, 118, 119, 119, 120,
-		120, 120, 121, 121, 122, 122, 122, 123,
-		123, 124, 124, 124, 125, 125, 126, 126,
-		126, 127, 127, 128, 128, 128, 129, 129,
-		130, 130, 130, 131, 131, 131, 132, 132,
-		133, 133, 133, 134, 134, 134, 135, 135,
-		136, 136, 136, 137, 137, 137, 138, 138,
-		138, 139, 139, 140, 140, 140, 141, 141,
-		141, 142, 142, 142, 143, 143, 143, 144,
-		144, 144, 145, 145, 145, 146, 146, 146,
-		147, 147, 147, 148, 148, 148, 149, 149,
-		150, 150, 150, 150, 151, 151, 151, 152,
-		152, 152, 153, 153, 153, 154, 154, 154,
-		155, 155, 155, 156, 156, 156, 157, 157,
-		157, 158, 158, 158, 159, 159, 159, 160
-	};
-	const u_int N = NELEM(sqrt_table);
-
-	dxdt = stroke->components[X].delta_mickeys;
-	dydt = stroke->components[Y].delta_mickeys;
-
-	*numerator = 0, *denominator = 0; /* default values. */
-
-	/* Compute a smoothened magnitude_squared of the stroke's velocity. */
-	vel_squared = dxdt * dxdt + dydt * dydt;
-	vel_squared_smooth = (3 * stroke->velocity_squared + vel_squared) >> 2;
-	stroke->velocity_squared = vel_squared_smooth; /* retained as history */
-	if ((vel_squared == 0) || (vel_squared_smooth == 0))
-		return; /* returning (numerator == 0) will imply zero movement*/
-
-	/*
-	 * In order to determine the overall movement scale factor,
-	 * we're actually interested in the effect of smoothening upon
-	 * the *magnitude* of velocity; i.e. we need to compute the
-	 * square-root of (vel_squared_smooth / vel_squared) in the
-	 * form of a numerator and denominator.
-	 */
-
-	/* Keep within the bounds of the square-root table. */
-	while ((vel_squared > N) || (vel_squared_smooth > N)) {
-		/* Dividing uniformly by 2 won't disturb the final ratio. */
-		vel_squared        >>= 1;
-		vel_squared_smooth >>= 1;
+	/* accumulate instantaneous movement */
+	strokep->pending_dx += strokep->instantaneous_dx;
+	strokep->pending_dy += strokep->instantaneous_dy;
+
+#define UPDATE_INSTANTANEOUS_AND_PENDING(I, P)                          \
+	if (abs((P)) <= atp_small_movement_threshold)                   \
+		(I) = 0; /* clobber small movement */                   \
+	else {                                                          \
+		if ((I) > 0) {                                          \
+			/*                                              \
+			 * Round up instantaneous movement to the nearest \
+			 * ceiling. This helps preserve small mickey    \
+			 * movements from being lost in following scaling \
+			 * operation.                                   \
+			 */                                             \
+			(I) = (((I) + (atp_mickeys_scale_factor - 1)) / \
+			       atp_mickeys_scale_factor) *              \
+			      atp_mickeys_scale_factor;                 \
+									\
+			/*                                              \
+			 * Deduct the rounded mickeys from pending mickeys. \
+			 * Note: we multiply by 2 to offset the previous \
+			 * accumulation of instantaneous movement into  \
+			 * pending.                                     \
+			 */                                             \
+			(P) -= ((I) << 1);                              \
+									\
+			/* truncate pending to 0 if it becomes negative. */ \
+			(P) = imax((P), 0);                             \
+		} else {                                                \
+			/*                                              \
+			 * Round down instantaneous movement to the nearest \
+			 * ceiling. This helps preserve small mickey    \
+			 * movements from being lost in following scaling \
+			 * operation.                                   \
+			 */                                             \
+			(I) = (((I) - (atp_mickeys_scale_factor - 1)) / \
+			       atp_mickeys_scale_factor) *              \
+			      atp_mickeys_scale_factor;                 \
+									\
+			/*                                              \
+			 * Deduct the rounded mickeys from pending mickeys. \
+			 * Note: we multiply by 2 to offset the previous \
+			 * accumulation of instantaneous movement into  \
+			 * pending.                                     \
+			 */                                             \
+			(P) -= ((I) << 1);                              \
+									\
+			/* truncate pending to 0 if it becomes positive. */ \
+			(P) = imin((P), 0);                             \
+		}                                                       \
 	}
 
-	*numerator   = sqrt_table[vel_squared_smooth - 1];
-	*denominator = sqrt_table[vel_squared - 1];
+	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx,
+	    strokep->pending_dx);
+	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy,
+	    strokep->pending_dy);
 }
 
 /*
  * Compute a smoothened value for the stroke's movement from
- * delta_mickeys in the X and Y components.
+ * instantaneous changes in the X and Y components.
  */
 static boolean_t
-atp_compute_stroke_movement(atp_stroke *stroke)
+atp_compute_stroke_movement(atp_stroke_t *strokep)
 {
-	int   num;              /* numerator of scale ratio */
-	int   denom;            /* denominator of scale ratio */
-
 	/*
 	 * Short movements are added first to the 'pending' bucket,
 	 * and then acted upon only when their aggregate exceeds a
 	 * threshold. This has the effect of filtering away movement
 	 * noise.
 	 */
-	if (atp_stroke_has_small_movement(stroke)) {
-		atp_update_pending_mickeys(&stroke->components[X]);
-		atp_update_pending_mickeys(&stroke->components[Y]);
-	} else {                /* large movement */
+	if (atp_stroke_has_small_movement(strokep))
+		atp_update_pending_mickeys(strokep);
+	else {                /* large movement */
 		/* clear away any pending mickeys if there are large movements*/
-		stroke->components[X].pending = 0;
-		stroke->components[Y].pending = 0;
+		strokep->pending_dx = 0;
+		strokep->pending_dy = 0;
 	}
 
-	/* Get the scale ratio and smoothen movement. */
-	atp_compute_smoothening_scale_ratio(stroke, &num, &denom);
-	if ((num == 0) || (denom == 0)) {
-		stroke->components[X].movement = 0;
-		stroke->components[Y].movement = 0;
-		stroke->velocity_squared >>= 1; /* Erode velocity_squared. */
-	} else {
-		stroke->components[X].movement =
-			(stroke->components[X].delta_mickeys * num) / denom;
-		stroke->components[Y].movement =
-			(stroke->components[Y].delta_mickeys * num) / denom;
-
-		stroke->cum_movement +=
-			abs(stroke->components[X].movement) +
-			abs(stroke->components[Y].movement);
+	/* scale movement */
+	strokep->movement_dx = (strokep->instantaneous_dx) /
+	    (int)atp_mickeys_scale_factor;
+	strokep->movement_dy = (strokep->instantaneous_dy) /
+	    (int)atp_mickeys_scale_factor;
+
+	if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) ||
+	    (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) {
+		strokep->movement_dx <<= 1;
+		strokep->movement_dy <<= 1;
 	}
 
-	return ((stroke->components[X].movement != 0) ||
-	    (stroke->components[Y].movement != 0));
+	strokep->cum_movement_x += strokep->movement_dx;
+	strokep->cum_movement_y += strokep->movement_dy;
+
+	return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0));
 }
 
-static __inline void
-atp_setup_reap_time(struct atp_softc *sc, struct timeval *tvp)
+/*
+ * Terminate a stroke. Aside from immature strokes, a slide or touch is
+ * retained as a zombies so as to reap all their termination siblings
+ * together; this helps establish the number of fingers involved at the
+ * end of a multi-touch gesture.
+ */
+static void
+atp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep)
 {
-	struct timeval reap_window = {
-		ATP_ZOMBIE_STROKE_REAP_WINDOW / 1000000,
-		ATP_ZOMBIE_STROKE_REAP_WINDOW % 1000000
-	};
+	if (strokep->flags & ATSF_ZOMBIE)
+		return;
+
+	/* Drop immature strokes rightaway. */
+	if (strokep->age <= atp_stroke_maturity_threshold) {
+		atp_free_stroke(sc, strokep);
+		return;
+	}
 
-	microtime(&sc->sc_reap_time);
-	timevaladd(&sc->sc_reap_time, &reap_window);
+	strokep->flags |= ATSF_ZOMBIE;
+	sc->sc_state |= ATP_ZOMBIES_EXIST;
 
-	sc->sc_reap_ctime = *tvp; /* ctime to reap */
+	usb_callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL,
+	    atp_reap_sibling_zombies, sc);
+
+	/*
+	 * Reset the double-click-n-drag at the termination of any
+	 * slide stroke.
+	 */
+	if (strokep->type == ATP_STROKE_SLIDE)
+		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
 }
 
-static void
-atp_reap_zombies(struct atp_softc *sc, u_int *n_reaped, u_int *reaped_xlocs)
+static boolean_t
+atp_is_horizontal_scroll(const atp_stroke_t *strokep)
 {
-	u_int       i;
-	atp_stroke *stroke;
+	if (abs(strokep->cum_movement_x) < atp_slide_min_movement)
+		return (false);
+	if (strokep->cum_movement_y == 0)
+		return (true);
+	return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4);
+}
+
+static boolean_t
+atp_is_vertical_scroll(const atp_stroke_t *strokep)
+{
+	if (abs(strokep->cum_movement_y) < atp_slide_min_movement)
+		return (false);
+	if (strokep->cum_movement_x == 0)
+		return (true);
+	return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4);
+}
 
-	*n_reaped = 0;
-	for (i = 0; i < sc->sc_n_strokes; i++) {
-		struct timeval  tdiff;
+static void
+atp_reap_sibling_zombies(void *arg)
+{
+	struct atp_softc *sc = (struct atp_softc *)arg;
+	u_int8_t n_touches_reaped = 0;
+	u_int8_t n_slides_reaped = 0;
+	u_int8_t n_horizontal_scrolls = 0;
+	u_int8_t n_vertical_scrolls = 0;
+	int horizontal_scroll = 0;
+	int vertical_scroll = 0;
+	atp_stroke_t *strokep;
+	atp_stroke_t *strokep_next;
 
-		stroke = &sc->sc_strokes[i];
+	DPRINTFN(ATP_LLEVEL_INFO, "\n");
 
-		if ((stroke->flags & ATSF_ZOMBIE) == 0)
+	TAILQ_FOREACH_MUTABLE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+		if ((strokep->flags & ATSF_ZOMBIE) == 0)
 			continue;
 
-		/* Compare this stroke's ctime with the ctime being reaped. */
-		if (timevalcmp(&stroke->ctime, &sc->sc_reap_ctime, >=)) {
-			tdiff = stroke->ctime;
-			timevalsub(&tdiff, &sc->sc_reap_ctime);
+		if (strokep->type == ATP_STROKE_TOUCH) {
+			n_touches_reaped++;
 		} else {
-			tdiff = sc->sc_reap_ctime;
-			timevalsub(&tdiff, &stroke->ctime);
-		}
-
-		if ((tdiff.tv_sec > (ATP_COINCIDENCE_THRESHOLD / 1000000)) ||
-		    ((tdiff.tv_sec == (ATP_COINCIDENCE_THRESHOLD / 1000000)) &&
-		     (tdiff.tv_usec > (ATP_COINCIDENCE_THRESHOLD % 1000000)))) {
-			continue; /* Skip non-siblings. */
+			n_slides_reaped++;
+
+			if (atp_is_horizontal_scroll(strokep)) {
+				n_horizontal_scrolls++;
+				horizontal_scroll += strokep->cum_movement_x;
+			} else if (atp_is_vertical_scroll(strokep)) {
+				n_vertical_scrolls++;
+				vertical_scroll +=  strokep->cum_movement_y;
+			}
 		}
 
-		/*
-		 * Reap this sibling zombie stroke.
-		 */
+		atp_free_stroke(sc, strokep);
+	}
 
-		if (reaped_xlocs != NULL)
-			reaped_xlocs[*n_reaped] = stroke->components[X].loc;
+	DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n",
+	    n_touches_reaped + n_slides_reaped);
+	sc->sc_state &= ~ATP_ZOMBIES_EXIST;
 
-		/* Erase the stroke from the sc. */
-		memcpy(&stroke[i], &stroke[i + 1],
-		    (sc->sc_n_strokes - i - 1) * sizeof(atp_stroke));
-		sc->sc_n_strokes--;
+	/* No further processing necessary if physical button is depressed. */
+	if (sc->sc_ibtn != 0)
+		return;
 
-		*n_reaped += 1;
-		--i; /* Decr. i to keep it unchanged for the next iteration */
-	}
+	if ((n_touches_reaped == 0) && (n_slides_reaped == 0))
+		return;
 
-	DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", *n_reaped);
+	/* Add a pair of virtual button events (button-down and button-up) if
+	 * the physical button isn't pressed. */
+	if (n_touches_reaped != 0) {
+		if (n_touches_reaped < atp_tap_minimum)
+			return;
 
-	/* There could still be zombies remaining in the system. */
-	for (i = 0; i < sc->sc_n_strokes; i++) {
-		stroke = &sc->sc_strokes[i];
-		if (stroke->flags & ATSF_ZOMBIE) {
-			DPRINTFN(ATP_LLEVEL_INFO, "zombies remain!\n");
-			atp_setup_reap_time(sc, &stroke->ctime);
+		switch (n_touches_reaped) {
+		case 1:
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
+			microtime(&sc->sc_touch_reap_time); /* remember this time */
+			break;
+		case 2:
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
+			break;
+		case 3:
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
+			break;
+		default:
+			/* we handle taps of only up to 3 fingers */
 			return;
 		}
+		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
+
+	} else if ((n_slides_reaped == 2) && (n_horizontal_scrolls == 2)) {
+		if (horizontal_scroll < 0)
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN);
+		else
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN);
+		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
 	}
+}
 
-	/* If we reach here, then no more zombies remain. */
-	sc->sc_state &= ~ATP_ZOMBIES_EXIST;
+/* Switch a given touch stroke to being a slide. */
+static void
+atp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep)
+{
+	strokep->type = ATP_STROKE_SLIDE;
+
+	/* Are we at the beginning of a double-click-n-drag? */
+	if ((sc->sc_n_strokes == 1) &&
+	    ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) &&
+	    timevalcmp(&strokep->ctime, &sc->sc_touch_reap_time, >)) {
+		struct timeval delta;
+		struct timeval window = {
+			atp_double_tap_threshold / 1000000,
+			atp_double_tap_threshold % 1000000
+		};
+
+		delta = strokep->ctime;
+		timevalsub(&delta, &sc->sc_touch_reap_time);
+		if (timevalcmp(&delta, &window, <=))
+			sc->sc_state |= ATP_DOUBLE_TAP_DRAG;
+	}
+}
+
+static void
+atp_reset_buf(struct atp_softc *sc)
+{
+	/* reset read queue */
+	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
 }
 
+static void
+atp_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz,
+    uint32_t buttons_in)
+{
+	uint32_t buttons_out;
+	uint8_t  buf[8];
 
-/* Device methods. */
-static device_probe_t  atp_probe;
-static device_attach_t atp_attach;
-static device_detach_t atp_detach;
-static usb_callback_t  atp_intr;
+	dx = imin(dx,  254); dx = imax(dx, -256);
+	dy = imin(dy,  254); dy = imax(dy, -256);
+	dz = imin(dz,  126); dz = imax(dz, -128);
 
-static const struct usb_config atp_config[ATP_N_TRANSFER] = {
-	[ATP_INTR_DT] = {
-		.type      = UE_INTERRUPT,
-		.endpoint  = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.flags = {
-			.pipe_bof = 1,
-			.short_xfer_ok = 1,
-		},
-		.bufsize   = 0, /* use wMaxPacketSize */
-		.callback  = &atp_intr,
-	},
-	[ATP_RESET] = {
-		.type      = UE_CONTROL,
-		.endpoint  = 0, /* Control pipe */
-		.direction = UE_DIR_ANY,
-		.bufsize = sizeof(struct usb_device_request) + MODE_LENGTH,
-		.callback  = &atp_reset_callback,
-		.interval = 0,  /* no pre-delay */
-	},
-};
+	buttons_out = MOUSE_MSC_BUTTONS;
+	if (buttons_in & MOUSE_BUTTON1DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON1UP;
+	else if (buttons_in & MOUSE_BUTTON2DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON2UP;
+	else if (buttons_in & MOUSE_BUTTON3DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON3UP;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n",
+	    dx, dy, buttons_out);
+
+	/* Encode the mouse data in standard format; refer to mouse(4) */
+	buf[0] = sc->sc_mode.syncmask[1];
+	buf[0] |= buttons_out;
+	buf[1] = dx >> 1;
+	buf[2] = dy >> 1;
+	buf[3] = dx - (dx >> 1);
+	buf[4] = dy - (dy >> 1);
+	/* Encode extra bytes for level 1 */
+	if (sc->sc_mode.level == 1) {
+		buf[5] = dz >> 1;
+		buf[6] = dz - (dz >> 1);
+		buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
+	}
+
+	usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+	    sc->sc_mode.packetsize, 1);
+}
 
 static int
 atp_probe(device_t self)
@@ -1647,25 +2157,58 @@ atp_probe(device_t self)
 	if (uaa->usb_mode != USB_MODE_HOST)
 		return (ENXIO);
 
-	if ((uaa->info.bInterfaceClass != UICLASS_HID) ||
-	    (uaa->info.bInterfaceProtocol != UIPROTO_MOUSE))
+	if (uaa->info.bInterfaceClass != UICLASS_HID)
 		return (ENXIO);
+	/*
+	 * Note: for some reason, the check
+	 * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true
+	 * for wellspring trackpads, so we've removed it from the common path.
+	 */
+
+	if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0)
+		return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ?
+			0 : ENXIO);
 
-	return (usbd_lookup_id_by_uaa(atp_devs, sizeof(atp_devs), uaa));
+	if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0)
+		if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX)
+			return (0);
+
+	return (ENXIO);
 }
 
 static int
 atp_attach(device_t dev)
 {
-	struct atp_softc      *sc = device_get_softc(dev);
+	struct atp_softc      *sc  = device_get_softc(dev);
 	struct usb_attach_arg *uaa = device_get_ivars(dev);
 	usb_error_t            err;
+	void *descriptor_ptr = NULL;
+	uint16_t descriptor_len;
+	unsigned long di;
 
 	DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc);
 
 	sc->sc_dev        = dev;
 	sc->sc_usb_device = uaa->device;
 
+	/* Get HID descriptor */
+	if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr,
+	    &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) !=
+	    USB_ERR_NORMAL_COMPLETION)
+		return (ENXIO);
+
+	/* Get HID report descriptor length */
+	sc->sc_expected_sensor_data_len = hid_report_size(descriptor_ptr,
+	    descriptor_len, hid_input, NULL);
+	kfree(descriptor_ptr, M_TEMP);
+
+	if ((sc->sc_expected_sensor_data_len <= 0) ||
+	    (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) {
+		DPRINTF("atp_attach: datalength invalid or too large: %d\n",
+			sc->sc_expected_sensor_data_len);
+		return (ENXIO);
+	}
+
 	/*
 	 * By default the touchpad behaves like an HID device, sending
 	 * packets with reportID = 2. Such reports contain only
@@ -1673,41 +2216,51 @@ atp_attach(device_t dev)
 	 * events,--but do not include data from the pressure
 	 * sensors. The device input mode can be switched from HID
 	 * reports to raw sensor data using vendor-specific USB
-	 * control commands; but first the mode must be read.
+	 * control commands.
 	 */
-	err = atp_req_get_report(sc->sc_usb_device, sc->sc_mode_bytes);
-	if (err != USB_ERR_NORMAL_COMPLETION) {
-		DPRINTF("failed to read device mode (%d)\n", err);
-		return (ENXIO);
-	}
-
-	if (atp_set_device_mode(dev, RAW_SENSOR_MODE) != 0) {
+	if ((err = atp_set_device_mode(sc, RAW_SENSOR_MODE)) != 0) {
 		DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err);
 		return (ENXIO);
 	}
 
-	mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE);
+	lockinit(&sc->sc_lock, "atpmtx", 0, LK_CANRECURSE);
 
-	err = usbd_transfer_setup(uaa->device,
-	    &uaa->info.bIfaceIndex, sc->sc_xfer, atp_config,
-	    ATP_N_TRANSFER, sc, &sc->sc_mutex);
+	di = USB_GET_DRIVER_INFO(uaa);
+
+	sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di);
+
+	switch(sc->sc_family) {
+	case TRACKPAD_FAMILY_FOUNTAIN_GEYSER:
+		sc->sc_params =
+		    &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
+		sc->sensor_data_interpreter = fg_interpret_sensor_data;
+		break;
+	case TRACKPAD_FAMILY_WELLSPRING:
+		sc->sc_params =
+		    &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
+		sc->sensor_data_interpreter = wsp_interpret_sensor_data;
+		break;
+	default:
+		goto detach;
+	}
 
+	err = usbd_transfer_setup(uaa->device,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_config,
+	    ATP_N_TRANSFER, sc, &sc->sc_lock);
 	if (err) {
 		DPRINTF("error=%s\n", usbd_errstr(err));
 		goto detach;
 	}
 
-	if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
-		&atp_fifo_methods, &sc->sc_fifo,
-		device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex,
-		UID_ROOT, GID_OPERATOR, 0644)) {
+	if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_lock,
+	    &atp_fifo_methods, &sc->sc_fifo,
+	    device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+	    UID_ROOT, GID_OPERATOR, 0644)) {
 		goto detach;
 	}
 
 	device_set_usb_desc(dev);
 
-	sc->sc_params           = &atp_dev_params[uaa->driver_info];
-
 	sc->sc_hw.buttons       = 3;
 	sc->sc_hw.iftype        = MOUSE_IF_USB;
 	sc->sc_hw.type          = MOUSE_PAD;
@@ -1716,17 +2269,16 @@ atp_attach(device_t dev)
 	sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
 	sc->sc_mode.rate        = -1;
 	sc->sc_mode.resolution  = MOUSE_RES_UNKNOWN;
-	sc->sc_mode.accelfactor = 0;
-	sc->sc_mode.level       = 0;
 	sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
 	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
 	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+	sc->sc_mode.accelfactor = 0;
+	sc->sc_mode.level       = 0;
 
 	sc->sc_state            = 0;
+	sc->sc_ibtn             = 0;
 
-	sc->sc_left_margin  = atp_mickeys_scale_factor;
-	sc->sc_right_margin = (sc->sc_params->n_xsensors - 1) *
-		atp_mickeys_scale_factor;
+	usb_callout_init_mtx(&sc->sc_callout, &sc->sc_lock, 0);
 
 	return (0);
 
@@ -1741,17 +2293,19 @@ atp_detach(device_t dev)
 	struct atp_softc *sc;
 
 	sc = device_get_softc(dev);
-	if (sc->sc_state & ATP_ENABLED) {
-		mtx_lock(&sc->sc_mutex);
+	atp_set_device_mode(sc, HID_MODE);
+
+	lockmgr(&sc->sc_lock, LK_EXCLUSIVE);
+	usb_callout_drain(&sc->sc_callout);
+	if (sc->sc_state & ATP_ENABLED)
 		atp_disable(sc);
-		mtx_unlock(&sc->sc_mutex);
-	}
+	lockmgr(&sc->sc_lock, LK_RELEASE);
 
 	usb_fifo_detach(&sc->sc_fifo);
 
 	usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER);
 
-	mtx_destroy(&sc->sc_mutex);
+	lockuninit(&sc->sc_lock);
 
 	return (0);
 }
@@ -1760,92 +2314,27 @@ static void
 atp_intr(struct usb_xfer *xfer, usb_error_t error)
 {
 	struct atp_softc      *sc = usbd_xfer_softc(xfer);
-	int                    len;
 	struct usb_page_cache *pc;
-	uint8_t                status_bits;
-	atp_pspan  pspans_x[ATP_MAX_PSPANS_PER_AXIS];
-	atp_pspan  pspans_y[ATP_MAX_PSPANS_PER_AXIS];
-	u_int      n_xpspans = 0, n_ypspans = 0;
-	u_int      reaped_xlocs[ATP_MAX_STROKES];
-	u_int      tap_fingers = 0;
+	int len;
 
 	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
 
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_TRANSFERRED:
-		if (len > sc->sc_params->data_len) {
-			DPRINTFN(ATP_LLEVEL_ERROR,
-			    "truncating large packet from %u to %u bytes\n",
-			    len, sc->sc_params->data_len);
-			len = sc->sc_params->data_len;
-		}
-		if (len < sc->sc_params->data_len)
-			goto tr_setup;
-
 		pc = usbd_xfer_get_frame(xfer, 0);
-		usbd_copy_out(pc, 0, sc->sensor_data, sc->sc_params->data_len);
-
-		/* Interpret sensor data */
-		atp_interpret_sensor_data(sc->sensor_data,
-		    sc->sc_params->n_xsensors, X, sc->cur_x,
-		    sc->sc_params->prot);
-		atp_interpret_sensor_data(sc->sensor_data,
-		    sc->sc_params->n_ysensors, Y,  sc->cur_y,
-		    sc->sc_params->prot);
-
-		/*
-		 * If this is the initial update (from an untouched
-		 * pad), we should set the base values for the sensor
-		 * data; deltas with respect to these base values can
-		 * be used as pressure readings subsequently.
-		 */
-		status_bits = sc->sensor_data[sc->sc_params->data_len - 1];
-		if ((sc->sc_params->prot == ATP_PROT_GEYSER3 &&
-		    (status_bits & ATP_STATUS_BASE_UPDATE)) ||
-		    !(sc->sc_state & ATP_VALID)) {
-			memcpy(sc->base_x, sc->cur_x,
-			    sc->sc_params->n_xsensors * sizeof(*(sc->base_x)));
-			memcpy(sc->base_y, sc->cur_y,
-			    sc->sc_params->n_ysensors * sizeof(*(sc->base_y)));
-			sc->sc_state |= ATP_VALID;
-			goto tr_setup;
+		usbd_copy_out(pc, 0, sc->sc_sensor_data, len);
+		if (len < sc->sc_expected_sensor_data_len) {
+			/* make sure we don't process old data */
+			memset(sc->sc_sensor_data + len, 0,
+			    sc->sc_expected_sensor_data_len - len);
 		}
 
-		/* Get pressure readings and detect p-spans for both axes. */
-		atp_get_pressures(sc->pressure_x, sc->cur_x, sc->base_x,
-		    sc->sc_params->n_xsensors);
-		atp_detect_pspans(sc->pressure_x, sc->sc_params->n_xsensors,
-		    ATP_MAX_PSPANS_PER_AXIS,
-		    pspans_x, &n_xpspans);
-		atp_get_pressures(sc->pressure_y, sc->cur_y, sc->base_y,
-		    sc->sc_params->n_ysensors);
-		atp_detect_pspans(sc->pressure_y, sc->sc_params->n_ysensors,
-		    ATP_MAX_PSPANS_PER_AXIS,
-		    pspans_y, &n_ypspans);
-
-		/* Update strokes with new pspans to detect movements. */
-		sc->sc_status.flags &= ~MOUSE_POSCHANGED;
-		if (atp_update_strokes(sc,
-			pspans_x, n_xpspans,
-			pspans_y, n_ypspans))
-			sc->sc_status.flags |= MOUSE_POSCHANGED;
-
-		/* Reap zombies if it is time. */
-		if (sc->sc_state & ATP_ZOMBIES_EXIST) {
-			struct timeval now;
-
-			getmicrotime(&now);
-			if (timevalcmp(&now, &sc->sc_reap_time, >=))
-				atp_reap_zombies(sc, &tap_fingers,
-				    reaped_xlocs);
-		}
-
-		sc->sc_status.flags &= ~MOUSE_STDBUTTONSCHANGED;
+		sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED |
+		    MOUSE_POSCHANGED);
 		sc->sc_status.obutton = sc->sc_status.button;
 
-		/* Get the state of the physical buttton. */
-		sc->sc_status.button = (status_bits & ATP_STATUS_BUTTON) ?
-			MOUSE_BUTTON1DOWN : 0;
+		(sc->sensor_data_interpreter)(sc, len);
+
 		if (sc->sc_status.button != 0) {
 			/* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */
 			sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
@@ -1855,108 +2344,67 @@ atp_intr(struct usb_xfer *xfer, usb_error_t error)
 		}
 
 		sc->sc_status.flags |=
-			sc->sc_status.button ^ sc->sc_status.obutton;
+		    sc->sc_status.button ^ sc->sc_status.obutton;
 		if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) {
-			DPRINTFN(ATP_LLEVEL_INFO, "button %s\n",
-			    ((sc->sc_status.button & MOUSE_BUTTON1DOWN) ?
-				"pressed" : "released"));
-		} else if ((sc->sc_status.obutton == 0) &&
-		    (sc->sc_status.button == 0) &&
-		    (tap_fingers != 0)) {
-			/* Ignore single-finger taps at the edges. */
-			if ((tap_fingers == 1) &&
-			    ((reaped_xlocs[0] <= sc->sc_left_margin) ||
-				(reaped_xlocs[0] > sc->sc_right_margin))) {
-				tap_fingers = 0;
-			}
-			DPRINTFN(ATP_LLEVEL_INFO,
-			    "tap_fingers: %u\n", tap_fingers);
+		    DPRINTFN(ATP_LLEVEL_INFO, "button %s\n",
+			((sc->sc_status.button & MOUSE_BUTTON1DOWN) ?
+			"pressed" : "released"));
 		}
 
-		if (sc->sc_status.flags &
-		    (MOUSE_POSCHANGED | MOUSE_STDBUTTONSCHANGED)) {
-			int   dx, dy;
-			u_int n_movements;
+		if (sc->sc_status.flags & (MOUSE_POSCHANGED |
+		    MOUSE_STDBUTTONSCHANGED)) {
 
-			dx = 0, dy = 0, n_movements = 0;
-			for (u_int i = 0; i < sc->sc_n_strokes; i++) {
-				atp_stroke *stroke = &sc->sc_strokes[i];
+			atp_stroke_t *strokep;
+			u_int8_t n_movements = 0;
+			int dx = 0;
+			int dy = 0;
+			int dz = 0;
 
-				if ((stroke->components[X].movement) ||
-				    (stroke->components[Y].movement)) {
-					dx += stroke->components[X].movement;
-					dy += stroke->components[Y].movement;
+			TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+				if (strokep->flags & ATSF_ZOMBIE)
+					continue;
+
+				dx += strokep->movement_dx;
+				dy += strokep->movement_dy;
+				if (strokep->movement_dx ||
+				    strokep->movement_dy)
 					n_movements++;
-				}
 			}
-			/*
-			 * Disregard movement if multiple
-			 * strokes record motion.
-			 */
-			if (n_movements != 1)
-				dx = 0, dy = 0;
-
-			sc->sc_status.dx += dx;
-			sc->sc_status.dy += dy;
-			atp_add_to_queue(sc, dx, -dy, sc->sc_status.button);
-		}
 
-		if (tap_fingers != 0) {
-			/* Add a pair of events (button-down and button-up). */
-			switch (tap_fingers) {
-			case 1: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON1DOWN);
-				break;
-			case 2: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON2DOWN);
-				break;
-			case 3: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON3DOWN);
-				break;
-			default: break;/* handle taps of only up to 3 fingers */
+			/* average movement if multiple strokes record motion.*/
+			if (n_movements > 1) {
+				dx /= (int)n_movements;
+				dy /= (int)n_movements;
 			}
-			atp_add_to_queue(sc, 0, 0, 0); /* button release */
-		}
 
-		/*
-		 * The device continues to trigger interrupts at a
-		 * fast rate even after touchpad activity has
-		 * stopped. Upon detecting that the device has
-		 * remained idle beyond a threshold, we reinitialize
-		 * it to silence the interrupts.
-		 */
-		if ((sc->sc_status.flags  == 0) &&
-		    (sc->sc_n_strokes     == 0) &&
-		    (sc->sc_status.button == 0)) {
-			sc->sc_idlecount++;
-			if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) {
-				DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
+			/* detect multi-finger vertical scrolls */
+			if (n_movements >= 2) {
+				boolean_t all_vertical_scrolls = true;
+				TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+					if (strokep->flags & ATSF_ZOMBIE)
+						continue;
 
-				/*
-				 * Use the last frame before we go idle for
-				 * calibration on pads which do not send
-				 * calibration frames.
-				 */
-				if (sc->sc_params->prot < ATP_PROT_GEYSER3) {
-					memcpy(sc->base_x, sc->cur_x,
-					    sc->sc_params->n_xsensors *
-					    sizeof(*(sc->base_x)));
-					memcpy(sc->base_y, sc->cur_y,
-					    sc->sc_params->n_ysensors *
-					    sizeof(*(sc->base_y)));
+					if (!atp_is_vertical_scroll(strokep))
+						all_vertical_scrolls = false;
+				}
+				if (all_vertical_scrolls) {
+					dz = dy;
+					dy = dx = 0;
 				}
-
-				sc->sc_idlecount = 0;
-				usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
 			}
-		} else {
-			sc->sc_idlecount = 0;
+
+			sc->sc_status.dx += dx;
+			sc->sc_status.dy += dy;
+			sc->sc_status.dz += dz;
+			atp_add_to_queue(sc, dx, -dy, -dz, sc->sc_status.button);
 		}
 
 	case USB_ST_SETUP:
 	tr_setup:
 		/* check if we can put more data into the FIFO */
-		if (usb_fifo_put_bytes_max(
-			    sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+		if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
 			usbd_xfer_set_frame_len(xfer, 0,
-			    sc->sc_params->data_len);
+			    sc->sc_expected_sensor_data_len);
 			usbd_transfer_submit(xfer);
 		}
 		break;
@@ -1969,53 +2417,6 @@ atp_intr(struct usb_xfer *xfer, usb_error_t error)
 		}
 		break;
 	}
-
-	return;
-}
-
-static void
-atp_add_to_queue(struct atp_softc *sc, int dx, int dy, uint32_t buttons_in)
-{
-	uint32_t buttons_out;
-	uint8_t  buf[8];
-
-	dx = imin(dx,  254); dx = imax(dx, -256);
-	dy = imin(dy,  254); dy = imax(dy, -256);
-
-	buttons_out = MOUSE_MSC_BUTTONS;
-	if (buttons_in & MOUSE_BUTTON1DOWN)
-		buttons_out &= ~MOUSE_MSC_BUTTON1UP;
-	else if (buttons_in & MOUSE_BUTTON2DOWN)
-		buttons_out &= ~MOUSE_MSC_BUTTON2UP;
-	else if (buttons_in & MOUSE_BUTTON3DOWN)
-		buttons_out &= ~MOUSE_MSC_BUTTON3UP;
-
-	DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n",
-	    dx, dy, buttons_out);
-
-	/* Encode the mouse data in standard format; refer to mouse(4) */
-	buf[0] = sc->sc_mode.syncmask[1];
-	buf[0] |= buttons_out;
-	buf[1] = dx >> 1;
-	buf[2] = dy >> 1;
-	buf[3] = dx - (dx >> 1);
-	buf[4] = dy - (dy >> 1);
-	/* Encode extra bytes for level 1 */
-	if (sc->sc_mode.level == 1) {
-		buf[5] = 0;                    /* dz */
-		buf[6] = 0;                    /* dz - (dz / 2) */
-		buf[7] = MOUSE_SYS_EXTBUTTONS; /* Extra buttons all up. */
-	}
-
-	usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
-	    sc->sc_mode.packetsize, 1);
-}
-
-static void
-atp_reset_buf(struct atp_softc *sc)
-{
-	/* reset read queue */
-	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
 }
 
 static void
@@ -2046,56 +2447,57 @@ static void
 atp_stop_read(struct usb_fifo *fifo)
 {
 	struct atp_softc *sc = usb_fifo_softc(fifo);
-
 	usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
 }
 
 static int
 atp_open(struct usb_fifo *fifo, int fflags)
 {
-	DPRINTFN(ATP_LLEVEL_INFO, "\n");
+	struct atp_softc *sc = usb_fifo_softc(fifo);
 
-	if (fflags & FREAD) {
-		struct atp_softc *sc = usb_fifo_softc(fifo);
-		int rc;
+	/* check for duplicate open, should not happen */
+	if (sc->sc_fflags & fflags)
+		return (EBUSY);
 
-		if (sc->sc_state & ATP_ENABLED)
-			return (EBUSY);
+	/* check for first open */
+	if (sc->sc_fflags == 0) {
+		int rc;
+		if ((rc = atp_enable(sc)) != 0)
+			return (rc);
+	}
 
+	if (fflags & FREAD) {
 		if (usb_fifo_alloc_buffer(fifo,
-			ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) {
+		    ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) {
 			return (ENOMEM);
 		}
-
-		rc = atp_enable(sc);
-		if (rc != 0) {
-			usb_fifo_free_buffer(fifo);
-			return (rc);
-		}
 	}
 
+	sc->sc_fflags |= (fflags & (FREAD | FWRITE));
 	return (0);
 }
 
 static void
 atp_close(struct usb_fifo *fifo, int fflags)
 {
-	if (fflags & FREAD) {
-		struct atp_softc *sc = usb_fifo_softc(fifo);
+	struct atp_softc *sc = usb_fifo_softc(fifo);
+	if (fflags & FREAD)
+		usb_fifo_free_buffer(fifo);
 
+	sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+	if (sc->sc_fflags == 0) {
 		atp_disable(sc);
-		usb_fifo_free_buffer(fifo);
 	}
 }
 
-int
+static int
 atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
 {
 	struct atp_softc *sc = usb_fifo_softc(fifo);
 	mousemode_t mode;
 	int error = 0;
 
-	mtx_lock(&sc->sc_mutex);
+	lockmgr(&sc->sc_lock, LK_EXCLUSIVE);
 
 	switch(cmd) {
 	case MOUSE_GETHWINFO:
@@ -2112,20 +2514,20 @@ atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
 			;
 		else if ((mode.level < 0) || (mode.level > 1)) {
 			error = EINVAL;
-			goto done;
+			break;
 		}
 		sc->sc_mode.level = mode.level;
 		sc->sc_pollrate   = mode.rate;
 		sc->sc_hw.buttons = 3;
 
 		if (sc->sc_mode.level == 0) {
-			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
-			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
 			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
 			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
 		} else if (sc->sc_mode.level == 1) {
-			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
-			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
 			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
 			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
 		}
@@ -2135,21 +2537,21 @@ atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
 		*(int *)addr = sc->sc_mode.level;
 		break;
 	case MOUSE_SETLEVEL:
-		if (*(int *)addr < 0 || *(int *)addr > 1) {
+		if ((*(int *)addr < 0) || (*(int *)addr > 1)) {
 			error = EINVAL;
-			goto done;
+			break;
 		}
 		sc->sc_mode.level = *(int *)addr;
 		sc->sc_hw.buttons = 3;
 
 		if (sc->sc_mode.level == 0) {
-			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
-			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
 			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
 			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
 		} else if (sc->sc_mode.level == 1) {
-			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
-			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
 			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
 			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
 		}
@@ -2161,9 +2563,9 @@ atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
 		*status = sc->sc_status;
 		sc->sc_status.obutton = sc->sc_status.button;
 		sc->sc_status.button  = 0;
-		sc->sc_status.dx = 0;
-		sc->sc_status.dy = 0;
-		sc->sc_status.dz = 0;
+		sc->sc_status.dx      = 0;
+		sc->sc_status.dy      = 0;
+		sc->sc_status.dz      = 0;
 
 		if (status->dx || status->dy || status->dz)
 			status->flags |= MOUSE_POSCHANGED;
@@ -2171,12 +2573,13 @@ atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
 			status->flags |= MOUSE_BUTTONSCHANGED;
 		break;
 	}
+
 	default:
 		error = ENOTTY;
+		break;
 	}
 
-done:
-	mtx_unlock(&sc->sc_mutex);
+	lockmgr(&sc->sc_lock, LK_RELEASE);
 	return (error);
 }
 
@@ -2185,49 +2588,44 @@ atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS)
 {
 	int error;
 	u_int tmp;
-	u_int prev_mickeys_scale_factor;
-
-	prev_mickeys_scale_factor = atp_mickeys_scale_factor;
 
 	tmp = atp_mickeys_scale_factor;
 	error = sysctl_handle_int(oidp, &tmp, 0, req);
 	if (error != 0 || req->newptr == NULL)
 		return (error);
 
-	if (tmp == prev_mickeys_scale_factor)
+	if (tmp == atp_mickeys_scale_factor)
 		return (0);     /* no change */
+	if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR)))
+		return (EINVAL);
 
 	atp_mickeys_scale_factor = tmp;
 	DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n",
 	    ATP_DRIVER_NAME, tmp);
 
-	/* Update dependent thresholds. */
-	if (atp_small_movement_threshold == (prev_mickeys_scale_factor >> 3))
-		atp_small_movement_threshold = atp_mickeys_scale_factor >> 3;
-	if (atp_max_delta_mickeys == ((3 * prev_mickeys_scale_factor) >> 1))
-		atp_max_delta_mickeys = ((3 * atp_mickeys_scale_factor) >>1);
-	if (atp_slide_min_movement == (prev_mickeys_scale_factor >> 3))
-		atp_slide_min_movement = atp_mickeys_scale_factor >> 3;
-
 	return (0);
 }
 
+static devclass_t atp_devclass;
+
 static device_method_t atp_methods[] = {
-	/* Device interface */
 	DEVMETHOD(device_probe,  atp_probe),
 	DEVMETHOD(device_attach, atp_attach),
 	DEVMETHOD(device_detach, atp_detach),
+
 	DEVMETHOD_END
 };
 
 static driver_t atp_driver = {
-	ATP_DRIVER_NAME,
-	atp_methods,
-	sizeof(struct atp_softc)
+	.name    = ATP_DRIVER_NAME,
+	.methods = atp_methods,
+	.size    = sizeof(struct atp_softc)
 };
 
-static devclass_t atp_devclass;
-
-DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, NULL);
+DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0);
 MODULE_DEPEND(atp, usb, 1, 1, 1);
 MODULE_VERSION(atp, 1);
+#ifndef __DragonFly__
+USB_PNP_HOST_INFO(fg_devs);
+USB_PNP_HOST_INFO(wsp_devs);
+#endif
diff --git a/sys/bus/u4b/input/atp/Makefile b/sys/bus/u4b/input/atp/Makefile
new file mode 100644
index 0000000000..e4051112df
--- /dev/null
+++ b/sys/bus/u4b/input/atp/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD: head/sys/modules/usb/atp/Makefile 314651 2017-03-04 10:10:17Z ngie $
+
+.PATH:	${.CURDIR}/..
+
+KMOD=	atp
+SRCS=	opt_bus.h opt_usb.h device_if.h bus_if.h usb_if.h usbdevs.h \
+	atp.c
+
+.include <bsd.kmod.mk>
diff --git a/sys/bus/u4b/usbdevs b/sys/bus/u4b/usbdevs
index ed3468878d..fb8d012bc6 100644
--- a/sys/bus/u4b/usbdevs
+++ b/sys/bus/u4b/usbdevs
@@ -1080,6 +1080,58 @@ product APPLE KBD_TP_JIS	0x0225	Apple Internal Keyboard/Trackpad (Wellspring/JIS
 product APPLE KBD_TP_ANSI2	0x0230	Apple Internal Keyboard/Trackpad (Wellspring2/ANSI)
 product APPLE KBD_TP_ISO2	0x0231	Apple Internal Keyboard/Trackpad (Wellspring2/ISO)
 product APPLE KBD_TP_JIS2	0x0232	Apple Internal Keyboard/Trackpad (Wellspring2/JIS)
+/* MacbookAir, aka wellspring */
+product APPLE WELLSPRING_ANSI	0x0223	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING_ISO	0x0224	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING_JIS	0x0225	Apple Internal Keyboard/Trackpad
+/* MacbookProPenryn, aka wellspring2 */
+product APPLE WELLSPRING2_ANSI	0x0230	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING2_ISO	0x0231	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING2_JIS	0x0232	Apple Internal Keyboard/Trackpad
+/* Macbook5,1 (unibody), aka wellspring3 */
+product APPLE WELLSPRING3_ANSI	0x0236	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING3_ISO	0x0237	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING3_JIS	0x0238	Apple Internal Keyboard/Trackpad
+/* MacbookAir3,2 (unibody), aka wellspring4 */
+product APPLE WELLSPRING4_ANSI	0x023f	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4_ISO	0x0240	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4_JIS	0x0241	Apple Internal Keyboard/Trackpad
+/* MacbookAir3,1 (unibody), aka wellspring4 */
+product APPLE WELLSPRING4A_ANSI	0x0242	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4A_ISO	0x0243	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4A_JIS	0x0244	Apple Internal Keyboard/Trackpad
+/* Macbook8 (unibody, March 2011) */
+product APPLE WELLSPRING5_ANSI	0x0245	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5_ISO	0x0246	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5_JIS	0x0247	Apple Internal Keyboard/Trackpad
+/* MacbookAir4,1 (unibody, July 2011) */
+product APPLE WELLSPRING6A_ANSI	0x0249	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6A_ISO	0x024a	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6A_JIS	0x024b	Apple Internal Keyboard/Trackpad
+/* MacbookAir4,2 (unibody, July 2011) */
+product APPLE WELLSPRING6_ANSI	0x024c	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6_ISO	0x024d	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6_JIS	0x024e	Apple Internal Keyboard/Trackpad
+/* Macbook8,2 (unibody) */
+product APPLE WELLSPRING5A_ANSI	0x0252	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5A_ISO	0x0253	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5A_JIS	0x0254	Apple Internal Keyboard/Trackpad
+/* MacbookPro10,1 (unibody, June 2012) */
+product APPLE WELLSPRING7_ANSI	0x0262	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7_ISO	0x0263	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7_JIS	0x0264	Apple Internal Keyboard/Trackpad
+/* MacbookPro10,2 (unibody, October 2012) */
+product APPLE WELLSPRING7A_ANSI	0x0259	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7A_ISO	0x025a	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7A_JIS	0x025b	Apple Internal Keyboard/Trackpad
+/* MacbookAir6,2 (unibody, June 2013) */
+product APPLE WELLSPRING8_ANSI	0x0290	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING8_ISO	0x0291	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING8_JIS	0x0292	Apple Internal Keyboard/Trackpad
+/* MacbookPro12,1 */
+product APPLE WELLSPRING9_ANSI	0x0272	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING9_ISO	0x0273	Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING9_JIS	0x0274	Apple Internal Keyboard/Trackpad
 product APPLE MOUSE		0x0301	Mouse M4848
 product APPLE OPTMOUSE		0x0302	Optical mouse
 product APPLE MIGHTYMOUSE	0x0304	Mighty Mouse
-- 
2.11.0

