atp.c revision 262417
1199086Srpaulo/*-
2262417Shselasky * Copyright (c) 2014 Rohit Grover
3199086Srpaulo * All rights reserved.
4199086Srpaulo *
5199086Srpaulo * Redistribution and use in source and binary forms, with or without
6199086Srpaulo * modification, are permitted provided that the following conditions
7199086Srpaulo * are met:
8199086Srpaulo * 1. Redistributions of source code must retain the above copyright
9199086Srpaulo *    notice, this list of conditions and the following disclaimer.
10199086Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
11199086Srpaulo *    notice, this list of conditions and the following disclaimer in the
12199086Srpaulo *    documentation and/or other materials provided with the distribution.
13199086Srpaulo *
14199086Srpaulo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15199086Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16199086Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17199086Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18199086Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19199086Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20199086Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21199086Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22199086Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23199086Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24199086Srpaulo * SUCH DAMAGE.
25199086Srpaulo */
26199086Srpaulo
27262417Shselasky/*
28262417Shselasky * Some tables, structures, definitions and constant values for the
29262417Shselasky * touchpad protocol has been copied from Linux's
30262417Shselasky * "drivers/input/mouse/bcm5974.c" which has the following copyright
31262417Shselasky * holders under GPLv2. All device specific code in this driver has
32262417Shselasky * been written from scratch. The decoding algorithm is based on
33262417Shselasky * output from FreeBSD's usbdump.
34262417Shselasky *
35262417Shselasky * Copyright (C) 2008      Henrik Rydberg (rydberg@euromail.se)
36262417Shselasky * Copyright (C) 2008      Scott Shawcroft (scott.shawcroft@gmail.com)
37262417Shselasky * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
38262417Shselasky * Copyright (C) 2005      Johannes Berg (johannes@sipsolutions.net)
39262417Shselasky * Copyright (C) 2005      Stelian Pop (stelian@popies.net)
40262417Shselasky * Copyright (C) 2005      Frank Arnold (frank@scirocco-5v-turbo.de)
41262417Shselasky * Copyright (C) 2005      Peter Osterlund (petero2@telia.com)
42262417Shselasky * Copyright (C) 2005      Michael Hanselmann (linux-kernel@hansmi.ch)
43262417Shselasky * Copyright (C) 2006      Nicolas Boichat (nicolas@boichat.ch)
44262417Shselasky */
45262417Shselasky
46262417Shselasky/*
47262417Shselasky * Author's note: 'atp' supports two distinct families of Apple trackpad
48262417Shselasky * products: the older Fountain/Geyser and the latest Wellspring trackpads.
49262417Shselasky * The first version made its appearance with FreeBSD 8 and worked only with
50262417Shselasky * the Fountain/Geyser hardware. A fork of this driver for Wellspring was
51262417Shselasky * contributed by Huang Wen Hui. This driver unifies the Wellspring effort
52262417Shselasky * and also improves upon the original work.
53262417Shselasky *
54262417Shselasky * I'm grateful to Stephan Scheunig, Angela Naegele, and Nokia IT-support
55262417Shselasky * for helping me with access to hardware. Thanks also go to Nokia for
56262417Shselasky * giving me an opportunity to do this work.
57262417Shselasky */
58262417Shselasky
59199086Srpaulo#include <sys/cdefs.h>
60199086Srpaulo__FBSDID("$FreeBSD: head/sys/dev/usb/input/atp.c 262417 2014-02-23 23:36:32Z hselasky $");
61199086Srpaulo
62262417Shselasky#include <sys/stdint.h>
63262417Shselasky#include <sys/stddef.h>
64199086Srpaulo#include <sys/param.h>
65262417Shselasky#include <sys/types.h>
66199086Srpaulo#include <sys/systm.h>
67199086Srpaulo#include <sys/kernel.h>
68262417Shselasky#include <sys/bus.h>
69199086Srpaulo#include <sys/module.h>
70199086Srpaulo#include <sys/lock.h>
71199086Srpaulo#include <sys/mutex.h>
72262417Shselasky#include <sys/sysctl.h>
73262417Shselasky#include <sys/malloc.h>
74199086Srpaulo#include <sys/conf.h>
75199086Srpaulo#include <sys/fcntl.h>
76199086Srpaulo#include <sys/file.h>
77199086Srpaulo#include <sys/selinfo.h>
78199086Srpaulo#include <sys/poll.h>
79199086Srpaulo
80199086Srpaulo#include <dev/usb/usb.h>
81199086Srpaulo#include <dev/usb/usbdi.h>
82199086Srpaulo#include <dev/usb/usbdi_util.h>
83199086Srpaulo#include <dev/usb/usbhid.h>
84262417Shselasky
85199086Srpaulo#include "usbdevs.h"
86199086Srpaulo
87199086Srpaulo#define USB_DEBUG_VAR atp_debug
88199086Srpaulo#include <dev/usb/usb_debug.h>
89199086Srpaulo
90199086Srpaulo#include <sys/mouse.h>
91199086Srpaulo
92199086Srpaulo#define ATP_DRIVER_NAME "atp"
93199086Srpaulo
94199086Srpaulo/*
95199086Srpaulo * Driver specific options: the following options may be set by
96199086Srpaulo * `options' statements in the kernel configuration file.
97199086Srpaulo */
98199086Srpaulo
99262417Shselasky/* The divisor used to translate sensor reported positions to mickeys. */
100199086Srpaulo#ifndef ATP_SCALE_FACTOR
101262417Shselasky#define ATP_SCALE_FACTOR                  16
102199086Srpaulo#endif
103199086Srpaulo
104262417Shselasky/* Threshold for small movement noise (in mickeys) */
105262417Shselasky#ifndef ATP_SMALL_MOVEMENT_THRESHOLD
106262417Shselasky#define ATP_SMALL_MOVEMENT_THRESHOLD      30
107262417Shselasky#endif
108262417Shselasky
109262417Shselasky/* Threshold of instantaneous deltas beyond which movement is considered fast.*/
110262417Shselasky#ifndef ATP_FAST_MOVEMENT_TRESHOLD
111262417Shselasky#define ATP_FAST_MOVEMENT_TRESHOLD        150
112262417Shselasky#endif
113262417Shselasky
114199086Srpaulo/*
115262417Shselasky * This is the age in microseconds beyond which a touch is considered
116262417Shselasky * to be a slide; and therefore a tap event isn't registered.
117199086Srpaulo */
118199086Srpaulo#ifndef ATP_TOUCH_TIMEOUT
119262417Shselasky#define ATP_TOUCH_TIMEOUT                 125000
120199086Srpaulo#endif
121199086Srpaulo
122262417Shselasky#ifndef ATP_IDLENESS_THRESHOLD
123262417Shselasky#define	ATP_IDLENESS_THRESHOLD 10
124262417Shselasky#endif
125262417Shselasky
126262417Shselasky#ifndef FG_SENSOR_NOISE_THRESHOLD
127262417Shselasky#define FG_SENSOR_NOISE_THRESHOLD 2
128262417Shselasky#endif
129262417Shselasky
130199086Srpaulo/*
131199086Srpaulo * A double-tap followed by a single-finger slide is treated as a
132199086Srpaulo * special gesture. The driver responds to this gesture by assuming a
133199086Srpaulo * virtual button-press for the lifetime of the slide. The following
134199086Srpaulo * threshold is the maximum time gap (in microseconds) between the two
135199086Srpaulo * tap events preceding the slide for such a gesture.
136199086Srpaulo */
137199086Srpaulo#ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD
138262417Shselasky#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD   200000
139199086Srpaulo#endif
140199086Srpaulo
141199086Srpaulo/*
142262417Shselasky * The wait duration in ticks after losing a touch contact before
143262417Shselasky * zombied strokes are reaped and turned into button events.
144199086Srpaulo */
145262417Shselasky#define ATP_ZOMBIE_STROKE_REAP_INTERVAL   (hz / 20)	/* 50 ms */
146199086Srpaulo
147262417Shselasky/* The multiplier used to translate sensor reported positions to mickeys. */
148262417Shselasky#define FG_SCALE_FACTOR                   380
149262417Shselasky
150199086Srpaulo/*
151262417Shselasky * The movement threshold for a stroke; this is the maximum difference
152262417Shselasky * in position which will be resolved as a continuation of a stroke
153262417Shselasky * component.
154199086Srpaulo */
155262417Shselasky#define FG_MAX_DELTA_MICKEYS             ((3 * (FG_SCALE_FACTOR)) >> 1)
156262417Shselasky
157262417Shselasky/* Distance-squared threshold for matching a finger with a known stroke */
158262417Shselasky#ifndef WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ
159262417Shselasky#define WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ 1000000
160199086Srpaulo#endif
161199086Srpaulo
162262417Shselasky/* Ignore pressure spans with cumulative press. below this value. */
163262417Shselasky#define FG_PSPAN_MIN_CUM_PRESSURE         10
164262417Shselasky
165262417Shselasky/* Maximum allowed width for pressure-spans.*/
166262417Shselasky#define FG_PSPAN_MAX_WIDTH                4
167262417Shselasky
168199086Srpaulo/* end of driver specific options */
169199086Srpaulo
170199086Srpaulo/* Tunables */
171262417Shselaskystatic SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB ATP");
172199086Srpaulo
173207077Sthompsa#ifdef USB_DEBUG
174199086Srpauloenum atp_log_level {
175199086Srpaulo	ATP_LLEVEL_DISABLED = 0,
176199086Srpaulo	ATP_LLEVEL_ERROR,
177199086Srpaulo	ATP_LLEVEL_DEBUG,       /* for troubleshooting */
178199086Srpaulo	ATP_LLEVEL_INFO,        /* for diagnostics */
179199086Srpaulo};
180199086Srpaulostatic int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */
181199086SrpauloSYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RW,
182199086Srpaulo    &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
183207077Sthompsa#endif /* USB_DEBUG */
184199086Srpaulo
185199086Srpaulostatic u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
186217323SmdfSYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RW,
187262417Shselasky    &atp_touch_timeout, 125000, "age threshold in microseconds for a touch");
188199086Srpaulo
189199086Srpaulostatic u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD;
190217323SmdfSYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RW,
191199086Srpaulo    &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD,
192262417Shselasky    "maximum time in microseconds to allow association between a double-tap and "
193262417Shselasky    "drag gesture");
194199086Srpaulo
195199086Srpaulostatic u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR;
196199086Srpaulostatic int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS);
197199086SrpauloSYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RW,
198199086Srpaulo    &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor),
199199086Srpaulo    atp_sysctl_scale_factor_handler, "IU", "movement scale factor");
200199086Srpaulo
201262417Shselaskystatic u_int atp_small_movement_threshold = ATP_SMALL_MOVEMENT_THRESHOLD;
202199086SrpauloSYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RW,
203262417Shselasky    &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD,
204199086Srpaulo    "the small movement black-hole for filtering noise");
205262417Shselasky
206262417Shselaskystatic u_int atp_tap_minimum = 1;
207262417ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RW,
208262417Shselasky    &atp_tap_minimum, 1, "Minimum number of taps before detection");
209262417Shselasky
210199086Srpaulo/*
211199086Srpaulo * Strokes which accumulate at least this amount of absolute movement
212199086Srpaulo * from the aggregate of their components are considered as
213199086Srpaulo * slides. Unit: mickeys.
214199086Srpaulo */
215262417Shselaskystatic u_int atp_slide_min_movement = 2 * ATP_SMALL_MOVEMENT_THRESHOLD;
216199086SrpauloSYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RW,
217262417Shselasky    &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD,
218199086Srpaulo    "strokes with at least this amt. of movement are considered slides");
219199086Srpaulo
220199086Srpaulo/*
221199086Srpaulo * The minimum age of a stroke for it to be considered mature; this
222199086Srpaulo * helps filter movements (noise) from immature strokes. Units: interrupts.
223199086Srpaulo */
224262417Shselaskystatic u_int atp_stroke_maturity_threshold = 4;
225199086SrpauloSYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RW,
226262417Shselasky    &atp_stroke_maturity_threshold, 4,
227199086Srpaulo    "the minimum age of a stroke for it to be considered mature");
228199086Srpaulo
229262417Shselaskytypedef enum atp_trackpad_family {
230262417Shselasky	TRACKPAD_FAMILY_FOUNTAIN_GEYSER,
231262417Shselasky	TRACKPAD_FAMILY_WELLSPRING,
232262417Shselasky	TRACKPAD_FAMILY_MAX /* keep this at the tail end of the enumeration */
233262417Shselasky} trackpad_family_t;
234199086Srpaulo
235262417Shselaskyenum fountain_geyser_product {
236262417Shselasky	FOUNTAIN,
237262417Shselasky	GEYSER1,
238262417Shselasky	GEYSER1_17inch,
239262417Shselasky	GEYSER2,
240262417Shselasky	GEYSER3,
241262417Shselasky	GEYSER4,
242262417Shselasky	FOUNTAIN_GEYSER_PRODUCT_MAX /* keep this at the end */
243262417Shselasky};
244199086Srpaulo
245262417Shselaskyenum wellspring_product {
246262417Shselasky	WELLSPRING1,
247262417Shselasky	WELLSPRING2,
248262417Shselasky	WELLSPRING3,
249262417Shselasky	WELLSPRING4,
250262417Shselasky	WELLSPRING4A,
251262417Shselasky	WELLSPRING5,
252262417Shselasky	WELLSPRING6A,
253262417Shselasky	WELLSPRING6,
254262417Shselasky	WELLSPRING5A,
255262417Shselasky	WELLSPRING7,
256262417Shselasky	WELLSPRING7A,
257262417Shselasky	WELLSPRING8,
258262417Shselasky	WELLSPRING_PRODUCT_MAX /* keep this at the end of the enumeration */
259262417Shselasky};
260199086Srpaulo
261262417Shselasky/* trackpad header types */
262262417Shselaskyenum fountain_geyser_trackpad_type {
263262417Shselasky	FG_TRACKPAD_TYPE_GEYSER1,
264262417Shselasky	FG_TRACKPAD_TYPE_GEYSER2,
265262417Shselasky	FG_TRACKPAD_TYPE_GEYSER3,
266262417Shselasky	FG_TRACKPAD_TYPE_GEYSER4,
267262417Shselasky};
268262417Shselaskyenum wellspring_trackpad_type {
269262417Shselasky	WSP_TRACKPAD_TYPE1,      /* plain trackpad */
270262417Shselasky	WSP_TRACKPAD_TYPE2,      /* button integrated in trackpad */
271262417Shselasky	WSP_TRACKPAD_TYPE3       /* additional header fields since June 2013 */
272262417Shselasky};
273199086Srpaulo
274262417Shselasky/*
275262417Shselasky * Trackpad family and product and family are encoded together in the
276262417Shselasky * driver_info value associated with a trackpad product.
277262417Shselasky */
278262417Shselasky#define N_PROD_BITS 8  /* Number of bits used to encode product */
279262417Shselasky#define ENCODE_DRIVER_INFO(FAMILY, PROD)      \
280262417Shselasky    (((FAMILY) << N_PROD_BITS) | (PROD))
281262417Shselasky#define DECODE_FAMILY_FROM_DRIVER_INFO(INFO)  ((INFO) >> N_PROD_BITS)
282262417Shselasky#define DECODE_PRODUCT_FROM_DRIVER_INFO(INFO) \
283262417Shselasky    ((INFO) & ((1 << N_PROD_BITS) - 1))
284262417Shselasky
285262417Shselasky#define FG_DRIVER_INFO(PRODUCT)               \
286262417Shselasky    ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_FOUNTAIN_GEYSER, PRODUCT)
287262417Shselasky#define WELLSPRING_DRIVER_INFO(PRODUCT)       \
288262417Shselasky    ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_WELLSPRING, PRODUCT)
289262417Shselasky
290262417Shselasky/*
291262417Shselasky * The following structure captures the state of a pressure span along
292262417Shselasky * an axis. Each contact with the touchpad results in separate
293262417Shselasky * pressure spans along the two axes.
294262417Shselasky */
295262417Shselaskytypedef struct fg_pspan {
296262417Shselasky	u_int width;       /* in units of sensors */
297262417Shselasky	u_int cum;         /* cumulative compression (from all sensors) */
298262417Shselasky	u_int cog;         /* center of gravity */
299262417Shselasky	u_int loc;         /* location (scaled using the mickeys factor) */
300262417Shselasky	boolean_t matched; /* to track pspans as they match against strokes. */
301262417Shselasky} fg_pspan;
302262417Shselasky
303262417Shselasky#define FG_MAX_PSPANS_PER_AXIS 3
304262417Shselasky#define FG_MAX_STROKES         (2 * FG_MAX_PSPANS_PER_AXIS)
305262417Shselasky
306262417Shselasky#define WELLSPRING_INTERFACE_INDEX 1
307262417Shselasky
308262417Shselasky/* trackpad finger data offsets, le16-aligned */
309262417Shselasky#define WSP_TYPE1_FINGER_DATA_OFFSET  (13 * 2)
310262417Shselasky#define WSP_TYPE2_FINGER_DATA_OFFSET  (15 * 2)
311262417Shselasky#define WSP_TYPE3_FINGER_DATA_OFFSET  (19 * 2)
312262417Shselasky
313262417Shselasky/* trackpad button data offsets */
314262417Shselasky#define WSP_TYPE2_BUTTON_DATA_OFFSET   15
315262417Shselasky#define WSP_TYPE3_BUTTON_DATA_OFFSET   23
316262417Shselasky
317262417Shselasky/* list of device capability bits */
318262417Shselasky#define HAS_INTEGRATED_BUTTON   1
319262417Shselasky
320262417Shselasky/* trackpad finger structure - little endian */
321262417Shselaskystruct wsp_finger_sensor_data {
322262417Shselasky	int16_t origin;       /* zero when switching track finger */
323262417Shselasky	int16_t abs_x;        /* absolute x coordinate */
324262417Shselasky	int16_t abs_y;        /* absolute y coordinate */
325262417Shselasky	int16_t rel_x;        /* relative x coordinate */
326262417Shselasky	int16_t rel_y;        /* relative y coordinate */
327262417Shselasky	int16_t tool_major;   /* tool area, major axis */
328262417Shselasky	int16_t tool_minor;   /* tool area, minor axis */
329262417Shselasky	int16_t orientation;  /* 16384 when point, else 15 bit angle */
330262417Shselasky	int16_t touch_major;  /* touch area, major axis */
331262417Shselasky	int16_t touch_minor;  /* touch area, minor axis */
332262417Shselasky	int16_t unused[3];    /* zeros */
333262417Shselasky	int16_t multi;        /* one finger: varies, more fingers: constant */
334262417Shselasky} __packed;
335262417Shselasky
336262417Shselaskytypedef struct wsp_finger {
337262417Shselasky	/* to track fingers as they match against strokes. */
338262417Shselasky	boolean_t matched;
339262417Shselasky
340262417Shselasky	/* location (scaled using the mickeys factor) */
341262417Shselasky	int x;
342262417Shselasky	int y;
343262417Shselasky} wsp_finger_t;
344262417Shselasky
345262417Shselasky#define WSP_MAX_FINGERS               16
346262417Shselasky#define WSP_SIZEOF_FINGER_SENSOR_DATA sizeof(struct wsp_finger_sensor_data)
347262417Shselasky#define WSP_SIZEOF_ALL_FINGER_DATA    (WSP_MAX_FINGERS * \
348262417Shselasky				       WSP_SIZEOF_FINGER_SENSOR_DATA)
349262417Shselasky#define WSP_MAX_FINGER_ORIENTATION    16384
350262417Shselasky
351262417Shselasky#define ATP_SENSOR_DATA_BUF_MAX       1024
352262417Shselasky#if (ATP_SENSOR_DATA_BUF_MAX < ((WSP_MAX_FINGERS * 14 * 2) + \
353262417Shselasky				WSP_TYPE3_FINGER_DATA_OFFSET))
354262417Shselasky/* note: 14 * 2 in the above is based on sizeof(struct wsp_finger_sensor_data)*/
355262417Shselasky#error "ATP_SENSOR_DATA_BUF_MAX is too small"
356262417Shselasky#endif
357262417Shselasky
358262417Shselasky#define ATP_MAX_STROKES               MAX(WSP_MAX_FINGERS, FG_MAX_STROKES)
359262417Shselasky
360262417Shselasky#define FG_MAX_XSENSORS 26
361262417Shselasky#define FG_MAX_YSENSORS 16
362262417Shselasky
363262417Shselasky/* device-specific configuration */
364262417Shselaskystruct fg_dev_params {
365262417Shselasky	u_int                              data_len;   /* for sensor data */
366262417Shselasky	u_int                              n_xsensors;
367262417Shselasky	u_int                              n_ysensors;
368262417Shselasky	enum fountain_geyser_trackpad_type prot;
369199086Srpaulo};
370262417Shselaskystruct wsp_dev_params {
371262417Shselasky	uint8_t  caps;               /* device capability bitmask */
372262417Shselasky	uint8_t  tp_type;            /* type of trackpad interface */
373262417Shselasky	uint8_t  finger_data_offset; /* offset to trackpad finger data */
374262417Shselasky};
375262417Shselasky
376262417Shselaskystatic const struct fg_dev_params fg_dev_params[FOUNTAIN_GEYSER_PRODUCT_MAX] = {
377262417Shselasky	[FOUNTAIN] = {
378262417Shselasky		.data_len   = 81,
379262417Shselasky		.n_xsensors = 16,
380262417Shselasky		.n_ysensors = 16,
381262417Shselasky		.prot       = FG_TRACKPAD_TYPE_GEYSER1
382199086Srpaulo	},
383262417Shselasky	[GEYSER1] = {
384199151Snwhitehorn		.data_len   = 81,
385199151Snwhitehorn		.n_xsensors = 16,
386199151Snwhitehorn		.n_ysensors = 16,
387262417Shselasky		.prot       = FG_TRACKPAD_TYPE_GEYSER1
388199151Snwhitehorn	},
389262417Shselasky	[GEYSER1_17inch] = {
390262417Shselasky		.data_len   = 81,
391262417Shselasky		.n_xsensors = 26,
392262417Shselasky		.n_ysensors = 16,
393262417Shselasky		.prot       = FG_TRACKPAD_TYPE_GEYSER1
394262417Shselasky	},
395262417Shselasky	[GEYSER2] = {
396199151Snwhitehorn		.data_len   = 64,
397199151Snwhitehorn		.n_xsensors = 15,
398199151Snwhitehorn		.n_ysensors = 9,
399262417Shselasky		.prot       = FG_TRACKPAD_TYPE_GEYSER2
400199151Snwhitehorn	},
401262417Shselasky	[GEYSER3] = {
402262417Shselasky		.data_len   = 64,
403262417Shselasky		.n_xsensors = 20,
404262417Shselasky		.n_ysensors = 10,
405262417Shselasky		.prot       = FG_TRACKPAD_TYPE_GEYSER3
406199151Snwhitehorn	},
407262417Shselasky	[GEYSER4] = {
408262417Shselasky		.data_len   = 64,
409262417Shselasky		.n_xsensors = 20,
410262417Shselasky		.n_ysensors = 10,
411262417Shselasky		.prot       = FG_TRACKPAD_TYPE_GEYSER4
412262417Shselasky	}
413199086Srpaulo};
414199086Srpaulo
415262417Shselaskystatic const STRUCT_USB_HOST_ID fg_devs[] = {
416262417Shselasky	/* PowerBooks Feb 2005, iBooks G4 */
417262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x020e, FG_DRIVER_INFO(FOUNTAIN)) },
418262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x020f, FG_DRIVER_INFO(FOUNTAIN)) },
419262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0210, FG_DRIVER_INFO(FOUNTAIN)) },
420262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x030a, FG_DRIVER_INFO(FOUNTAIN)) },
421262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x030b, FG_DRIVER_INFO(GEYSER1)) },
422262417Shselasky
423262417Shselasky	/* PowerBooks Oct 2005 */
424262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0214, FG_DRIVER_INFO(GEYSER2)) },
425262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0215, FG_DRIVER_INFO(GEYSER2)) },
426262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0216, FG_DRIVER_INFO(GEYSER2)) },
427262417Shselasky
428199086Srpaulo	/* Core Duo MacBook & MacBook Pro */
429262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0217, FG_DRIVER_INFO(GEYSER3)) },
430262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0218, FG_DRIVER_INFO(GEYSER3)) },
431262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0219, FG_DRIVER_INFO(GEYSER3)) },
432199086Srpaulo
433199086Srpaulo	/* Core2 Duo MacBook & MacBook Pro */
434262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x021a, FG_DRIVER_INFO(GEYSER4)) },
435262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x021b, FG_DRIVER_INFO(GEYSER4)) },
436262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x021c, FG_DRIVER_INFO(GEYSER4)) },
437199086Srpaulo
438199086Srpaulo	/* Core2 Duo MacBook3,1 */
439262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x0229, FG_DRIVER_INFO(GEYSER4)) },
440262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x022a, FG_DRIVER_INFO(GEYSER4)) },
441262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x022b, FG_DRIVER_INFO(GEYSER4)) },
442199151Snwhitehorn
443262417Shselasky	/* 17 inch PowerBook */
444262417Shselasky	{ USB_VPI(USB_VENDOR_APPLE, 0x020d, FG_DRIVER_INFO(GEYSER1_17inch)) },
445262417Shselasky};
446199151Snwhitehorn
447262417Shselaskystatic const struct wsp_dev_params wsp_dev_params[WELLSPRING_PRODUCT_MAX] = {
448262417Shselasky	[WELLSPRING1] = {
449262417Shselasky		.caps       = 0,
450262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE1,
451262417Shselasky		.finger_data_offset  = WSP_TYPE1_FINGER_DATA_OFFSET,
452262417Shselasky	},
453262417Shselasky	[WELLSPRING2] = {
454262417Shselasky		.caps       = 0,
455262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE1,
456262417Shselasky		.finger_data_offset  = WSP_TYPE1_FINGER_DATA_OFFSET,
457262417Shselasky	},
458262417Shselasky	[WELLSPRING3] = {
459262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
460262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
461262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
462262417Shselasky	},
463262417Shselasky	[WELLSPRING4] = {
464262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
465262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
466262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
467262417Shselasky	},
468262417Shselasky	[WELLSPRING4A] = {
469262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
470262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
471262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
472262417Shselasky	},
473262417Shselasky	[WELLSPRING5] = {
474262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
475262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
476262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
477262417Shselasky	},
478262417Shselasky	[WELLSPRING6] = {
479262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
480262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
481262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
482262417Shselasky	},
483262417Shselasky	[WELLSPRING5A] = {
484262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
485262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
486262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
487262417Shselasky	},
488262417Shselasky	[WELLSPRING6A] = {
489262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
490262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
491262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
492262417Shselasky	},
493262417Shselasky	[WELLSPRING7] = {
494262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
495262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
496262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
497262417Shselasky	},
498262417Shselasky	[WELLSPRING7A] = {
499262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
500262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE2,
501262417Shselasky		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
502262417Shselasky	},
503262417Shselasky	[WELLSPRING8] = {
504262417Shselasky		.caps       = HAS_INTEGRATED_BUTTON,
505262417Shselasky		.tp_type    = WSP_TRACKPAD_TYPE3,
506262417Shselasky		.finger_data_offset  = WSP_TYPE3_FINGER_DATA_OFFSET,
507262417Shselasky	},
508262417Shselasky};
509199151Snwhitehorn
510262417Shselasky#define ATP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
511199151Snwhitehorn
512262417Shselaskystatic const STRUCT_USB_HOST_ID wsp_devs[] = {
513262417Shselasky	/* MacbookAir1.1 */
514262417Shselasky	ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
515262417Shselasky	ATP_DEV(APPLE, WELLSPRING_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
516262417Shselasky	ATP_DEV(APPLE, WELLSPRING_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
517262417Shselasky
518262417Shselasky	/* MacbookProPenryn, aka wellspring2 */
519262417Shselasky	ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
520262417Shselasky	ATP_DEV(APPLE, WELLSPRING2_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
521262417Shselasky	ATP_DEV(APPLE, WELLSPRING2_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
522262417Shselasky
523262417Shselasky	/* Macbook5,1 (unibody), aka wellspring3 */
524262417Shselasky	ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
525262417Shselasky	ATP_DEV(APPLE, WELLSPRING3_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
526262417Shselasky	ATP_DEV(APPLE, WELLSPRING3_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
527262417Shselasky
528262417Shselasky	/* MacbookAir3,2 (unibody), aka wellspring4 */
529262417Shselasky	ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
530262417Shselasky	ATP_DEV(APPLE, WELLSPRING4_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
531262417Shselasky	ATP_DEV(APPLE, WELLSPRING4_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
532262417Shselasky
533262417Shselasky	/* MacbookAir3,1 (unibody), aka wellspring4 */
534262417Shselasky	ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
535262417Shselasky	ATP_DEV(APPLE, WELLSPRING4A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
536262417Shselasky	ATP_DEV(APPLE, WELLSPRING4A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
537262417Shselasky
538262417Shselasky	/* Macbook8 (unibody, March 2011) */
539262417Shselasky	ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
540262417Shselasky	ATP_DEV(APPLE, WELLSPRING5_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
541262417Shselasky	ATP_DEV(APPLE, WELLSPRING5_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
542262417Shselasky
543262417Shselasky	/* MacbookAir4,1 (unibody, July 2011) */
544262417Shselasky	ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
545262417Shselasky	ATP_DEV(APPLE, WELLSPRING6A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
546262417Shselasky	ATP_DEV(APPLE, WELLSPRING6A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
547262417Shselasky
548262417Shselasky	/* MacbookAir4,2 (unibody, July 2011) */
549262417Shselasky	ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
550262417Shselasky	ATP_DEV(APPLE, WELLSPRING6_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
551262417Shselasky	ATP_DEV(APPLE, WELLSPRING6_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
552262417Shselasky
553262417Shselasky	/* Macbook8,2 (unibody) */
554262417Shselasky	ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
555262417Shselasky	ATP_DEV(APPLE, WELLSPRING5A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
556262417Shselasky	ATP_DEV(APPLE, WELLSPRING5A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
557262417Shselasky
558262417Shselasky	/* MacbookPro10,1 (unibody, June 2012) */
559262417Shselasky	/* MacbookPro11,? (unibody, June 2013) */
560262417Shselasky	ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
561262417Shselasky	ATP_DEV(APPLE, WELLSPRING7_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
562262417Shselasky	ATP_DEV(APPLE, WELLSPRING7_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
563262417Shselasky
564262417Shselasky	/* MacbookPro10,2 (unibody, October 2012) */
565262417Shselasky	ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
566262417Shselasky	ATP_DEV(APPLE, WELLSPRING7A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
567262417Shselasky	ATP_DEV(APPLE, WELLSPRING7A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
568262417Shselasky
569262417Shselasky	/* MacbookAir6,2 (unibody, June 2013) */
570262417Shselasky	ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
571262417Shselasky	ATP_DEV(APPLE, WELLSPRING8_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
572262417Shselasky	ATP_DEV(APPLE, WELLSPRING8_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
573199086Srpaulo};
574199086Srpaulo
575199086Srpaulotypedef enum atp_stroke_type {
576199086Srpaulo	ATP_STROKE_TOUCH,
577199086Srpaulo	ATP_STROKE_SLIDE,
578199086Srpaulo} atp_stroke_type;
579199086Srpaulo
580262417Shselaskytypedef enum atp_axis {
581262417Shselasky	X = 0,
582262417Shselasky	Y = 1,
583262417Shselasky	NUM_AXES
584262417Shselasky} atp_axis;
585199086Srpaulo
586262417Shselasky#define ATP_FIFO_BUF_SIZE        8 /* bytes */
587262417Shselasky#define ATP_FIFO_QUEUE_MAXLEN   50 /* units */
588262417Shselasky
589262417Shselaskyenum {
590262417Shselasky	ATP_INTR_DT,
591262417Shselasky	ATP_RESET,
592262417Shselasky	ATP_N_TRANSFER,
593262417Shselasky};
594262417Shselasky
595262417Shselaskytypedef struct fg_stroke_component {
596199086Srpaulo	/* Fields encapsulating the pressure-span. */
597199086Srpaulo	u_int loc;              /* location (scaled) */
598199086Srpaulo	u_int cum_pressure;     /* cumulative compression */
599199086Srpaulo	u_int max_cum_pressure; /* max cumulative compression */
600199086Srpaulo	boolean_t matched; /*to track components as they match against pspans.*/
601199086Srpaulo
602199086Srpaulo	int   delta_mickeys;    /* change in location (un-smoothened movement)*/
603262417Shselasky} fg_stroke_component_t;
604199086Srpaulo
605199086Srpaulo/*
606199086Srpaulo * The following structure captures a finger contact with the
607199086Srpaulo * touchpad. A stroke comprises two p-span components and some state.
608199086Srpaulo */
609199086Srpaulotypedef struct atp_stroke {
610262417Shselasky	TAILQ_ENTRY(atp_stroke) entry;
611199086Srpaulo
612262417Shselasky	atp_stroke_type type;
613262417Shselasky	uint32_t        flags; /* the state of this stroke */
614262417Shselasky#define ATSF_ZOMBIE 0x1
615262417Shselasky	boolean_t       matched;          /* to track match against fingers.*/
616199086Srpaulo
617262417Shselasky	struct timeval  ctime; /* create time; for coincident siblings. */
618199086Srpaulo
619262417Shselasky	/*
620262417Shselasky	 * Unit: interrupts; we maintain this value in
621262417Shselasky	 * addition to 'ctime' in order to avoid the
622262417Shselasky	 * expensive call to microtime() at every
623262417Shselasky	 * interrupt.
624262417Shselasky	 */
625262417Shselasky	uint32_t age;
626199086Srpaulo
627262417Shselasky	/* Location */
628262417Shselasky	int x;
629262417Shselasky	int y;
630199086Srpaulo
631262417Shselasky	/* Fields containing information about movement. */
632262417Shselasky	int   instantaneous_dx; /* curr. change in X location (un-smoothened) */
633262417Shselasky	int   instantaneous_dy; /* curr. change in Y location (un-smoothened) */
634262417Shselasky	int   pending_dx;       /* cum. of pending short movements */
635262417Shselasky	int   pending_dy;       /* cum. of pending short movements */
636262417Shselasky	int   movement_dx;      /* interpreted smoothened movement */
637262417Shselasky	int   movement_dy;      /* interpreted smoothened movement */
638262417Shselasky	int   cum_movement_x;   /* cum. horizontal movement */
639262417Shselasky	int   cum_movement_y;   /* cum. vertical movement */
640262417Shselasky
641262417Shselasky	/*
642262417Shselasky	 * The following member is relevant only for fountain-geyser trackpads.
643262417Shselasky	 * For these, there is the need to track pressure-spans and cumulative
644262417Shselasky	 * pressures for stroke components.
645262417Shselasky	 */
646262417Shselasky	fg_stroke_component_t components[NUM_AXES];
647262417Shselasky} atp_stroke_t;
648262417Shselasky
649262417Shselaskystruct atp_softc; /* forward declaration */
650262417Shselaskytypedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len);
651262417Shselasky
652199086Srpaulostruct atp_softc {
653262417Shselasky	device_t            sc_dev;
654262417Shselasky	struct usb_device  *sc_usb_device;
655262417Shselasky	struct mtx          sc_mutex; /* for synchronization */
656262417Shselasky	struct usb_fifo_sc  sc_fifo;
657199086Srpaulo
658262417Shselasky#define	MODE_LENGTH 8
659262417Shselasky	char                sc_mode_bytes[MODE_LENGTH]; /* device mode */
660199086Srpaulo
661262417Shselasky	trackpad_family_t   sc_family;
662262417Shselasky	const void         *sc_params; /* device configuration */
663262417Shselasky	sensor_data_interpreter_t sensor_data_interpreter;
664199086Srpaulo
665262417Shselasky	mousehw_t           sc_hw;
666262417Shselasky	mousemode_t         sc_mode;
667262417Shselasky	mousestatus_t       sc_status;
668199086Srpaulo
669262417Shselasky	u_int               sc_state;
670262417Shselasky#define ATP_ENABLED          0x01
671262417Shselasky#define ATP_ZOMBIES_EXIST    0x02
672262417Shselasky#define ATP_DOUBLE_TAP_DRAG  0x04
673262417Shselasky#define ATP_VALID            0x08
674199086Srpaulo
675262417Shselasky	struct usb_xfer    *sc_xfer[ATP_N_TRANSFER];
676199086Srpaulo
677262417Shselasky	u_int               sc_pollrate;
678262417Shselasky	int                 sc_fflags;
679199086Srpaulo
680262417Shselasky	atp_stroke_t        sc_strokes_data[ATP_MAX_STROKES];
681262417Shselasky	TAILQ_HEAD(,atp_stroke) sc_stroke_free;
682262417Shselasky	TAILQ_HEAD(,atp_stroke) sc_stroke_used;
683262417Shselasky	u_int               sc_n_strokes;
684262417Shselasky
685262417Shselasky	struct callout	    sc_callout;
686262417Shselasky
687262417Shselasky	/*
688262417Shselasky	 * button status. Set to non-zero if the mouse-button is physically
689262417Shselasky	 * pressed. This state variable is exposed through softc to allow
690262417Shselasky	 * reap_sibling_zombies to avoid registering taps while the trackpad
691262417Shselasky	 * button is pressed.
692262417Shselasky         */
693262417Shselasky	uint8_t             sc_ibtn;
694262417Shselasky
695262417Shselasky	/*
696262417Shselasky	 * Time when touch zombies were last reaped; useful for detecting
697262417Shselasky	 * double-touch-n-drag.
698262417Shselasky	 */
699262417Shselasky	struct timeval      sc_touch_reap_time;
700262417Shselasky
701262417Shselasky	u_int	            sc_idlecount;
702262417Shselasky
703262417Shselasky	/* Regarding the data transferred from t-pad in USB INTR packets. */
704262417Shselasky	u_int   sc_expected_sensor_data_len;
705262417Shselasky	uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4);
706262417Shselasky
707262417Shselasky	int      sc_cur_x[FG_MAX_XSENSORS];      /* current sensor readings */
708262417Shselasky	int      sc_cur_y[FG_MAX_YSENSORS];
709262417Shselasky	int      sc_base_x[FG_MAX_XSENSORS];     /* base sensor readings */
710262417Shselasky	int      sc_base_y[FG_MAX_YSENSORS];
711262417Shselasky	int      sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */
712262417Shselasky	int      sc_pressure_y[FG_MAX_YSENSORS];
713262417Shselasky	fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS];
714262417Shselasky	fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS];
715199086Srpaulo};
716199086Srpaulo
717199086Srpaulo/*
718262417Shselasky * The last byte of the fountain-geyser sensor data contains status bits; the
719199086Srpaulo * following values define the meanings of these bits.
720262417Shselasky * (only Geyser 3/4)
721199086Srpaulo */
722262417Shselaskyenum geyser34_status_bits {
723262417Shselasky	FG_STATUS_BUTTON      = (uint8_t)0x01, /* The button was pressed */
724262417Shselasky	FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
725199086Srpaulo};
726199086Srpaulo
727199086Srpaulotypedef enum interface_mode {
728262417Shselasky	RAW_SENSOR_MODE = (uint8_t)0x01,
729199086Srpaulo	HID_MODE        = (uint8_t)0x08
730199086Srpaulo} interface_mode;
731199086Srpaulo
732262417Shselasky
733199086Srpaulo/*
734199086Srpaulo * function prototypes
735199086Srpaulo */
736199086Srpaulostatic usb_fifo_cmd_t   atp_start_read;
737199086Srpaulostatic usb_fifo_cmd_t   atp_stop_read;
738199086Srpaulostatic usb_fifo_open_t  atp_open;
739199086Srpaulostatic usb_fifo_close_t atp_close;
740199086Srpaulostatic usb_fifo_ioctl_t atp_ioctl;
741199086Srpaulo
742199086Srpaulostatic struct usb_fifo_methods atp_fifo_methods = {
743199086Srpaulo	.f_open       = &atp_open,
744199086Srpaulo	.f_close      = &atp_close,
745199086Srpaulo	.f_ioctl      = &atp_ioctl,
746199086Srpaulo	.f_start_read = &atp_start_read,
747199086Srpaulo	.f_stop_read  = &atp_stop_read,
748199086Srpaulo	.basename[0]  = ATP_DRIVER_NAME,
749199086Srpaulo};
750199086Srpaulo
751199086Srpaulo/* device initialization and shutdown */
752262417Shselaskystatic usb_error_t   atp_set_device_mode(struct atp_softc *, interface_mode);
753262417Shselaskystatic void	     atp_reset_callback(struct usb_xfer *, usb_error_t);
754262417Shselaskystatic int	     atp_enable(struct atp_softc *);
755262417Shselaskystatic void	     atp_disable(struct atp_softc *);
756199086Srpaulo
757199086Srpaulo/* sensor interpretation */
758262417Shselaskystatic void	     fg_interpret_sensor_data(struct atp_softc *, u_int);
759262417Shselaskystatic void	     fg_extract_sensor_data(const int8_t *, u_int, atp_axis,
760262417Shselasky    int *, enum fountain_geyser_trackpad_type);
761262417Shselaskystatic void	     fg_get_pressures(int *, const int *, const int *, int);
762262417Shselaskystatic void	     fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *);
763262417Shselaskystatic void	     wsp_interpret_sensor_data(struct atp_softc *, u_int);
764199086Srpaulo
765199086Srpaulo/* movement detection */
766262417Shselaskystatic boolean_t     fg_match_stroke_component(fg_stroke_component_t *,
767262417Shselasky    const fg_pspan *, atp_stroke_type);
768262417Shselaskystatic void	     fg_match_strokes_against_pspans(struct atp_softc *,
769262417Shselasky    atp_axis, fg_pspan *, u_int, u_int);
770262417Shselaskystatic boolean_t     wsp_match_strokes_against_fingers(struct atp_softc *,
771262417Shselasky    wsp_finger_t *, u_int);
772262417Shselaskystatic boolean_t     fg_update_strokes(struct atp_softc *, fg_pspan *, u_int,
773262417Shselasky    fg_pspan *, u_int);
774262417Shselaskystatic boolean_t     wsp_update_strokes(struct atp_softc *,
775262417Shselasky    wsp_finger_t [WSP_MAX_FINGERS], u_int);
776262417Shselaskystatic void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *);
777262417Shselaskystatic void	     fg_add_new_strokes(struct atp_softc *, fg_pspan *,
778262417Shselasky    u_int, fg_pspan *, u_int);
779262417Shselaskystatic void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *);
780262417Shselaskystatic void	     atp_advance_stroke_state(struct atp_softc *,
781262417Shselasky    atp_stroke_t *, boolean_t *);
782262417Shselaskystatic boolean_t atp_stroke_has_small_movement(const atp_stroke_t *);
783262417Shselaskystatic void	     atp_update_pending_mickeys(atp_stroke_t *);
784262417Shselaskystatic boolean_t     atp_compute_stroke_movement(atp_stroke_t *);
785262417Shselaskystatic void	     atp_terminate_stroke(struct atp_softc *, atp_stroke_t *);
786199086Srpaulo
787199086Srpaulo/* tap detection */
788262417Shselaskystatic boolean_t atp_is_horizontal_scroll(const atp_stroke_t *);
789262417Shselaskystatic boolean_t atp_is_vertical_scroll(const atp_stroke_t *);
790262417Shselaskystatic void	     atp_reap_sibling_zombies(void *);
791262417Shselaskystatic void	     atp_convert_to_slide(struct atp_softc *, atp_stroke_t *);
792199086Srpaulo
793199086Srpaulo/* updating fifo */
794262417Shselaskystatic void	     atp_reset_buf(struct atp_softc *);
795262417Shselaskystatic void	     atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t);
796199086Srpaulo
797262417Shselaskystatic const sensor_data_interpreter_t atp_sensor_data_interpreters[TRACKPAD_FAMILY_MAX] = {
798262417Shselasky	[TRACKPAD_FAMILY_FOUNTAIN_GEYSER] = fg_interpret_sensor_data,
799262417Shselasky	[TRACKPAD_FAMILY_WELLSPRING]      = wsp_interpret_sensor_data,
800262417Shselasky};
801199086Srpaulo
802262417Shselasky/* Device methods. */
803262417Shselaskystatic device_probe_t  atp_probe;
804262417Shselaskystatic device_attach_t atp_attach;
805262417Shselaskystatic device_detach_t atp_detach;
806262417Shselaskystatic usb_callback_t  atp_intr;
807262417Shselasky
808262417Shselaskystatic const struct usb_config atp_xfer_config[ATP_N_TRANSFER] = {
809262417Shselasky	[ATP_INTR_DT] = {
810262417Shselasky		.type      = UE_INTERRUPT,
811262417Shselasky		.endpoint  = UE_ADDR_ANY,
812262417Shselasky		.direction = UE_DIR_IN,
813262417Shselasky		.flags = {
814262417Shselasky			.pipe_bof = 1, /* block pipe on failure */
815262417Shselasky			.short_xfer_ok = 1,
816262417Shselasky		},
817262417Shselasky		.bufsize   = ATP_SENSOR_DATA_BUF_MAX,
818262417Shselasky		.callback  = &atp_intr,
819262417Shselasky	},
820262417Shselasky	[ATP_RESET] = {
821262417Shselasky		.type      = UE_CONTROL,
822262417Shselasky		.endpoint  = 0, /* Control pipe */
823262417Shselasky		.direction = UE_DIR_ANY,
824262417Shselasky		.bufsize   = sizeof(struct usb_device_request) + MODE_LENGTH,
825262417Shselasky		.callback  = &atp_reset_callback,
826262417Shselasky		.interval  = 0,  /* no pre-delay */
827262417Shselasky	},
828262417Shselasky};
829262417Shselasky
830262417Shselaskystatic atp_stroke_t *
831262417Shselaskyatp_alloc_stroke(struct atp_softc *sc)
832199086Srpaulo{
833262417Shselasky	atp_stroke_t *pstroke;
834199086Srpaulo
835262417Shselasky	pstroke = TAILQ_FIRST(&sc->sc_stroke_free);
836262417Shselasky	if (pstroke == NULL)
837262417Shselasky		goto done;
838199086Srpaulo
839262417Shselasky	TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry);
840262417Shselasky	memset(pstroke, 0, sizeof(*pstroke));
841262417Shselasky	TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry);
842262417Shselasky
843262417Shselasky	sc->sc_n_strokes++;
844262417Shselaskydone:
845262417Shselasky	return (pstroke);
846199086Srpaulo}
847199086Srpaulo
848262417Shselaskystatic void
849262417Shselaskyatp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke)
850199086Srpaulo{
851262417Shselasky	if (pstroke == NULL)
852262417Shselasky		return;
853199086Srpaulo
854262417Shselasky	sc->sc_n_strokes--;
855199086Srpaulo
856262417Shselasky	TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry);
857262417Shselasky	TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry);
858262417Shselasky}
859199086Srpaulo
860262417Shselaskystatic void
861262417Shselaskyatp_init_stroke_pool(struct atp_softc *sc)
862262417Shselasky{
863262417Shselasky	u_int x;
864199086Srpaulo
865262417Shselasky	TAILQ_INIT(&sc->sc_stroke_free);
866262417Shselasky	TAILQ_INIT(&sc->sc_stroke_used);
867262417Shselasky
868262417Shselasky	sc->sc_n_strokes = 0;
869262417Shselasky
870262417Shselasky	memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data));
871262417Shselasky
872262417Shselasky	for (x = 0; x != ATP_MAX_STROKES; x++) {
873262417Shselasky		TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x],
874262417Shselasky		    entry);
875262417Shselasky	}
876199086Srpaulo}
877199086Srpaulo
878262417Shselaskystatic usb_error_t
879262417Shselaskyatp_set_device_mode(struct atp_softc *sc, interface_mode newMode)
880262417Shselasky{
881262417Shselasky	uint8_t mode_value;
882262417Shselasky	usb_error_t err;
883262417Shselasky
884262417Shselasky	if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE))
885262417Shselasky		return (USB_ERR_INVAL);
886262417Shselasky
887262417Shselasky	if ((newMode == RAW_SENSOR_MODE) &&
888262417Shselasky	    (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER))
889262417Shselasky		mode_value = (uint8_t)0x04;
890262417Shselasky	else
891262417Shselasky		mode_value = newMode;
892262417Shselasky
893262417Shselasky	err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */,
894262417Shselasky	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
895262417Shselasky	    0x03 /* type */, 0x00 /* id */);
896262417Shselasky	if (err != USB_ERR_NORMAL_COMPLETION) {
897262417Shselasky		DPRINTF("Failed to read device mode (%d)\n", err);
898262417Shselasky		return (err);
899262417Shselasky	}
900262417Shselasky
901262417Shselasky	if (sc->sc_mode_bytes[0] == mode_value)
902262417Shselasky		return (err);
903262417Shselasky
904262417Shselasky	/*
905262417Shselasky	 * XXX Need to wait at least 250ms for hardware to get
906262417Shselasky	 * ready. The device mode handling appears to be handled
907262417Shselasky	 * asynchronously and we should not issue these commands too
908262417Shselasky	 * quickly.
909262417Shselasky	 */
910262417Shselasky	pause("WHW", hz / 4);
911262417Shselasky
912262417Shselasky	sc->sc_mode_bytes[0] = mode_value;
913262417Shselasky	return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */,
914262417Shselasky	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
915262417Shselasky	    0x03 /* type */, 0x00 /* id */));
916262417Shselasky}
917262417Shselasky
918262417Shselaskystatic void
919199680Sthompsaatp_reset_callback(struct usb_xfer *xfer, usb_error_t error)
920199680Sthompsa{
921199680Sthompsa	usb_device_request_t   req;
922199680Sthompsa	struct usb_page_cache *pc;
923199680Sthompsa	struct atp_softc      *sc = usbd_xfer_softc(xfer);
924199680Sthompsa
925262417Shselasky	uint8_t mode_value;
926262417Shselasky	if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)
927262417Shselasky		mode_value = 0x04;
928262417Shselasky	else
929262417Shselasky		mode_value = RAW_SENSOR_MODE;
930262417Shselasky
931199680Sthompsa	switch (USB_GET_STATE(xfer)) {
932199680Sthompsa	case USB_ST_SETUP:
933262417Shselasky		sc->sc_mode_bytes[0] = mode_value;
934199680Sthompsa		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
935199680Sthompsa		req.bRequest = UR_SET_REPORT;
936199680Sthompsa		USETW2(req.wValue,
937199680Sthompsa		    (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
938199680Sthompsa		USETW(req.wIndex, 0);
939199680Sthompsa		USETW(req.wLength, MODE_LENGTH);
940199680Sthompsa
941199680Sthompsa		pc = usbd_xfer_get_frame(xfer, 0);
942199680Sthompsa		usbd_copy_in(pc, 0, &req, sizeof(req));
943199680Sthompsa		pc = usbd_xfer_get_frame(xfer, 1);
944199680Sthompsa		usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH);
945199680Sthompsa
946199680Sthompsa		usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
947199680Sthompsa		usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH);
948199680Sthompsa		usbd_xfer_set_frames(xfer, 2);
949199680Sthompsa		usbd_transfer_submit(xfer);
950199680Sthompsa		break;
951199680Sthompsa
952199680Sthompsa	case USB_ST_TRANSFERRED:
953199680Sthompsa	default:
954199680Sthompsa		break;
955199680Sthompsa	}
956199680Sthompsa}
957199680Sthompsa
958199086Srpaulostatic int
959199086Srpauloatp_enable(struct atp_softc *sc)
960199086Srpaulo{
961262417Shselasky	if (sc->sc_state & ATP_ENABLED)
962262417Shselasky		return (0);
963199086Srpaulo
964199086Srpaulo	/* reset status */
965199086Srpaulo	memset(&sc->sc_status, 0, sizeof(sc->sc_status));
966262417Shselasky
967262417Shselasky	atp_init_stroke_pool(sc);
968262417Shselasky
969199086Srpaulo	sc->sc_state |= ATP_ENABLED;
970199086Srpaulo
971199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
972199086Srpaulo	return (0);
973199086Srpaulo}
974199086Srpaulo
975199086Srpaulostatic void
976199086Srpauloatp_disable(struct atp_softc *sc)
977199086Srpaulo{
978199151Snwhitehorn	sc->sc_state &= ~(ATP_ENABLED | ATP_VALID);
979199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
980199086Srpaulo}
981199086Srpaulo
982262417Shselaskystatic void
983262417Shselaskyfg_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
984199086Srpaulo{
985262417Shselasky	u_int n_xpspans = 0;
986262417Shselasky	u_int n_ypspans = 0;
987262417Shselasky	uint8_t status_bits;
988199086Srpaulo
989262417Shselasky	const struct fg_dev_params *params =
990262417Shselasky	    (const struct fg_dev_params *)sc->sc_params;
991199086Srpaulo
992262417Shselasky	fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X,
993262417Shselasky	    sc->sc_cur_x, params->prot);
994262417Shselasky	fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y,
995262417Shselasky	    sc->sc_cur_y, params->prot);
996199086Srpaulo
997262417Shselasky	/*
998262417Shselasky	 * If this is the initial update (from an untouched
999262417Shselasky	 * pad), we should set the base values for the sensor
1000262417Shselasky	 * data; deltas with respect to these base values can
1001262417Shselasky	 * be used as pressure readings subsequently.
1002262417Shselasky	 */
1003262417Shselasky	status_bits = sc->sc_sensor_data[params->data_len - 1];
1004262417Shselasky	if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) ||
1005262417Shselasky	     (params->prot == FG_TRACKPAD_TYPE_GEYSER4))  &&
1006262417Shselasky	    ((sc->sc_state & ATP_VALID) == 0)) {
1007262417Shselasky		if (status_bits & FG_STATUS_BASE_UPDATE) {
1008262417Shselasky			memcpy(sc->sc_base_x, sc->sc_cur_x,
1009262417Shselasky			    params->n_xsensors * sizeof(*sc->sc_base_x));
1010262417Shselasky			memcpy(sc->sc_base_y, sc->sc_cur_y,
1011262417Shselasky			    params->n_ysensors * sizeof(*sc->sc_base_y));
1012262417Shselasky			sc->sc_state |= ATP_VALID;
1013262417Shselasky			return;
1014199086Srpaulo		}
1015199086Srpaulo	}
1016199086Srpaulo
1017262417Shselasky	/* Get pressure readings and detect p-spans for both axes. */
1018262417Shselasky	fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x,
1019262417Shselasky	    params->n_xsensors);
1020262417Shselasky	fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors,
1021262417Shselasky	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans);
1022262417Shselasky	fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y,
1023262417Shselasky	    params->n_ysensors);
1024262417Shselasky	fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors,
1025262417Shselasky	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans);
1026199086Srpaulo
1027262417Shselasky	/* Update strokes with new pspans to detect movements. */
1028262417Shselasky	if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans))
1029262417Shselasky		sc->sc_status.flags |= MOUSE_POSCHANGED;
1030199086Srpaulo
1031262417Shselasky	sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0;
1032262417Shselasky	sc->sc_status.button = sc->sc_ibtn;
1033199086Srpaulo
1034262417Shselasky	/*
1035262417Shselasky	 * The Fountain/Geyser device continues to trigger interrupts
1036262417Shselasky	 * at a fast rate even after touchpad activity has
1037262417Shselasky	 * stopped. Upon detecting that the device has remained idle
1038262417Shselasky	 * beyond a threshold, we reinitialize it to silence the
1039262417Shselasky	 * interrupts.
1040262417Shselasky	 */
1041262417Shselasky	if ((sc->sc_status.flags  == 0) && (sc->sc_n_strokes == 0)) {
1042262417Shselasky		sc->sc_idlecount++;
1043262417Shselasky		if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) {
1044262417Shselasky			/*
1045262417Shselasky			 * Use the last frame before we go idle for
1046262417Shselasky			 * calibration on pads which do not send
1047262417Shselasky			 * calibration frames.
1048262417Shselasky			 */
1049262417Shselasky			const struct fg_dev_params *params =
1050262417Shselasky			    (const struct fg_dev_params *)sc->sc_params;
1051199086Srpaulo
1052262417Shselasky			DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
1053199086Srpaulo
1054262417Shselasky			if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) {
1055262417Shselasky				memcpy(sc->sc_base_x, sc->sc_cur_x,
1056262417Shselasky				    params->n_xsensors * sizeof(*(sc->sc_base_x)));
1057262417Shselasky				memcpy(sc->sc_base_y, sc->sc_cur_y,
1058262417Shselasky				    params->n_ysensors * sizeof(*(sc->sc_base_y)));
1059262417Shselasky			}
1060199086Srpaulo
1061262417Shselasky			sc->sc_idlecount = 0;
1062262417Shselasky			usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
1063199086Srpaulo		}
1064262417Shselasky	} else {
1065262417Shselasky		sc->sc_idlecount = 0;
1066199086Srpaulo	}
1067199086Srpaulo}
1068199086Srpaulo
1069199086Srpaulo/*
1070199086Srpaulo * Interpret the data from the X and Y pressure sensors. This function
1071199086Srpaulo * is called separately for the X and Y sensor arrays. The data in the
1072199086Srpaulo * USB packet is laid out in the following manner:
1073199086Srpaulo *
1074199086Srpaulo * sensor_data:
1075199086Srpaulo *            --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4
1076199086Srpaulo *  indices:   0  1  2  3  4  5  6  7  8 ...  15  ... 20 21 22 23 24
1077199086Srpaulo *
1078199086Srpaulo * '--' (in the above) indicates that the value is unimportant.
1079199086Srpaulo *
1080199086Srpaulo * Information about the above layout was obtained from the
1081199086Srpaulo * implementation of the AppleTouch driver in Linux.
1082199086Srpaulo *
1083199086Srpaulo * parameters:
1084199086Srpaulo *   sensor_data
1085199086Srpaulo *       raw sensor data from the USB packet.
1086199086Srpaulo *   num
1087199086Srpaulo *       The number of elements in the array 'arr'.
1088199151Snwhitehorn *   axis
1089199151Snwhitehorn *       Axis of data to fetch
1090199086Srpaulo *   arr
1091199086Srpaulo *       The array to be initialized with the readings.
1092199151Snwhitehorn *   prot
1093199151Snwhitehorn *       The protocol to use to interpret the data
1094199086Srpaulo */
1095262417Shselaskystatic void
1096262417Shselaskyfg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
1097262417Shselasky    int	*arr, enum fountain_geyser_trackpad_type prot)
1098199086Srpaulo{
1099199086Srpaulo	u_int i;
1100199086Srpaulo	u_int di;   /* index into sensor data */
1101199086Srpaulo
1102199151Snwhitehorn	switch (prot) {
1103262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER1:
1104199151Snwhitehorn		/*
1105199151Snwhitehorn		 * For Geyser 1, the sensors are laid out in pairs
1106199151Snwhitehorn		 * every 5 bytes.
1107199151Snwhitehorn		 */
1108199151Snwhitehorn		for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) {
1109199151Snwhitehorn			arr[i] = sensor_data[di];
1110199151Snwhitehorn			arr[i+8] = sensor_data[di+2];
1111262417Shselasky			if ((axis == X) && (num > 16))
1112199151Snwhitehorn				arr[i+16] = sensor_data[di+40];
1113199151Snwhitehorn		}
1114199151Snwhitehorn
1115199151Snwhitehorn		break;
1116262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER2:
1117262417Shselasky		for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) {
1118262417Shselasky			arr[i++] = sensor_data[di++];
1119262417Shselasky			arr[i++] = sensor_data[di++];
1120262417Shselasky			di++;
1121262417Shselasky		}
1122262417Shselasky		break;
1123262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER3:
1124262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER4:
1125199151Snwhitehorn		for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) {
1126199151Snwhitehorn			arr[i++] = sensor_data[di++];
1127199151Snwhitehorn			arr[i++] = sensor_data[di++];
1128199151Snwhitehorn			di++;
1129199151Snwhitehorn		}
1130199151Snwhitehorn		break;
1131262417Shselasky	default:
1132262417Shselasky		break;
1133199086Srpaulo	}
1134199086Srpaulo}
1135199086Srpaulo
1136262417Shselaskystatic void
1137262417Shselaskyfg_get_pressures(int *p, const int *cur, const int *base, int n)
1138199086Srpaulo{
1139199086Srpaulo	int i;
1140199086Srpaulo
1141199086Srpaulo	for (i = 0; i < n; i++) {
1142199086Srpaulo		p[i] = cur[i] - base[i];
1143199086Srpaulo		if (p[i] > 127)
1144199086Srpaulo			p[i] -= 256;
1145199086Srpaulo		if (p[i] < -127)
1146199086Srpaulo			p[i] += 256;
1147199086Srpaulo		if (p[i] < 0)
1148199086Srpaulo			p[i] = 0;
1149199086Srpaulo
1150199086Srpaulo		/*
1151199086Srpaulo		 * Shave off pressures below the noise-pressure
1152199086Srpaulo		 * threshold; this will reduce the contribution from
1153199086Srpaulo		 * lower pressure readings.
1154199086Srpaulo		 */
1155262417Shselasky		if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD)
1156199086Srpaulo			p[i] = 0; /* filter away noise */
1157199086Srpaulo		else
1158262417Shselasky			p[i] -= FG_SENSOR_NOISE_THRESHOLD;
1159199086Srpaulo	}
1160199086Srpaulo}
1161199086Srpaulo
1162199086Srpaulostatic void
1163262417Shselaskyfg_detect_pspans(int *p, u_int num_sensors,
1164262417Shselasky    u_int      max_spans, /* max # of pspans permitted */
1165262417Shselasky    fg_pspan  *spans,     /* finger spans */
1166262417Shselasky    u_int     *nspans_p)  /* num spans detected */
1167199086Srpaulo{
1168199086Srpaulo	u_int i;
1169199086Srpaulo	int   maxp;             /* max pressure seen within a span */
1170199086Srpaulo	u_int num_spans = 0;
1171199086Srpaulo
1172262417Shselasky	enum fg_pspan_state {
1173199086Srpaulo		ATP_PSPAN_INACTIVE,
1174199086Srpaulo		ATP_PSPAN_INCREASING,
1175199086Srpaulo		ATP_PSPAN_DECREASING,
1176199086Srpaulo	} state; /* state of the pressure span */
1177199086Srpaulo
1178199086Srpaulo	/*
1179199086Srpaulo	 * The following is a simple state machine to track
1180199086Srpaulo	 * the phase of the pressure span.
1181199086Srpaulo	 */
1182262417Shselasky	memset(spans, 0, max_spans * sizeof(fg_pspan));
1183199086Srpaulo	maxp = 0;
1184199086Srpaulo	state = ATP_PSPAN_INACTIVE;
1185199086Srpaulo	for (i = 0; i < num_sensors; i++) {
1186199086Srpaulo		if (num_spans >= max_spans)
1187199086Srpaulo			break;
1188199086Srpaulo
1189199086Srpaulo		if (p[i] == 0) {
1190199086Srpaulo			if (state == ATP_PSPAN_INACTIVE) {
1191199086Srpaulo				/*
1192199086Srpaulo				 * There is no pressure information for this
1193199086Srpaulo				 * sensor, and we aren't tracking a finger.
1194199086Srpaulo				 */
1195199086Srpaulo				continue;
1196199086Srpaulo			} else {
1197199086Srpaulo				state = ATP_PSPAN_INACTIVE;
1198199086Srpaulo				maxp = 0;
1199199086Srpaulo				num_spans++;
1200199086Srpaulo			}
1201199086Srpaulo		} else {
1202199086Srpaulo			switch (state) {
1203199086Srpaulo			case ATP_PSPAN_INACTIVE:
1204199086Srpaulo				state = ATP_PSPAN_INCREASING;
1205199086Srpaulo				maxp  = p[i];
1206199086Srpaulo				break;
1207199086Srpaulo
1208199086Srpaulo			case ATP_PSPAN_INCREASING:
1209199086Srpaulo				if (p[i] > maxp)
1210199086Srpaulo					maxp = p[i];
1211199086Srpaulo				else if (p[i] <= (maxp >> 1))
1212199086Srpaulo					state = ATP_PSPAN_DECREASING;
1213199086Srpaulo				break;
1214199086Srpaulo
1215199086Srpaulo			case ATP_PSPAN_DECREASING:
1216199086Srpaulo				if (p[i] > p[i - 1]) {
1217199086Srpaulo					/*
1218199086Srpaulo					 * This is the beginning of
1219199086Srpaulo					 * another span; change state
1220199086Srpaulo					 * to give the appearance that
1221199086Srpaulo					 * we're starting from an
1222199086Srpaulo					 * inactive span, and then
1223199086Srpaulo					 * re-process this reading in
1224199086Srpaulo					 * the next iteration.
1225199086Srpaulo					 */
1226199086Srpaulo					num_spans++;
1227199086Srpaulo					state = ATP_PSPAN_INACTIVE;
1228199086Srpaulo					maxp  = 0;
1229199086Srpaulo					i--;
1230199086Srpaulo					continue;
1231199086Srpaulo				}
1232199086Srpaulo				break;
1233199086Srpaulo			}
1234199086Srpaulo
1235199086Srpaulo			/* Update the finger span with this reading. */
1236199086Srpaulo			spans[num_spans].width++;
1237199086Srpaulo			spans[num_spans].cum += p[i];
1238199086Srpaulo			spans[num_spans].cog += p[i] * (i + 1);
1239199086Srpaulo		}
1240199086Srpaulo	}
1241199086Srpaulo	if (state != ATP_PSPAN_INACTIVE)
1242199086Srpaulo		num_spans++;    /* close the last finger span */
1243199086Srpaulo
1244199086Srpaulo	/* post-process the spans */
1245199086Srpaulo	for (i = 0; i < num_spans; i++) {
1246199086Srpaulo		/* filter away unwanted pressure spans */
1247262417Shselasky		if ((spans[i].cum < FG_PSPAN_MIN_CUM_PRESSURE) ||
1248262417Shselasky		    (spans[i].width > FG_PSPAN_MAX_WIDTH)) {
1249199086Srpaulo			if ((i + 1) < num_spans) {
1250199086Srpaulo				memcpy(&spans[i], &spans[i + 1],
1251262417Shselasky				    (num_spans - i - 1) * sizeof(fg_pspan));
1252199086Srpaulo				i--;
1253199086Srpaulo			}
1254199086Srpaulo			num_spans--;
1255199086Srpaulo			continue;
1256199086Srpaulo		}
1257199086Srpaulo
1258199086Srpaulo		/* compute this span's representative location */
1259262417Shselasky		spans[i].loc = spans[i].cog * FG_SCALE_FACTOR /
1260199086Srpaulo			spans[i].cum;
1261199086Srpaulo
1262262417Shselasky		spans[i].matched = false; /* not yet matched against a stroke */
1263199086Srpaulo	}
1264199086Srpaulo
1265199086Srpaulo	*nspans_p = num_spans;
1266199086Srpaulo}
1267199086Srpaulo
1268262417Shselaskystatic void
1269262417Shselaskywsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
1270262417Shselasky{
1271262417Shselasky	const struct wsp_dev_params *params = sc->sc_params;
1272262417Shselasky	wsp_finger_t fingers[WSP_MAX_FINGERS];
1273262417Shselasky	struct wsp_finger_sensor_data *source_fingerp;
1274262417Shselasky	u_int n_source_fingers;
1275262417Shselasky	u_int n_fingers;
1276262417Shselasky	u_int i;
1277262417Shselasky
1278262417Shselasky	/* validate sensor data length */
1279262417Shselasky	if ((data_len < params->finger_data_offset) ||
1280262417Shselasky	    ((data_len - params->finger_data_offset) %
1281262417Shselasky	     WSP_SIZEOF_FINGER_SENSOR_DATA) != 0)
1282262417Shselasky		return;
1283262417Shselasky
1284262417Shselasky	/* compute number of source fingers */
1285262417Shselasky	n_source_fingers = (data_len - params->finger_data_offset) /
1286262417Shselasky	    WSP_SIZEOF_FINGER_SENSOR_DATA;
1287262417Shselasky
1288262417Shselasky	if (n_source_fingers > WSP_MAX_FINGERS)
1289262417Shselasky		n_source_fingers = WSP_MAX_FINGERS;
1290262417Shselasky
1291262417Shselasky	/* iterate over the source data collecting useful fingers */
1292262417Shselasky	n_fingers = 0;
1293262417Shselasky	source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data +
1294262417Shselasky	     params->finger_data_offset);
1295262417Shselasky
1296262417Shselasky	for (i = 0; i < n_source_fingers; i++, source_fingerp++) {
1297262417Shselasky		/* swap endianness, if any */
1298262417Shselasky		if (le16toh(0x1234) != 0x1234) {
1299262417Shselasky			source_fingerp->origin      = le16toh((uint16_t)source_fingerp->origin);
1300262417Shselasky			source_fingerp->abs_x       = le16toh((uint16_t)source_fingerp->abs_x);
1301262417Shselasky			source_fingerp->abs_y       = le16toh((uint16_t)source_fingerp->abs_y);
1302262417Shselasky			source_fingerp->rel_x       = le16toh((uint16_t)source_fingerp->rel_x);
1303262417Shselasky			source_fingerp->rel_y       = le16toh((uint16_t)source_fingerp->rel_y);
1304262417Shselasky			source_fingerp->tool_major  = le16toh((uint16_t)source_fingerp->tool_major);
1305262417Shselasky			source_fingerp->tool_minor  = le16toh((uint16_t)source_fingerp->tool_minor);
1306262417Shselasky			source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation);
1307262417Shselasky			source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major);
1308262417Shselasky			source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor);
1309262417Shselasky			source_fingerp->multi       = le16toh((uint16_t)source_fingerp->multi);
1310262417Shselasky		}
1311262417Shselasky
1312262417Shselasky		/* check for minium threshold */
1313262417Shselasky		if (source_fingerp->touch_major == 0)
1314262417Shselasky			continue;
1315262417Shselasky
1316262417Shselasky		fingers[n_fingers].matched = false;
1317262417Shselasky		fingers[n_fingers].x       = source_fingerp->abs_x;
1318262417Shselasky		fingers[n_fingers].y       = -source_fingerp->abs_y;
1319262417Shselasky
1320262417Shselasky		n_fingers++;
1321262417Shselasky	}
1322262417Shselasky
1323262417Shselasky	if ((sc->sc_n_strokes == 0) && (n_fingers == 0))
1324262417Shselasky		return;
1325262417Shselasky
1326262417Shselasky	if (wsp_update_strokes(sc, fingers, n_fingers))
1327262417Shselasky		sc->sc_status.flags |= MOUSE_POSCHANGED;
1328262417Shselasky
1329262417Shselasky	switch(params->tp_type) {
1330262417Shselasky	case WSP_TRACKPAD_TYPE2:
1331262417Shselasky		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET];
1332262417Shselasky		break;
1333262417Shselasky	case WSP_TRACKPAD_TYPE3:
1334262417Shselasky		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET];
1335262417Shselasky		break;
1336262417Shselasky	default:
1337262417Shselasky		break;
1338262417Shselasky	}
1339262417Shselasky	sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0;
1340262417Shselasky}
1341262417Shselasky
1342199086Srpaulo/*
1343199086Srpaulo * Match a pressure-span against a stroke-component. If there is a
1344262417Shselasky * match, update the component's state and return true.
1345199086Srpaulo */
1346199086Srpaulostatic boolean_t
1347262417Shselaskyfg_match_stroke_component(fg_stroke_component_t *component,
1348262417Shselasky    const fg_pspan *pspan, atp_stroke_type stroke_type)
1349199086Srpaulo{
1350200241Srpaulo	int   delta_mickeys;
1351200241Srpaulo	u_int min_pressure;
1352199086Srpaulo
1353200241Srpaulo	delta_mickeys = pspan->loc - component->loc;
1354200241Srpaulo
1355262417Shselasky	if (abs(delta_mickeys) > (int)FG_MAX_DELTA_MICKEYS)
1356262417Shselasky		return (false); /* the finger span is too far out; no match */
1357199086Srpaulo
1358262417Shselasky	component->loc = pspan->loc;
1359200241Srpaulo
1360200241Srpaulo	/*
1361200241Srpaulo	 * A sudden and significant increase in a pspan's cumulative
1362200241Srpaulo	 * pressure indicates the incidence of a new finger
1363200241Srpaulo	 * contact. This usually revises the pspan's
1364200241Srpaulo	 * centre-of-gravity, and hence the location of any/all
1365200241Srpaulo	 * matching stroke component(s). But such a change should
1366200241Srpaulo	 * *not* be interpreted as a movement.
1367200241Srpaulo	 */
1368262417Shselasky	if (pspan->cum > ((3 * component->cum_pressure) >> 1))
1369200241Srpaulo		delta_mickeys = 0;
1370200241Srpaulo
1371199086Srpaulo	component->cum_pressure = pspan->cum;
1372199086Srpaulo	if (pspan->cum > component->max_cum_pressure)
1373199086Srpaulo		component->max_cum_pressure = pspan->cum;
1374199086Srpaulo
1375199086Srpaulo	/*
1376200241Srpaulo	 * Disregard the component's movement if its cumulative
1377200241Srpaulo	 * pressure drops below a fraction of the maximum; this
1378200241Srpaulo	 * fraction is determined based on the stroke's type.
1379199086Srpaulo	 */
1380200241Srpaulo	if (stroke_type == ATP_STROKE_TOUCH)
1381200241Srpaulo		min_pressure = (3 * component->max_cum_pressure) >> 2;
1382200241Srpaulo	else
1383200241Srpaulo		min_pressure = component->max_cum_pressure >> 2;
1384200241Srpaulo	if (component->cum_pressure < min_pressure)
1385199086Srpaulo		delta_mickeys = 0;
1386199086Srpaulo
1387199086Srpaulo	component->delta_mickeys = delta_mickeys;
1388262417Shselasky	return (true);
1389199086Srpaulo}
1390199086Srpaulo
1391199086Srpaulostatic void
1392262417Shselaskyfg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
1393262417Shselasky    fg_pspan *pspans, u_int n_pspans, u_int repeat_count)
1394199086Srpaulo{
1395262417Shselasky	atp_stroke_t *strokep;
1396199086Srpaulo	u_int repeat_index = 0;
1397262417Shselasky	u_int i;
1398199086Srpaulo
1399199086Srpaulo	/* Determine the index of the multi-span. */
1400199086Srpaulo	if (repeat_count) {
1401199086Srpaulo		for (i = 0; i < n_pspans; i++) {
1402262417Shselasky			if (pspans[i].cum > pspans[repeat_index].cum)
1403199086Srpaulo				repeat_index = i;
1404199086Srpaulo		}
1405199086Srpaulo	}
1406199086Srpaulo
1407262417Shselasky	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1408262417Shselasky		if (strokep->components[axis].matched)
1409199086Srpaulo			continue; /* skip matched components */
1410199086Srpaulo
1411262417Shselasky		for (i = 0; i < n_pspans; i++) {
1412262417Shselasky			if (pspans[i].matched)
1413199086Srpaulo				continue; /* skip matched pspans */
1414199086Srpaulo
1415262417Shselasky			if (fg_match_stroke_component(
1416262417Shselasky			    &strokep->components[axis], &pspans[i],
1417262417Shselasky			    strokep->type)) {
1418262417Shselasky
1419199086Srpaulo				/* There is a match. */
1420262417Shselasky				strokep->components[axis].matched = true;
1421199086Srpaulo
1422199086Srpaulo				/* Take care to repeat at the multi-span. */
1423262417Shselasky				if ((repeat_count > 0) && (i == repeat_index))
1424199086Srpaulo					repeat_count--;
1425199086Srpaulo				else
1426262417Shselasky					pspans[i].matched = true;
1427199086Srpaulo
1428262417Shselasky				break; /* skip to the next strokep */
1429199086Srpaulo			}
1430199086Srpaulo		} /* loop over pspans */
1431199086Srpaulo	} /* loop over strokes */
1432199086Srpaulo}
1433199086Srpaulo
1434262417Shselaskystatic boolean_t
1435262417Shselaskywsp_match_strokes_against_fingers(struct atp_softc *sc,
1436262417Shselasky    wsp_finger_t *fingers, u_int n_fingers)
1437262417Shselasky{
1438262417Shselasky	boolean_t movement = false;
1439262417Shselasky	atp_stroke_t *strokep;
1440262417Shselasky	u_int i;
1441262417Shselasky
1442262417Shselasky	/* reset the matched status for all strokes */
1443262417Shselasky	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry)
1444262417Shselasky		strokep->matched = false;
1445262417Shselasky
1446262417Shselasky	for (i = 0; i != n_fingers; i++) {
1447262417Shselasky		u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ;
1448262417Shselasky		atp_stroke_t *strokep_best = NULL;
1449262417Shselasky
1450262417Shselasky		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1451262417Shselasky			int instantaneous_dx;
1452262417Shselasky			int instantaneous_dy;
1453262417Shselasky			u_int d_squared;
1454262417Shselasky
1455262417Shselasky			if (strokep->matched)
1456262417Shselasky				continue;
1457262417Shselasky
1458262417Shselasky			instantaneous_dx = fingers[i].x - strokep->x;
1459262417Shselasky			instantaneous_dy = fingers[i].y - strokep->y;
1460262417Shselasky
1461262417Shselasky			/* skip strokes which are far away */
1462262417Shselasky			d_squared =
1463262417Shselasky			    (instantaneous_dx * instantaneous_dx) +
1464262417Shselasky			    (instantaneous_dy * instantaneous_dy);
1465262417Shselasky
1466262417Shselasky			if (d_squared < least_distance_sq) {
1467262417Shselasky				least_distance_sq = d_squared;
1468262417Shselasky				strokep_best = strokep;
1469262417Shselasky			}
1470262417Shselasky		}
1471262417Shselasky
1472262417Shselasky		strokep = strokep_best;
1473262417Shselasky
1474262417Shselasky		if (strokep != NULL) {
1475262417Shselasky			fingers[i].matched = true;
1476262417Shselasky
1477262417Shselasky			strokep->matched          = true;
1478262417Shselasky			strokep->instantaneous_dx = fingers[i].x - strokep->x;
1479262417Shselasky			strokep->instantaneous_dy = fingers[i].y - strokep->y;
1480262417Shselasky			strokep->x                = fingers[i].x;
1481262417Shselasky			strokep->y                = fingers[i].y;
1482262417Shselasky
1483262417Shselasky			atp_advance_stroke_state(sc, strokep, &movement);
1484262417Shselasky		}
1485262417Shselasky	}
1486262417Shselasky	return (movement);
1487262417Shselasky}
1488262417Shselasky
1489199086Srpaulo/*
1490199086Srpaulo * Update strokes by matching against current pressure-spans.
1491262417Shselasky * Return true if any movement is detected.
1492199086Srpaulo */
1493199086Srpaulostatic boolean_t
1494262417Shselaskyfg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
1495262417Shselasky    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
1496199086Srpaulo{
1497262417Shselasky	atp_stroke_t *strokep;
1498262417Shselasky	atp_stroke_t *strokep_next;
1499262417Shselasky	boolean_t movement = false;
1500262417Shselasky	u_int repeat_count = 0;
1501262417Shselasky	u_int i;
1502262417Shselasky	u_int j;
1503199086Srpaulo
1504199086Srpaulo	/* Reset X and Y components of all strokes as unmatched. */
1505262417Shselasky	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1506262417Shselasky		strokep->components[X].matched = false;
1507262417Shselasky		strokep->components[Y].matched = false;
1508199086Srpaulo	}
1509199086Srpaulo
1510199086Srpaulo	/*
1511199086Srpaulo	 * Usually, the X and Y pspans come in pairs (the common case
1512199086Srpaulo	 * being a single pair). It is possible, however, that
1513199086Srpaulo	 * multiple contacts resolve to a single pspan along an
1514199086Srpaulo	 * axis, as illustrated in the following:
1515199086Srpaulo	 *
1516199086Srpaulo	 *   F = finger-contact
1517199086Srpaulo	 *
1518199086Srpaulo	 *                pspan  pspan
1519199086Srpaulo	 *        +-----------------------+
1520199086Srpaulo	 *        |         .      .      |
1521199086Srpaulo	 *        |         .      .      |
1522199086Srpaulo	 *        |         .      .      |
1523199086Srpaulo	 *        |         .      .      |
1524199086Srpaulo	 *  pspan |.........F......F      |
1525199086Srpaulo	 *        |                       |
1526199086Srpaulo	 *        |                       |
1527199086Srpaulo	 *        |                       |
1528199086Srpaulo	 *        +-----------------------+
1529199086Srpaulo	 *
1530199086Srpaulo	 *
1531199086Srpaulo	 * The above case can be detected by a difference in the
1532199086Srpaulo	 * number of X and Y pspans. When this happens, X and Y pspans
1533199086Srpaulo	 * aren't easy to pair or match against strokes.
1534199086Srpaulo	 *
1535199086Srpaulo	 * When X and Y pspans differ in number, the axis with the
1536199086Srpaulo	 * smaller number of pspans is regarded as having a repeating
1537199086Srpaulo	 * pspan (or a multi-pspan)--in the above illustration, the
1538199086Srpaulo	 * Y-axis has a repeating pspan. Our approach is to try to
1539199086Srpaulo	 * match the multi-pspan repeatedly against strokes. The
1540199086Srpaulo	 * difference between the number of X and Y pspans gives us a
1541199086Srpaulo	 * crude repeat_count for matching multi-pspans--i.e. the
1542199086Srpaulo	 * multi-pspan along the Y axis (above) has a repeat_count of 1.
1543199086Srpaulo	 */
1544199086Srpaulo	repeat_count = abs(n_xpspans - n_ypspans);
1545199086Srpaulo
1546262417Shselasky	fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
1547199086Srpaulo	    (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
1548199086Srpaulo		repeat_count : 0));
1549262417Shselasky	fg_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
1550199086Srpaulo	    (((repeat_count != 0) && (n_ypspans < n_xpspans)) ?
1551199086Srpaulo		repeat_count : 0));
1552199086Srpaulo
1553199086Srpaulo	/* Update the state of strokes based on the above pspan matches. */
1554262417Shselasky	TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
1555262417Shselasky
1556262417Shselasky		if (strokep->components[X].matched &&
1557262417Shselasky		    strokep->components[Y].matched) {
1558262417Shselasky			strokep->matched = true;
1559262417Shselasky			strokep->instantaneous_dx =
1560262417Shselasky			    strokep->components[X].delta_mickeys;
1561262417Shselasky			strokep->instantaneous_dy =
1562262417Shselasky			    strokep->components[Y].delta_mickeys;
1563262417Shselasky			atp_advance_stroke_state(sc, strokep, &movement);
1564199086Srpaulo		} else {
1565199086Srpaulo			/*
1566199086Srpaulo			 * At least one component of this stroke
1567199086Srpaulo			 * didn't match against current pspans;
1568199086Srpaulo			 * terminate it.
1569199086Srpaulo			 */
1570262417Shselasky			atp_terminate_stroke(sc, strokep);
1571199086Srpaulo		}
1572199086Srpaulo	}
1573199086Srpaulo
1574199086Srpaulo	/* Add new strokes for pairs of unmatched pspans */
1575199086Srpaulo	for (i = 0; i < n_xpspans; i++) {
1576262417Shselasky		if (pspans_x[i].matched == false) break;
1577199086Srpaulo	}
1578199086Srpaulo	for (j = 0; j < n_ypspans; j++) {
1579262417Shselasky		if (pspans_y[j].matched == false) break;
1580199086Srpaulo	}
1581199086Srpaulo	if ((i < n_xpspans) && (j < n_ypspans)) {
1582207077Sthompsa#ifdef USB_DEBUG
1583199086Srpaulo		if (atp_debug >= ATP_LLEVEL_INFO) {
1584199086Srpaulo			printf("unmatched pspans:");
1585199086Srpaulo			for (; i < n_xpspans; i++) {
1586199086Srpaulo				if (pspans_x[i].matched)
1587199086Srpaulo					continue;
1588199086Srpaulo				printf(" X:[loc:%u,cum:%u]",
1589199086Srpaulo				    pspans_x[i].loc, pspans_x[i].cum);
1590199086Srpaulo			}
1591199086Srpaulo			for (; j < n_ypspans; j++) {
1592199086Srpaulo				if (pspans_y[j].matched)
1593199086Srpaulo					continue;
1594199086Srpaulo				printf(" Y:[loc:%u,cum:%u]",
1595199086Srpaulo				    pspans_y[j].loc, pspans_y[j].cum);
1596199086Srpaulo			}
1597199086Srpaulo			printf("\n");
1598199086Srpaulo		}
1599207077Sthompsa#endif /* USB_DEBUG */
1600199086Srpaulo		if ((n_xpspans == 1) && (n_ypspans == 1))
1601199086Srpaulo			/* The common case of a single pair of new pspans. */
1602262417Shselasky			fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
1603199086Srpaulo		else
1604262417Shselasky			fg_add_new_strokes(sc, pspans_x, n_xpspans,
1605199086Srpaulo			    pspans_y, n_ypspans);
1606199086Srpaulo	}
1607199086Srpaulo
1608207077Sthompsa#ifdef USB_DEBUG
1609199086Srpaulo	if (atp_debug >= ATP_LLEVEL_INFO) {
1610262417Shselasky		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1611262417Shselasky			printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c"
1612262417Shselasky			    ",%clc:%u,dm:%d,cum:%d,max:%d,%c",
1613262417Shselasky			    (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "",
1614262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
1615262417Shselasky			    strokep->components[X].loc,
1616262417Shselasky			    strokep->components[X].delta_mickeys,
1617262417Shselasky			    strokep->components[X].cum_pressure,
1618262417Shselasky			    strokep->components[X].max_cum_pressure,
1619262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>',
1620262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
1621262417Shselasky			    strokep->components[Y].loc,
1622262417Shselasky			    strokep->components[Y].delta_mickeys,
1623262417Shselasky			    strokep->components[Y].cum_pressure,
1624262417Shselasky			    strokep->components[Y].max_cum_pressure,
1625262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>');
1626199086Srpaulo		}
1627262417Shselasky		if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL)
1628199086Srpaulo			printf("\n");
1629199086Srpaulo	}
1630207077Sthompsa#endif /* USB_DEBUG */
1631262417Shselasky	return (movement);
1632262417Shselasky}
1633199086Srpaulo
1634262417Shselasky/*
1635262417Shselasky * Update strokes by matching against current pressure-spans.
1636262417Shselasky * Return true if any movement is detected.
1637262417Shselasky */
1638262417Shselaskystatic boolean_t
1639262417Shselaskywsp_update_strokes(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers)
1640262417Shselasky{
1641262417Shselasky	boolean_t movement = false;
1642262417Shselasky	atp_stroke_t *strokep_next;
1643262417Shselasky	atp_stroke_t *strokep;
1644262417Shselasky	u_int i;
1645262417Shselasky
1646262417Shselasky	if (sc->sc_n_strokes > 0) {
1647262417Shselasky		movement = wsp_match_strokes_against_fingers(
1648262417Shselasky		    sc, fingers, n_fingers);
1649262417Shselasky
1650262417Shselasky		/* handle zombie strokes */
1651262417Shselasky		TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
1652262417Shselasky			if (strokep->matched)
1653262417Shselasky				continue;
1654262417Shselasky			atp_terminate_stroke(sc, strokep);
1655262417Shselasky		}
1656262417Shselasky	}
1657262417Shselasky
1658262417Shselasky	/* initialize unmatched fingers as strokes */
1659262417Shselasky	for (i = 0; i != n_fingers; i++) {
1660262417Shselasky		if (fingers[i].matched)
1661262417Shselasky			continue;
1662262417Shselasky
1663262417Shselasky		wsp_add_stroke(sc, fingers + i);
1664262417Shselasky	}
1665199086Srpaulo	return (movement);
1666199086Srpaulo}
1667199086Srpaulo
1668199086Srpaulo/* Initialize a stroke using a pressure-span. */
1669262417Shselaskystatic void
1670262417Shselaskyfg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x,
1671262417Shselasky    const fg_pspan *pspan_y)
1672199086Srpaulo{
1673262417Shselasky	atp_stroke_t *strokep;
1674199086Srpaulo
1675262417Shselasky	strokep = atp_alloc_stroke(sc);
1676262417Shselasky	if (strokep == NULL)
1677199086Srpaulo		return;
1678199086Srpaulo
1679199086Srpaulo	/*
1680199086Srpaulo	 * Strokes begin as potential touches. If a stroke survives
1681199086Srpaulo	 * longer than a threshold, or if it records significant
1682199086Srpaulo	 * cumulative movement, then it is considered a 'slide'.
1683199086Srpaulo	 */
1684262417Shselasky	strokep->type    = ATP_STROKE_TOUCH;
1685262417Shselasky	strokep->matched = false;
1686262417Shselasky	microtime(&strokep->ctime);
1687262417Shselasky	strokep->age     = 1;		/* number of interrupts */
1688262417Shselasky	strokep->x       = pspan_x->loc;
1689262417Shselasky	strokep->y       = pspan_y->loc;
1690199086Srpaulo
1691262417Shselasky	strokep->components[X].loc              = pspan_x->loc;
1692262417Shselasky	strokep->components[X].cum_pressure     = pspan_x->cum;
1693262417Shselasky	strokep->components[X].max_cum_pressure = pspan_x->cum;
1694262417Shselasky	strokep->components[X].matched          = true;
1695199086Srpaulo
1696262417Shselasky	strokep->components[Y].loc              = pspan_y->loc;
1697262417Shselasky	strokep->components[Y].cum_pressure     = pspan_y->cum;
1698262417Shselasky	strokep->components[Y].max_cum_pressure = pspan_y->cum;
1699262417Shselasky	strokep->components[Y].matched          = true;
1700199086Srpaulo
1701199086Srpaulo	if (sc->sc_n_strokes > 1) {
1702199086Srpaulo		/* Reset double-tap-n-drag if we have more than one strokes. */
1703199086Srpaulo		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
1704199086Srpaulo	}
1705199086Srpaulo
1706199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n",
1707262417Shselasky	    strokep->components[X].loc,
1708262417Shselasky	    strokep->components[Y].loc,
1709262417Shselasky	    (u_int)strokep->ctime.tv_sec,
1710262417Shselasky	    (unsigned long int)strokep->ctime.tv_usec);
1711199086Srpaulo}
1712199086Srpaulo
1713199086Srpaulostatic void
1714262417Shselaskyfg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
1715262417Shselasky    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
1716199086Srpaulo{
1717262417Shselasky	fg_pspan spans[2][FG_MAX_PSPANS_PER_AXIS];
1718233774Shselasky	u_int nspans[2];
1719233774Shselasky	u_int i;
1720233774Shselasky	u_int j;
1721199086Srpaulo
1722199086Srpaulo	/* Copy unmatched pspans into the local arrays. */
1723199086Srpaulo	for (i = 0, nspans[X] = 0; i < n_xpspans; i++) {
1724262417Shselasky		if (pspans_x[i].matched == false) {
1725199086Srpaulo			spans[X][nspans[X]] = pspans_x[i];
1726199086Srpaulo			nspans[X]++;
1727199086Srpaulo		}
1728199086Srpaulo	}
1729199086Srpaulo	for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) {
1730262417Shselasky		if (pspans_y[j].matched == false) {
1731199086Srpaulo			spans[Y][nspans[Y]] = pspans_y[j];
1732199086Srpaulo			nspans[Y]++;
1733199086Srpaulo		}
1734199086Srpaulo	}
1735199086Srpaulo
1736199086Srpaulo	if (nspans[X] == nspans[Y]) {
1737199086Srpaulo		/* Create new strokes from pairs of unmatched pspans */
1738199086Srpaulo		for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++)
1739262417Shselasky			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
1740199086Srpaulo	} else {
1741199086Srpaulo		u_int    cum = 0;
1742199086Srpaulo		atp_axis repeat_axis;      /* axis with multi-pspans */
1743199086Srpaulo		u_int    repeat_count;     /* repeat count for the multi-pspan*/
1744199086Srpaulo		u_int    repeat_index = 0; /* index of the multi-span */
1745199086Srpaulo
1746199086Srpaulo		repeat_axis  = (nspans[X] > nspans[Y]) ? Y : X;
1747199086Srpaulo		repeat_count = abs(nspans[X] - nspans[Y]);
1748199086Srpaulo		for (i = 0; i < nspans[repeat_axis]; i++) {
1749199086Srpaulo			if (spans[repeat_axis][i].cum > cum) {
1750199086Srpaulo				repeat_index = i;
1751199086Srpaulo				cum = spans[repeat_axis][i].cum;
1752199086Srpaulo			}
1753199086Srpaulo		}
1754199086Srpaulo
1755199086Srpaulo		/* Create new strokes from pairs of unmatched pspans */
1756199086Srpaulo		i = 0, j = 0;
1757199086Srpaulo		for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) {
1758262417Shselasky			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
1759199086Srpaulo
1760199086Srpaulo			/* Take care to repeat at the multi-pspan. */
1761199086Srpaulo			if (repeat_count > 0) {
1762199086Srpaulo				if ((repeat_axis == X) &&
1763199086Srpaulo				    (repeat_index == i)) {
1764199086Srpaulo					i--; /* counter loop increment */
1765199086Srpaulo					repeat_count--;
1766199086Srpaulo				} else if ((repeat_axis == Y) &&
1767199086Srpaulo				    (repeat_index == j)) {
1768199086Srpaulo					j--; /* counter loop increment */
1769199086Srpaulo					repeat_count--;
1770199086Srpaulo				}
1771199086Srpaulo			}
1772199086Srpaulo		}
1773199086Srpaulo	}
1774199086Srpaulo}
1775199086Srpaulo
1776262417Shselasky/* Initialize a stroke from an unmatched finger. */
1777262417Shselaskystatic void
1778262417Shselaskywsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp)
1779199086Srpaulo{
1780262417Shselasky	atp_stroke_t *strokep;
1781199086Srpaulo
1782262417Shselasky	strokep = atp_alloc_stroke(sc);
1783262417Shselasky	if (strokep == NULL)
1784200241Srpaulo		return;
1785200241Srpaulo
1786262417Shselasky	/*
1787262417Shselasky	 * Strokes begin as potential touches. If a stroke survives
1788262417Shselasky	 * longer than a threshold, or if it records significant
1789262417Shselasky	 * cumulative movement, then it is considered a 'slide'.
1790262417Shselasky	 */
1791262417Shselasky	strokep->type    = ATP_STROKE_TOUCH;
1792262417Shselasky	strokep->matched = true;
1793262417Shselasky	microtime(&strokep->ctime);
1794262417Shselasky	strokep->age = 1;	/* number of interrupts */
1795262417Shselasky	strokep->x = fingerp->x;
1796262417Shselasky	strokep->y = fingerp->y;
1797199086Srpaulo
1798262417Shselasky	/* Reset double-tap-n-drag if we have more than one strokes. */
1799262417Shselasky	if (sc->sc_n_strokes > 1)
1800262417Shselasky		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
1801200241Srpaulo
1802262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y);
1803199086Srpaulo}
1804199086Srpaulo
1805262417Shselaskystatic void
1806262417Shselaskyatp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep,
1807262417Shselasky    boolean_t *movementp)
1808200241Srpaulo{
1809262417Shselasky	/* Revitalize stroke if it had previously been marked as a zombie. */
1810262417Shselasky	if (strokep->flags & ATSF_ZOMBIE)
1811262417Shselasky		strokep->flags &= ~ATSF_ZOMBIE;
1812200241Srpaulo
1813262417Shselasky	strokep->age++;
1814262417Shselasky	if (strokep->age <= atp_stroke_maturity_threshold) {
1815262417Shselasky		/* Avoid noise from immature strokes. */
1816262417Shselasky		strokep->instantaneous_dx = 0;
1817262417Shselasky		strokep->instantaneous_dy = 0;
1818200241Srpaulo	}
1819200241Srpaulo
1820262417Shselasky	if (atp_compute_stroke_movement(strokep))
1821262417Shselasky		*movementp = true;
1822199086Srpaulo
1823262417Shselasky	if (strokep->type != ATP_STROKE_TOUCH)
1824199086Srpaulo		return;
1825199086Srpaulo
1826262417Shselasky	/* Convert touch strokes to slides upon detecting movement or age. */
1827262417Shselasky	if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) ||
1828262417Shselasky	    (abs(strokep->cum_movement_y) > atp_slide_min_movement))
1829262417Shselasky		atp_convert_to_slide(sc, strokep);
1830262417Shselasky	else {
1831262417Shselasky		/* Compute the stroke's age. */
1832262417Shselasky		struct timeval tdiff;
1833262417Shselasky		getmicrotime(&tdiff);
1834262417Shselasky		if (timevalcmp(&tdiff, &strokep->ctime, >)) {
1835262417Shselasky			timevalsub(&tdiff, &strokep->ctime);
1836199086Srpaulo
1837262417Shselasky			if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) ||
1838262417Shselasky			    ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) &&
1839262417Shselasky			     (tdiff.tv_usec >= (atp_touch_timeout % 1000000))))
1840262417Shselasky				atp_convert_to_slide(sc, strokep);
1841199086Srpaulo		}
1842199086Srpaulo	}
1843199086Srpaulo}
1844199086Srpaulo
1845262417Shselaskystatic boolean_t
1846262417Shselaskyatp_stroke_has_small_movement(const atp_stroke_t *strokep)
1847199086Srpaulo{
1848262417Shselasky	return (((u_int)abs(strokep->instantaneous_dx) <=
1849262417Shselasky		 atp_small_movement_threshold) &&
1850262417Shselasky		((u_int)abs(strokep->instantaneous_dy) <=
1851262417Shselasky		 atp_small_movement_threshold));
1852199086Srpaulo}
1853199086Srpaulo
1854199086Srpaulo/*
1855262417Shselasky * Accumulate instantaneous changes into the stroke's 'pending' bucket; if
1856199086Srpaulo * the aggregate exceeds the small_movement_threshold, then retain
1857262417Shselasky * instantaneous changes for later.
1858199086Srpaulo */
1859199086Srpaulostatic void
1860262417Shselaskyatp_update_pending_mickeys(atp_stroke_t *strokep)
1861199086Srpaulo{
1862262417Shselasky	/* accumulate instantaneous movement */
1863262417Shselasky	strokep->pending_dx += strokep->instantaneous_dx;
1864262417Shselasky	strokep->pending_dy += strokep->instantaneous_dy;
1865199086Srpaulo
1866262417Shselasky#define UPDATE_INSTANTANEOUS_AND_PENDING(I, P)                          \
1867262417Shselasky	if (abs((P)) <= atp_small_movement_threshold)                   \
1868262417Shselasky		(I) = 0; /* clobber small movement */                   \
1869262417Shselasky	else {                                                          \
1870262417Shselasky		if ((I) > 0) {                                          \
1871262417Shselasky			/*                                              \
1872262417Shselasky			 * Round up instantaneous movement to the nearest \
1873262417Shselasky			 * ceiling. This helps preserve small mickey    \
1874262417Shselasky			 * movements from being lost in following scaling \
1875262417Shselasky			 * operation.                                   \
1876262417Shselasky			 */                                             \
1877262417Shselasky			(I) = (((I) + (atp_mickeys_scale_factor - 1)) / \
1878262417Shselasky			       atp_mickeys_scale_factor) *              \
1879262417Shselasky			      atp_mickeys_scale_factor;                 \
1880262417Shselasky									\
1881262417Shselasky			/*                                              \
1882262417Shselasky			 * Deduct the rounded mickeys from pending mickeys. \
1883262417Shselasky			 * Note: we multiply by 2 to offset the previous \
1884262417Shselasky			 * accumulation of instantaneous movement into  \
1885262417Shselasky			 * pending.                                     \
1886262417Shselasky			 */                                             \
1887262417Shselasky			(P) -= ((I) << 1);                              \
1888262417Shselasky									\
1889262417Shselasky			/* truncate pending to 0 if it becomes negative. */ \
1890262417Shselasky			(P) = imax((P), 0);                             \
1891262417Shselasky		} else {                                                \
1892262417Shselasky			/*                                              \
1893262417Shselasky			 * Round down instantaneous movement to the nearest \
1894262417Shselasky			 * ceiling. This helps preserve small mickey    \
1895262417Shselasky			 * movements from being lost in following scaling \
1896262417Shselasky			 * operation.                                   \
1897262417Shselasky			 */                                             \
1898262417Shselasky			(I) = (((I) - (atp_mickeys_scale_factor - 1)) / \
1899262417Shselasky			       atp_mickeys_scale_factor) *              \
1900262417Shselasky			      atp_mickeys_scale_factor;                 \
1901262417Shselasky									\
1902262417Shselasky			/*                                              \
1903262417Shselasky			 * Deduct the rounded mickeys from pending mickeys. \
1904262417Shselasky			 * Note: we multiply by 2 to offset the previous \
1905262417Shselasky			 * accumulation of instantaneous movement into  \
1906262417Shselasky			 * pending.                                     \
1907262417Shselasky			 */                                             \
1908262417Shselasky			(P) -= ((I) << 1);                              \
1909262417Shselasky									\
1910262417Shselasky			/* truncate pending to 0 if it becomes positive. */ \
1911262417Shselasky			(P) = imin((P), 0);                             \
1912262417Shselasky		}                                                       \
1913199086Srpaulo	}
1914199086Srpaulo
1915262417Shselasky	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx,
1916262417Shselasky	    strokep->pending_dx);
1917262417Shselasky	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy,
1918262417Shselasky	    strokep->pending_dy);
1919199086Srpaulo}
1920199086Srpaulo
1921199086Srpaulo/*
1922199086Srpaulo * Compute a smoothened value for the stroke's movement from
1923262417Shselasky * instantaneous changes in the X and Y components.
1924199086Srpaulo */
1925199086Srpaulostatic boolean_t
1926262417Shselaskyatp_compute_stroke_movement(atp_stroke_t *strokep)
1927199086Srpaulo{
1928199086Srpaulo	/*
1929199086Srpaulo	 * Short movements are added first to the 'pending' bucket,
1930199086Srpaulo	 * and then acted upon only when their aggregate exceeds a
1931199086Srpaulo	 * threshold. This has the effect of filtering away movement
1932199086Srpaulo	 * noise.
1933199086Srpaulo	 */
1934262417Shselasky	if (atp_stroke_has_small_movement(strokep))
1935262417Shselasky		atp_update_pending_mickeys(strokep);
1936262417Shselasky	else {                /* large movement */
1937199086Srpaulo		/* clear away any pending mickeys if there are large movements*/
1938262417Shselasky		strokep->pending_dx = 0;
1939262417Shselasky		strokep->pending_dy = 0;
1940199086Srpaulo	}
1941199086Srpaulo
1942262417Shselasky	/* scale movement */
1943262417Shselasky	strokep->movement_dx = (strokep->instantaneous_dx) /
1944262417Shselasky	    (int)atp_mickeys_scale_factor;
1945262417Shselasky	strokep->movement_dy = (strokep->instantaneous_dy) /
1946262417Shselasky	    (int)atp_mickeys_scale_factor;
1947199086Srpaulo
1948262417Shselasky	if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) ||
1949262417Shselasky	    (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) {
1950262417Shselasky		strokep->movement_dx <<= 1;
1951262417Shselasky		strokep->movement_dy <<= 1;
1952199086Srpaulo	}
1953199086Srpaulo
1954262417Shselasky	strokep->cum_movement_x += strokep->movement_dx;
1955262417Shselasky	strokep->cum_movement_y += strokep->movement_dy;
1956262417Shselasky
1957262417Shselasky	return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0));
1958199086Srpaulo}
1959199086Srpaulo
1960262417Shselasky/*
1961262417Shselasky * Terminate a stroke. Aside from immature strokes, a slide or touch is
1962262417Shselasky * retained as a zombies so as to reap all their termination siblings
1963262417Shselasky * together; this helps establish the number of fingers involved at the
1964262417Shselasky * end of a multi-touch gesture.
1965262417Shselasky */
1966262417Shselaskystatic void
1967262417Shselaskyatp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep)
1968199086Srpaulo{
1969262417Shselasky	if (strokep->flags & ATSF_ZOMBIE)
1970262417Shselasky		return;
1971199086Srpaulo
1972262417Shselasky	/* Drop immature strokes rightaway. */
1973262417Shselasky	if (strokep->age <= atp_stroke_maturity_threshold) {
1974262417Shselasky		atp_free_stroke(sc, strokep);
1975262417Shselasky		return;
1976262417Shselasky	}
1977199086Srpaulo
1978262417Shselasky	strokep->flags |= ATSF_ZOMBIE;
1979262417Shselasky	sc->sc_state |= ATP_ZOMBIES_EXIST;
1980262417Shselasky
1981262417Shselasky	callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL,
1982262417Shselasky	    atp_reap_sibling_zombies, sc);
1983262417Shselasky
1984262417Shselasky	/*
1985262417Shselasky	 * Reset the double-click-n-drag at the termination of any
1986262417Shselasky	 * slide stroke.
1987262417Shselasky	 */
1988262417Shselasky	if (strokep->type == ATP_STROKE_SLIDE)
1989262417Shselasky		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
1990199086Srpaulo}
1991199086Srpaulo
1992262417Shselaskystatic boolean_t
1993262417Shselaskyatp_is_horizontal_scroll(const atp_stroke_t *strokep)
1994262417Shselasky{
1995262417Shselasky	if (abs(strokep->cum_movement_x) < atp_slide_min_movement)
1996262417Shselasky		return (false);
1997262417Shselasky	if (strokep->cum_movement_y == 0)
1998262417Shselasky		return (true);
1999262417Shselasky	return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4);
2000262417Shselasky}
2001262417Shselasky
2002262417Shselaskystatic boolean_t
2003262417Shselaskyatp_is_vertical_scroll(const atp_stroke_t *strokep)
2004262417Shselasky{
2005262417Shselasky	if (abs(strokep->cum_movement_y) < atp_slide_min_movement)
2006262417Shselasky		return (false);
2007262417Shselasky	if (strokep->cum_movement_x == 0)
2008262417Shselasky		return (true);
2009262417Shselasky	return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4);
2010262417Shselasky}
2011262417Shselasky
2012199086Srpaulostatic void
2013262417Shselaskyatp_reap_sibling_zombies(void *arg)
2014199086Srpaulo{
2015262417Shselasky	struct atp_softc *sc = (struct atp_softc *)arg;
2016262417Shselasky	u_int8_t n_touches_reaped = 0;
2017262417Shselasky	u_int8_t n_slides_reaped = 0;
2018262417Shselasky	u_int8_t n_horizontal_scrolls = 0;
2019262417Shselasky	u_int8_t n_vertical_scrolls = 0;
2020262417Shselasky	int horizontal_scroll = 0;
2021262417Shselasky	int vertical_scroll = 0;
2022262417Shselasky	atp_stroke_t *strokep;
2023262417Shselasky	atp_stroke_t *strokep_next;
2024199086Srpaulo
2025262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "\n");
2026199086Srpaulo
2027262417Shselasky	TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
2028262417Shselasky		if ((strokep->flags & ATSF_ZOMBIE) == 0)
2029199086Srpaulo			continue;
2030199086Srpaulo
2031262417Shselasky		if (strokep->type == ATP_STROKE_TOUCH) {
2032262417Shselasky			n_touches_reaped++;
2033199086Srpaulo		} else {
2034262417Shselasky			n_slides_reaped++;
2035199086Srpaulo
2036262417Shselasky			if (atp_is_horizontal_scroll(strokep)) {
2037262417Shselasky				n_horizontal_scrolls++;
2038262417Shselasky				horizontal_scroll += strokep->cum_movement_x;
2039262417Shselasky			} else if (atp_is_vertical_scroll(strokep)) {
2040262417Shselasky				n_vertical_scrolls++;
2041262417Shselasky				vertical_scroll +=  strokep->cum_movement_y;
2042262417Shselasky			}
2043199086Srpaulo		}
2044199086Srpaulo
2045262417Shselasky		atp_free_stroke(sc, strokep);
2046262417Shselasky	}
2047199086Srpaulo
2048262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n",
2049262417Shselasky	    n_touches_reaped + n_slides_reaped);
2050262417Shselasky	sc->sc_state &= ~ATP_ZOMBIES_EXIST;
2051199086Srpaulo
2052262417Shselasky	/* No further processing necessary if physical button is depressed. */
2053262417Shselasky	if (sc->sc_ibtn != 0)
2054262417Shselasky		return;
2055199086Srpaulo
2056262417Shselasky	if ((n_touches_reaped == 0) && (n_slides_reaped == 0))
2057262417Shselasky		return;
2058199086Srpaulo
2059262417Shselasky	/* Add a pair of virtual button events (button-down and button-up) if
2060262417Shselasky	 * the physical button isn't pressed. */
2061262417Shselasky	if (n_touches_reaped != 0) {
2062262417Shselasky		if (n_touches_reaped < atp_tap_minimum)
2063262417Shselasky			return;
2064199086Srpaulo
2065262417Shselasky		switch (n_touches_reaped) {
2066262417Shselasky		case 1:
2067262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
2068262417Shselasky			microtime(&sc->sc_touch_reap_time); /* remember this time */
2069262417Shselasky			break;
2070262417Shselasky		case 2:
2071262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
2072262417Shselasky			break;
2073262417Shselasky		case 3:
2074262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
2075262417Shselasky			break;
2076262417Shselasky		default:
2077262417Shselasky			/* we handle taps of only up to 3 fingers */
2078262417Shselasky			break;
2079199086Srpaulo		}
2080262417Shselasky		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
2081262417Shselasky
2082262417Shselasky	} else if (n_slides_reaped == 2) {
2083262417Shselasky		if (n_horizontal_scrolls == 2) {
2084262417Shselasky			if (horizontal_scroll < 0)
2085262417Shselasky				atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN);
2086262417Shselasky			else
2087262417Shselasky				atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN);
2088262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
2089262417Shselasky		}
2090199086Srpaulo	}
2091262417Shselasky}
2092199086Srpaulo
2093262417Shselasky/* Switch a given touch stroke to being a slide. */
2094262417Shselaskystatic void
2095262417Shselaskyatp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep)
2096262417Shselasky{
2097262417Shselasky	strokep->type = ATP_STROKE_SLIDE;
2098262417Shselasky
2099262417Shselasky	/* Are we at the beginning of a double-click-n-drag? */
2100262417Shselasky	if ((sc->sc_n_strokes == 1) &&
2101262417Shselasky	    ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) &&
2102262417Shselasky	    timevalcmp(&strokep->ctime, &sc->sc_touch_reap_time, >)) {
2103262417Shselasky		struct timeval delta;
2104262417Shselasky		struct timeval window = {
2105262417Shselasky			atp_double_tap_threshold / 1000000,
2106262417Shselasky			atp_double_tap_threshold % 1000000
2107262417Shselasky		};
2108262417Shselasky
2109262417Shselasky		delta = strokep->ctime;
2110262417Shselasky		timevalsub(&delta, &sc->sc_touch_reap_time);
2111262417Shselasky		if (timevalcmp(&delta, &window, <=))
2112262417Shselasky			sc->sc_state |= ATP_DOUBLE_TAP_DRAG;
2113262417Shselasky	}
2114199086Srpaulo}
2115199086Srpaulo
2116262417Shselaskystatic void
2117262417Shselaskyatp_reset_buf(struct atp_softc *sc)
2118262417Shselasky{
2119262417Shselasky	/* reset read queue */
2120262417Shselasky	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
2121262417Shselasky}
2122199086Srpaulo
2123262417Shselaskystatic void
2124262417Shselaskyatp_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz,
2125262417Shselasky    uint32_t buttons_in)
2126262417Shselasky{
2127262417Shselasky	uint32_t buttons_out;
2128262417Shselasky	uint8_t  buf[8];
2129199086Srpaulo
2130262417Shselasky	dx = imin(dx,  254); dx = imax(dx, -256);
2131262417Shselasky	dy = imin(dy,  254); dy = imax(dy, -256);
2132262417Shselasky	dz = imin(dz,  126); dz = imax(dz, -128);
2133199086Srpaulo
2134262417Shselasky	buttons_out = MOUSE_MSC_BUTTONS;
2135262417Shselasky	if (buttons_in & MOUSE_BUTTON1DOWN)
2136262417Shselasky		buttons_out &= ~MOUSE_MSC_BUTTON1UP;
2137262417Shselasky	else if (buttons_in & MOUSE_BUTTON2DOWN)
2138262417Shselasky		buttons_out &= ~MOUSE_MSC_BUTTON2UP;
2139262417Shselasky	else if (buttons_in & MOUSE_BUTTON3DOWN)
2140262417Shselasky		buttons_out &= ~MOUSE_MSC_BUTTON3UP;
2141262417Shselasky
2142262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n",
2143262417Shselasky	    dx, dy, buttons_out);
2144262417Shselasky
2145262417Shselasky	/* Encode the mouse data in standard format; refer to mouse(4) */
2146262417Shselasky	buf[0] = sc->sc_mode.syncmask[1];
2147262417Shselasky	buf[0] |= buttons_out;
2148262417Shselasky	buf[1] = dx >> 1;
2149262417Shselasky	buf[2] = dy >> 1;
2150262417Shselasky	buf[3] = dx - (dx >> 1);
2151262417Shselasky	buf[4] = dy - (dy >> 1);
2152262417Shselasky	/* Encode extra bytes for level 1 */
2153262417Shselasky	if (sc->sc_mode.level == 1) {
2154262417Shselasky		buf[5] = dz >> 1;
2155262417Shselasky		buf[6] = dz - (dz >> 1);
2156262417Shselasky		buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
2157262417Shselasky	}
2158262417Shselasky
2159262417Shselasky	usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
2160262417Shselasky	    sc->sc_mode.packetsize, 1);
2161262417Shselasky}
2162262417Shselasky
2163199086Srpaulostatic int
2164199086Srpauloatp_probe(device_t self)
2165199086Srpaulo{
2166199086Srpaulo	struct usb_attach_arg *uaa = device_get_ivars(self);
2167199086Srpaulo
2168199086Srpaulo	if (uaa->usb_mode != USB_MODE_HOST)
2169199086Srpaulo		return (ENXIO);
2170199086Srpaulo
2171262417Shselasky	if (uaa->info.bInterfaceClass != UICLASS_HID)
2172199086Srpaulo		return (ENXIO);
2173262417Shselasky	/*
2174262417Shselasky	 * Note: for some reason, the check
2175262417Shselasky	 * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true
2176262417Shselasky	 * for wellspring trackpads, so we've removed it from the common path.
2177262417Shselasky	 */
2178199086Srpaulo
2179262417Shselasky	if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0)
2180262417Shselasky		return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ?
2181262417Shselasky			0 : ENXIO);
2182262417Shselasky
2183262417Shselasky	if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0)
2184262417Shselasky		if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX)
2185262417Shselasky			return (0);
2186262417Shselasky
2187262417Shselasky	return (ENXIO);
2188199086Srpaulo}
2189199086Srpaulo
2190199086Srpaulostatic int
2191199086Srpauloatp_attach(device_t dev)
2192199086Srpaulo{
2193262417Shselasky	struct atp_softc      *sc  = device_get_softc(dev);
2194199086Srpaulo	struct usb_attach_arg *uaa = device_get_ivars(dev);
2195199086Srpaulo	usb_error_t            err;
2196262417Shselasky	void *descriptor_ptr = NULL;
2197262417Shselasky	uint16_t descriptor_len;
2198262417Shselasky	unsigned long di;
2199199086Srpaulo
2200199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc);
2201199086Srpaulo
2202199086Srpaulo	sc->sc_dev        = dev;
2203199086Srpaulo	sc->sc_usb_device = uaa->device;
2204199086Srpaulo
2205262417Shselasky	/* Get HID descriptor */
2206262417Shselasky	if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr,
2207262417Shselasky	    &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) !=
2208262417Shselasky	    USB_ERR_NORMAL_COMPLETION)
2209262417Shselasky		return (ENXIO);
2210262417Shselasky
2211262417Shselasky	/* Get HID report descriptor length */
2212262417Shselasky	sc->sc_expected_sensor_data_len = hid_report_size(descriptor_ptr,
2213262417Shselasky	    descriptor_len, hid_input, NULL);
2214262417Shselasky	free(descriptor_ptr, M_TEMP);
2215262417Shselasky
2216262417Shselasky	if ((sc->sc_expected_sensor_data_len <= 0) ||
2217262417Shselasky	    (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) {
2218262417Shselasky		DPRINTF("atp_attach: datalength invalid or too large: %d\n",
2219262417Shselasky			sc->sc_expected_sensor_data_len);
2220262417Shselasky		return (ENXIO);
2221262417Shselasky	}
2222262417Shselasky
2223199086Srpaulo	/*
2224199086Srpaulo	 * By default the touchpad behaves like an HID device, sending
2225199086Srpaulo	 * packets with reportID = 2. Such reports contain only
2226199086Srpaulo	 * limited information--they encode movement deltas and button
2227199086Srpaulo	 * events,--but do not include data from the pressure
2228199086Srpaulo	 * sensors. The device input mode can be switched from HID
2229199086Srpaulo	 * reports to raw sensor data using vendor-specific USB
2230262417Shselasky	 * control commands.
2231199086Srpaulo	 */
2232262417Shselasky	if ((err = atp_set_device_mode(sc, RAW_SENSOR_MODE)) != 0) {
2233199086Srpaulo		DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err);
2234199086Srpaulo		return (ENXIO);
2235199086Srpaulo	}
2236199086Srpaulo
2237199086Srpaulo	mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE);
2238199086Srpaulo
2239262417Shselasky	di = USB_GET_DRIVER_INFO(uaa);
2240262417Shselasky
2241262417Shselasky	sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di);
2242262417Shselasky
2243262417Shselasky	switch(sc->sc_family) {
2244262417Shselasky	case TRACKPAD_FAMILY_FOUNTAIN_GEYSER:
2245262417Shselasky		sc->sc_params =
2246262417Shselasky		    &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
2247262417Shselasky		sc->sensor_data_interpreter = fg_interpret_sensor_data;
2248262417Shselasky		break;
2249262417Shselasky	case TRACKPAD_FAMILY_WELLSPRING:
2250262417Shselasky		sc->sc_params =
2251262417Shselasky		    &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
2252262417Shselasky		sc->sensor_data_interpreter = wsp_interpret_sensor_data;
2253262417Shselasky		break;
2254262417Shselasky	default:
2255262417Shselasky		goto detach;
2256262417Shselasky	}
2257262417Shselasky
2258199086Srpaulo	err = usbd_transfer_setup(uaa->device,
2259262417Shselasky	    &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_config,
2260199086Srpaulo	    ATP_N_TRANSFER, sc, &sc->sc_mutex);
2261199086Srpaulo	if (err) {
2262199086Srpaulo		DPRINTF("error=%s\n", usbd_errstr(err));
2263199086Srpaulo		goto detach;
2264199086Srpaulo	}
2265199086Srpaulo
2266199086Srpaulo	if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
2267262417Shselasky	    &atp_fifo_methods, &sc->sc_fifo,
2268262417Shselasky	    device_get_unit(dev), -1, uaa->info.bIfaceIndex,
2269262417Shselasky	    UID_ROOT, GID_OPERATOR, 0644)) {
2270199086Srpaulo		goto detach;
2271199086Srpaulo	}
2272199086Srpaulo
2273199086Srpaulo	device_set_usb_desc(dev);
2274199086Srpaulo
2275199086Srpaulo	sc->sc_hw.buttons       = 3;
2276199086Srpaulo	sc->sc_hw.iftype        = MOUSE_IF_USB;
2277199086Srpaulo	sc->sc_hw.type          = MOUSE_PAD;
2278199086Srpaulo	sc->sc_hw.model         = MOUSE_MODEL_GENERIC;
2279199086Srpaulo	sc->sc_hw.hwid          = 0;
2280199086Srpaulo	sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
2281199086Srpaulo	sc->sc_mode.rate        = -1;
2282199086Srpaulo	sc->sc_mode.resolution  = MOUSE_RES_UNKNOWN;
2283199086Srpaulo	sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
2284199086Srpaulo	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
2285199086Srpaulo	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
2286262417Shselasky	sc->sc_mode.accelfactor = 0;
2287262417Shselasky	sc->sc_mode.level       = 0;
2288199086Srpaulo
2289199086Srpaulo	sc->sc_state            = 0;
2290262417Shselasky	sc->sc_ibtn             = 0;
2291199086Srpaulo
2292262417Shselasky	callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0);
2293199086Srpaulo
2294199086Srpaulo	return (0);
2295199086Srpaulo
2296199086Srpaulodetach:
2297199086Srpaulo	atp_detach(dev);
2298199086Srpaulo	return (ENOMEM);
2299199086Srpaulo}
2300199086Srpaulo
2301199086Srpaulostatic int
2302199086Srpauloatp_detach(device_t dev)
2303199086Srpaulo{
2304199086Srpaulo	struct atp_softc *sc;
2305199086Srpaulo
2306199086Srpaulo	sc = device_get_softc(dev);
2307262417Shselasky	atp_set_device_mode(sc, HID_MODE);
2308262417Shselasky
2309262417Shselasky	mtx_lock(&sc->sc_mutex);
2310262417Shselasky	callout_drain(&sc->sc_callout);
2311262417Shselasky	if (sc->sc_state & ATP_ENABLED)
2312199086Srpaulo		atp_disable(sc);
2313262417Shselasky	mtx_unlock(&sc->sc_mutex);
2314199086Srpaulo
2315199086Srpaulo	usb_fifo_detach(&sc->sc_fifo);
2316199086Srpaulo
2317199086Srpaulo	usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER);
2318199086Srpaulo
2319199086Srpaulo	mtx_destroy(&sc->sc_mutex);
2320199086Srpaulo
2321199086Srpaulo	return (0);
2322199086Srpaulo}
2323199086Srpaulo
2324199086Srpaulostatic void
2325199086Srpauloatp_intr(struct usb_xfer *xfer, usb_error_t error)
2326199086Srpaulo{
2327199086Srpaulo	struct atp_softc      *sc = usbd_xfer_softc(xfer);
2328199086Srpaulo	struct usb_page_cache *pc;
2329262417Shselasky	int len;
2330199086Srpaulo
2331199086Srpaulo	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
2332199086Srpaulo
2333199086Srpaulo	switch (USB_GET_STATE(xfer)) {
2334199086Srpaulo	case USB_ST_TRANSFERRED:
2335199086Srpaulo		pc = usbd_xfer_get_frame(xfer, 0);
2336262417Shselasky		usbd_copy_out(pc, 0, sc->sc_sensor_data, len);
2337262417Shselasky		if (len < sc->sc_expected_sensor_data_len) {
2338262417Shselasky			/* make sure we don't process old data */
2339262417Shselasky			memset(sc->sc_sensor_data + len, 0,
2340262417Shselasky			    sc->sc_expected_sensor_data_len - len);
2341199086Srpaulo		}
2342199086Srpaulo
2343262417Shselasky		sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED |
2344262417Shselasky		    MOUSE_POSCHANGED);
2345262417Shselasky		sc->sc_status.obutton = sc->sc_status.button;
2346199086Srpaulo
2347262417Shselasky		(sc->sensor_data_interpreter)(sc, len);
2348199086Srpaulo
2349199086Srpaulo		if (sc->sc_status.button != 0) {
2350199086Srpaulo			/* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */
2351199086Srpaulo			sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
2352199086Srpaulo		} else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) {
2353199086Srpaulo			/* Assume a button-press with DOUBLE_TAP_N_DRAG. */
2354199086Srpaulo			sc->sc_status.button = MOUSE_BUTTON1DOWN;
2355199086Srpaulo		}
2356199086Srpaulo
2357199086Srpaulo		sc->sc_status.flags |=
2358262417Shselasky		    sc->sc_status.button ^ sc->sc_status.obutton;
2359199086Srpaulo		if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) {
2360262417Shselasky		    DPRINTFN(ATP_LLEVEL_INFO, "button %s\n",
2361262417Shselasky			((sc->sc_status.button & MOUSE_BUTTON1DOWN) ?
2362262417Shselasky			"pressed" : "released"));
2363199086Srpaulo		}
2364199086Srpaulo
2365262417Shselasky		if (sc->sc_status.flags & (MOUSE_POSCHANGED |
2366262417Shselasky		    MOUSE_STDBUTTONSCHANGED)) {
2367199086Srpaulo
2368262417Shselasky			atp_stroke_t *strokep;
2369262417Shselasky			u_int8_t n_movements = 0;
2370262417Shselasky			int dx = 0;
2371262417Shselasky			int dy = 0;
2372199086Srpaulo
2373262417Shselasky			TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
2374262417Shselasky				dx += strokep->movement_dx;
2375262417Shselasky				dy += strokep->movement_dy;
2376262417Shselasky				if (strokep->movement_dx ||
2377262417Shselasky				    strokep->movement_dy)
2378199086Srpaulo					n_movements++;
2379199086Srpaulo			}
2380199086Srpaulo
2381262417Shselasky			/* average movement if multiple strokes record motion.*/
2382262417Shselasky			if (n_movements > 1) {
2383262417Shselasky				dx /= (int)n_movements;
2384262417Shselasky				dy /= (int)n_movements;
2385262417Shselasky			}
2386262417Shselasky
2387199086Srpaulo			sc->sc_status.dx += dx;
2388199086Srpaulo			sc->sc_status.dy += dy;
2389262417Shselasky			atp_add_to_queue(sc, dx, -dy, 0, sc->sc_status.button);
2390199086Srpaulo		}
2391199086Srpaulo
2392199086Srpaulo	case USB_ST_SETUP:
2393199086Srpaulo	tr_setup:
2394199086Srpaulo		/* check if we can put more data into the FIFO */
2395262417Shselasky		if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
2396199086Srpaulo			usbd_xfer_set_frame_len(xfer, 0,
2397262417Shselasky			    sc->sc_expected_sensor_data_len);
2398199086Srpaulo			usbd_transfer_submit(xfer);
2399199086Srpaulo		}
2400199086Srpaulo		break;
2401199086Srpaulo
2402199086Srpaulo	default:                        /* Error */
2403199086Srpaulo		if (error != USB_ERR_CANCELLED) {
2404199086Srpaulo			/* try clear stall first */
2405199086Srpaulo			usbd_xfer_set_stall(xfer);
2406199086Srpaulo			goto tr_setup;
2407199086Srpaulo		}
2408199086Srpaulo		break;
2409199086Srpaulo	}
2410199086Srpaulo}
2411199086Srpaulo
2412199086Srpaulostatic void
2413199086Srpauloatp_start_read(struct usb_fifo *fifo)
2414199086Srpaulo{
2415199086Srpaulo	struct atp_softc *sc = usb_fifo_softc(fifo);
2416199086Srpaulo	int rate;
2417199086Srpaulo
2418199086Srpaulo	/* Check if we should override the default polling interval */
2419199086Srpaulo	rate = sc->sc_pollrate;
2420199086Srpaulo	/* Range check rate */
2421199086Srpaulo	if (rate > 1000)
2422199086Srpaulo		rate = 1000;
2423199086Srpaulo	/* Check for set rate */
2424199086Srpaulo	if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) {
2425199086Srpaulo		/* Stop current transfer, if any */
2426199086Srpaulo		usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
2427199086Srpaulo		/* Set new interval */
2428199086Srpaulo		usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate);
2429199086Srpaulo		/* Only set pollrate once */
2430199086Srpaulo		sc->sc_pollrate = 0;
2431199086Srpaulo	}
2432199086Srpaulo
2433199086Srpaulo	usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]);
2434199086Srpaulo}
2435199086Srpaulo
2436199086Srpaulostatic void
2437199086Srpauloatp_stop_read(struct usb_fifo *fifo)
2438199086Srpaulo{
2439199086Srpaulo	struct atp_softc *sc = usb_fifo_softc(fifo);
2440199086Srpaulo	usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
2441199086Srpaulo}
2442199086Srpaulo
2443199086Srpaulostatic int
2444199086Srpauloatp_open(struct usb_fifo *fifo, int fflags)
2445199086Srpaulo{
2446262417Shselasky	struct atp_softc *sc = usb_fifo_softc(fifo);
2447199086Srpaulo
2448262417Shselasky	/* check for duplicate open, should not happen */
2449262417Shselasky	if (sc->sc_fflags & fflags)
2450262417Shselasky		return (EBUSY);
2451262417Shselasky
2452262417Shselasky	/* check for first open */
2453262417Shselasky	if (sc->sc_fflags == 0) {
2454199086Srpaulo		int rc;
2455262417Shselasky		if ((rc = atp_enable(sc)) != 0)
2456262417Shselasky			return (rc);
2457262417Shselasky	}
2458199086Srpaulo
2459262417Shselasky	if (fflags & FREAD) {
2460199086Srpaulo		if (usb_fifo_alloc_buffer(fifo,
2461262417Shselasky		    ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) {
2462199086Srpaulo			return (ENOMEM);
2463199086Srpaulo		}
2464199086Srpaulo	}
2465199086Srpaulo
2466262417Shselasky	sc->sc_fflags |= (fflags & (FREAD | FWRITE));
2467199086Srpaulo	return (0);
2468199086Srpaulo}
2469199086Srpaulo
2470199086Srpaulostatic void
2471199086Srpauloatp_close(struct usb_fifo *fifo, int fflags)
2472199086Srpaulo{
2473262417Shselasky	struct atp_softc *sc = usb_fifo_softc(fifo);
2474262417Shselasky	if (fflags & FREAD)
2475262417Shselasky		usb_fifo_free_buffer(fifo);
2476199086Srpaulo
2477262417Shselasky	sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
2478262417Shselasky	if (sc->sc_fflags == 0) {
2479199086Srpaulo		atp_disable(sc);
2480199086Srpaulo	}
2481199086Srpaulo}
2482199086Srpaulo
2483262417Shselaskystatic int
2484199086Srpauloatp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
2485199086Srpaulo{
2486199086Srpaulo	struct atp_softc *sc = usb_fifo_softc(fifo);
2487199086Srpaulo	mousemode_t mode;
2488199086Srpaulo	int error = 0;
2489199086Srpaulo
2490199086Srpaulo	mtx_lock(&sc->sc_mutex);
2491199086Srpaulo
2492199086Srpaulo	switch(cmd) {
2493199086Srpaulo	case MOUSE_GETHWINFO:
2494199086Srpaulo		*(mousehw_t *)addr = sc->sc_hw;
2495199086Srpaulo		break;
2496199086Srpaulo	case MOUSE_GETMODE:
2497199086Srpaulo		*(mousemode_t *)addr = sc->sc_mode;
2498199086Srpaulo		break;
2499199086Srpaulo	case MOUSE_SETMODE:
2500199086Srpaulo		mode = *(mousemode_t *)addr;
2501199086Srpaulo
2502199086Srpaulo		if (mode.level == -1)
2503199086Srpaulo			/* Don't change the current setting */
2504199086Srpaulo			;
2505199086Srpaulo		else if ((mode.level < 0) || (mode.level > 1)) {
2506199086Srpaulo			error = EINVAL;
2507262417Shselasky			break;
2508199086Srpaulo		}
2509199086Srpaulo		sc->sc_mode.level = mode.level;
2510199086Srpaulo		sc->sc_pollrate   = mode.rate;
2511199086Srpaulo		sc->sc_hw.buttons = 3;
2512199086Srpaulo
2513199086Srpaulo		if (sc->sc_mode.level == 0) {
2514262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
2515262417Shselasky			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
2516199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
2517199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
2518199086Srpaulo		} else if (sc->sc_mode.level == 1) {
2519262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
2520262417Shselasky			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
2521199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
2522199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
2523199086Srpaulo		}
2524199086Srpaulo		atp_reset_buf(sc);
2525199086Srpaulo		break;
2526199086Srpaulo	case MOUSE_GETLEVEL:
2527199086Srpaulo		*(int *)addr = sc->sc_mode.level;
2528199086Srpaulo		break;
2529199086Srpaulo	case MOUSE_SETLEVEL:
2530262417Shselasky		if ((*(int *)addr < 0) || (*(int *)addr > 1)) {
2531199086Srpaulo			error = EINVAL;
2532262417Shselasky			break;
2533199086Srpaulo		}
2534199086Srpaulo		sc->sc_mode.level = *(int *)addr;
2535199086Srpaulo		sc->sc_hw.buttons = 3;
2536199086Srpaulo
2537199086Srpaulo		if (sc->sc_mode.level == 0) {
2538262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
2539262417Shselasky			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
2540199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
2541199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
2542199086Srpaulo		} else if (sc->sc_mode.level == 1) {
2543262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
2544262417Shselasky			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
2545199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
2546199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
2547199086Srpaulo		}
2548199086Srpaulo		atp_reset_buf(sc);
2549199086Srpaulo		break;
2550199086Srpaulo	case MOUSE_GETSTATUS: {
2551199086Srpaulo		mousestatus_t *status = (mousestatus_t *)addr;
2552199086Srpaulo
2553199086Srpaulo		*status = sc->sc_status;
2554199086Srpaulo		sc->sc_status.obutton = sc->sc_status.button;
2555199086Srpaulo		sc->sc_status.button  = 0;
2556262417Shselasky		sc->sc_status.dx      = 0;
2557262417Shselasky		sc->sc_status.dy      = 0;
2558262417Shselasky		sc->sc_status.dz      = 0;
2559199086Srpaulo
2560199086Srpaulo		if (status->dx || status->dy || status->dz)
2561199086Srpaulo			status->flags |= MOUSE_POSCHANGED;
2562199086Srpaulo		if (status->button != status->obutton)
2563199086Srpaulo			status->flags |= MOUSE_BUTTONSCHANGED;
2564199086Srpaulo		break;
2565199086Srpaulo	}
2566262417Shselasky
2567199086Srpaulo	default:
2568199086Srpaulo		error = ENOTTY;
2569262417Shselasky		break;
2570199086Srpaulo	}
2571199086Srpaulo
2572199086Srpaulo	mtx_unlock(&sc->sc_mutex);
2573199086Srpaulo	return (error);
2574199086Srpaulo}
2575199086Srpaulo
2576199086Srpaulostatic int
2577199086Srpauloatp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS)
2578199086Srpaulo{
2579199086Srpaulo	int error;
2580199086Srpaulo	u_int tmp;
2581199086Srpaulo
2582199086Srpaulo	tmp = atp_mickeys_scale_factor;
2583199086Srpaulo	error = sysctl_handle_int(oidp, &tmp, 0, req);
2584199086Srpaulo	if (error != 0 || req->newptr == NULL)
2585199086Srpaulo		return (error);
2586199086Srpaulo
2587262417Shselasky	if (tmp == atp_mickeys_scale_factor)
2588199086Srpaulo		return (0);     /* no change */
2589262417Shselasky	if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR)))
2590262417Shselasky		return (EINVAL);
2591199086Srpaulo
2592199086Srpaulo	atp_mickeys_scale_factor = tmp;
2593199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n",
2594199086Srpaulo	    ATP_DRIVER_NAME, tmp);
2595199086Srpaulo
2596199086Srpaulo	return (0);
2597199086Srpaulo}
2598199086Srpaulo
2599262417Shselaskystatic devclass_t atp_devclass;
2600262417Shselasky
2601199086Srpaulostatic device_method_t atp_methods[] = {
2602199086Srpaulo	DEVMETHOD(device_probe,  atp_probe),
2603199086Srpaulo	DEVMETHOD(device_attach, atp_attach),
2604199086Srpaulo	DEVMETHOD(device_detach, atp_detach),
2605262417Shselasky
2606262417Shselasky	DEVMETHOD_END
2607199086Srpaulo};
2608199086Srpaulo
2609199086Srpaulostatic driver_t atp_driver = {
2610262417Shselasky	.name    = ATP_DRIVER_NAME,
2611233774Shselasky	.methods = atp_methods,
2612262417Shselasky	.size    = sizeof(struct atp_softc)
2613199086Srpaulo};
2614199086Srpaulo
2615199086SrpauloDRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0);
2616199086SrpauloMODULE_DEPEND(atp, usb, 1, 1, 1);
2617212122SthompsaMODULE_VERSION(atp, 1);
2618