summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/dev/usb/input/atp.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/dev/usb/input/atp.c')
-rw-r--r--freebsd/sys/dev/usb/input/atp.c2636
1 files changed, 2636 insertions, 0 deletions
diff --git a/freebsd/sys/dev/usb/input/atp.c b/freebsd/sys/dev/usb/input/atp.c
new file mode 100644
index 00000000..461d4fff
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/atp.c
@@ -0,0 +1,2636 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*-
+ * Copyright (c) 2014 Rohit Grover
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * 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.
+ */
+
+/*
+ * 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <rtems/bsd/sys/param.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/mutex.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 <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#include <rtems/bsd/local/usbdevs.h>
+
+#define USB_DEBUG_VAR atp_debug
+#include <dev/usb/usb_debug.h>
+
+#include <sys/mouse.h>
+
+#define ATP_DRIVER_NAME "atp"
+
+/*
+ * Driver specific options: the following options may be set by
+ * `options' statements in the kernel configuration file.
+ */
+
+/* The divisor used to translate sensor reported positions to mickeys. */
+#ifndef ATP_SCALE_FACTOR
+#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.
+ */
+#ifndef ATP_TOUCH_TIMEOUT
+#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
+
+/*
+ * A double-tap followed by a single-finger slide is treated as a
+ * special gesture. The driver responds to this gesture by assuming a
+ * virtual button-press for the lifetime of the slide. The following
+ * threshold is the maximum time gap (in microseconds) between the two
+ * tap events preceding the slide for such a gesture.
+ */
+#ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD
+#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000
+#endif
+
+/*
+ * The wait duration in ticks after losing a touch contact before
+ * zombied strokes are reaped and turned into button events.
+ */
+#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 movement threshold for a stroke; this is the maximum difference
+ * in position which will be resolved as a continuation of a stroke
+ * component.
+ */
+#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
+
+/* 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");
+
+#ifdef USB_DEBUG
+enum atp_log_level {
+ ATP_LLEVEL_DISABLED = 0,
+ ATP_LLEVEL_ERROR,
+ ATP_LLEVEL_DEBUG, /* for troubleshooting */
+ ATP_LLEVEL_INFO, /* for diagnostics */
+};
+static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */
+SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
+#endif /* USB_DEBUG */
+
+static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RWTUN,
+ &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_RWTUN,
+ &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_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_RWTUN,
+ &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor),
+ atp_sysctl_scale_factor_handler, "IU", "movement scale factor");
+
+static u_int atp_small_movement_threshold = ATP_SMALL_MOVEMENT_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RWTUN,
+ &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD,
+ "the small movement black-hole for filtering noise");
+
+static u_int atp_tap_minimum = 1;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RWTUN,
+ &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 = 2 * ATP_SMALL_MOVEMENT_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RWTUN,
+ &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD,
+ "strokes with at least this amt. of movement are considered slides");
+
+/*
+ * 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 = 4;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RWTUN,
+ &atp_stroke_maturity_threshold, 4,
+ "the minimum age of a stroke for it to be considered mature");
+
+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 */
+};
+
+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 */
+};
+
+/* trackpad header types */
+enum fountain_geyser_trackpad_type {
+ FG_TRACKPAD_TYPE_GEYSER1,
+ FG_TRACKPAD_TYPE_GEYSER2,
+ FG_TRACKPAD_TYPE_GEYSER3,
+ FG_TRACKPAD_TYPE_GEYSER4,
+};
+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
+ },
+ [GEYSER1] = {
+ .data_len = 81,
+ .n_xsensors = 16,
+ .n_ysensors = 16,
+ .prot = FG_TRACKPAD_TYPE_GEYSER1
+ },
+ [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 = FG_TRACKPAD_TYPE_GEYSER2
+ },
+ [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 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, 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, 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, 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, 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,
+ },
+};
+
+#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;
+
+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 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.*/
+
+ int delta_mickeys; /* change in location (un-smoothened movement)*/
+} 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 {
+ TAILQ_ENTRY(atp_stroke) entry;
+
+ atp_stroke_type type;
+ uint32_t flags; /* the state of this stroke */
+#define ATSF_ZOMBIE 0x1
+ boolean_t matched; /* to track match against fingers.*/
+
+ 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;
+ struct mtx sc_mutex; /* 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 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 fountain-geyser sensor data contains status bits; the
+ * following values define the meanings of these bits.
+ * (only Geyser 3/4)
+ */
+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)0x01,
+ HID_MODE = (uint8_t)0x08
+} interface_mode;
+
+
+/*
+ * function prototypes
+ */
+static usb_fifo_cmd_t atp_start_read;
+static usb_fifo_cmd_t atp_stop_read;
+static usb_fifo_open_t atp_open;
+static usb_fifo_close_t atp_close;
+static usb_fifo_ioctl_t atp_ioctl;
+
+static struct usb_fifo_methods atp_fifo_methods = {
+ .f_open = &atp_open,
+ .f_close = &atp_close,
+ .f_ioctl = &atp_ioctl,
+ .f_start_read = &atp_start_read,
+ .f_stop_read = &atp_stop_read,
+ .basename[0] = ATP_DRIVER_NAME,
+};
+
+/* device initialization and shutdown */
+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 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 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 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 *);
+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);
+
+ sc->sc_n_strokes++;
+done:
+ return (pstroke);
+}
+
+static void
+atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke)
+{
+ if (pstroke == NULL)
+ return;
+
+ sc->sc_n_strokes--;
+
+ TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry);
+ TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry);
+}
+
+static void
+atp_init_stroke_pool(struct atp_softc *sc)
+{
+ u_int x;
+
+ TAILQ_INIT(&sc->sc_stroke_free);
+ TAILQ_INIT(&sc->sc_stroke_used);
+
+ sc->sc_n_strokes = 0;
+
+ memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data));
+
+ for (x = 0; x != ATP_MAX_STROKES; x++) {
+ TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x],
+ entry);
+ }
+}
+
+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.
+ */
+ pause("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] = mode_value;
+ 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);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH);
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH);
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+
+ case USB_ST_TRANSFERRED:
+ default:
+ break;
+ }
+}
+
+static int
+atp_enable(struct atp_softc *sc)
+{
+ if (sc->sc_state & ATP_ENABLED)
+ return (0);
+
+ /* reset status */
+ memset(&sc->sc_status, 0, sizeof(sc->sc_status));
+
+ atp_init_stroke_pool(sc);
+
+ sc->sc_state |= ATP_ENABLED;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
+ return (0);
+}
+
+static void
+atp_disable(struct atp_softc *sc)
+{
+ sc->sc_state &= ~(ATP_ENABLED | ATP_VALID);
+ DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
+}
+
+static void
+fg_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
+{
+ u_int n_xpspans = 0;
+ u_int n_ypspans = 0;
+ uint8_t status_bits;
+
+ const struct fg_dev_params *params =
+ (const struct fg_dev_params *)sc->sc_params;
+
+ 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);
+
+ /*
+ * 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;
+ }
+ }
+
+ /* 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);
+
+ /* 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;
+
+ sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0;
+ sc->sc_status.button = sc->sc_ibtn;
+
+ /*
+ * 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;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
+
+ 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)));
+ }
+
+ sc->sc_idlecount = 0;
+ usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
+ }
+ } else {
+ sc->sc_idlecount = 0;
+ }
+}
+
+/*
+ * Interpret the data from the X and Y pressure sensors. This function
+ * is called separately for the X and Y sensor arrays. The data in the
+ * USB packet is laid out in the following manner:
+ *
+ * sensor_data:
+ * --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4
+ * indices: 0 1 2 3 4 5 6 7 8 ... 15 ... 20 21 22 23 24
+ *
+ * '--' (in the above) indicates that the value is unimportant.
+ *
+ * Information about the above layout was obtained from the
+ * implementation of the AppleTouch driver in Linux.
+ *
+ * parameters:
+ * sensor_data
+ * raw sensor data from the USB packet.
+ * num
+ * The number of elements in the array 'arr'.
+ * axis
+ * Axis of data to fetch
+ * arr
+ * The array to be initialized with the readings.
+ * prot
+ * The protocol to use to interpret the data
+ */
+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 FG_TRACKPAD_TYPE_GEYSER1:
+ /*
+ * For Geyser 1, the sensors are laid out in pairs
+ * every 5 bytes.
+ */
+ 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))
+ arr[i+16] = sensor_data[di+40];
+ }
+
+ break;
+ 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 void
+fg_get_pressures(int *p, const int *cur, const int *base, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ p[i] = cur[i] - base[i];
+ if (p[i] > 127)
+ p[i] -= 256;
+ if (p[i] < -127)
+ p[i] += 256;
+ if (p[i] < 0)
+ p[i] = 0;
+
+ /*
+ * Shave off pressures below the noise-pressure
+ * threshold; this will reduce the contribution from
+ * lower pressure readings.
+ */
+ if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD)
+ p[i] = 0; /* filter away noise */
+ else
+ p[i] -= FG_SENSOR_NOISE_THRESHOLD;
+ }
+}
+
+static void
+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 fg_pspan_state {
+ ATP_PSPAN_INACTIVE,
+ ATP_PSPAN_INCREASING,
+ ATP_PSPAN_DECREASING,
+ } state; /* state of the pressure span */
+
+ /*
+ * The following is a simple state machine to track
+ * the phase of the pressure span.
+ */
+ memset(spans, 0, max_spans * sizeof(fg_pspan));
+ maxp = 0;
+ state = ATP_PSPAN_INACTIVE;
+ for (i = 0; i < num_sensors; i++) {
+ if (num_spans >= max_spans)
+ break;
+
+ if (p[i] == 0) {
+ if (state == ATP_PSPAN_INACTIVE) {
+ /*
+ * There is no pressure information for this
+ * sensor, and we aren't tracking a finger.
+ */
+ continue;
+ } else {
+ state = ATP_PSPAN_INACTIVE;
+ maxp = 0;
+ num_spans++;
+ }
+ } else {
+ switch (state) {
+ case ATP_PSPAN_INACTIVE:
+ state = ATP_PSPAN_INCREASING;
+ maxp = p[i];
+ break;
+
+ case ATP_PSPAN_INCREASING:
+ if (p[i] > maxp)
+ maxp = p[i];
+ else if (p[i] <= (maxp >> 1))
+ state = ATP_PSPAN_DECREASING;
+ break;
+
+ case ATP_PSPAN_DECREASING:
+ if (p[i] > p[i - 1]) {
+ /*
+ * This is the beginning of
+ * another span; change state
+ * to give the appearance that
+ * we're starting from an
+ * inactive span, and then
+ * re-process this reading in
+ * the next iteration.
+ */
+ num_spans++;
+ state = ATP_PSPAN_INACTIVE;
+ maxp = 0;
+ i--;
+ continue;
+ }
+ break;
+ }
+
+ /* Update the finger span with this reading. */
+ spans[num_spans].width++;
+ spans[num_spans].cum += p[i];
+ spans[num_spans].cog += p[i] * (i + 1);
+ }
+ }
+ if (state != ATP_PSPAN_INACTIVE)
+ num_spans++; /* close the last finger span */
+
+ /* post-process the spans */
+ for (i = 0; i < num_spans; i++) {
+ /* filter away unwanted pressure spans */
+ 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(fg_pspan));
+ i--;
+ }
+ num_spans--;
+ continue;
+ }
+
+ /* compute this span's representative location */
+ spans[i].loc = spans[i].cog * FG_SCALE_FACTOR /
+ spans[i].cum;
+
+ 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.
+ */
+static boolean_t
+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) > (int)FG_MAX_DELTA_MICKEYS)
+ return (false); /* the finger span is too far out; no match */
+
+ component->loc = pspan->loc;
+
+ /*
+ * A sudden and significant increase in a pspan's cumulative
+ * pressure indicates the incidence of a new finger
+ * contact. This usually revises the pspan's
+ * centre-of-gravity, and hence the location of any/all
+ * matching stroke component(s). But such a change should
+ * *not* be interpreted as a movement.
+ */
+ if (pspan->cum > ((3 * component->cum_pressure) >> 1))
+ delta_mickeys = 0;
+
+ component->cum_pressure = pspan->cum;
+ if (pspan->cum > component->max_cum_pressure)
+ component->max_cum_pressure = pspan->cum;
+
+ /*
+ * Disregard the component's movement if its cumulative
+ * pressure drops below a fraction of the maximum; this
+ * fraction is determined based on the stroke's type.
+ */
+ if (stroke_type == ATP_STROKE_TOUCH)
+ min_pressure = (3 * component->max_cum_pressure) >> 2;
+ else
+ min_pressure = component->max_cum_pressure >> 2;
+ if (component->cum_pressure < min_pressure)
+ delta_mickeys = 0;
+
+ component->delta_mickeys = delta_mickeys;
+ return (true);
+}
+
+static void
+fg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
+ fg_pspan *pspans, u_int n_pspans, u_int repeat_count)
+{
+ atp_stroke_t *strokep;
+ u_int repeat_index = 0;
+ u_int i;
+
+ /* Determine the index of the multi-span. */
+ if (repeat_count) {
+ for (i = 0; i < n_pspans; i++) {
+ if (pspans[i].cum > pspans[repeat_index].cum)
+ repeat_index = i;
+ }
+ }
+
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ if (strokep->components[axis].matched)
+ continue; /* skip matched components */
+
+ for (i = 0; i < n_pspans; i++) {
+ if (pspans[i].matched)
+ continue; /* skip matched pspans */
+
+ if (fg_match_stroke_component(
+ &strokep->components[axis], &pspans[i],
+ strokep->type)) {
+
+ /* There is a match. */
+ strokep->components[axis].matched = true;
+
+ /* Take care to repeat at the multi-span. */
+ if ((repeat_count > 0) && (i == repeat_index))
+ repeat_count--;
+ else
+ pspans[i].matched = true;
+
+ 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.
+ */
+static boolean_t
+fg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+ u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
+{
+ 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. */
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ strokep->components[X].matched = false;
+ strokep->components[Y].matched = false;
+ }
+
+ /*
+ * Usually, the X and Y pspans come in pairs (the common case
+ * being a single pair). It is possible, however, that
+ * multiple contacts resolve to a single pspan along an
+ * axis, as illustrated in the following:
+ *
+ * F = finger-contact
+ *
+ * pspan pspan
+ * +-----------------------+
+ * | . . |
+ * | . . |
+ * | . . |
+ * | . . |
+ * pspan |.........F......F |
+ * | |
+ * | |
+ * | |
+ * +-----------------------+
+ *
+ *
+ * The above case can be detected by a difference in the
+ * number of X and Y pspans. When this happens, X and Y pspans
+ * aren't easy to pair or match against strokes.
+ *
+ * When X and Y pspans differ in number, the axis with the
+ * smaller number of pspans is regarded as having a repeating
+ * pspan (or a multi-pspan)--in the above illustration, the
+ * Y-axis has a repeating pspan. Our approach is to try to
+ * match the multi-pspan repeatedly against strokes. The
+ * difference between the number of X and Y pspans gives us a
+ * crude repeat_count for matching multi-pspans--i.e. the
+ * multi-pspan along the Y axis (above) has a repeat_count of 1.
+ */
+ repeat_count = abs(n_xpspans - n_ypspans);
+
+ fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
+ (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
+ repeat_count : 0));
+ 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. */
+ TAILQ_FOREACH_SAFE(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, strokep);
+ }
+ }
+
+ /* Add new strokes for pairs of unmatched pspans */
+ for (i = 0; i < n_xpspans; i++) {
+ if (pspans_x[i].matched == false) break;
+ }
+ for (j = 0; j < n_ypspans; j++) {
+ if (pspans_y[j].matched == false) break;
+ }
+ if ((i < n_xpspans) && (j < n_ypspans)) {
+#ifdef USB_DEBUG
+ if (atp_debug >= ATP_LLEVEL_INFO) {
+ printf("unmatched pspans:");
+ for (; i < n_xpspans; i++) {
+ if (pspans_x[i].matched)
+ continue;
+ printf(" X:[loc:%u,cum:%u]",
+ pspans_x[i].loc, pspans_x[i].cum);
+ }
+ for (; j < n_ypspans; j++) {
+ if (pspans_y[j].matched)
+ continue;
+ printf(" Y:[loc:%u,cum:%u]",
+ pspans_y[j].loc, pspans_y[j].cum);
+ }
+ printf("\n");
+ }
+#endif /* USB_DEBUG */
+ if ((n_xpspans == 1) && (n_ypspans == 1))
+ /* The common case of a single pair of new pspans. */
+ fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
+ else
+ fg_add_new_strokes(sc, pspans_x, n_xpspans,
+ pspans_y, n_ypspans);
+ }
+
+#ifdef USB_DEBUG
+ if (atp_debug >= ATP_LLEVEL_INFO) {
+ 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 (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_SAFE(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 void
+fg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x,
+ const fg_pspan *pspan_y)
+{
+ atp_stroke_t *strokep;
+
+ strokep = atp_alloc_stroke(sc);
+ if (strokep == NULL)
+ return;
+
+ /*
+ * 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 = 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;
+
+ 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",
+ strokep->components[X].loc,
+ strokep->components[Y].loc,
+ (u_int)strokep->ctime.tv_sec,
+ (unsigned long int)strokep->ctime.tv_usec);
+}
+
+static void
+fg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+ u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
+{
+ 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) {
+ 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) {
+ spans[Y][nspans[Y]] = pspans_y[j];
+ nspans[Y]++;
+ }
+ }
+
+ 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++)
+ fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+ } else {
+ u_int cum = 0;
+ atp_axis repeat_axis; /* axis with multi-pspans */
+ u_int repeat_count; /* repeat count for the multi-pspan*/
+ u_int repeat_index = 0; /* index of the multi-span */
+
+ repeat_axis = (nspans[X] > nspans[Y]) ? Y : X;
+ repeat_count = abs(nspans[X] - nspans[Y]);
+ for (i = 0; i < nspans[repeat_axis]; i++) {
+ if (spans[repeat_axis][i].cum > cum) {
+ repeat_index = i;
+ cum = spans[repeat_axis][i].cum;
+ }
+ }
+
+ /* Create new strokes from pairs of unmatched pspans */
+ i = 0, j = 0;
+ for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) {
+ fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+
+ /* Take care to repeat at the multi-pspan. */
+ if (repeat_count > 0) {
+ if ((repeat_axis == X) &&
+ (repeat_index == i)) {
+ i--; /* counter loop increment */
+ repeat_count--;
+ } else if ((repeat_axis == Y) &&
+ (repeat_index == j)) {
+ j--; /* counter loop increment */
+ repeat_count--;
+ }
+ }
+ }
+ }
+}
+
+/* Initialize a stroke from an unmatched finger. */
+static void
+wsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp)
+{
+ atp_stroke_t *strokep;
+
+ strokep = atp_alloc_stroke(sc);
+ if (strokep == NULL)
+ return;
+
+ /*
+ * 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;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y);
+}
+
+static void
+atp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep,
+ boolean_t *movementp)
+{
+ /* Revitalize stroke if it had previously been marked as a zombie. */
+ if (strokep->flags & ATSF_ZOMBIE)
+ strokep->flags &= ~ATSF_ZOMBIE;
+
+ strokep->age++;
+ if (strokep->age <= atp_stroke_maturity_threshold) {
+ /* Avoid noise from immature strokes. */
+ strokep->instantaneous_dx = 0;
+ strokep->instantaneous_dy = 0;
+ }
+
+ if (atp_compute_stroke_movement(strokep))
+ *movementp = true;
+
+ if (strokep->type != ATP_STROKE_TOUCH)
+ return;
+
+ /* 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 ((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);
+ }
+ }
+}
+
+static boolean_t
+atp_stroke_has_small_movement(const atp_stroke_t *strokep)
+{
+ return (((u_int)abs(strokep->instantaneous_dx) <=
+ atp_small_movement_threshold) &&
+ ((u_int)abs(strokep->instantaneous_dy) <=
+ atp_small_movement_threshold));
+}
+
+/*
+ * Accumulate instantaneous changes into the stroke's 'pending' bucket; if
+ * the aggregate exceeds the small_movement_threshold, then retain
+ * instantaneous changes for later.
+ */
+static void
+atp_update_pending_mickeys(atp_stroke_t *strokep)
+{
+ /* 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); \
+ } \
+ }
+
+ 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
+ * instantaneous changes in the X and Y components.
+ */
+static boolean_t
+atp_compute_stroke_movement(atp_stroke_t *strokep)
+{
+ /*
+ * 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(strokep))
+ atp_update_pending_mickeys(strokep);
+ else { /* large movement */
+ /* clear away any pending mickeys if there are large movements*/
+ strokep->pending_dx = 0;
+ strokep->pending_dy = 0;
+ }
+
+ /* 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;
+ }
+
+ strokep->cum_movement_x += strokep->movement_dx;
+ strokep->cum_movement_y += strokep->movement_dy;
+
+ return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0));
+}
+
+/*
+ * 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)
+{
+ if (strokep->flags & ATSF_ZOMBIE)
+ return;
+
+ /* Drop immature strokes rightaway. */
+ if (strokep->age <= atp_stroke_maturity_threshold) {
+ atp_free_stroke(sc, strokep);
+ return;
+ }
+
+ strokep->flags |= ATSF_ZOMBIE;
+ sc->sc_state |= ATP_ZOMBIES_EXIST;
+
+ 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 boolean_t
+atp_is_horizontal_scroll(const atp_stroke_t *strokep)
+{
+ 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);
+}
+
+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;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "\n");
+
+ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+ if ((strokep->flags & ATSF_ZOMBIE) == 0)
+ continue;
+
+ if (strokep->type == ATP_STROKE_TOUCH) {
+ n_touches_reaped++;
+ } else {
+ 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;
+ }
+ }
+
+ atp_free_stroke(sc, strokep);
+ }
+
+ DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n",
+ n_touches_reaped + n_slides_reaped);
+ sc->sc_state &= ~ATP_ZOMBIES_EXIST;
+
+ /* No further processing necessary if physical button is depressed. */
+ if (sc->sc_ibtn != 0)
+ return;
+
+ if ((n_touches_reaped == 0) && (n_slides_reaped == 0))
+ return;
+
+ /* 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;
+
+ 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 */
+ }
+}
+
+/* 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];
+
+ dx = imin(dx, 254); dx = imax(dx, -256);
+ dy = imin(dy, 254); dy = imax(dy, -256);
+ dz = imin(dz, 126); dz = imax(dz, -128);
+
+ 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)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ 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);
+
+ 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 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);
+ free(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
+ * limited information--they encode movement deltas and button
+ * 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.
+ */
+ 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);
+
+ 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_mutex);
+ 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), -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644)) {
+ goto detach;
+ }
+
+ device_set_usb_desc(dev);
+
+ sc->sc_hw.buttons = 3;
+ sc->sc_hw.iftype = MOUSE_IF_USB;
+ sc->sc_hw.type = MOUSE_PAD;
+ sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+ sc->sc_hw.hwid = 0;
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.rate = -1;
+ sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
+ 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;
+
+ callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0);
+
+ return (0);
+
+detach:
+ atp_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+atp_detach(device_t dev)
+{
+ struct atp_softc *sc;
+
+ sc = device_get_softc(dev);
+ atp_set_device_mode(sc, HID_MODE);
+
+ mtx_lock(&sc->sc_mutex);
+ callout_drain(&sc->sc_callout);
+ if (sc->sc_state & ATP_ENABLED)
+ atp_disable(sc);
+ mtx_unlock(&sc->sc_mutex);
+
+ usb_fifo_detach(&sc->sc_fifo);
+
+ usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER);
+
+ mtx_destroy(&sc->sc_mutex);
+
+ return (0);
+}
+
+static void
+atp_intr(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct atp_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ 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);
+ }
+
+ sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED |
+ MOUSE_POSCHANGED);
+ sc->sc_status.obutton = sc->sc_status.button;
+
+ (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;
+ } else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) {
+ /* Assume a button-press with DOUBLE_TAP_N_DRAG. */
+ sc->sc_status.button = MOUSE_BUTTON1DOWN;
+ }
+
+ sc->sc_status.flags |=
+ 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"));
+ }
+
+ if (sc->sc_status.flags & (MOUSE_POSCHANGED |
+ MOUSE_STDBUTTONSCHANGED)) {
+
+ atp_stroke_t *strokep;
+ u_int8_t n_movements = 0;
+ int dx = 0;
+ int dy = 0;
+ int dz = 0;
+
+ 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++;
+ }
+
+ /* average movement if multiple strokes record motion.*/
+ if (n_movements > 1) {
+ dx /= (int)n_movements;
+ dy /= (int)n_movements;
+ }
+
+ /* 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;
+
+ if (!atp_is_vertical_scroll(strokep))
+ all_vertical_scrolls = false;
+ }
+ if (all_vertical_scrolls) {
+ dz = dy;
+ dy = dx = 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) {
+ usbd_xfer_set_frame_len(xfer, 0,
+ sc->sc_expected_sensor_data_len);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+atp_start_read(struct usb_fifo *fifo)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+ int rate;
+
+ /* Check if we should override the default polling interval */
+ rate = sc->sc_pollrate;
+ /* Range check rate */
+ if (rate > 1000)
+ rate = 1000;
+ /* Check for set rate */
+ if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) {
+ /* Stop current transfer, if any */
+ usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
+ /* Set new interval */
+ usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate);
+ /* Only set pollrate once */
+ sc->sc_pollrate = 0;
+ }
+
+ usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]);
+}
+
+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)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+
+ /* check for duplicate open, should not happen */
+ if (sc->sc_fflags & fflags)
+ 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)) {
+ return (ENOMEM);
+ }
+ }
+
+ sc->sc_fflags |= (fflags & (FREAD | FWRITE));
+ return (0);
+}
+
+static void
+atp_close(struct usb_fifo *fifo, int fflags)
+{
+ 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);
+ }
+}
+
+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);
+
+ switch(cmd) {
+ case MOUSE_GETHWINFO:
+ *(mousehw_t *)addr = sc->sc_hw;
+ break;
+ case MOUSE_GETMODE:
+ *(mousemode_t *)addr = sc->sc_mode;
+ break;
+ case MOUSE_SETMODE:
+ mode = *(mousemode_t *)addr;
+
+ if (mode.level == -1)
+ /* Don't change the current setting */
+ ;
+ else if ((mode.level < 0) || (mode.level > 1)) {
+ error = EINVAL;
+ 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.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.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ atp_reset_buf(sc);
+ break;
+ case MOUSE_GETLEVEL:
+ *(int *)addr = sc->sc_mode.level;
+ break;
+ case MOUSE_SETLEVEL:
+ if ((*(int *)addr < 0) || (*(int *)addr > 1)) {
+ error = EINVAL;
+ 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.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.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ atp_reset_buf(sc);
+ break;
+ case MOUSE_GETSTATUS: {
+ mousestatus_t *status = (mousestatus_t *)addr;
+
+ *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;
+
+ if (status->dx || status->dy || status->dz)
+ status->flags |= MOUSE_POSCHANGED;
+ if (status->button != status->obutton)
+ status->flags |= MOUSE_BUTTONSCHANGED;
+ break;
+ }
+
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ mtx_unlock(&sc->sc_mutex);
+ return (error);
+}
+
+static int
+atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS)
+{
+ int error;
+ u_int tmp;
+
+ tmp = atp_mickeys_scale_factor;
+ error = sysctl_handle_int(oidp, &tmp, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ 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);
+
+ return (0);
+}
+
+static devclass_t atp_devclass;
+
+static device_method_t atp_methods[] = {
+ DEVMETHOD(device_probe, atp_probe),
+ DEVMETHOD(device_attach, atp_attach),
+ DEVMETHOD(device_detach, atp_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t atp_driver = {
+ .name = ATP_DRIVER_NAME,
+ .methods = atp_methods,
+ .size = sizeof(struct atp_softc)
+};
+
+DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0);
+MODULE_DEPEND(atp, usb, 1, 1, 1);
+MODULE_VERSION(atp, 1);
+USB_PNP_HOST_INFO(fg_devs);
+USB_PNP_HOST_INFO(wsp_devs);