diff options
author | hselasky <hselasky@FreeBSD.org> | 2014-02-23 23:36:32 +0000 |
---|---|---|
committer | hselasky <hselasky@FreeBSD.org> | 2014-02-23 23:36:32 +0000 |
commit | be5d8616b641b051f85c82c0e06d22ccdb8df36d (patch) | |
tree | d5029af427875c08fa17c33718c420d9405cf99d | |
parent | c0bb2e6d46cf4cd5c28cf0b99d652a5e685e2f7b (diff) | |
download | FreeBSD-src-be5d8616b641b051f85c82c0e06d22ccdb8df36d.zip FreeBSD-src-be5d8616b641b051f85c82c0e06d22ccdb8df36d.tar.gz |
Update ATP driver:
- Support for double-tap and drag.
- Support for 2-finger horizontal scrolling which translates to page-back/forward events.
- Single finger tap is equivalent to a left-button press.
- Two-finger taps are mapped to the right-button click.
- Three fingers are mapped to middle button.
- Add sysctl to disable single finger tapping.
- Fix for multiple open of /dev/atp0
- Enhanced support for the Fountain/Geyser family by adding Geyser4.
- Update manual page.
Submitted by: Rohit Grover <rgrover1@gmail.com>
MFC after: 2 weeks
-rw-r--r-- | share/man/man4/atp.4 | 65 | ||||
-rw-r--r-- | sys/dev/usb/input/atp.c | 2663 |
2 files changed, 1573 insertions, 1155 deletions
diff --git a/share/man/man4/atp.4 b/share/man/man4/atp.4 index 27d6397..a6e750a 100644 --- a/share/man/man4/atp.4 +++ b/share/man/man4/atp.4 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2009 Rohit Grover <rgrover1 at gmail dot com>. +.\" Copyright (c) 2014 Rohit Grover <rgrover1 at gmail dot com>. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -27,7 +27,7 @@ .\" .\" $FreeBSD$ .\" -.Dd February 7, 2014 +.Dd February 23, 2014 .Dt ATP 4 .Os .Sh NAME @@ -41,8 +41,7 @@ your kernel configuration file: .Cd "device usb" .Ed .Pp -Alternatively, to load the driver as a -module at boot time, place the following line in +Alternatively, to load the driver as a module at boot time, place the following line in .Xr loader.conf 5 : .Bd -literal -offset indent atp_load="YES" @@ -50,24 +49,20 @@ atp_load="YES" .Sh DESCRIPTION The .Nm -driver provides support for the Apple Internal Trackpad -device found in many Apple laptops. +driver provides support for the Apple Internal Trackpad device found in many +Apple laptops. Older (Fountain/Geyser) and the newer (Wellspring) trackpad +families are all supported through a unified driver. +.Pp +The driver simulates a three\-button mouse using multi\-finger tap detection. +Single finger tap generates a left\-button click; two\-finger tap maps to the +middle button; whereas a three\-finger tap gets treated as a right button +click. +.Pp +There is support for 2-finger horizontal scrolling, which translates to +page\-back/forward events. .Pp -The driver simulates a three\-button mouse using multi\-finger tap -detection. -. -A single\-finger tap generates a left button click; -two\-finger tap maps to the middle button; whereas a three\-finger tap -gets treated as a right button click. -. A double\-tap followed by a drag is treated as a selection gesture; a virtual left\-button click is assumed for the lifespan of the drag. -. -.Nm -attempts to filter away activity at the horizontal edges of the -trackpad\-\-this is to keep unintentional palm movement from being -considered as user input. -. .Pp .Nm supports dynamic reconfiguration using @@ -76,6 +71,28 @@ through nodes under .Nm hw.usb.atp . Pointer sensitivity can be controlled using the sysctl tunable .Nm hw.usb.atp.scale_factor . +Smaller values of +.Fa scale_factor +result in faster movement. +. +A simple high-pass filter is used to reduce contributions +from small movements; the threshold for this filter may be controlled by +.Nm hw.usb.atp.small_movement . +. +The maximum tolerable duration of a touch gesture is controlled by +.Nm hw.usb.atp.touch_timeout +(in microseconds); beyond this period, touches are considered to be slides. +(This conversion also happens when a finger stroke accumulates at least +.Nm hw.usb.atp.slide_min_movement +movement (in mickeys). +. +The maximum time (in microseconds) to allow an association between a double- +tap and drag gesture may be controlled by +.Nm hw.usb.atp.double_tap_threshold . +. +Should one want to disable tap detection and rely only upon physical button +presses, set the following sysctl to a value of 2 +.Nm hw.usb.atp.tap_minimum . . .Sh HARDWARE The @@ -84,6 +101,8 @@ driver provides support for the following Product IDs: .Pp .Bl -bullet -compact .It +PowerBooks, iBooks (IDs: 0x020e, 0x020f, 0x0210, 0x0214, 0x0215, 0x0216) +.It Core Duo MacBook & MacBook Pro (IDs: 0x0217, 0x0218, 0x0219) .It Core2 Duo MacBook & MacBook Pro (IDs: 0x021a, 0x021b, 0x021c) @@ -95,6 +114,14 @@ Core2 Duo MacBook3,1 (IDs: 0x0229, 0x022a, 0x022b) 15 inch PowerBook (IDs: 0x020e, 0x020f, 0x0215) .It 17 inch PowerBook (ID: 0x020d) +.It +Almost all recent Macbook-Pros and Airs (IDs: 0x0223, 0x0223, 0x0224, 0x0224, +0x0225, 0x0225, 0x0230, 0x0230, 0x0231, 0x0231, 0x0232, 0x0232, 0x0236, +0x0236, 0x0237, 0x0237, 0x0238, 0x0238, 0x023f, 0x023f, 0x0240, 0x0241, +0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, 0x0249, 0x024a, 0x024b, +0x024c, 0x024d, 0x024e, 0x0252, 0x0252, 0x0253, 0x0253, 0x0254, 0x0254, +0x0259, 0x025a, 0x025b, 0x0262, 0x0262, 0x0263, 0x0264, 0x0290, 0x0291, +0x0292) .El .Pp To discover the product\-id of a touchpad, search for 'Trackpad' in the diff --git a/sys/dev/usb/input/atp.c b/sys/dev/usb/input/atp.c index 8a55e32..98c4d5e 100644 --- a/sys/dev/usb/input/atp.c +++ b/sys/dev/usb/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 @@ -24,29 +24,64 @@ * 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 <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 "usbdevs.h" #define USB_DEBUG_VAR atp_debug @@ -61,17 +96,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 +135,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 { @@ -130,12 +184,13 @@ SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RW, 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"); + &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"); + "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); @@ -143,264 +198,538 @@ 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"); -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"); -/* - * 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"); /* * 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"); -/* 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) } + +static const STRUCT_USB_HOST_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; -typedef struct atp_stroke_component { +#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.*/ - /* 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 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 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 */ @@ -420,100 +749,188 @@ 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); +static const sensor_data_interpreter_t atp_sensor_data_interpreters[TRACKPAD_FAMILY_MAX] = { + [TRACKPAD_FAMILY_FOUNTAIN_GEYSER] = fg_interpret_sensor_data, + [TRACKPAD_FAMILY_WELLSPRING] = wsp_interpret_sensor_data, +}; + +/* 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; -usb_error_t -atp_req_get_report(struct usb_device *udev, void *data) +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) { - struct usb_device_request req; + atp_stroke_t *pstroke; - 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); + pstroke = TAILQ_FIRST(&sc->sc_stroke_free); + if (pstroke == NULL) + goto done; - return (usbd_do_request(udev, NULL /* mutex */, &req, data)); + 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 int -atp_set_device_mode(device_t dev, interface_mode mode) +static void +atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke) { - struct atp_softc *sc; - usb_device_request_t req; - usb_error_t err; + if (pstroke == NULL) + return; - if ((mode != RAW_SENSOR_MODE) && (mode != HID_MODE)) - return (ENXIO); + sc->sc_n_strokes--; - sc = device_get_softc(dev); + TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry); + TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry); +} - 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); +static void +atp_init_stroke_pool(struct atp_softc *sc) +{ + u_int x; - return (0); + 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 */)); } -void +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, @@ -541,17 +958,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"); @@ -561,126 +975,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); - } + u_int n_xpspans = 0; + u_int n_ypspans = 0; + uint8_t status_bits; - 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); - } - } + const struct fg_dev_params *params = + (const struct fg_dev_params *)sc->sc_params; - 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); - } + 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->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); - } - - 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; } } @@ -710,15 +1092,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. @@ -726,24 +1108,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; @@ -761,24 +1152,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 ((u_int)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, @@ -788,7 +1179,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++) { @@ -853,11 +1244,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--; @@ -865,32 +1256,106 @@ 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 ((u_int)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; + component->loc = pspan->loc; /* * A sudden and significant increase in a pspan's cumulative @@ -900,7 +1365,7 @@ atp_match_stroke_component(atp_stroke_component *component, * matching stroke component(s). But such a change should * *not* be interpreted as a movement. */ - if (pspan->cum > ((3 * component->cum_pressure) >> 1)) + if (pspan->cum > ((3 * component->cum_pressure) >> 1)) delta_mickeys = 0; component->cum_pressure = pspan->cum; @@ -920,72 +1385,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; } /* @@ -1024,35 +1543,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_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, 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 @@ -1075,109 +1599,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_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 __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 */ - - 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; + 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[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) { - atp_pspan spans[2][ATP_MAX_PSPANS_PER_AXIS]; + 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]++; } @@ -1186,7 +1736,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 */ @@ -1205,7 +1755,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) { @@ -1223,413 +1773,392 @@ 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 (((u_int)abs(stroke->components[X].delta_mickeys) <= - atp_small_movement_threshold) && - ((u_int)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 ((u_int)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 = sizeof(sqrt_table) / sizeof(sqrt_table[0]); - - 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 */ + 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) +{ + 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) { - u_int i; - atp_stroke *stroke; + 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_SAFE(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); + 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; + } } - 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. */ - } + atp_free_stroke(sc, strokep); + } - /* - * Reap this sibling zombie stroke. - */ + DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", + n_touches_reaped + n_slides_reaped); + sc->sc_state &= ~ATP_ZOMBIES_EXIST; - if (reaped_xlocs != NULL) - reaped_xlocs[*n_reaped] = stroke->components[X].loc; + /* No further processing necessary if physical button is depressed. */ + if (sc->sc_ibtn != 0) + return; - /* 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--; + 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; - *n_reaped += 1; - --i; /* Decr. i to keep it unchanged for the next iteration */ + 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 */ + break; + } + atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */ + + } else if (n_slides_reaped == 2) { + if (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 */ + } } +} - DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", *n_reaped); +/* 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; - /* 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); - return; - } + /* 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; } +} - /* If we reach here, then no more zombies remain. */ - sc->sc_state &= ~ATP_ZOMBIES_EXIST; +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) @@ -1639,25 +2168,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); + + if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0) + if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX) + return (0); - return (usbd_lookup_id_by_uaa(atp_devs, sizeof(atp_devs), uaa)); + 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); + 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 @@ -1665,41 +2227,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); + 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_config, + &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)) { + &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; @@ -1708,17 +2280,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; + callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0); return (0); @@ -1733,11 +2304,13 @@ 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); + + mtx_lock(&sc->sc_mutex); + callout_drain(&sc->sc_callout); + if (sc->sc_state & ATP_ENABLED) atp_disable(sc); - mtx_unlock(&sc->sc_mutex); - } + mtx_unlock(&sc->sc_mutex); usb_fifo_detach(&sc->sc_fifo); @@ -1752,92 +2325,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 > (int)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 < (int)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; @@ -1847,108 +2355,46 @@ 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; - 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) { + 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"); - - /* - * 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))); - } - - 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; + atp_add_to_queue(sc, dx, -dy, 0, 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; @@ -1961,53 +2407,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 @@ -2038,50 +2437,50 @@ 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); @@ -2105,20 +2504,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; } @@ -2128,21 +2527,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; } @@ -2154,9 +2553,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; @@ -2164,11 +2563,12 @@ 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); return (error); } @@ -2178,49 +2578,40 @@ 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), - { 0, 0 } + + DEVMETHOD_END }; static driver_t atp_driver = { - .name = ATP_DRIVER_NAME, + .name = ATP_DRIVER_NAME, .methods = atp_methods, - .size = sizeof(struct atp_softc) + .size = sizeof(struct atp_softc) }; -static devclass_t atp_devclass; - DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0); MODULE_DEPEND(atp, usb, 1, 1, 1); MODULE_VERSION(atp, 1); |