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$");
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 */
181276701ShselaskySYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RWTUN,
182199086Srpaulo    &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
183207077Sthompsa#endif /* USB_DEBUG */
184199086Srpaulo
185199086Srpaulostatic u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
186276701ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RWTUN,
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;
190276701ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RWTUN,
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);
197276701ShselaskySYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RWTUN,
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;
202276701ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RWTUN,
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;
207276701ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RWTUN,
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;
216276701ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RWTUN,
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;
225276701ShselaskySYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RWTUN,
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
512262478Shselasky/* TODO: STRUCT_USB_HOST_ID */
513262478Shselaskystatic const struct usb_device_id wsp_devs[] = {
514262417Shselasky	/* MacbookAir1.1 */
515262417Shselasky	ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
516262417Shselasky	ATP_DEV(APPLE, WELLSPRING_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
517262417Shselasky	ATP_DEV(APPLE, WELLSPRING_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
518262417Shselasky
519262417Shselasky	/* MacbookProPenryn, aka wellspring2 */
520262417Shselasky	ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
521262417Shselasky	ATP_DEV(APPLE, WELLSPRING2_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
522262417Shselasky	ATP_DEV(APPLE, WELLSPRING2_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
523262417Shselasky
524262417Shselasky	/* Macbook5,1 (unibody), aka wellspring3 */
525262417Shselasky	ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
526262417Shselasky	ATP_DEV(APPLE, WELLSPRING3_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
527262417Shselasky	ATP_DEV(APPLE, WELLSPRING3_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
528262417Shselasky
529262417Shselasky	/* MacbookAir3,2 (unibody), aka wellspring4 */
530262417Shselasky	ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
531262417Shselasky	ATP_DEV(APPLE, WELLSPRING4_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
532262417Shselasky	ATP_DEV(APPLE, WELLSPRING4_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
533262417Shselasky
534262417Shselasky	/* MacbookAir3,1 (unibody), aka wellspring4 */
535262417Shselasky	ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
536262417Shselasky	ATP_DEV(APPLE, WELLSPRING4A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
537262417Shselasky	ATP_DEV(APPLE, WELLSPRING4A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
538262417Shselasky
539262417Shselasky	/* Macbook8 (unibody, March 2011) */
540262417Shselasky	ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
541262417Shselasky	ATP_DEV(APPLE, WELLSPRING5_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
542262417Shselasky	ATP_DEV(APPLE, WELLSPRING5_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
543262417Shselasky
544262417Shselasky	/* MacbookAir4,1 (unibody, July 2011) */
545262417Shselasky	ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
546262417Shselasky	ATP_DEV(APPLE, WELLSPRING6A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
547262417Shselasky	ATP_DEV(APPLE, WELLSPRING6A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
548262417Shselasky
549262417Shselasky	/* MacbookAir4,2 (unibody, July 2011) */
550262417Shselasky	ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
551262417Shselasky	ATP_DEV(APPLE, WELLSPRING6_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
552262417Shselasky	ATP_DEV(APPLE, WELLSPRING6_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
553262417Shselasky
554262417Shselasky	/* Macbook8,2 (unibody) */
555262417Shselasky	ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
556262417Shselasky	ATP_DEV(APPLE, WELLSPRING5A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
557262417Shselasky	ATP_DEV(APPLE, WELLSPRING5A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
558262417Shselasky
559262417Shselasky	/* MacbookPro10,1 (unibody, June 2012) */
560262417Shselasky	/* MacbookPro11,? (unibody, June 2013) */
561262417Shselasky	ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
562262417Shselasky	ATP_DEV(APPLE, WELLSPRING7_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
563262417Shselasky	ATP_DEV(APPLE, WELLSPRING7_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
564262417Shselasky
565262417Shselasky	/* MacbookPro10,2 (unibody, October 2012) */
566262417Shselasky	ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
567262417Shselasky	ATP_DEV(APPLE, WELLSPRING7A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
568262417Shselasky	ATP_DEV(APPLE, WELLSPRING7A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
569262417Shselasky
570262417Shselasky	/* MacbookAir6,2 (unibody, June 2013) */
571262417Shselasky	ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
572262417Shselasky	ATP_DEV(APPLE, WELLSPRING8_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
573262417Shselasky	ATP_DEV(APPLE, WELLSPRING8_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
574199086Srpaulo};
575199086Srpaulo
576199086Srpaulotypedef enum atp_stroke_type {
577199086Srpaulo	ATP_STROKE_TOUCH,
578199086Srpaulo	ATP_STROKE_SLIDE,
579199086Srpaulo} atp_stroke_type;
580199086Srpaulo
581262417Shselaskytypedef enum atp_axis {
582262417Shselasky	X = 0,
583262417Shselasky	Y = 1,
584262417Shselasky	NUM_AXES
585262417Shselasky} atp_axis;
586199086Srpaulo
587262417Shselasky#define ATP_FIFO_BUF_SIZE        8 /* bytes */
588262417Shselasky#define ATP_FIFO_QUEUE_MAXLEN   50 /* units */
589262417Shselasky
590262417Shselaskyenum {
591262417Shselasky	ATP_INTR_DT,
592262417Shselasky	ATP_RESET,
593262417Shselasky	ATP_N_TRANSFER,
594262417Shselasky};
595262417Shselasky
596262417Shselaskytypedef struct fg_stroke_component {
597199086Srpaulo	/* Fields encapsulating the pressure-span. */
598199086Srpaulo	u_int loc;              /* location (scaled) */
599199086Srpaulo	u_int cum_pressure;     /* cumulative compression */
600199086Srpaulo	u_int max_cum_pressure; /* max cumulative compression */
601199086Srpaulo	boolean_t matched; /*to track components as they match against pspans.*/
602199086Srpaulo
603199086Srpaulo	int   delta_mickeys;    /* change in location (un-smoothened movement)*/
604262417Shselasky} fg_stroke_component_t;
605199086Srpaulo
606199086Srpaulo/*
607199086Srpaulo * The following structure captures a finger contact with the
608199086Srpaulo * touchpad. A stroke comprises two p-span components and some state.
609199086Srpaulo */
610199086Srpaulotypedef struct atp_stroke {
611262417Shselasky	TAILQ_ENTRY(atp_stroke) entry;
612199086Srpaulo
613262417Shselasky	atp_stroke_type type;
614262417Shselasky	uint32_t        flags; /* the state of this stroke */
615262417Shselasky#define ATSF_ZOMBIE 0x1
616262417Shselasky	boolean_t       matched;          /* to track match against fingers.*/
617199086Srpaulo
618262417Shselasky	struct timeval  ctime; /* create time; for coincident siblings. */
619199086Srpaulo
620262417Shselasky	/*
621262417Shselasky	 * Unit: interrupts; we maintain this value in
622262417Shselasky	 * addition to 'ctime' in order to avoid the
623262417Shselasky	 * expensive call to microtime() at every
624262417Shselasky	 * interrupt.
625262417Shselasky	 */
626262417Shselasky	uint32_t age;
627199086Srpaulo
628262417Shselasky	/* Location */
629262417Shselasky	int x;
630262417Shselasky	int y;
631199086Srpaulo
632262417Shselasky	/* Fields containing information about movement. */
633262417Shselasky	int   instantaneous_dx; /* curr. change in X location (un-smoothened) */
634262417Shselasky	int   instantaneous_dy; /* curr. change in Y location (un-smoothened) */
635262417Shselasky	int   pending_dx;       /* cum. of pending short movements */
636262417Shselasky	int   pending_dy;       /* cum. of pending short movements */
637262417Shselasky	int   movement_dx;      /* interpreted smoothened movement */
638262417Shselasky	int   movement_dy;      /* interpreted smoothened movement */
639262417Shselasky	int   cum_movement_x;   /* cum. horizontal movement */
640262417Shselasky	int   cum_movement_y;   /* cum. vertical movement */
641262417Shselasky
642262417Shselasky	/*
643262417Shselasky	 * The following member is relevant only for fountain-geyser trackpads.
644262417Shselasky	 * For these, there is the need to track pressure-spans and cumulative
645262417Shselasky	 * pressures for stroke components.
646262417Shselasky	 */
647262417Shselasky	fg_stroke_component_t components[NUM_AXES];
648262417Shselasky} atp_stroke_t;
649262417Shselasky
650262417Shselaskystruct atp_softc; /* forward declaration */
651262417Shselaskytypedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len);
652262417Shselasky
653199086Srpaulostruct atp_softc {
654262417Shselasky	device_t            sc_dev;
655262417Shselasky	struct usb_device  *sc_usb_device;
656262417Shselasky	struct mtx          sc_mutex; /* for synchronization */
657262417Shselasky	struct usb_fifo_sc  sc_fifo;
658199086Srpaulo
659262417Shselasky#define	MODE_LENGTH 8
660262417Shselasky	char                sc_mode_bytes[MODE_LENGTH]; /* device mode */
661199086Srpaulo
662262417Shselasky	trackpad_family_t   sc_family;
663262417Shselasky	const void         *sc_params; /* device configuration */
664262417Shselasky	sensor_data_interpreter_t sensor_data_interpreter;
665199086Srpaulo
666262417Shselasky	mousehw_t           sc_hw;
667262417Shselasky	mousemode_t         sc_mode;
668262417Shselasky	mousestatus_t       sc_status;
669199086Srpaulo
670262417Shselasky	u_int               sc_state;
671262417Shselasky#define ATP_ENABLED          0x01
672262417Shselasky#define ATP_ZOMBIES_EXIST    0x02
673262417Shselasky#define ATP_DOUBLE_TAP_DRAG  0x04
674262417Shselasky#define ATP_VALID            0x08
675199086Srpaulo
676262417Shselasky	struct usb_xfer    *sc_xfer[ATP_N_TRANSFER];
677199086Srpaulo
678262417Shselasky	u_int               sc_pollrate;
679262417Shselasky	int                 sc_fflags;
680199086Srpaulo
681262417Shselasky	atp_stroke_t        sc_strokes_data[ATP_MAX_STROKES];
682262417Shselasky	TAILQ_HEAD(,atp_stroke) sc_stroke_free;
683262417Shselasky	TAILQ_HEAD(,atp_stroke) sc_stroke_used;
684262417Shselasky	u_int               sc_n_strokes;
685262417Shselasky
686262417Shselasky	struct callout	    sc_callout;
687262417Shselasky
688262417Shselasky	/*
689262417Shselasky	 * button status. Set to non-zero if the mouse-button is physically
690262417Shselasky	 * pressed. This state variable is exposed through softc to allow
691262417Shselasky	 * reap_sibling_zombies to avoid registering taps while the trackpad
692262417Shselasky	 * button is pressed.
693262417Shselasky         */
694262417Shselasky	uint8_t             sc_ibtn;
695262417Shselasky
696262417Shselasky	/*
697262417Shselasky	 * Time when touch zombies were last reaped; useful for detecting
698262417Shselasky	 * double-touch-n-drag.
699262417Shselasky	 */
700262417Shselasky	struct timeval      sc_touch_reap_time;
701262417Shselasky
702262417Shselasky	u_int	            sc_idlecount;
703262417Shselasky
704262417Shselasky	/* Regarding the data transferred from t-pad in USB INTR packets. */
705262417Shselasky	u_int   sc_expected_sensor_data_len;
706262417Shselasky	uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4);
707262417Shselasky
708262417Shselasky	int      sc_cur_x[FG_MAX_XSENSORS];      /* current sensor readings */
709262417Shselasky	int      sc_cur_y[FG_MAX_YSENSORS];
710262417Shselasky	int      sc_base_x[FG_MAX_XSENSORS];     /* base sensor readings */
711262417Shselasky	int      sc_base_y[FG_MAX_YSENSORS];
712262417Shselasky	int      sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */
713262417Shselasky	int      sc_pressure_y[FG_MAX_YSENSORS];
714262417Shselasky	fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS];
715262417Shselasky	fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS];
716199086Srpaulo};
717199086Srpaulo
718199086Srpaulo/*
719262417Shselasky * The last byte of the fountain-geyser sensor data contains status bits; the
720199086Srpaulo * following values define the meanings of these bits.
721262417Shselasky * (only Geyser 3/4)
722199086Srpaulo */
723262417Shselaskyenum geyser34_status_bits {
724262417Shselasky	FG_STATUS_BUTTON      = (uint8_t)0x01, /* The button was pressed */
725262417Shselasky	FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
726199086Srpaulo};
727199086Srpaulo
728199086Srpaulotypedef enum interface_mode {
729262417Shselasky	RAW_SENSOR_MODE = (uint8_t)0x01,
730199086Srpaulo	HID_MODE        = (uint8_t)0x08
731199086Srpaulo} interface_mode;
732199086Srpaulo
733262417Shselasky
734199086Srpaulo/*
735199086Srpaulo * function prototypes
736199086Srpaulo */
737199086Srpaulostatic usb_fifo_cmd_t   atp_start_read;
738199086Srpaulostatic usb_fifo_cmd_t   atp_stop_read;
739199086Srpaulostatic usb_fifo_open_t  atp_open;
740199086Srpaulostatic usb_fifo_close_t atp_close;
741199086Srpaulostatic usb_fifo_ioctl_t atp_ioctl;
742199086Srpaulo
743199086Srpaulostatic struct usb_fifo_methods atp_fifo_methods = {
744199086Srpaulo	.f_open       = &atp_open,
745199086Srpaulo	.f_close      = &atp_close,
746199086Srpaulo	.f_ioctl      = &atp_ioctl,
747199086Srpaulo	.f_start_read = &atp_start_read,
748199086Srpaulo	.f_stop_read  = &atp_stop_read,
749199086Srpaulo	.basename[0]  = ATP_DRIVER_NAME,
750199086Srpaulo};
751199086Srpaulo
752199086Srpaulo/* device initialization and shutdown */
753262417Shselaskystatic usb_error_t   atp_set_device_mode(struct atp_softc *, interface_mode);
754262417Shselaskystatic void	     atp_reset_callback(struct usb_xfer *, usb_error_t);
755262417Shselaskystatic int	     atp_enable(struct atp_softc *);
756262417Shselaskystatic void	     atp_disable(struct atp_softc *);
757199086Srpaulo
758199086Srpaulo/* sensor interpretation */
759262417Shselaskystatic void	     fg_interpret_sensor_data(struct atp_softc *, u_int);
760262417Shselaskystatic void	     fg_extract_sensor_data(const int8_t *, u_int, atp_axis,
761262417Shselasky    int *, enum fountain_geyser_trackpad_type);
762262417Shselaskystatic void	     fg_get_pressures(int *, const int *, const int *, int);
763262417Shselaskystatic void	     fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *);
764262417Shselaskystatic void	     wsp_interpret_sensor_data(struct atp_softc *, u_int);
765199086Srpaulo
766199086Srpaulo/* movement detection */
767262417Shselaskystatic boolean_t     fg_match_stroke_component(fg_stroke_component_t *,
768262417Shselasky    const fg_pspan *, atp_stroke_type);
769262417Shselaskystatic void	     fg_match_strokes_against_pspans(struct atp_softc *,
770262417Shselasky    atp_axis, fg_pspan *, u_int, u_int);
771262417Shselaskystatic boolean_t     wsp_match_strokes_against_fingers(struct atp_softc *,
772262417Shselasky    wsp_finger_t *, u_int);
773262417Shselaskystatic boolean_t     fg_update_strokes(struct atp_softc *, fg_pspan *, u_int,
774262417Shselasky    fg_pspan *, u_int);
775262417Shselaskystatic boolean_t     wsp_update_strokes(struct atp_softc *,
776262417Shselasky    wsp_finger_t [WSP_MAX_FINGERS], u_int);
777262417Shselaskystatic void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *);
778262417Shselaskystatic void	     fg_add_new_strokes(struct atp_softc *, fg_pspan *,
779262417Shselasky    u_int, fg_pspan *, u_int);
780262417Shselaskystatic void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *);
781262417Shselaskystatic void	     atp_advance_stroke_state(struct atp_softc *,
782262417Shselasky    atp_stroke_t *, boolean_t *);
783262417Shselaskystatic boolean_t atp_stroke_has_small_movement(const atp_stroke_t *);
784262417Shselaskystatic void	     atp_update_pending_mickeys(atp_stroke_t *);
785262417Shselaskystatic boolean_t     atp_compute_stroke_movement(atp_stroke_t *);
786262417Shselaskystatic void	     atp_terminate_stroke(struct atp_softc *, atp_stroke_t *);
787199086Srpaulo
788199086Srpaulo/* tap detection */
789262417Shselaskystatic boolean_t atp_is_horizontal_scroll(const atp_stroke_t *);
790262417Shselaskystatic boolean_t atp_is_vertical_scroll(const atp_stroke_t *);
791262417Shselaskystatic void	     atp_reap_sibling_zombies(void *);
792262417Shselaskystatic void	     atp_convert_to_slide(struct atp_softc *, atp_stroke_t *);
793199086Srpaulo
794199086Srpaulo/* updating fifo */
795262417Shselaskystatic void	     atp_reset_buf(struct atp_softc *);
796262417Shselaskystatic void	     atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t);
797199086Srpaulo
798262417Shselasky/* Device methods. */
799262417Shselaskystatic device_probe_t  atp_probe;
800262417Shselaskystatic device_attach_t atp_attach;
801262417Shselaskystatic device_detach_t atp_detach;
802262417Shselaskystatic usb_callback_t  atp_intr;
803262417Shselasky
804262417Shselaskystatic const struct usb_config atp_xfer_config[ATP_N_TRANSFER] = {
805262417Shselasky	[ATP_INTR_DT] = {
806262417Shselasky		.type      = UE_INTERRUPT,
807262417Shselasky		.endpoint  = UE_ADDR_ANY,
808262417Shselasky		.direction = UE_DIR_IN,
809262417Shselasky		.flags = {
810262417Shselasky			.pipe_bof = 1, /* block pipe on failure */
811262417Shselasky			.short_xfer_ok = 1,
812262417Shselasky		},
813262417Shselasky		.bufsize   = ATP_SENSOR_DATA_BUF_MAX,
814262417Shselasky		.callback  = &atp_intr,
815262417Shselasky	},
816262417Shselasky	[ATP_RESET] = {
817262417Shselasky		.type      = UE_CONTROL,
818262417Shselasky		.endpoint  = 0, /* Control pipe */
819262417Shselasky		.direction = UE_DIR_ANY,
820262417Shselasky		.bufsize   = sizeof(struct usb_device_request) + MODE_LENGTH,
821262417Shselasky		.callback  = &atp_reset_callback,
822262417Shselasky		.interval  = 0,  /* no pre-delay */
823262417Shselasky	},
824262417Shselasky};
825262417Shselasky
826262417Shselaskystatic atp_stroke_t *
827262417Shselaskyatp_alloc_stroke(struct atp_softc *sc)
828199086Srpaulo{
829262417Shselasky	atp_stroke_t *pstroke;
830199086Srpaulo
831262417Shselasky	pstroke = TAILQ_FIRST(&sc->sc_stroke_free);
832262417Shselasky	if (pstroke == NULL)
833262417Shselasky		goto done;
834199086Srpaulo
835262417Shselasky	TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry);
836262417Shselasky	memset(pstroke, 0, sizeof(*pstroke));
837262417Shselasky	TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry);
838262417Shselasky
839262417Shselasky	sc->sc_n_strokes++;
840262417Shselaskydone:
841262417Shselasky	return (pstroke);
842199086Srpaulo}
843199086Srpaulo
844262417Shselaskystatic void
845262417Shselaskyatp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke)
846199086Srpaulo{
847262417Shselasky	if (pstroke == NULL)
848262417Shselasky		return;
849199086Srpaulo
850262417Shselasky	sc->sc_n_strokes--;
851199086Srpaulo
852262417Shselasky	TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry);
853262417Shselasky	TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry);
854262417Shselasky}
855199086Srpaulo
856262417Shselaskystatic void
857262417Shselaskyatp_init_stroke_pool(struct atp_softc *sc)
858262417Shselasky{
859262417Shselasky	u_int x;
860199086Srpaulo
861262417Shselasky	TAILQ_INIT(&sc->sc_stroke_free);
862262417Shselasky	TAILQ_INIT(&sc->sc_stroke_used);
863262417Shselasky
864262417Shselasky	sc->sc_n_strokes = 0;
865262417Shselasky
866262417Shselasky	memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data));
867262417Shselasky
868262417Shselasky	for (x = 0; x != ATP_MAX_STROKES; x++) {
869262417Shselasky		TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x],
870262417Shselasky		    entry);
871262417Shselasky	}
872199086Srpaulo}
873199086Srpaulo
874262417Shselaskystatic usb_error_t
875262417Shselaskyatp_set_device_mode(struct atp_softc *sc, interface_mode newMode)
876262417Shselasky{
877262417Shselasky	uint8_t mode_value;
878262417Shselasky	usb_error_t err;
879262417Shselasky
880262417Shselasky	if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE))
881262417Shselasky		return (USB_ERR_INVAL);
882262417Shselasky
883262417Shselasky	if ((newMode == RAW_SENSOR_MODE) &&
884262417Shselasky	    (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER))
885262417Shselasky		mode_value = (uint8_t)0x04;
886262417Shselasky	else
887262417Shselasky		mode_value = newMode;
888262417Shselasky
889262417Shselasky	err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */,
890262417Shselasky	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
891262417Shselasky	    0x03 /* type */, 0x00 /* id */);
892262417Shselasky	if (err != USB_ERR_NORMAL_COMPLETION) {
893262417Shselasky		DPRINTF("Failed to read device mode (%d)\n", err);
894262417Shselasky		return (err);
895262417Shselasky	}
896262417Shselasky
897262417Shselasky	if (sc->sc_mode_bytes[0] == mode_value)
898262417Shselasky		return (err);
899262417Shselasky
900262417Shselasky	/*
901262417Shselasky	 * XXX Need to wait at least 250ms for hardware to get
902262417Shselasky	 * ready. The device mode handling appears to be handled
903262417Shselasky	 * asynchronously and we should not issue these commands too
904262417Shselasky	 * quickly.
905262417Shselasky	 */
906262417Shselasky	pause("WHW", hz / 4);
907262417Shselasky
908262417Shselasky	sc->sc_mode_bytes[0] = mode_value;
909262417Shselasky	return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */,
910262417Shselasky	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
911262417Shselasky	    0x03 /* type */, 0x00 /* id */));
912262417Shselasky}
913262417Shselasky
914262417Shselaskystatic void
915199680Sthompsaatp_reset_callback(struct usb_xfer *xfer, usb_error_t error)
916199680Sthompsa{
917199680Sthompsa	usb_device_request_t   req;
918199680Sthompsa	struct usb_page_cache *pc;
919199680Sthompsa	struct atp_softc      *sc = usbd_xfer_softc(xfer);
920199680Sthompsa
921262417Shselasky	uint8_t mode_value;
922262417Shselasky	if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)
923262417Shselasky		mode_value = 0x04;
924262417Shselasky	else
925262417Shselasky		mode_value = RAW_SENSOR_MODE;
926262417Shselasky
927199680Sthompsa	switch (USB_GET_STATE(xfer)) {
928199680Sthompsa	case USB_ST_SETUP:
929262417Shselasky		sc->sc_mode_bytes[0] = mode_value;
930199680Sthompsa		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
931199680Sthompsa		req.bRequest = UR_SET_REPORT;
932199680Sthompsa		USETW2(req.wValue,
933199680Sthompsa		    (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
934199680Sthompsa		USETW(req.wIndex, 0);
935199680Sthompsa		USETW(req.wLength, MODE_LENGTH);
936199680Sthompsa
937199680Sthompsa		pc = usbd_xfer_get_frame(xfer, 0);
938199680Sthompsa		usbd_copy_in(pc, 0, &req, sizeof(req));
939199680Sthompsa		pc = usbd_xfer_get_frame(xfer, 1);
940199680Sthompsa		usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH);
941199680Sthompsa
942199680Sthompsa		usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
943199680Sthompsa		usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH);
944199680Sthompsa		usbd_xfer_set_frames(xfer, 2);
945199680Sthompsa		usbd_transfer_submit(xfer);
946199680Sthompsa		break;
947199680Sthompsa
948199680Sthompsa	case USB_ST_TRANSFERRED:
949199680Sthompsa	default:
950199680Sthompsa		break;
951199680Sthompsa	}
952199680Sthompsa}
953199680Sthompsa
954199086Srpaulostatic int
955199086Srpauloatp_enable(struct atp_softc *sc)
956199086Srpaulo{
957262417Shselasky	if (sc->sc_state & ATP_ENABLED)
958262417Shselasky		return (0);
959199086Srpaulo
960199086Srpaulo	/* reset status */
961199086Srpaulo	memset(&sc->sc_status, 0, sizeof(sc->sc_status));
962262417Shselasky
963262417Shselasky	atp_init_stroke_pool(sc);
964262417Shselasky
965199086Srpaulo	sc->sc_state |= ATP_ENABLED;
966199086Srpaulo
967199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
968199086Srpaulo	return (0);
969199086Srpaulo}
970199086Srpaulo
971199086Srpaulostatic void
972199086Srpauloatp_disable(struct atp_softc *sc)
973199086Srpaulo{
974199151Snwhitehorn	sc->sc_state &= ~(ATP_ENABLED | ATP_VALID);
975199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
976199086Srpaulo}
977199086Srpaulo
978262417Shselaskystatic void
979262417Shselaskyfg_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
980199086Srpaulo{
981262417Shselasky	u_int n_xpspans = 0;
982262417Shselasky	u_int n_ypspans = 0;
983262417Shselasky	uint8_t status_bits;
984199086Srpaulo
985262417Shselasky	const struct fg_dev_params *params =
986262417Shselasky	    (const struct fg_dev_params *)sc->sc_params;
987199086Srpaulo
988262417Shselasky	fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X,
989262417Shselasky	    sc->sc_cur_x, params->prot);
990262417Shselasky	fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y,
991262417Shselasky	    sc->sc_cur_y, params->prot);
992199086Srpaulo
993262417Shselasky	/*
994262417Shselasky	 * If this is the initial update (from an untouched
995262417Shselasky	 * pad), we should set the base values for the sensor
996262417Shselasky	 * data; deltas with respect to these base values can
997262417Shselasky	 * be used as pressure readings subsequently.
998262417Shselasky	 */
999262417Shselasky	status_bits = sc->sc_sensor_data[params->data_len - 1];
1000262417Shselasky	if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) ||
1001262417Shselasky	     (params->prot == FG_TRACKPAD_TYPE_GEYSER4))  &&
1002262417Shselasky	    ((sc->sc_state & ATP_VALID) == 0)) {
1003262417Shselasky		if (status_bits & FG_STATUS_BASE_UPDATE) {
1004262417Shselasky			memcpy(sc->sc_base_x, sc->sc_cur_x,
1005262417Shselasky			    params->n_xsensors * sizeof(*sc->sc_base_x));
1006262417Shselasky			memcpy(sc->sc_base_y, sc->sc_cur_y,
1007262417Shselasky			    params->n_ysensors * sizeof(*sc->sc_base_y));
1008262417Shselasky			sc->sc_state |= ATP_VALID;
1009262417Shselasky			return;
1010199086Srpaulo		}
1011199086Srpaulo	}
1012199086Srpaulo
1013262417Shselasky	/* Get pressure readings and detect p-spans for both axes. */
1014262417Shselasky	fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x,
1015262417Shselasky	    params->n_xsensors);
1016262417Shselasky	fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors,
1017262417Shselasky	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans);
1018262417Shselasky	fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y,
1019262417Shselasky	    params->n_ysensors);
1020262417Shselasky	fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors,
1021262417Shselasky	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans);
1022199086Srpaulo
1023262417Shselasky	/* Update strokes with new pspans to detect movements. */
1024262417Shselasky	if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans))
1025262417Shselasky		sc->sc_status.flags |= MOUSE_POSCHANGED;
1026199086Srpaulo
1027262417Shselasky	sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0;
1028262417Shselasky	sc->sc_status.button = sc->sc_ibtn;
1029199086Srpaulo
1030262417Shselasky	/*
1031262417Shselasky	 * The Fountain/Geyser device continues to trigger interrupts
1032262417Shselasky	 * at a fast rate even after touchpad activity has
1033262417Shselasky	 * stopped. Upon detecting that the device has remained idle
1034262417Shselasky	 * beyond a threshold, we reinitialize it to silence the
1035262417Shselasky	 * interrupts.
1036262417Shselasky	 */
1037262417Shselasky	if ((sc->sc_status.flags  == 0) && (sc->sc_n_strokes == 0)) {
1038262417Shselasky		sc->sc_idlecount++;
1039262417Shselasky		if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) {
1040262417Shselasky			/*
1041262417Shselasky			 * Use the last frame before we go idle for
1042262417Shselasky			 * calibration on pads which do not send
1043262417Shselasky			 * calibration frames.
1044262417Shselasky			 */
1045262417Shselasky			const struct fg_dev_params *params =
1046262417Shselasky			    (const struct fg_dev_params *)sc->sc_params;
1047199086Srpaulo
1048262417Shselasky			DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
1049199086Srpaulo
1050262417Shselasky			if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) {
1051262417Shselasky				memcpy(sc->sc_base_x, sc->sc_cur_x,
1052262417Shselasky				    params->n_xsensors * sizeof(*(sc->sc_base_x)));
1053262417Shselasky				memcpy(sc->sc_base_y, sc->sc_cur_y,
1054262417Shselasky				    params->n_ysensors * sizeof(*(sc->sc_base_y)));
1055262417Shselasky			}
1056199086Srpaulo
1057262417Shselasky			sc->sc_idlecount = 0;
1058262417Shselasky			usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
1059199086Srpaulo		}
1060262417Shselasky	} else {
1061262417Shselasky		sc->sc_idlecount = 0;
1062199086Srpaulo	}
1063199086Srpaulo}
1064199086Srpaulo
1065199086Srpaulo/*
1066199086Srpaulo * Interpret the data from the X and Y pressure sensors. This function
1067199086Srpaulo * is called separately for the X and Y sensor arrays. The data in the
1068199086Srpaulo * USB packet is laid out in the following manner:
1069199086Srpaulo *
1070199086Srpaulo * sensor_data:
1071199086Srpaulo *            --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4
1072199086Srpaulo *  indices:   0  1  2  3  4  5  6  7  8 ...  15  ... 20 21 22 23 24
1073199086Srpaulo *
1074199086Srpaulo * '--' (in the above) indicates that the value is unimportant.
1075199086Srpaulo *
1076199086Srpaulo * Information about the above layout was obtained from the
1077199086Srpaulo * implementation of the AppleTouch driver in Linux.
1078199086Srpaulo *
1079199086Srpaulo * parameters:
1080199086Srpaulo *   sensor_data
1081199086Srpaulo *       raw sensor data from the USB packet.
1082199086Srpaulo *   num
1083199086Srpaulo *       The number of elements in the array 'arr'.
1084199151Snwhitehorn *   axis
1085199151Snwhitehorn *       Axis of data to fetch
1086199086Srpaulo *   arr
1087199086Srpaulo *       The array to be initialized with the readings.
1088199151Snwhitehorn *   prot
1089199151Snwhitehorn *       The protocol to use to interpret the data
1090199086Srpaulo */
1091262417Shselaskystatic void
1092262417Shselaskyfg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
1093262417Shselasky    int	*arr, enum fountain_geyser_trackpad_type prot)
1094199086Srpaulo{
1095199086Srpaulo	u_int i;
1096199086Srpaulo	u_int di;   /* index into sensor data */
1097199086Srpaulo
1098199151Snwhitehorn	switch (prot) {
1099262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER1:
1100199151Snwhitehorn		/*
1101199151Snwhitehorn		 * For Geyser 1, the sensors are laid out in pairs
1102199151Snwhitehorn		 * every 5 bytes.
1103199151Snwhitehorn		 */
1104199151Snwhitehorn		for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) {
1105199151Snwhitehorn			arr[i] = sensor_data[di];
1106199151Snwhitehorn			arr[i+8] = sensor_data[di+2];
1107262417Shselasky			if ((axis == X) && (num > 16))
1108199151Snwhitehorn				arr[i+16] = sensor_data[di+40];
1109199151Snwhitehorn		}
1110199151Snwhitehorn
1111199151Snwhitehorn		break;
1112262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER2:
1113262417Shselasky		for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) {
1114262417Shselasky			arr[i++] = sensor_data[di++];
1115262417Shselasky			arr[i++] = sensor_data[di++];
1116262417Shselasky			di++;
1117262417Shselasky		}
1118262417Shselasky		break;
1119262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER3:
1120262417Shselasky	case FG_TRACKPAD_TYPE_GEYSER4:
1121199151Snwhitehorn		for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) {
1122199151Snwhitehorn			arr[i++] = sensor_data[di++];
1123199151Snwhitehorn			arr[i++] = sensor_data[di++];
1124199151Snwhitehorn			di++;
1125199151Snwhitehorn		}
1126199151Snwhitehorn		break;
1127262417Shselasky	default:
1128262417Shselasky		break;
1129199086Srpaulo	}
1130199086Srpaulo}
1131199086Srpaulo
1132262417Shselaskystatic void
1133262417Shselaskyfg_get_pressures(int *p, const int *cur, const int *base, int n)
1134199086Srpaulo{
1135199086Srpaulo	int i;
1136199086Srpaulo
1137199086Srpaulo	for (i = 0; i < n; i++) {
1138199086Srpaulo		p[i] = cur[i] - base[i];
1139199086Srpaulo		if (p[i] > 127)
1140199086Srpaulo			p[i] -= 256;
1141199086Srpaulo		if (p[i] < -127)
1142199086Srpaulo			p[i] += 256;
1143199086Srpaulo		if (p[i] < 0)
1144199086Srpaulo			p[i] = 0;
1145199086Srpaulo
1146199086Srpaulo		/*
1147199086Srpaulo		 * Shave off pressures below the noise-pressure
1148199086Srpaulo		 * threshold; this will reduce the contribution from
1149199086Srpaulo		 * lower pressure readings.
1150199086Srpaulo		 */
1151262417Shselasky		if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD)
1152199086Srpaulo			p[i] = 0; /* filter away noise */
1153199086Srpaulo		else
1154262417Shselasky			p[i] -= FG_SENSOR_NOISE_THRESHOLD;
1155199086Srpaulo	}
1156199086Srpaulo}
1157199086Srpaulo
1158199086Srpaulostatic void
1159262417Shselaskyfg_detect_pspans(int *p, u_int num_sensors,
1160262417Shselasky    u_int      max_spans, /* max # of pspans permitted */
1161262417Shselasky    fg_pspan  *spans,     /* finger spans */
1162262417Shselasky    u_int     *nspans_p)  /* num spans detected */
1163199086Srpaulo{
1164199086Srpaulo	u_int i;
1165199086Srpaulo	int   maxp;             /* max pressure seen within a span */
1166199086Srpaulo	u_int num_spans = 0;
1167199086Srpaulo
1168262417Shselasky	enum fg_pspan_state {
1169199086Srpaulo		ATP_PSPAN_INACTIVE,
1170199086Srpaulo		ATP_PSPAN_INCREASING,
1171199086Srpaulo		ATP_PSPAN_DECREASING,
1172199086Srpaulo	} state; /* state of the pressure span */
1173199086Srpaulo
1174199086Srpaulo	/*
1175199086Srpaulo	 * The following is a simple state machine to track
1176199086Srpaulo	 * the phase of the pressure span.
1177199086Srpaulo	 */
1178262417Shselasky	memset(spans, 0, max_spans * sizeof(fg_pspan));
1179199086Srpaulo	maxp = 0;
1180199086Srpaulo	state = ATP_PSPAN_INACTIVE;
1181199086Srpaulo	for (i = 0; i < num_sensors; i++) {
1182199086Srpaulo		if (num_spans >= max_spans)
1183199086Srpaulo			break;
1184199086Srpaulo
1185199086Srpaulo		if (p[i] == 0) {
1186199086Srpaulo			if (state == ATP_PSPAN_INACTIVE) {
1187199086Srpaulo				/*
1188199086Srpaulo				 * There is no pressure information for this
1189199086Srpaulo				 * sensor, and we aren't tracking a finger.
1190199086Srpaulo				 */
1191199086Srpaulo				continue;
1192199086Srpaulo			} else {
1193199086Srpaulo				state = ATP_PSPAN_INACTIVE;
1194199086Srpaulo				maxp = 0;
1195199086Srpaulo				num_spans++;
1196199086Srpaulo			}
1197199086Srpaulo		} else {
1198199086Srpaulo			switch (state) {
1199199086Srpaulo			case ATP_PSPAN_INACTIVE:
1200199086Srpaulo				state = ATP_PSPAN_INCREASING;
1201199086Srpaulo				maxp  = p[i];
1202199086Srpaulo				break;
1203199086Srpaulo
1204199086Srpaulo			case ATP_PSPAN_INCREASING:
1205199086Srpaulo				if (p[i] > maxp)
1206199086Srpaulo					maxp = p[i];
1207199086Srpaulo				else if (p[i] <= (maxp >> 1))
1208199086Srpaulo					state = ATP_PSPAN_DECREASING;
1209199086Srpaulo				break;
1210199086Srpaulo
1211199086Srpaulo			case ATP_PSPAN_DECREASING:
1212199086Srpaulo				if (p[i] > p[i - 1]) {
1213199086Srpaulo					/*
1214199086Srpaulo					 * This is the beginning of
1215199086Srpaulo					 * another span; change state
1216199086Srpaulo					 * to give the appearance that
1217199086Srpaulo					 * we're starting from an
1218199086Srpaulo					 * inactive span, and then
1219199086Srpaulo					 * re-process this reading in
1220199086Srpaulo					 * the next iteration.
1221199086Srpaulo					 */
1222199086Srpaulo					num_spans++;
1223199086Srpaulo					state = ATP_PSPAN_INACTIVE;
1224199086Srpaulo					maxp  = 0;
1225199086Srpaulo					i--;
1226199086Srpaulo					continue;
1227199086Srpaulo				}
1228199086Srpaulo				break;
1229199086Srpaulo			}
1230199086Srpaulo
1231199086Srpaulo			/* Update the finger span with this reading. */
1232199086Srpaulo			spans[num_spans].width++;
1233199086Srpaulo			spans[num_spans].cum += p[i];
1234199086Srpaulo			spans[num_spans].cog += p[i] * (i + 1);
1235199086Srpaulo		}
1236199086Srpaulo	}
1237199086Srpaulo	if (state != ATP_PSPAN_INACTIVE)
1238199086Srpaulo		num_spans++;    /* close the last finger span */
1239199086Srpaulo
1240199086Srpaulo	/* post-process the spans */
1241199086Srpaulo	for (i = 0; i < num_spans; i++) {
1242199086Srpaulo		/* filter away unwanted pressure spans */
1243262417Shselasky		if ((spans[i].cum < FG_PSPAN_MIN_CUM_PRESSURE) ||
1244262417Shselasky		    (spans[i].width > FG_PSPAN_MAX_WIDTH)) {
1245199086Srpaulo			if ((i + 1) < num_spans) {
1246199086Srpaulo				memcpy(&spans[i], &spans[i + 1],
1247262417Shselasky				    (num_spans - i - 1) * sizeof(fg_pspan));
1248199086Srpaulo				i--;
1249199086Srpaulo			}
1250199086Srpaulo			num_spans--;
1251199086Srpaulo			continue;
1252199086Srpaulo		}
1253199086Srpaulo
1254199086Srpaulo		/* compute this span's representative location */
1255262417Shselasky		spans[i].loc = spans[i].cog * FG_SCALE_FACTOR /
1256199086Srpaulo			spans[i].cum;
1257199086Srpaulo
1258262417Shselasky		spans[i].matched = false; /* not yet matched against a stroke */
1259199086Srpaulo	}
1260199086Srpaulo
1261199086Srpaulo	*nspans_p = num_spans;
1262199086Srpaulo}
1263199086Srpaulo
1264262417Shselaskystatic void
1265262417Shselaskywsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
1266262417Shselasky{
1267262417Shselasky	const struct wsp_dev_params *params = sc->sc_params;
1268262417Shselasky	wsp_finger_t fingers[WSP_MAX_FINGERS];
1269262417Shselasky	struct wsp_finger_sensor_data *source_fingerp;
1270262417Shselasky	u_int n_source_fingers;
1271262417Shselasky	u_int n_fingers;
1272262417Shselasky	u_int i;
1273262417Shselasky
1274262417Shselasky	/* validate sensor data length */
1275262417Shselasky	if ((data_len < params->finger_data_offset) ||
1276262417Shselasky	    ((data_len - params->finger_data_offset) %
1277262417Shselasky	     WSP_SIZEOF_FINGER_SENSOR_DATA) != 0)
1278262417Shselasky		return;
1279262417Shselasky
1280262417Shselasky	/* compute number of source fingers */
1281262417Shselasky	n_source_fingers = (data_len - params->finger_data_offset) /
1282262417Shselasky	    WSP_SIZEOF_FINGER_SENSOR_DATA;
1283262417Shselasky
1284262417Shselasky	if (n_source_fingers > WSP_MAX_FINGERS)
1285262417Shselasky		n_source_fingers = WSP_MAX_FINGERS;
1286262417Shselasky
1287262417Shselasky	/* iterate over the source data collecting useful fingers */
1288262417Shselasky	n_fingers = 0;
1289262417Shselasky	source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data +
1290262417Shselasky	     params->finger_data_offset);
1291262417Shselasky
1292262417Shselasky	for (i = 0; i < n_source_fingers; i++, source_fingerp++) {
1293262417Shselasky		/* swap endianness, if any */
1294262417Shselasky		if (le16toh(0x1234) != 0x1234) {
1295262417Shselasky			source_fingerp->origin      = le16toh((uint16_t)source_fingerp->origin);
1296262417Shselasky			source_fingerp->abs_x       = le16toh((uint16_t)source_fingerp->abs_x);
1297262417Shselasky			source_fingerp->abs_y       = le16toh((uint16_t)source_fingerp->abs_y);
1298262417Shselasky			source_fingerp->rel_x       = le16toh((uint16_t)source_fingerp->rel_x);
1299262417Shselasky			source_fingerp->rel_y       = le16toh((uint16_t)source_fingerp->rel_y);
1300262417Shselasky			source_fingerp->tool_major  = le16toh((uint16_t)source_fingerp->tool_major);
1301262417Shselasky			source_fingerp->tool_minor  = le16toh((uint16_t)source_fingerp->tool_minor);
1302262417Shselasky			source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation);
1303262417Shselasky			source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major);
1304262417Shselasky			source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor);
1305262417Shselasky			source_fingerp->multi       = le16toh((uint16_t)source_fingerp->multi);
1306262417Shselasky		}
1307262417Shselasky
1308262417Shselasky		/* check for minium threshold */
1309262417Shselasky		if (source_fingerp->touch_major == 0)
1310262417Shselasky			continue;
1311262417Shselasky
1312262417Shselasky		fingers[n_fingers].matched = false;
1313262417Shselasky		fingers[n_fingers].x       = source_fingerp->abs_x;
1314262417Shselasky		fingers[n_fingers].y       = -source_fingerp->abs_y;
1315262417Shselasky
1316262417Shselasky		n_fingers++;
1317262417Shselasky	}
1318262417Shselasky
1319262417Shselasky	if ((sc->sc_n_strokes == 0) && (n_fingers == 0))
1320262417Shselasky		return;
1321262417Shselasky
1322262417Shselasky	if (wsp_update_strokes(sc, fingers, n_fingers))
1323262417Shselasky		sc->sc_status.flags |= MOUSE_POSCHANGED;
1324262417Shselasky
1325262417Shselasky	switch(params->tp_type) {
1326262417Shselasky	case WSP_TRACKPAD_TYPE2:
1327262417Shselasky		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET];
1328262417Shselasky		break;
1329262417Shselasky	case WSP_TRACKPAD_TYPE3:
1330262417Shselasky		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET];
1331262417Shselasky		break;
1332262417Shselasky	default:
1333262417Shselasky		break;
1334262417Shselasky	}
1335262417Shselasky	sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0;
1336262417Shselasky}
1337262417Shselasky
1338199086Srpaulo/*
1339199086Srpaulo * Match a pressure-span against a stroke-component. If there is a
1340262417Shselasky * match, update the component's state and return true.
1341199086Srpaulo */
1342199086Srpaulostatic boolean_t
1343262417Shselaskyfg_match_stroke_component(fg_stroke_component_t *component,
1344262417Shselasky    const fg_pspan *pspan, atp_stroke_type stroke_type)
1345199086Srpaulo{
1346200241Srpaulo	int   delta_mickeys;
1347200241Srpaulo	u_int min_pressure;
1348199086Srpaulo
1349200241Srpaulo	delta_mickeys = pspan->loc - component->loc;
1350200241Srpaulo
1351262417Shselasky	if (abs(delta_mickeys) > (int)FG_MAX_DELTA_MICKEYS)
1352262417Shselasky		return (false); /* the finger span is too far out; no match */
1353199086Srpaulo
1354262417Shselasky	component->loc = pspan->loc;
1355200241Srpaulo
1356200241Srpaulo	/*
1357200241Srpaulo	 * A sudden and significant increase in a pspan's cumulative
1358200241Srpaulo	 * pressure indicates the incidence of a new finger
1359200241Srpaulo	 * contact. This usually revises the pspan's
1360200241Srpaulo	 * centre-of-gravity, and hence the location of any/all
1361200241Srpaulo	 * matching stroke component(s). But such a change should
1362200241Srpaulo	 * *not* be interpreted as a movement.
1363200241Srpaulo	 */
1364262417Shselasky	if (pspan->cum > ((3 * component->cum_pressure) >> 1))
1365200241Srpaulo		delta_mickeys = 0;
1366200241Srpaulo
1367199086Srpaulo	component->cum_pressure = pspan->cum;
1368199086Srpaulo	if (pspan->cum > component->max_cum_pressure)
1369199086Srpaulo		component->max_cum_pressure = pspan->cum;
1370199086Srpaulo
1371199086Srpaulo	/*
1372200241Srpaulo	 * Disregard the component's movement if its cumulative
1373200241Srpaulo	 * pressure drops below a fraction of the maximum; this
1374200241Srpaulo	 * fraction is determined based on the stroke's type.
1375199086Srpaulo	 */
1376200241Srpaulo	if (stroke_type == ATP_STROKE_TOUCH)
1377200241Srpaulo		min_pressure = (3 * component->max_cum_pressure) >> 2;
1378200241Srpaulo	else
1379200241Srpaulo		min_pressure = component->max_cum_pressure >> 2;
1380200241Srpaulo	if (component->cum_pressure < min_pressure)
1381199086Srpaulo		delta_mickeys = 0;
1382199086Srpaulo
1383199086Srpaulo	component->delta_mickeys = delta_mickeys;
1384262417Shselasky	return (true);
1385199086Srpaulo}
1386199086Srpaulo
1387199086Srpaulostatic void
1388262417Shselaskyfg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
1389262417Shselasky    fg_pspan *pspans, u_int n_pspans, u_int repeat_count)
1390199086Srpaulo{
1391262417Shselasky	atp_stroke_t *strokep;
1392199086Srpaulo	u_int repeat_index = 0;
1393262417Shselasky	u_int i;
1394199086Srpaulo
1395199086Srpaulo	/* Determine the index of the multi-span. */
1396199086Srpaulo	if (repeat_count) {
1397199086Srpaulo		for (i = 0; i < n_pspans; i++) {
1398262417Shselasky			if (pspans[i].cum > pspans[repeat_index].cum)
1399199086Srpaulo				repeat_index = i;
1400199086Srpaulo		}
1401199086Srpaulo	}
1402199086Srpaulo
1403262417Shselasky	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1404262417Shselasky		if (strokep->components[axis].matched)
1405199086Srpaulo			continue; /* skip matched components */
1406199086Srpaulo
1407262417Shselasky		for (i = 0; i < n_pspans; i++) {
1408262417Shselasky			if (pspans[i].matched)
1409199086Srpaulo				continue; /* skip matched pspans */
1410199086Srpaulo
1411262417Shselasky			if (fg_match_stroke_component(
1412262417Shselasky			    &strokep->components[axis], &pspans[i],
1413262417Shselasky			    strokep->type)) {
1414262417Shselasky
1415199086Srpaulo				/* There is a match. */
1416262417Shselasky				strokep->components[axis].matched = true;
1417199086Srpaulo
1418199086Srpaulo				/* Take care to repeat at the multi-span. */
1419262417Shselasky				if ((repeat_count > 0) && (i == repeat_index))
1420199086Srpaulo					repeat_count--;
1421199086Srpaulo				else
1422262417Shselasky					pspans[i].matched = true;
1423199086Srpaulo
1424262417Shselasky				break; /* skip to the next strokep */
1425199086Srpaulo			}
1426199086Srpaulo		} /* loop over pspans */
1427199086Srpaulo	} /* loop over strokes */
1428199086Srpaulo}
1429199086Srpaulo
1430262417Shselaskystatic boolean_t
1431262417Shselaskywsp_match_strokes_against_fingers(struct atp_softc *sc,
1432262417Shselasky    wsp_finger_t *fingers, u_int n_fingers)
1433262417Shselasky{
1434262417Shselasky	boolean_t movement = false;
1435262417Shselasky	atp_stroke_t *strokep;
1436262417Shselasky	u_int i;
1437262417Shselasky
1438262417Shselasky	/* reset the matched status for all strokes */
1439262417Shselasky	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry)
1440262417Shselasky		strokep->matched = false;
1441262417Shselasky
1442262417Shselasky	for (i = 0; i != n_fingers; i++) {
1443262417Shselasky		u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ;
1444262417Shselasky		atp_stroke_t *strokep_best = NULL;
1445262417Shselasky
1446262417Shselasky		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1447262417Shselasky			int instantaneous_dx;
1448262417Shselasky			int instantaneous_dy;
1449262417Shselasky			u_int d_squared;
1450262417Shselasky
1451262417Shselasky			if (strokep->matched)
1452262417Shselasky				continue;
1453262417Shselasky
1454262417Shselasky			instantaneous_dx = fingers[i].x - strokep->x;
1455262417Shselasky			instantaneous_dy = fingers[i].y - strokep->y;
1456262417Shselasky
1457262417Shselasky			/* skip strokes which are far away */
1458262417Shselasky			d_squared =
1459262417Shselasky			    (instantaneous_dx * instantaneous_dx) +
1460262417Shselasky			    (instantaneous_dy * instantaneous_dy);
1461262417Shselasky
1462262417Shselasky			if (d_squared < least_distance_sq) {
1463262417Shselasky				least_distance_sq = d_squared;
1464262417Shselasky				strokep_best = strokep;
1465262417Shselasky			}
1466262417Shselasky		}
1467262417Shselasky
1468262417Shselasky		strokep = strokep_best;
1469262417Shselasky
1470262417Shselasky		if (strokep != NULL) {
1471262417Shselasky			fingers[i].matched = true;
1472262417Shselasky
1473262417Shselasky			strokep->matched          = true;
1474262417Shselasky			strokep->instantaneous_dx = fingers[i].x - strokep->x;
1475262417Shselasky			strokep->instantaneous_dy = fingers[i].y - strokep->y;
1476262417Shselasky			strokep->x                = fingers[i].x;
1477262417Shselasky			strokep->y                = fingers[i].y;
1478262417Shselasky
1479262417Shselasky			atp_advance_stroke_state(sc, strokep, &movement);
1480262417Shselasky		}
1481262417Shselasky	}
1482262417Shselasky	return (movement);
1483262417Shselasky}
1484262417Shselasky
1485199086Srpaulo/*
1486199086Srpaulo * Update strokes by matching against current pressure-spans.
1487262417Shselasky * Return true if any movement is detected.
1488199086Srpaulo */
1489199086Srpaulostatic boolean_t
1490262417Shselaskyfg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
1491262417Shselasky    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
1492199086Srpaulo{
1493262417Shselasky	atp_stroke_t *strokep;
1494262417Shselasky	atp_stroke_t *strokep_next;
1495262417Shselasky	boolean_t movement = false;
1496262417Shselasky	u_int repeat_count = 0;
1497262417Shselasky	u_int i;
1498262417Shselasky	u_int j;
1499199086Srpaulo
1500199086Srpaulo	/* Reset X and Y components of all strokes as unmatched. */
1501262417Shselasky	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1502262417Shselasky		strokep->components[X].matched = false;
1503262417Shselasky		strokep->components[Y].matched = false;
1504199086Srpaulo	}
1505199086Srpaulo
1506199086Srpaulo	/*
1507199086Srpaulo	 * Usually, the X and Y pspans come in pairs (the common case
1508199086Srpaulo	 * being a single pair). It is possible, however, that
1509199086Srpaulo	 * multiple contacts resolve to a single pspan along an
1510199086Srpaulo	 * axis, as illustrated in the following:
1511199086Srpaulo	 *
1512199086Srpaulo	 *   F = finger-contact
1513199086Srpaulo	 *
1514199086Srpaulo	 *                pspan  pspan
1515199086Srpaulo	 *        +-----------------------+
1516199086Srpaulo	 *        |         .      .      |
1517199086Srpaulo	 *        |         .      .      |
1518199086Srpaulo	 *        |         .      .      |
1519199086Srpaulo	 *        |         .      .      |
1520199086Srpaulo	 *  pspan |.........F......F      |
1521199086Srpaulo	 *        |                       |
1522199086Srpaulo	 *        |                       |
1523199086Srpaulo	 *        |                       |
1524199086Srpaulo	 *        +-----------------------+
1525199086Srpaulo	 *
1526199086Srpaulo	 *
1527199086Srpaulo	 * The above case can be detected by a difference in the
1528199086Srpaulo	 * number of X and Y pspans. When this happens, X and Y pspans
1529199086Srpaulo	 * aren't easy to pair or match against strokes.
1530199086Srpaulo	 *
1531199086Srpaulo	 * When X and Y pspans differ in number, the axis with the
1532199086Srpaulo	 * smaller number of pspans is regarded as having a repeating
1533199086Srpaulo	 * pspan (or a multi-pspan)--in the above illustration, the
1534199086Srpaulo	 * Y-axis has a repeating pspan. Our approach is to try to
1535199086Srpaulo	 * match the multi-pspan repeatedly against strokes. The
1536199086Srpaulo	 * difference between the number of X and Y pspans gives us a
1537199086Srpaulo	 * crude repeat_count for matching multi-pspans--i.e. the
1538199086Srpaulo	 * multi-pspan along the Y axis (above) has a repeat_count of 1.
1539199086Srpaulo	 */
1540199086Srpaulo	repeat_count = abs(n_xpspans - n_ypspans);
1541199086Srpaulo
1542262417Shselasky	fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
1543199086Srpaulo	    (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
1544199086Srpaulo		repeat_count : 0));
1545262417Shselasky	fg_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
1546199086Srpaulo	    (((repeat_count != 0) && (n_ypspans < n_xpspans)) ?
1547199086Srpaulo		repeat_count : 0));
1548199086Srpaulo
1549199086Srpaulo	/* Update the state of strokes based on the above pspan matches. */
1550262417Shselasky	TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
1551262417Shselasky
1552262417Shselasky		if (strokep->components[X].matched &&
1553262417Shselasky		    strokep->components[Y].matched) {
1554262417Shselasky			strokep->matched = true;
1555262417Shselasky			strokep->instantaneous_dx =
1556262417Shselasky			    strokep->components[X].delta_mickeys;
1557262417Shselasky			strokep->instantaneous_dy =
1558262417Shselasky			    strokep->components[Y].delta_mickeys;
1559262417Shselasky			atp_advance_stroke_state(sc, strokep, &movement);
1560199086Srpaulo		} else {
1561199086Srpaulo			/*
1562199086Srpaulo			 * At least one component of this stroke
1563199086Srpaulo			 * didn't match against current pspans;
1564199086Srpaulo			 * terminate it.
1565199086Srpaulo			 */
1566262417Shselasky			atp_terminate_stroke(sc, strokep);
1567199086Srpaulo		}
1568199086Srpaulo	}
1569199086Srpaulo
1570199086Srpaulo	/* Add new strokes for pairs of unmatched pspans */
1571199086Srpaulo	for (i = 0; i < n_xpspans; i++) {
1572262417Shselasky		if (pspans_x[i].matched == false) break;
1573199086Srpaulo	}
1574199086Srpaulo	for (j = 0; j < n_ypspans; j++) {
1575262417Shselasky		if (pspans_y[j].matched == false) break;
1576199086Srpaulo	}
1577199086Srpaulo	if ((i < n_xpspans) && (j < n_ypspans)) {
1578207077Sthompsa#ifdef USB_DEBUG
1579199086Srpaulo		if (atp_debug >= ATP_LLEVEL_INFO) {
1580199086Srpaulo			printf("unmatched pspans:");
1581199086Srpaulo			for (; i < n_xpspans; i++) {
1582199086Srpaulo				if (pspans_x[i].matched)
1583199086Srpaulo					continue;
1584199086Srpaulo				printf(" X:[loc:%u,cum:%u]",
1585199086Srpaulo				    pspans_x[i].loc, pspans_x[i].cum);
1586199086Srpaulo			}
1587199086Srpaulo			for (; j < n_ypspans; j++) {
1588199086Srpaulo				if (pspans_y[j].matched)
1589199086Srpaulo					continue;
1590199086Srpaulo				printf(" Y:[loc:%u,cum:%u]",
1591199086Srpaulo				    pspans_y[j].loc, pspans_y[j].cum);
1592199086Srpaulo			}
1593199086Srpaulo			printf("\n");
1594199086Srpaulo		}
1595207077Sthompsa#endif /* USB_DEBUG */
1596199086Srpaulo		if ((n_xpspans == 1) && (n_ypspans == 1))
1597199086Srpaulo			/* The common case of a single pair of new pspans. */
1598262417Shselasky			fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
1599199086Srpaulo		else
1600262417Shselasky			fg_add_new_strokes(sc, pspans_x, n_xpspans,
1601199086Srpaulo			    pspans_y, n_ypspans);
1602199086Srpaulo	}
1603199086Srpaulo
1604207077Sthompsa#ifdef USB_DEBUG
1605199086Srpaulo	if (atp_debug >= ATP_LLEVEL_INFO) {
1606262417Shselasky		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
1607262417Shselasky			printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c"
1608262417Shselasky			    ",%clc:%u,dm:%d,cum:%d,max:%d,%c",
1609262417Shselasky			    (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "",
1610262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
1611262417Shselasky			    strokep->components[X].loc,
1612262417Shselasky			    strokep->components[X].delta_mickeys,
1613262417Shselasky			    strokep->components[X].cum_pressure,
1614262417Shselasky			    strokep->components[X].max_cum_pressure,
1615262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>',
1616262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
1617262417Shselasky			    strokep->components[Y].loc,
1618262417Shselasky			    strokep->components[Y].delta_mickeys,
1619262417Shselasky			    strokep->components[Y].cum_pressure,
1620262417Shselasky			    strokep->components[Y].max_cum_pressure,
1621262417Shselasky			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>');
1622199086Srpaulo		}
1623262417Shselasky		if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL)
1624199086Srpaulo			printf("\n");
1625199086Srpaulo	}
1626207077Sthompsa#endif /* USB_DEBUG */
1627262417Shselasky	return (movement);
1628262417Shselasky}
1629199086Srpaulo
1630262417Shselasky/*
1631262417Shselasky * Update strokes by matching against current pressure-spans.
1632262417Shselasky * Return true if any movement is detected.
1633262417Shselasky */
1634262417Shselaskystatic boolean_t
1635262417Shselaskywsp_update_strokes(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers)
1636262417Shselasky{
1637262417Shselasky	boolean_t movement = false;
1638262417Shselasky	atp_stroke_t *strokep_next;
1639262417Shselasky	atp_stroke_t *strokep;
1640262417Shselasky	u_int i;
1641262417Shselasky
1642262417Shselasky	if (sc->sc_n_strokes > 0) {
1643262417Shselasky		movement = wsp_match_strokes_against_fingers(
1644262417Shselasky		    sc, fingers, n_fingers);
1645262417Shselasky
1646262417Shselasky		/* handle zombie strokes */
1647262417Shselasky		TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
1648262417Shselasky			if (strokep->matched)
1649262417Shselasky				continue;
1650262417Shselasky			atp_terminate_stroke(sc, strokep);
1651262417Shselasky		}
1652262417Shselasky	}
1653262417Shselasky
1654262417Shselasky	/* initialize unmatched fingers as strokes */
1655262417Shselasky	for (i = 0; i != n_fingers; i++) {
1656262417Shselasky		if (fingers[i].matched)
1657262417Shselasky			continue;
1658262417Shselasky
1659262417Shselasky		wsp_add_stroke(sc, fingers + i);
1660262417Shselasky	}
1661199086Srpaulo	return (movement);
1662199086Srpaulo}
1663199086Srpaulo
1664199086Srpaulo/* Initialize a stroke using a pressure-span. */
1665262417Shselaskystatic void
1666262417Shselaskyfg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x,
1667262417Shselasky    const fg_pspan *pspan_y)
1668199086Srpaulo{
1669262417Shselasky	atp_stroke_t *strokep;
1670199086Srpaulo
1671262417Shselasky	strokep = atp_alloc_stroke(sc);
1672262417Shselasky	if (strokep == NULL)
1673199086Srpaulo		return;
1674199086Srpaulo
1675199086Srpaulo	/*
1676199086Srpaulo	 * Strokes begin as potential touches. If a stroke survives
1677199086Srpaulo	 * longer than a threshold, or if it records significant
1678199086Srpaulo	 * cumulative movement, then it is considered a 'slide'.
1679199086Srpaulo	 */
1680262417Shselasky	strokep->type    = ATP_STROKE_TOUCH;
1681262417Shselasky	strokep->matched = false;
1682262417Shselasky	microtime(&strokep->ctime);
1683262417Shselasky	strokep->age     = 1;		/* number of interrupts */
1684262417Shselasky	strokep->x       = pspan_x->loc;
1685262417Shselasky	strokep->y       = pspan_y->loc;
1686199086Srpaulo
1687262417Shselasky	strokep->components[X].loc              = pspan_x->loc;
1688262417Shselasky	strokep->components[X].cum_pressure     = pspan_x->cum;
1689262417Shselasky	strokep->components[X].max_cum_pressure = pspan_x->cum;
1690262417Shselasky	strokep->components[X].matched          = true;
1691199086Srpaulo
1692262417Shselasky	strokep->components[Y].loc              = pspan_y->loc;
1693262417Shselasky	strokep->components[Y].cum_pressure     = pspan_y->cum;
1694262417Shselasky	strokep->components[Y].max_cum_pressure = pspan_y->cum;
1695262417Shselasky	strokep->components[Y].matched          = true;
1696199086Srpaulo
1697199086Srpaulo	if (sc->sc_n_strokes > 1) {
1698199086Srpaulo		/* Reset double-tap-n-drag if we have more than one strokes. */
1699199086Srpaulo		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
1700199086Srpaulo	}
1701199086Srpaulo
1702199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n",
1703262417Shselasky	    strokep->components[X].loc,
1704262417Shselasky	    strokep->components[Y].loc,
1705262417Shselasky	    (u_int)strokep->ctime.tv_sec,
1706262417Shselasky	    (unsigned long int)strokep->ctime.tv_usec);
1707199086Srpaulo}
1708199086Srpaulo
1709199086Srpaulostatic void
1710262417Shselaskyfg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
1711262417Shselasky    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
1712199086Srpaulo{
1713262417Shselasky	fg_pspan spans[2][FG_MAX_PSPANS_PER_AXIS];
1714233774Shselasky	u_int nspans[2];
1715233774Shselasky	u_int i;
1716233774Shselasky	u_int j;
1717199086Srpaulo
1718199086Srpaulo	/* Copy unmatched pspans into the local arrays. */
1719199086Srpaulo	for (i = 0, nspans[X] = 0; i < n_xpspans; i++) {
1720262417Shselasky		if (pspans_x[i].matched == false) {
1721199086Srpaulo			spans[X][nspans[X]] = pspans_x[i];
1722199086Srpaulo			nspans[X]++;
1723199086Srpaulo		}
1724199086Srpaulo	}
1725199086Srpaulo	for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) {
1726262417Shselasky		if (pspans_y[j].matched == false) {
1727199086Srpaulo			spans[Y][nspans[Y]] = pspans_y[j];
1728199086Srpaulo			nspans[Y]++;
1729199086Srpaulo		}
1730199086Srpaulo	}
1731199086Srpaulo
1732199086Srpaulo	if (nspans[X] == nspans[Y]) {
1733199086Srpaulo		/* Create new strokes from pairs of unmatched pspans */
1734199086Srpaulo		for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++)
1735262417Shselasky			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
1736199086Srpaulo	} else {
1737199086Srpaulo		u_int    cum = 0;
1738199086Srpaulo		atp_axis repeat_axis;      /* axis with multi-pspans */
1739199086Srpaulo		u_int    repeat_count;     /* repeat count for the multi-pspan*/
1740199086Srpaulo		u_int    repeat_index = 0; /* index of the multi-span */
1741199086Srpaulo
1742199086Srpaulo		repeat_axis  = (nspans[X] > nspans[Y]) ? Y : X;
1743199086Srpaulo		repeat_count = abs(nspans[X] - nspans[Y]);
1744199086Srpaulo		for (i = 0; i < nspans[repeat_axis]; i++) {
1745199086Srpaulo			if (spans[repeat_axis][i].cum > cum) {
1746199086Srpaulo				repeat_index = i;
1747199086Srpaulo				cum = spans[repeat_axis][i].cum;
1748199086Srpaulo			}
1749199086Srpaulo		}
1750199086Srpaulo
1751199086Srpaulo		/* Create new strokes from pairs of unmatched pspans */
1752199086Srpaulo		i = 0, j = 0;
1753199086Srpaulo		for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) {
1754262417Shselasky			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
1755199086Srpaulo
1756199086Srpaulo			/* Take care to repeat at the multi-pspan. */
1757199086Srpaulo			if (repeat_count > 0) {
1758199086Srpaulo				if ((repeat_axis == X) &&
1759199086Srpaulo				    (repeat_index == i)) {
1760199086Srpaulo					i--; /* counter loop increment */
1761199086Srpaulo					repeat_count--;
1762199086Srpaulo				} else if ((repeat_axis == Y) &&
1763199086Srpaulo				    (repeat_index == j)) {
1764199086Srpaulo					j--; /* counter loop increment */
1765199086Srpaulo					repeat_count--;
1766199086Srpaulo				}
1767199086Srpaulo			}
1768199086Srpaulo		}
1769199086Srpaulo	}
1770199086Srpaulo}
1771199086Srpaulo
1772262417Shselasky/* Initialize a stroke from an unmatched finger. */
1773262417Shselaskystatic void
1774262417Shselaskywsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp)
1775199086Srpaulo{
1776262417Shselasky	atp_stroke_t *strokep;
1777199086Srpaulo
1778262417Shselasky	strokep = atp_alloc_stroke(sc);
1779262417Shselasky	if (strokep == NULL)
1780200241Srpaulo		return;
1781200241Srpaulo
1782262417Shselasky	/*
1783262417Shselasky	 * Strokes begin as potential touches. If a stroke survives
1784262417Shselasky	 * longer than a threshold, or if it records significant
1785262417Shselasky	 * cumulative movement, then it is considered a 'slide'.
1786262417Shselasky	 */
1787262417Shselasky	strokep->type    = ATP_STROKE_TOUCH;
1788262417Shselasky	strokep->matched = true;
1789262417Shselasky	microtime(&strokep->ctime);
1790262417Shselasky	strokep->age = 1;	/* number of interrupts */
1791262417Shselasky	strokep->x = fingerp->x;
1792262417Shselasky	strokep->y = fingerp->y;
1793199086Srpaulo
1794262417Shselasky	/* Reset double-tap-n-drag if we have more than one strokes. */
1795262417Shselasky	if (sc->sc_n_strokes > 1)
1796262417Shselasky		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
1797200241Srpaulo
1798262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y);
1799199086Srpaulo}
1800199086Srpaulo
1801262417Shselaskystatic void
1802262417Shselaskyatp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep,
1803262417Shselasky    boolean_t *movementp)
1804200241Srpaulo{
1805262417Shselasky	/* Revitalize stroke if it had previously been marked as a zombie. */
1806262417Shselasky	if (strokep->flags & ATSF_ZOMBIE)
1807262417Shselasky		strokep->flags &= ~ATSF_ZOMBIE;
1808200241Srpaulo
1809262417Shselasky	strokep->age++;
1810262417Shselasky	if (strokep->age <= atp_stroke_maturity_threshold) {
1811262417Shselasky		/* Avoid noise from immature strokes. */
1812262417Shselasky		strokep->instantaneous_dx = 0;
1813262417Shselasky		strokep->instantaneous_dy = 0;
1814200241Srpaulo	}
1815200241Srpaulo
1816262417Shselasky	if (atp_compute_stroke_movement(strokep))
1817262417Shselasky		*movementp = true;
1818199086Srpaulo
1819262417Shselasky	if (strokep->type != ATP_STROKE_TOUCH)
1820199086Srpaulo		return;
1821199086Srpaulo
1822262417Shselasky	/* Convert touch strokes to slides upon detecting movement or age. */
1823262417Shselasky	if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) ||
1824262417Shselasky	    (abs(strokep->cum_movement_y) > atp_slide_min_movement))
1825262417Shselasky		atp_convert_to_slide(sc, strokep);
1826262417Shselasky	else {
1827262417Shselasky		/* Compute the stroke's age. */
1828262417Shselasky		struct timeval tdiff;
1829262417Shselasky		getmicrotime(&tdiff);
1830262417Shselasky		if (timevalcmp(&tdiff, &strokep->ctime, >)) {
1831262417Shselasky			timevalsub(&tdiff, &strokep->ctime);
1832199086Srpaulo
1833262417Shselasky			if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) ||
1834262417Shselasky			    ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) &&
1835262417Shselasky			     (tdiff.tv_usec >= (atp_touch_timeout % 1000000))))
1836262417Shselasky				atp_convert_to_slide(sc, strokep);
1837199086Srpaulo		}
1838199086Srpaulo	}
1839199086Srpaulo}
1840199086Srpaulo
1841262417Shselaskystatic boolean_t
1842262417Shselaskyatp_stroke_has_small_movement(const atp_stroke_t *strokep)
1843199086Srpaulo{
1844262417Shselasky	return (((u_int)abs(strokep->instantaneous_dx) <=
1845262417Shselasky		 atp_small_movement_threshold) &&
1846262417Shselasky		((u_int)abs(strokep->instantaneous_dy) <=
1847262417Shselasky		 atp_small_movement_threshold));
1848199086Srpaulo}
1849199086Srpaulo
1850199086Srpaulo/*
1851262417Shselasky * Accumulate instantaneous changes into the stroke's 'pending' bucket; if
1852199086Srpaulo * the aggregate exceeds the small_movement_threshold, then retain
1853262417Shselasky * instantaneous changes for later.
1854199086Srpaulo */
1855199086Srpaulostatic void
1856262417Shselaskyatp_update_pending_mickeys(atp_stroke_t *strokep)
1857199086Srpaulo{
1858262417Shselasky	/* accumulate instantaneous movement */
1859262417Shselasky	strokep->pending_dx += strokep->instantaneous_dx;
1860262417Shselasky	strokep->pending_dy += strokep->instantaneous_dy;
1861199086Srpaulo
1862262417Shselasky#define UPDATE_INSTANTANEOUS_AND_PENDING(I, P)                          \
1863262417Shselasky	if (abs((P)) <= atp_small_movement_threshold)                   \
1864262417Shselasky		(I) = 0; /* clobber small movement */                   \
1865262417Shselasky	else {                                                          \
1866262417Shselasky		if ((I) > 0) {                                          \
1867262417Shselasky			/*                                              \
1868262417Shselasky			 * Round up instantaneous movement to the nearest \
1869262417Shselasky			 * ceiling. This helps preserve small mickey    \
1870262417Shselasky			 * movements from being lost in following scaling \
1871262417Shselasky			 * operation.                                   \
1872262417Shselasky			 */                                             \
1873262417Shselasky			(I) = (((I) + (atp_mickeys_scale_factor - 1)) / \
1874262417Shselasky			       atp_mickeys_scale_factor) *              \
1875262417Shselasky			      atp_mickeys_scale_factor;                 \
1876262417Shselasky									\
1877262417Shselasky			/*                                              \
1878262417Shselasky			 * Deduct the rounded mickeys from pending mickeys. \
1879262417Shselasky			 * Note: we multiply by 2 to offset the previous \
1880262417Shselasky			 * accumulation of instantaneous movement into  \
1881262417Shselasky			 * pending.                                     \
1882262417Shselasky			 */                                             \
1883262417Shselasky			(P) -= ((I) << 1);                              \
1884262417Shselasky									\
1885262417Shselasky			/* truncate pending to 0 if it becomes negative. */ \
1886262417Shselasky			(P) = imax((P), 0);                             \
1887262417Shselasky		} else {                                                \
1888262417Shselasky			/*                                              \
1889262417Shselasky			 * Round down instantaneous movement to the nearest \
1890262417Shselasky			 * ceiling. This helps preserve small mickey    \
1891262417Shselasky			 * movements from being lost in following scaling \
1892262417Shselasky			 * operation.                                   \
1893262417Shselasky			 */                                             \
1894262417Shselasky			(I) = (((I) - (atp_mickeys_scale_factor - 1)) / \
1895262417Shselasky			       atp_mickeys_scale_factor) *              \
1896262417Shselasky			      atp_mickeys_scale_factor;                 \
1897262417Shselasky									\
1898262417Shselasky			/*                                              \
1899262417Shselasky			 * Deduct the rounded mickeys from pending mickeys. \
1900262417Shselasky			 * Note: we multiply by 2 to offset the previous \
1901262417Shselasky			 * accumulation of instantaneous movement into  \
1902262417Shselasky			 * pending.                                     \
1903262417Shselasky			 */                                             \
1904262417Shselasky			(P) -= ((I) << 1);                              \
1905262417Shselasky									\
1906262417Shselasky			/* truncate pending to 0 if it becomes positive. */ \
1907262417Shselasky			(P) = imin((P), 0);                             \
1908262417Shselasky		}                                                       \
1909199086Srpaulo	}
1910199086Srpaulo
1911262417Shselasky	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx,
1912262417Shselasky	    strokep->pending_dx);
1913262417Shselasky	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy,
1914262417Shselasky	    strokep->pending_dy);
1915199086Srpaulo}
1916199086Srpaulo
1917199086Srpaulo/*
1918199086Srpaulo * Compute a smoothened value for the stroke's movement from
1919262417Shselasky * instantaneous changes in the X and Y components.
1920199086Srpaulo */
1921199086Srpaulostatic boolean_t
1922262417Shselaskyatp_compute_stroke_movement(atp_stroke_t *strokep)
1923199086Srpaulo{
1924199086Srpaulo	/*
1925199086Srpaulo	 * Short movements are added first to the 'pending' bucket,
1926199086Srpaulo	 * and then acted upon only when their aggregate exceeds a
1927199086Srpaulo	 * threshold. This has the effect of filtering away movement
1928199086Srpaulo	 * noise.
1929199086Srpaulo	 */
1930262417Shselasky	if (atp_stroke_has_small_movement(strokep))
1931262417Shselasky		atp_update_pending_mickeys(strokep);
1932262417Shselasky	else {                /* large movement */
1933199086Srpaulo		/* clear away any pending mickeys if there are large movements*/
1934262417Shselasky		strokep->pending_dx = 0;
1935262417Shselasky		strokep->pending_dy = 0;
1936199086Srpaulo	}
1937199086Srpaulo
1938262417Shselasky	/* scale movement */
1939262417Shselasky	strokep->movement_dx = (strokep->instantaneous_dx) /
1940262417Shselasky	    (int)atp_mickeys_scale_factor;
1941262417Shselasky	strokep->movement_dy = (strokep->instantaneous_dy) /
1942262417Shselasky	    (int)atp_mickeys_scale_factor;
1943199086Srpaulo
1944262417Shselasky	if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) ||
1945262417Shselasky	    (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) {
1946262417Shselasky		strokep->movement_dx <<= 1;
1947262417Shselasky		strokep->movement_dy <<= 1;
1948199086Srpaulo	}
1949199086Srpaulo
1950262417Shselasky	strokep->cum_movement_x += strokep->movement_dx;
1951262417Shselasky	strokep->cum_movement_y += strokep->movement_dy;
1952262417Shselasky
1953262417Shselasky	return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0));
1954199086Srpaulo}
1955199086Srpaulo
1956262417Shselasky/*
1957262417Shselasky * Terminate a stroke. Aside from immature strokes, a slide or touch is
1958262417Shselasky * retained as a zombies so as to reap all their termination siblings
1959262417Shselasky * together; this helps establish the number of fingers involved at the
1960262417Shselasky * end of a multi-touch gesture.
1961262417Shselasky */
1962262417Shselaskystatic void
1963262417Shselaskyatp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep)
1964199086Srpaulo{
1965262417Shselasky	if (strokep->flags & ATSF_ZOMBIE)
1966262417Shselasky		return;
1967199086Srpaulo
1968262417Shselasky	/* Drop immature strokes rightaway. */
1969262417Shselasky	if (strokep->age <= atp_stroke_maturity_threshold) {
1970262417Shselasky		atp_free_stroke(sc, strokep);
1971262417Shselasky		return;
1972262417Shselasky	}
1973199086Srpaulo
1974262417Shselasky	strokep->flags |= ATSF_ZOMBIE;
1975262417Shselasky	sc->sc_state |= ATP_ZOMBIES_EXIST;
1976262417Shselasky
1977262417Shselasky	callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL,
1978262417Shselasky	    atp_reap_sibling_zombies, sc);
1979262417Shselasky
1980262417Shselasky	/*
1981262417Shselasky	 * Reset the double-click-n-drag at the termination of any
1982262417Shselasky	 * slide stroke.
1983262417Shselasky	 */
1984262417Shselasky	if (strokep->type == ATP_STROKE_SLIDE)
1985262417Shselasky		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
1986199086Srpaulo}
1987199086Srpaulo
1988262417Shselaskystatic boolean_t
1989262417Shselaskyatp_is_horizontal_scroll(const atp_stroke_t *strokep)
1990262417Shselasky{
1991262417Shselasky	if (abs(strokep->cum_movement_x) < atp_slide_min_movement)
1992262417Shselasky		return (false);
1993262417Shselasky	if (strokep->cum_movement_y == 0)
1994262417Shselasky		return (true);
1995262417Shselasky	return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4);
1996262417Shselasky}
1997262417Shselasky
1998262417Shselaskystatic boolean_t
1999262417Shselaskyatp_is_vertical_scroll(const atp_stroke_t *strokep)
2000262417Shselasky{
2001262417Shselasky	if (abs(strokep->cum_movement_y) < atp_slide_min_movement)
2002262417Shselasky		return (false);
2003262417Shselasky	if (strokep->cum_movement_x == 0)
2004262417Shselasky		return (true);
2005262417Shselasky	return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4);
2006262417Shselasky}
2007262417Shselasky
2008199086Srpaulostatic void
2009262417Shselaskyatp_reap_sibling_zombies(void *arg)
2010199086Srpaulo{
2011262417Shselasky	struct atp_softc *sc = (struct atp_softc *)arg;
2012262417Shselasky	u_int8_t n_touches_reaped = 0;
2013262417Shselasky	u_int8_t n_slides_reaped = 0;
2014262417Shselasky	u_int8_t n_horizontal_scrolls = 0;
2015262417Shselasky	u_int8_t n_vertical_scrolls = 0;
2016262417Shselasky	int horizontal_scroll = 0;
2017262417Shselasky	int vertical_scroll = 0;
2018262417Shselasky	atp_stroke_t *strokep;
2019262417Shselasky	atp_stroke_t *strokep_next;
2020199086Srpaulo
2021262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "\n");
2022199086Srpaulo
2023262417Shselasky	TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
2024262417Shselasky		if ((strokep->flags & ATSF_ZOMBIE) == 0)
2025199086Srpaulo			continue;
2026199086Srpaulo
2027262417Shselasky		if (strokep->type == ATP_STROKE_TOUCH) {
2028262417Shselasky			n_touches_reaped++;
2029199086Srpaulo		} else {
2030262417Shselasky			n_slides_reaped++;
2031199086Srpaulo
2032262417Shselasky			if (atp_is_horizontal_scroll(strokep)) {
2033262417Shselasky				n_horizontal_scrolls++;
2034262417Shselasky				horizontal_scroll += strokep->cum_movement_x;
2035262417Shselasky			} else if (atp_is_vertical_scroll(strokep)) {
2036262417Shselasky				n_vertical_scrolls++;
2037262417Shselasky				vertical_scroll +=  strokep->cum_movement_y;
2038262417Shselasky			}
2039199086Srpaulo		}
2040199086Srpaulo
2041262417Shselasky		atp_free_stroke(sc, strokep);
2042262417Shselasky	}
2043199086Srpaulo
2044262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n",
2045262417Shselasky	    n_touches_reaped + n_slides_reaped);
2046262417Shselasky	sc->sc_state &= ~ATP_ZOMBIES_EXIST;
2047199086Srpaulo
2048262417Shselasky	/* No further processing necessary if physical button is depressed. */
2049262417Shselasky	if (sc->sc_ibtn != 0)
2050262417Shselasky		return;
2051199086Srpaulo
2052262417Shselasky	if ((n_touches_reaped == 0) && (n_slides_reaped == 0))
2053262417Shselasky		return;
2054199086Srpaulo
2055262417Shselasky	/* Add a pair of virtual button events (button-down and button-up) if
2056262417Shselasky	 * the physical button isn't pressed. */
2057262417Shselasky	if (n_touches_reaped != 0) {
2058262417Shselasky		if (n_touches_reaped < atp_tap_minimum)
2059262417Shselasky			return;
2060199086Srpaulo
2061262417Shselasky		switch (n_touches_reaped) {
2062262417Shselasky		case 1:
2063262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
2064262417Shselasky			microtime(&sc->sc_touch_reap_time); /* remember this time */
2065262417Shselasky			break;
2066262417Shselasky		case 2:
2067262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
2068262417Shselasky			break;
2069262417Shselasky		case 3:
2070262417Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
2071262417Shselasky			break;
2072262417Shselasky		default:
2073262417Shselasky			/* we handle taps of only up to 3 fingers */
2074262439Shselasky			return;
2075199086Srpaulo		}
2076262417Shselasky		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
2077262417Shselasky
2078262439Shselasky	} else if ((n_slides_reaped == 2) && (n_horizontal_scrolls == 2)) {
2079262439Shselasky		if (horizontal_scroll < 0)
2080262439Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN);
2081262439Shselasky		else
2082262439Shselasky			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN);
2083262439Shselasky		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
2084199086Srpaulo	}
2085262417Shselasky}
2086199086Srpaulo
2087262417Shselasky/* Switch a given touch stroke to being a slide. */
2088262417Shselaskystatic void
2089262417Shselaskyatp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep)
2090262417Shselasky{
2091262417Shselasky	strokep->type = ATP_STROKE_SLIDE;
2092262417Shselasky
2093262417Shselasky	/* Are we at the beginning of a double-click-n-drag? */
2094262417Shselasky	if ((sc->sc_n_strokes == 1) &&
2095262417Shselasky	    ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) &&
2096262417Shselasky	    timevalcmp(&strokep->ctime, &sc->sc_touch_reap_time, >)) {
2097262417Shselasky		struct timeval delta;
2098262417Shselasky		struct timeval window = {
2099262417Shselasky			atp_double_tap_threshold / 1000000,
2100262417Shselasky			atp_double_tap_threshold % 1000000
2101262417Shselasky		};
2102262417Shselasky
2103262417Shselasky		delta = strokep->ctime;
2104262417Shselasky		timevalsub(&delta, &sc->sc_touch_reap_time);
2105262417Shselasky		if (timevalcmp(&delta, &window, <=))
2106262417Shselasky			sc->sc_state |= ATP_DOUBLE_TAP_DRAG;
2107262417Shselasky	}
2108199086Srpaulo}
2109199086Srpaulo
2110262417Shselaskystatic void
2111262417Shselaskyatp_reset_buf(struct atp_softc *sc)
2112262417Shselasky{
2113262417Shselasky	/* reset read queue */
2114262417Shselasky	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
2115262417Shselasky}
2116199086Srpaulo
2117262417Shselaskystatic void
2118262417Shselaskyatp_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz,
2119262417Shselasky    uint32_t buttons_in)
2120262417Shselasky{
2121262417Shselasky	uint32_t buttons_out;
2122262417Shselasky	uint8_t  buf[8];
2123199086Srpaulo
2124262417Shselasky	dx = imin(dx,  254); dx = imax(dx, -256);
2125262417Shselasky	dy = imin(dy,  254); dy = imax(dy, -256);
2126262417Shselasky	dz = imin(dz,  126); dz = imax(dz, -128);
2127199086Srpaulo
2128262417Shselasky	buttons_out = MOUSE_MSC_BUTTONS;
2129262417Shselasky	if (buttons_in & MOUSE_BUTTON1DOWN)
2130262417Shselasky		buttons_out &= ~MOUSE_MSC_BUTTON1UP;
2131262417Shselasky	else if (buttons_in & MOUSE_BUTTON2DOWN)
2132262417Shselasky		buttons_out &= ~MOUSE_MSC_BUTTON2UP;
2133262417Shselasky	else if (buttons_in & MOUSE_BUTTON3DOWN)
2134262417Shselasky		buttons_out &= ~MOUSE_MSC_BUTTON3UP;
2135262417Shselasky
2136262417Shselasky	DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n",
2137262417Shselasky	    dx, dy, buttons_out);
2138262417Shselasky
2139262417Shselasky	/* Encode the mouse data in standard format; refer to mouse(4) */
2140262417Shselasky	buf[0] = sc->sc_mode.syncmask[1];
2141262417Shselasky	buf[0] |= buttons_out;
2142262417Shselasky	buf[1] = dx >> 1;
2143262417Shselasky	buf[2] = dy >> 1;
2144262417Shselasky	buf[3] = dx - (dx >> 1);
2145262417Shselasky	buf[4] = dy - (dy >> 1);
2146262417Shselasky	/* Encode extra bytes for level 1 */
2147262417Shselasky	if (sc->sc_mode.level == 1) {
2148262417Shselasky		buf[5] = dz >> 1;
2149262417Shselasky		buf[6] = dz - (dz >> 1);
2150262417Shselasky		buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
2151262417Shselasky	}
2152262417Shselasky
2153262417Shselasky	usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
2154262417Shselasky	    sc->sc_mode.packetsize, 1);
2155262417Shselasky}
2156262417Shselasky
2157199086Srpaulostatic int
2158199086Srpauloatp_probe(device_t self)
2159199086Srpaulo{
2160199086Srpaulo	struct usb_attach_arg *uaa = device_get_ivars(self);
2161199086Srpaulo
2162199086Srpaulo	if (uaa->usb_mode != USB_MODE_HOST)
2163199086Srpaulo		return (ENXIO);
2164199086Srpaulo
2165262417Shselasky	if (uaa->info.bInterfaceClass != UICLASS_HID)
2166199086Srpaulo		return (ENXIO);
2167262417Shselasky	/*
2168262417Shselasky	 * Note: for some reason, the check
2169262417Shselasky	 * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true
2170262417Shselasky	 * for wellspring trackpads, so we've removed it from the common path.
2171262417Shselasky	 */
2172199086Srpaulo
2173262417Shselasky	if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0)
2174262417Shselasky		return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ?
2175262417Shselasky			0 : ENXIO);
2176262417Shselasky
2177262417Shselasky	if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0)
2178262417Shselasky		if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX)
2179262417Shselasky			return (0);
2180262417Shselasky
2181262417Shselasky	return (ENXIO);
2182199086Srpaulo}
2183199086Srpaulo
2184199086Srpaulostatic int
2185199086Srpauloatp_attach(device_t dev)
2186199086Srpaulo{
2187262417Shselasky	struct atp_softc      *sc  = device_get_softc(dev);
2188199086Srpaulo	struct usb_attach_arg *uaa = device_get_ivars(dev);
2189199086Srpaulo	usb_error_t            err;
2190262417Shselasky	void *descriptor_ptr = NULL;
2191262417Shselasky	uint16_t descriptor_len;
2192262417Shselasky	unsigned long di;
2193199086Srpaulo
2194199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc);
2195199086Srpaulo
2196199086Srpaulo	sc->sc_dev        = dev;
2197199086Srpaulo	sc->sc_usb_device = uaa->device;
2198199086Srpaulo
2199262417Shselasky	/* Get HID descriptor */
2200262417Shselasky	if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr,
2201262417Shselasky	    &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) !=
2202262417Shselasky	    USB_ERR_NORMAL_COMPLETION)
2203262417Shselasky		return (ENXIO);
2204262417Shselasky
2205262417Shselasky	/* Get HID report descriptor length */
2206262417Shselasky	sc->sc_expected_sensor_data_len = hid_report_size(descriptor_ptr,
2207262417Shselasky	    descriptor_len, hid_input, NULL);
2208262417Shselasky	free(descriptor_ptr, M_TEMP);
2209262417Shselasky
2210262417Shselasky	if ((sc->sc_expected_sensor_data_len <= 0) ||
2211262417Shselasky	    (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) {
2212262417Shselasky		DPRINTF("atp_attach: datalength invalid or too large: %d\n",
2213262417Shselasky			sc->sc_expected_sensor_data_len);
2214262417Shselasky		return (ENXIO);
2215262417Shselasky	}
2216262417Shselasky
2217199086Srpaulo	/*
2218199086Srpaulo	 * By default the touchpad behaves like an HID device, sending
2219199086Srpaulo	 * packets with reportID = 2. Such reports contain only
2220199086Srpaulo	 * limited information--they encode movement deltas and button
2221199086Srpaulo	 * events,--but do not include data from the pressure
2222199086Srpaulo	 * sensors. The device input mode can be switched from HID
2223199086Srpaulo	 * reports to raw sensor data using vendor-specific USB
2224262417Shselasky	 * control commands.
2225199086Srpaulo	 */
2226262417Shselasky	if ((err = atp_set_device_mode(sc, RAW_SENSOR_MODE)) != 0) {
2227199086Srpaulo		DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err);
2228199086Srpaulo		return (ENXIO);
2229199086Srpaulo	}
2230199086Srpaulo
2231199086Srpaulo	mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE);
2232199086Srpaulo
2233262417Shselasky	di = USB_GET_DRIVER_INFO(uaa);
2234262417Shselasky
2235262417Shselasky	sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di);
2236262417Shselasky
2237262417Shselasky	switch(sc->sc_family) {
2238262417Shselasky	case TRACKPAD_FAMILY_FOUNTAIN_GEYSER:
2239262417Shselasky		sc->sc_params =
2240262417Shselasky		    &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
2241262417Shselasky		sc->sensor_data_interpreter = fg_interpret_sensor_data;
2242262417Shselasky		break;
2243262417Shselasky	case TRACKPAD_FAMILY_WELLSPRING:
2244262417Shselasky		sc->sc_params =
2245262417Shselasky		    &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
2246262417Shselasky		sc->sensor_data_interpreter = wsp_interpret_sensor_data;
2247262417Shselasky		break;
2248262417Shselasky	default:
2249262417Shselasky		goto detach;
2250262417Shselasky	}
2251262417Shselasky
2252199086Srpaulo	err = usbd_transfer_setup(uaa->device,
2253262417Shselasky	    &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_config,
2254199086Srpaulo	    ATP_N_TRANSFER, sc, &sc->sc_mutex);
2255199086Srpaulo	if (err) {
2256199086Srpaulo		DPRINTF("error=%s\n", usbd_errstr(err));
2257199086Srpaulo		goto detach;
2258199086Srpaulo	}
2259199086Srpaulo
2260199086Srpaulo	if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
2261262417Shselasky	    &atp_fifo_methods, &sc->sc_fifo,
2262262417Shselasky	    device_get_unit(dev), -1, uaa->info.bIfaceIndex,
2263262417Shselasky	    UID_ROOT, GID_OPERATOR, 0644)) {
2264199086Srpaulo		goto detach;
2265199086Srpaulo	}
2266199086Srpaulo
2267199086Srpaulo	device_set_usb_desc(dev);
2268199086Srpaulo
2269199086Srpaulo	sc->sc_hw.buttons       = 3;
2270199086Srpaulo	sc->sc_hw.iftype        = MOUSE_IF_USB;
2271199086Srpaulo	sc->sc_hw.type          = MOUSE_PAD;
2272199086Srpaulo	sc->sc_hw.model         = MOUSE_MODEL_GENERIC;
2273199086Srpaulo	sc->sc_hw.hwid          = 0;
2274199086Srpaulo	sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
2275199086Srpaulo	sc->sc_mode.rate        = -1;
2276199086Srpaulo	sc->sc_mode.resolution  = MOUSE_RES_UNKNOWN;
2277199086Srpaulo	sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
2278199086Srpaulo	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
2279199086Srpaulo	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
2280262417Shselasky	sc->sc_mode.accelfactor = 0;
2281262417Shselasky	sc->sc_mode.level       = 0;
2282199086Srpaulo
2283199086Srpaulo	sc->sc_state            = 0;
2284262417Shselasky	sc->sc_ibtn             = 0;
2285199086Srpaulo
2286262417Shselasky	callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0);
2287199086Srpaulo
2288199086Srpaulo	return (0);
2289199086Srpaulo
2290199086Srpaulodetach:
2291199086Srpaulo	atp_detach(dev);
2292199086Srpaulo	return (ENOMEM);
2293199086Srpaulo}
2294199086Srpaulo
2295199086Srpaulostatic int
2296199086Srpauloatp_detach(device_t dev)
2297199086Srpaulo{
2298199086Srpaulo	struct atp_softc *sc;
2299199086Srpaulo
2300199086Srpaulo	sc = device_get_softc(dev);
2301262417Shselasky	atp_set_device_mode(sc, HID_MODE);
2302262417Shselasky
2303262417Shselasky	mtx_lock(&sc->sc_mutex);
2304262417Shselasky	callout_drain(&sc->sc_callout);
2305262417Shselasky	if (sc->sc_state & ATP_ENABLED)
2306199086Srpaulo		atp_disable(sc);
2307262417Shselasky	mtx_unlock(&sc->sc_mutex);
2308199086Srpaulo
2309199086Srpaulo	usb_fifo_detach(&sc->sc_fifo);
2310199086Srpaulo
2311199086Srpaulo	usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER);
2312199086Srpaulo
2313199086Srpaulo	mtx_destroy(&sc->sc_mutex);
2314199086Srpaulo
2315199086Srpaulo	return (0);
2316199086Srpaulo}
2317199086Srpaulo
2318199086Srpaulostatic void
2319199086Srpauloatp_intr(struct usb_xfer *xfer, usb_error_t error)
2320199086Srpaulo{
2321199086Srpaulo	struct atp_softc      *sc = usbd_xfer_softc(xfer);
2322199086Srpaulo	struct usb_page_cache *pc;
2323262417Shselasky	int len;
2324199086Srpaulo
2325199086Srpaulo	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
2326199086Srpaulo
2327199086Srpaulo	switch (USB_GET_STATE(xfer)) {
2328199086Srpaulo	case USB_ST_TRANSFERRED:
2329199086Srpaulo		pc = usbd_xfer_get_frame(xfer, 0);
2330262417Shselasky		usbd_copy_out(pc, 0, sc->sc_sensor_data, len);
2331262417Shselasky		if (len < sc->sc_expected_sensor_data_len) {
2332262417Shselasky			/* make sure we don't process old data */
2333262417Shselasky			memset(sc->sc_sensor_data + len, 0,
2334262417Shselasky			    sc->sc_expected_sensor_data_len - len);
2335199086Srpaulo		}
2336199086Srpaulo
2337262417Shselasky		sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED |
2338262417Shselasky		    MOUSE_POSCHANGED);
2339262417Shselasky		sc->sc_status.obutton = sc->sc_status.button;
2340199086Srpaulo
2341262417Shselasky		(sc->sensor_data_interpreter)(sc, len);
2342199086Srpaulo
2343199086Srpaulo		if (sc->sc_status.button != 0) {
2344199086Srpaulo			/* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */
2345199086Srpaulo			sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
2346199086Srpaulo		} else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) {
2347199086Srpaulo			/* Assume a button-press with DOUBLE_TAP_N_DRAG. */
2348199086Srpaulo			sc->sc_status.button = MOUSE_BUTTON1DOWN;
2349199086Srpaulo		}
2350199086Srpaulo
2351199086Srpaulo		sc->sc_status.flags |=
2352262417Shselasky		    sc->sc_status.button ^ sc->sc_status.obutton;
2353199086Srpaulo		if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) {
2354262417Shselasky		    DPRINTFN(ATP_LLEVEL_INFO, "button %s\n",
2355262417Shselasky			((sc->sc_status.button & MOUSE_BUTTON1DOWN) ?
2356262417Shselasky			"pressed" : "released"));
2357199086Srpaulo		}
2358199086Srpaulo
2359262417Shselasky		if (sc->sc_status.flags & (MOUSE_POSCHANGED |
2360262417Shselasky		    MOUSE_STDBUTTONSCHANGED)) {
2361199086Srpaulo
2362262417Shselasky			atp_stroke_t *strokep;
2363262417Shselasky			u_int8_t n_movements = 0;
2364262417Shselasky			int dx = 0;
2365262417Shselasky			int dy = 0;
2366262439Shselasky			int dz = 0;
2367199086Srpaulo
2368262417Shselasky			TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
2369262439Shselasky				if (strokep->flags & ATSF_ZOMBIE)
2370262439Shselasky					continue;
2371262439Shselasky
2372262417Shselasky				dx += strokep->movement_dx;
2373262417Shselasky				dy += strokep->movement_dy;
2374262417Shselasky				if (strokep->movement_dx ||
2375262417Shselasky				    strokep->movement_dy)
2376199086Srpaulo					n_movements++;
2377199086Srpaulo			}
2378199086Srpaulo
2379262417Shselasky			/* average movement if multiple strokes record motion.*/
2380262417Shselasky			if (n_movements > 1) {
2381262417Shselasky				dx /= (int)n_movements;
2382262417Shselasky				dy /= (int)n_movements;
2383262417Shselasky			}
2384262417Shselasky
2385262439Shselasky			/* detect multi-finger vertical scrolls */
2386262439Shselasky			if (n_movements >= 2) {
2387262439Shselasky				boolean_t all_vertical_scrolls = true;
2388262439Shselasky				TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
2389262439Shselasky					if (strokep->flags & ATSF_ZOMBIE)
2390262439Shselasky						continue;
2391262439Shselasky
2392262439Shselasky					if (!atp_is_vertical_scroll(strokep))
2393262439Shselasky						all_vertical_scrolls = false;
2394262439Shselasky				}
2395262439Shselasky				if (all_vertical_scrolls) {
2396262439Shselasky					dz = dy;
2397262439Shselasky					dy = dx = 0;
2398262439Shselasky				}
2399262439Shselasky			}
2400262439Shselasky
2401199086Srpaulo			sc->sc_status.dx += dx;
2402199086Srpaulo			sc->sc_status.dy += dy;
2403262439Shselasky			sc->sc_status.dz += dz;
2404262439Shselasky			atp_add_to_queue(sc, dx, -dy, -dz, sc->sc_status.button);
2405199086Srpaulo		}
2406199086Srpaulo
2407199086Srpaulo	case USB_ST_SETUP:
2408199086Srpaulo	tr_setup:
2409199086Srpaulo		/* check if we can put more data into the FIFO */
2410262417Shselasky		if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
2411199086Srpaulo			usbd_xfer_set_frame_len(xfer, 0,
2412262417Shselasky			    sc->sc_expected_sensor_data_len);
2413199086Srpaulo			usbd_transfer_submit(xfer);
2414199086Srpaulo		}
2415199086Srpaulo		break;
2416199086Srpaulo
2417199086Srpaulo	default:                        /* Error */
2418199086Srpaulo		if (error != USB_ERR_CANCELLED) {
2419199086Srpaulo			/* try clear stall first */
2420199086Srpaulo			usbd_xfer_set_stall(xfer);
2421199086Srpaulo			goto tr_setup;
2422199086Srpaulo		}
2423199086Srpaulo		break;
2424199086Srpaulo	}
2425199086Srpaulo}
2426199086Srpaulo
2427199086Srpaulostatic void
2428199086Srpauloatp_start_read(struct usb_fifo *fifo)
2429199086Srpaulo{
2430199086Srpaulo	struct atp_softc *sc = usb_fifo_softc(fifo);
2431199086Srpaulo	int rate;
2432199086Srpaulo
2433199086Srpaulo	/* Check if we should override the default polling interval */
2434199086Srpaulo	rate = sc->sc_pollrate;
2435199086Srpaulo	/* Range check rate */
2436199086Srpaulo	if (rate > 1000)
2437199086Srpaulo		rate = 1000;
2438199086Srpaulo	/* Check for set rate */
2439199086Srpaulo	if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) {
2440199086Srpaulo		/* Stop current transfer, if any */
2441199086Srpaulo		usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
2442199086Srpaulo		/* Set new interval */
2443199086Srpaulo		usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate);
2444199086Srpaulo		/* Only set pollrate once */
2445199086Srpaulo		sc->sc_pollrate = 0;
2446199086Srpaulo	}
2447199086Srpaulo
2448199086Srpaulo	usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]);
2449199086Srpaulo}
2450199086Srpaulo
2451199086Srpaulostatic void
2452199086Srpauloatp_stop_read(struct usb_fifo *fifo)
2453199086Srpaulo{
2454199086Srpaulo	struct atp_softc *sc = usb_fifo_softc(fifo);
2455199086Srpaulo	usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
2456199086Srpaulo}
2457199086Srpaulo
2458199086Srpaulostatic int
2459199086Srpauloatp_open(struct usb_fifo *fifo, int fflags)
2460199086Srpaulo{
2461262417Shselasky	struct atp_softc *sc = usb_fifo_softc(fifo);
2462199086Srpaulo
2463262417Shselasky	/* check for duplicate open, should not happen */
2464262417Shselasky	if (sc->sc_fflags & fflags)
2465262417Shselasky		return (EBUSY);
2466262417Shselasky
2467262417Shselasky	/* check for first open */
2468262417Shselasky	if (sc->sc_fflags == 0) {
2469199086Srpaulo		int rc;
2470262417Shselasky		if ((rc = atp_enable(sc)) != 0)
2471262417Shselasky			return (rc);
2472262417Shselasky	}
2473199086Srpaulo
2474262417Shselasky	if (fflags & FREAD) {
2475199086Srpaulo		if (usb_fifo_alloc_buffer(fifo,
2476262417Shselasky		    ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) {
2477199086Srpaulo			return (ENOMEM);
2478199086Srpaulo		}
2479199086Srpaulo	}
2480199086Srpaulo
2481262417Shselasky	sc->sc_fflags |= (fflags & (FREAD | FWRITE));
2482199086Srpaulo	return (0);
2483199086Srpaulo}
2484199086Srpaulo
2485199086Srpaulostatic void
2486199086Srpauloatp_close(struct usb_fifo *fifo, int fflags)
2487199086Srpaulo{
2488262417Shselasky	struct atp_softc *sc = usb_fifo_softc(fifo);
2489262417Shselasky	if (fflags & FREAD)
2490262417Shselasky		usb_fifo_free_buffer(fifo);
2491199086Srpaulo
2492262417Shselasky	sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
2493262417Shselasky	if (sc->sc_fflags == 0) {
2494199086Srpaulo		atp_disable(sc);
2495199086Srpaulo	}
2496199086Srpaulo}
2497199086Srpaulo
2498262417Shselaskystatic int
2499199086Srpauloatp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
2500199086Srpaulo{
2501199086Srpaulo	struct atp_softc *sc = usb_fifo_softc(fifo);
2502199086Srpaulo	mousemode_t mode;
2503199086Srpaulo	int error = 0;
2504199086Srpaulo
2505199086Srpaulo	mtx_lock(&sc->sc_mutex);
2506199086Srpaulo
2507199086Srpaulo	switch(cmd) {
2508199086Srpaulo	case MOUSE_GETHWINFO:
2509199086Srpaulo		*(mousehw_t *)addr = sc->sc_hw;
2510199086Srpaulo		break;
2511199086Srpaulo	case MOUSE_GETMODE:
2512199086Srpaulo		*(mousemode_t *)addr = sc->sc_mode;
2513199086Srpaulo		break;
2514199086Srpaulo	case MOUSE_SETMODE:
2515199086Srpaulo		mode = *(mousemode_t *)addr;
2516199086Srpaulo
2517199086Srpaulo		if (mode.level == -1)
2518199086Srpaulo			/* Don't change the current setting */
2519199086Srpaulo			;
2520199086Srpaulo		else if ((mode.level < 0) || (mode.level > 1)) {
2521199086Srpaulo			error = EINVAL;
2522262417Shselasky			break;
2523199086Srpaulo		}
2524199086Srpaulo		sc->sc_mode.level = mode.level;
2525199086Srpaulo		sc->sc_pollrate   = mode.rate;
2526199086Srpaulo		sc->sc_hw.buttons = 3;
2527199086Srpaulo
2528199086Srpaulo		if (sc->sc_mode.level == 0) {
2529262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
2530262417Shselasky			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
2531199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
2532199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
2533199086Srpaulo		} else if (sc->sc_mode.level == 1) {
2534262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
2535262417Shselasky			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
2536199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
2537199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
2538199086Srpaulo		}
2539199086Srpaulo		atp_reset_buf(sc);
2540199086Srpaulo		break;
2541199086Srpaulo	case MOUSE_GETLEVEL:
2542199086Srpaulo		*(int *)addr = sc->sc_mode.level;
2543199086Srpaulo		break;
2544199086Srpaulo	case MOUSE_SETLEVEL:
2545262417Shselasky		if ((*(int *)addr < 0) || (*(int *)addr > 1)) {
2546199086Srpaulo			error = EINVAL;
2547262417Shselasky			break;
2548199086Srpaulo		}
2549199086Srpaulo		sc->sc_mode.level = *(int *)addr;
2550199086Srpaulo		sc->sc_hw.buttons = 3;
2551199086Srpaulo
2552199086Srpaulo		if (sc->sc_mode.level == 0) {
2553262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
2554262417Shselasky			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
2555199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
2556199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
2557199086Srpaulo		} else if (sc->sc_mode.level == 1) {
2558262417Shselasky			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
2559262417Shselasky			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
2560199086Srpaulo			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
2561199086Srpaulo			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
2562199086Srpaulo		}
2563199086Srpaulo		atp_reset_buf(sc);
2564199086Srpaulo		break;
2565199086Srpaulo	case MOUSE_GETSTATUS: {
2566199086Srpaulo		mousestatus_t *status = (mousestatus_t *)addr;
2567199086Srpaulo
2568199086Srpaulo		*status = sc->sc_status;
2569199086Srpaulo		sc->sc_status.obutton = sc->sc_status.button;
2570199086Srpaulo		sc->sc_status.button  = 0;
2571262417Shselasky		sc->sc_status.dx      = 0;
2572262417Shselasky		sc->sc_status.dy      = 0;
2573262417Shselasky		sc->sc_status.dz      = 0;
2574199086Srpaulo
2575199086Srpaulo		if (status->dx || status->dy || status->dz)
2576199086Srpaulo			status->flags |= MOUSE_POSCHANGED;
2577199086Srpaulo		if (status->button != status->obutton)
2578199086Srpaulo			status->flags |= MOUSE_BUTTONSCHANGED;
2579199086Srpaulo		break;
2580199086Srpaulo	}
2581262417Shselasky
2582199086Srpaulo	default:
2583199086Srpaulo		error = ENOTTY;
2584262417Shselasky		break;
2585199086Srpaulo	}
2586199086Srpaulo
2587199086Srpaulo	mtx_unlock(&sc->sc_mutex);
2588199086Srpaulo	return (error);
2589199086Srpaulo}
2590199086Srpaulo
2591199086Srpaulostatic int
2592199086Srpauloatp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS)
2593199086Srpaulo{
2594199086Srpaulo	int error;
2595199086Srpaulo	u_int tmp;
2596199086Srpaulo
2597199086Srpaulo	tmp = atp_mickeys_scale_factor;
2598199086Srpaulo	error = sysctl_handle_int(oidp, &tmp, 0, req);
2599199086Srpaulo	if (error != 0 || req->newptr == NULL)
2600199086Srpaulo		return (error);
2601199086Srpaulo
2602262417Shselasky	if (tmp == atp_mickeys_scale_factor)
2603199086Srpaulo		return (0);     /* no change */
2604262417Shselasky	if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR)))
2605262417Shselasky		return (EINVAL);
2606199086Srpaulo
2607199086Srpaulo	atp_mickeys_scale_factor = tmp;
2608199086Srpaulo	DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n",
2609199086Srpaulo	    ATP_DRIVER_NAME, tmp);
2610199086Srpaulo
2611199086Srpaulo	return (0);
2612199086Srpaulo}
2613199086Srpaulo
2614262417Shselaskystatic devclass_t atp_devclass;
2615262417Shselasky
2616199086Srpaulostatic device_method_t atp_methods[] = {
2617199086Srpaulo	DEVMETHOD(device_probe,  atp_probe),
2618199086Srpaulo	DEVMETHOD(device_attach, atp_attach),
2619199086Srpaulo	DEVMETHOD(device_detach, atp_detach),
2620262417Shselasky
2621262417Shselasky	DEVMETHOD_END
2622199086Srpaulo};
2623199086Srpaulo
2624199086Srpaulostatic driver_t atp_driver = {
2625262417Shselasky	.name    = ATP_DRIVER_NAME,
2626233774Shselasky	.methods = atp_methods,
2627262417Shselasky	.size    = sizeof(struct atp_softc)
2628199086Srpaulo};
2629199086Srpaulo
2630199086SrpauloDRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0);
2631199086SrpauloMODULE_DEPEND(atp, usb, 1, 1, 1);
2632212122SthompsaMODULE_VERSION(atp, 1);
2633292080SimpUSB_PNP_HOST_INFO(fg_devs);
2634292080SimpUSB_PNP_HOST_INFO(wsp_devs);
2635