svn commit: r199086 - in head/sys: conf dev/usb/input modules/usb modules/usb/atp

Rui Paulo rpaulo at FreeBSD.org
Mon Nov 9 15:59:10 UTC 2009


Author: rpaulo
Date: Mon Nov  9 15:59:09 2009
New Revision: 199086
URL: http://svn.freebsd.org/changeset/base/199086

Log:
  Driver for the Apple Touchpad present on MacBook (non-Pro & Pro).
  
  Submitted by:	Rohit Grover <rgrover1 at gmail.com>
  MFC after:	2 months

Added:
  head/sys/dev/usb/input/atp.c   (contents, props changed)
  head/sys/modules/usb/atp/
  head/sys/modules/usb/atp/Makefile   (contents, props changed)
Modified:
  head/sys/conf/files
  head/sys/modules/usb/Makefile

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Mon Nov  9 15:11:37 2009	(r199085)
+++ head/sys/conf/files	Mon Nov  9 15:59:09 2009	(r199086)
@@ -1698,6 +1698,7 @@ dev/usb/misc/udbp.c		optional udbp
 #
 # USB input drivers
 #
+dev/usb/input/atp.c		optional atp
 dev/usb/input/uhid.c		optional uhid
 dev/usb/input/ukbd.c		optional ukbd
 dev/usb/input/ums.c		optional ums

Added: head/sys/dev/usb/input/atp.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/usb/input/atp.c	Mon Nov  9 15:59:09 2009	(r199086)
@@ -0,0 +1,2047 @@
+/*-
+ * Copyright (c) 2009 Rohit Grover
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR atp_debug
+#include <dev/usb/usb_debug.h>
+
+#include <sys/mouse.h>
+
+#define ATP_DRIVER_NAME "atp"
+
+/*
+ * Driver specific options: the following options may be set by
+ * `options' statements in the kernel configuration file.
+ */
+
+/* The multiplier used to translate sensor reported positions to mickeys. */
+#ifndef ATP_SCALE_FACTOR
+#define ATP_SCALE_FACTOR 48
+#endif
+
+/*
+ * This is the age (in microseconds) beyond which a touch is
+ * considered to be a slide; and therefore a tap event isn't registered.
+ */
+#ifndef ATP_TOUCH_TIMEOUT
+#define ATP_TOUCH_TIMEOUT 125000
+#endif
+
+/*
+ * A double-tap followed by a single-finger slide is treated as a
+ * special gesture. The driver responds to this gesture by assuming a
+ * virtual button-press for the lifetime of the slide. The following
+ * threshold is the maximum time gap (in microseconds) between the two
+ * tap events preceding the slide for such a gesture.
+ */
+#ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD
+#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000
+#endif
+
+/*
+ * The device provides us only with pressure readings from an array of
+ * X and Y sensors; for our algorithms, we need to interpret groups
+ * (typically pairs) of X and Y readings as being related to a single
+ * finger stroke. We can relate X and Y readings based on their times
+ * of incidence. The coincidence window should be at least 10000us
+ * since it is used against values from getmicrotime(), which has a
+ * precision of around 10ms.
+ */
+#ifndef ATP_COINCIDENCE_THRESHOLD
+#define ATP_COINCIDENCE_THRESHOLD  40000 /* unit: microseconds */
+#if ATP_COINCIDENCE_THRESHOLD > 100000
+#error "ATP_COINCIDENCE_THRESHOLD too large"
+#endif
+#endif /* #ifndef ATP_COINCIDENCE_THRESHOLD */
+
+/*
+ * The wait duration (in microseconds) after losing a touch contact
+ * before zombied strokes are reaped and turned into button events.
+ */
+#define ATP_ZOMBIE_STROKE_REAP_WINDOW   50000
+#if ATP_ZOMBIE_STROKE_REAP_WINDOW > 100000
+#error "ATP_ZOMBIE_STROKE_REAP_WINDOW too large"
+#endif
+
+/* end of driver specific options */
+
+
+/* Tunables */
+SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB atp");
+
+#if USB_DEBUG
+enum atp_log_level {
+	ATP_LLEVEL_DISABLED = 0,
+	ATP_LLEVEL_ERROR,
+	ATP_LLEVEL_DEBUG,       /* for troubleshooting */
+	ATP_LLEVEL_INFO,        /* for diagnostics */
+};
+static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */
+SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RW,
+    &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
+#endif /* #if USB_DEBUG */
+
+static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
+SYSCTL_INT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RW, &atp_touch_timeout,
+    125000, "age threshold (in micros) for a touch");
+
+static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD;
+SYSCTL_INT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RW,
+    &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD,
+    "maximum time (in micros) between a double-tap");
+
+static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR;
+static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS);
+SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RW,
+    &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor),
+    atp_sysctl_scale_factor_handler, "IU", "movement scale factor");
+
+static u_int atp_small_movement_threshold = ATP_SCALE_FACTOR >> 3;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RW,
+    &atp_small_movement_threshold, ATP_SCALE_FACTOR >> 3,
+    "the small movement black-hole for filtering noise");
+/*
+ * The movement threshold for a stroke; this is the maximum difference
+ * in position which will be resolved as a continuation of a stroke
+ * component.
+ */
+static u_int atp_max_delta_mickeys = ((3 * ATP_SCALE_FACTOR) >> 1);
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, max_delta_mickeys, CTLFLAG_RW,
+    &atp_max_delta_mickeys, ((3 * ATP_SCALE_FACTOR) >> 1),
+    "max. mickeys-delta which will match against an existing stroke");
+/*
+ * Strokes which accumulate at least this amount of absolute movement
+ * from the aggregate of their components are considered as
+ * slides. Unit: mickeys.
+ */
+static u_int atp_slide_min_movement = (ATP_SCALE_FACTOR >> 3);
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RW,
+    &atp_slide_min_movement, (ATP_SCALE_FACTOR >> 3),
+    "strokes with at least this amt. of movement are considered slides");
+
+/*
+ * The minimum age of a stroke for it to be considered mature; this
+ * helps filter movements (noise) from immature strokes. Units: interrupts.
+ */
+static u_int atp_stroke_maturity_threshold = 2;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RW,
+    &atp_stroke_maturity_threshold, 2,
+    "the minimum age of a stroke for it to be considered mature");
+
+/* Accept pressure readings from sensors only if above this value. */
+static u_int atp_sensor_noise_threshold = 2;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, sensor_noise_threshold, CTLFLAG_RW,
+    &atp_sensor_noise_threshold, 2,
+    "accept pressure readings from sensors only if above this value");
+
+/* Ignore pressure spans with cumulative press. below this value. */
+static u_int atp_pspan_min_cum_pressure = 10;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_min_cum_pressure, CTLFLAG_RW,
+    &atp_pspan_min_cum_pressure, 10,
+    "ignore pressure spans with cumulative press. below this value");
+
+/* Maximum allowed width for pressure-spans.*/
+static u_int atp_pspan_max_width = 4;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_max_width, CTLFLAG_RW,
+    &atp_pspan_max_width, 4,
+    "maximum allowed width (in sensors) for pressure-spans");
+
+
+/* Define the various flavours of devices supported by this driver. */
+enum {
+	ATP_DEV_PARAMS_0,
+	ATP_N_DEV_PARAMS
+};
+struct atp_dev_params {
+	u_int            data_len;   /* for sensor data */
+	u_int            n_xsensors;
+	u_int            n_ysensors;
+} atp_dev_params[ATP_N_DEV_PARAMS] = {
+	[ATP_DEV_PARAMS_0] = {
+		.data_len   = 64,
+		.n_xsensors = 20,
+		.n_ysensors = 10
+	},
+};
+
+static const struct usb_device_id atp_devs[] = {
+	/* Core Duo MacBook & MacBook Pro */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0217, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0218, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0219, ATP_DEV_PARAMS_0) },
+
+	/* Core2 Duo MacBook & MacBook Pro */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021a, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021b, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021c, ATP_DEV_PARAMS_0) },
+
+	/* Core2 Duo MacBook3,1 */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0229, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x022a, ATP_DEV_PARAMS_0) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x022b, ATP_DEV_PARAMS_0) },
+};
+
+/*
+ * The following structure captures the state of a pressure span along
+ * an axis. Each contact with the touchpad results in separate
+ * pressure spans along the two axes.
+ */
+typedef struct atp_pspan {
+	u_int width;   /* in units of sensors */
+	u_int cum;     /* cumulative compression (from all sensors) */
+	u_int cog;     /* center of gravity */
+	u_int loc;     /* location (scaled using the mickeys factor) */
+	boolean_t matched; /* to track pspans as they match against strokes. */
+} atp_pspan;
+
+typedef enum atp_stroke_type {
+	ATP_STROKE_TOUCH,
+	ATP_STROKE_SLIDE,
+} atp_stroke_type;
+
+#define ATP_MAX_PSPANS_PER_AXIS 3
+
+typedef struct atp_stroke_component {
+	/* Fields encapsulating the pressure-span. */
+	u_int loc;              /* location (scaled) */
+	u_int cum_pressure;     /* cumulative compression */
+	u_int max_cum_pressure; /* max cumulative compression */
+	boolean_t matched; /*to track components as they match against pspans.*/
+
+	/* Fields containing information about movement. */
+	int   delta_mickeys;    /* change in location (un-smoothened movement)*/
+	int   pending;          /* cum. of pending short movements */
+	int   movement;         /* current smoothened movement */
+} atp_stroke_component;
+
+typedef enum atp_axis {
+	X = 0,
+	Y = 1
+} atp_axis;
+
+#define ATP_MAX_STROKES         (2 * ATP_MAX_PSPANS_PER_AXIS)
+
+/*
+ * The following structure captures a finger contact with the
+ * touchpad. A stroke comprises two p-span components and some state.
+ */
+typedef struct atp_stroke {
+	atp_stroke_type      type;
+	struct timeval       ctime; /* create time; for coincident siblings. */
+	u_int                age;   /*
+				     * Unit: interrupts; we maintain
+				     * this value in addition to
+				     * 'ctime' in order to avoid the
+				     * expensive call to microtime()
+				     * at every interrupt.
+				     */
+
+	atp_stroke_component components[2];
+	u_int                velocity_squared; /*
+						* Average magnitude (squared)
+						* of recent velocity.
+						*/
+	u_int                cum_movement; /* cum. absolute movement so far */
+
+	uint32_t             flags;  /* the state of this stroke */
+#define ATSF_ZOMBIE          0x1
+} atp_stroke;
+
+#define ATP_FIFO_BUF_SIZE        8 /* bytes */
+#define ATP_FIFO_QUEUE_MAXLEN   50 /* units */
+
+enum {
+	ATP_INTR_DT,
+	ATP_N_TRANSFER,
+};
+
+struct atp_softc {
+	device_t               sc_dev;
+	struct usb_device     *sc_usb_device;
+#define MODE_LENGTH 8
+	char                   sc_mode_bytes[MODE_LENGTH]; /* device mode */
+	struct mtx             sc_mutex; /* for synchronization */
+	struct usb_xfer       *sc_xfer[ATP_N_TRANSFER];
+	struct usb_fifo_sc     sc_fifo;
+
+	struct atp_dev_params *sc_params;
+
+	mousehw_t              sc_hw;
+	mousemode_t            sc_mode;
+	u_int                  sc_pollrate;
+	mousestatus_t          sc_status;
+	u_int                  sc_state;
+#define ATP_ENABLED            0x01
+#define ATP_ZOMBIES_EXIST      0x02
+#define ATP_DOUBLE_TAP_DRAG    0x04
+
+	u_int                  sc_left_margin;
+	u_int                  sc_right_margin;
+
+	atp_stroke             sc_strokes[ATP_MAX_STROKES];
+	u_int                  sc_n_strokes;
+
+	int8_t                *sensor_data; /* from interrupt packet */
+	int                   *base_x;      /* base sensor readings */
+	int                   *base_y;
+	int                   *cur_x;       /* current sensor readings */
+	int                   *cur_y;
+	int                   *pressure_x;  /* computed pressures */
+	int                   *pressure_y;
+
+	u_int                  sc_idlecount; /* preceding idle interrupts */
+#define ATP_IDLENESS_THRESHOLD 10
+
+	struct timeval         sc_reap_time;
+	struct timeval         sc_reap_ctime; /*ctime of siblings to be reaped*/
+};
+
+/*
+ * The last byte of the sensor data contains status bits; the
+ * following values define the meanings of these bits.
+ */
+enum atp_status_bits {
+	ATP_STATUS_BUTTON      = (uint8_t)0x01, /* The button was pressed */
+	ATP_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
+};
+
+typedef enum interface_mode {
+	RAW_SENSOR_MODE = (uint8_t)0x04,
+	HID_MODE        = (uint8_t)0x08
+} interface_mode;
+
+/*
+ * function prototypes
+ */
+static usb_fifo_cmd_t   atp_start_read;
+static usb_fifo_cmd_t   atp_stop_read;
+static usb_fifo_open_t  atp_open;
+static usb_fifo_close_t atp_close;
+static usb_fifo_ioctl_t atp_ioctl;
+
+static struct usb_fifo_methods atp_fifo_methods = {
+	.f_open       = &atp_open,
+	.f_close      = &atp_close,
+	.f_ioctl      = &atp_ioctl,
+	.f_start_read = &atp_start_read,
+	.f_stop_read  = &atp_stop_read,
+	.basename[0]  = ATP_DRIVER_NAME,
+};
+
+/* device initialization and shutdown */
+static usb_error_t   atp_req_get_report(struct usb_device *udev, void *data);
+static int           atp_set_device_mode(device_t dev, interface_mode mode);
+static int           atp_enable(struct atp_softc *sc);
+static void          atp_disable(struct atp_softc *sc);
+static int           atp_softc_populate(struct atp_softc *);
+static void          atp_softc_unpopulate(struct atp_softc *);
+
+/* sensor interpretation */
+static __inline void atp_interpret_sensor_data(const int8_t *, u_int, u_int,
+			 int *);
+static __inline void atp_get_pressures(int *, const int *, const int *, int);
+static void          atp_detect_pspans(int *, u_int, u_int, atp_pspan *,
+			 u_int *);
+
+/* movement detection */
+static boolean_t     atp_match_stroke_component(atp_stroke_component *,
+			 const atp_pspan *);
+static void          atp_match_strokes_against_pspans(struct atp_softc *,
+			 atp_axis, atp_pspan *, u_int, u_int);
+static boolean_t     atp_update_strokes(struct atp_softc *,
+			 atp_pspan *, u_int, atp_pspan *, u_int);
+static __inline void atp_add_stroke(struct atp_softc *, const atp_pspan *,
+			 const atp_pspan *);
+static void          atp_add_new_strokes(struct atp_softc *, atp_pspan *,
+			 u_int, atp_pspan *, u_int);
+static void          atp_advance_stroke_state(struct atp_softc *,
+			 atp_stroke *, boolean_t *);
+static void          atp_terminate_stroke(struct atp_softc *, u_int);
+static __inline boolean_t atp_stroke_has_small_movement(const atp_stroke *);
+static __inline void atp_update_pending_mickeys(atp_stroke_component *);
+static void          atp_compute_smoothening_scale_ratio(atp_stroke *, int *,
+			 int *);
+static boolean_t     atp_compute_stroke_movement(atp_stroke *);
+
+/* tap detection */
+static __inline void atp_setup_reap_time(struct atp_softc *, struct timeval *);
+static void          atp_reap_zombies(struct atp_softc *, u_int *, u_int *);
+
+/* updating fifo */
+static void          atp_reset_buf(struct atp_softc *sc);
+static void          atp_add_to_queue(struct atp_softc *, int, int, uint32_t);
+
+
+usb_error_t
+atp_req_get_report(struct usb_device *udev, void *data)
+{
+	struct usb_device_request req;
+
+	req.bmRequestType = UT_READ_CLASS_INTERFACE;
+	req.bRequest = UR_GET_REPORT;
+	USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
+	USETW(req.wIndex, 0);
+	USETW(req.wLength, MODE_LENGTH);
+
+	return (usbd_do_request(udev, NULL /* mutex */, &req, data));
+}
+
+static int
+atp_set_device_mode(device_t dev, interface_mode mode)
+{
+	struct atp_softc     *sc;
+	usb_device_request_t  req;
+	usb_error_t           err;
+
+	if ((mode != RAW_SENSOR_MODE) && (mode != HID_MODE))
+		return (ENXIO);
+
+	sc = device_get_softc(dev);
+
+	sc->sc_mode_bytes[0] = mode;
+	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.bRequest = UR_SET_REPORT;
+	USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
+	USETW(req.wIndex, 0);
+	USETW(req.wLength, MODE_LENGTH);
+	err = usbd_do_request(sc->sc_usb_device, NULL, &req, sc->sc_mode_bytes);
+	if (err != USB_ERR_NORMAL_COMPLETION)
+		return (ENXIO);
+
+	return (0);
+}
+
+static int
+atp_enable(struct atp_softc *sc)
+{
+	/* Allocate the dynamic buffers */
+	if (atp_softc_populate(sc) != 0) {
+		atp_softc_unpopulate(sc);
+		return (ENOMEM);
+	}
+
+	/* reset status */
+	memset(sc->sc_strokes, 0, sizeof(sc->sc_strokes));
+	sc->sc_n_strokes = 0;
+	memset(&sc->sc_status, 0, sizeof(sc->sc_status));
+	sc->sc_idlecount = 0;
+	sc->sc_state |= ATP_ENABLED;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
+	return (0);
+}
+
+static void
+atp_disable(struct atp_softc *sc)
+{
+	atp_softc_unpopulate(sc);
+
+	sc->sc_state &= ~ATP_ENABLED;
+	DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
+}
+
+/* Allocate dynamic memory for some fields in softc. */
+static int
+atp_softc_populate(struct atp_softc *sc)
+{
+	const struct atp_dev_params *params = sc->sc_params;
+
+	if (params == NULL) {
+		DPRINTF("params uninitialized!\n");
+		return (ENXIO);
+	}
+	if (params->data_len) {
+		sc->sensor_data = malloc(params->data_len * sizeof(int8_t),
+		    M_USB, M_WAITOK);
+		if (sc->sensor_data == NULL) {
+			DPRINTF("mem for sensor_data\n");
+			return (ENXIO);
+		}
+	}
+
+	if (params->n_xsensors != 0) {
+		sc->base_x = malloc(params->n_xsensors * sizeof(*(sc->base_x)),
+		    M_USB, M_WAITOK);
+		if (sc->base_x == NULL) {
+			DPRINTF("mem for sc->base_x\n");
+			return (ENXIO);
+		}
+
+		sc->cur_x = malloc(params->n_xsensors * sizeof(*(sc->cur_x)),
+		    M_USB, M_WAITOK);
+		if (sc->cur_x == NULL) {
+			DPRINTF("mem for sc->cur_x\n");
+			return (ENXIO);
+		}
+
+		sc->pressure_x =
+			malloc(params->n_xsensors * sizeof(*(sc->pressure_x)),
+			    M_USB, M_WAITOK);
+		if (sc->pressure_x == NULL) {
+			DPRINTF("mem. for pressure_x\n");
+			return (ENXIO);
+		}
+	}
+
+	if (params->n_ysensors != 0) {
+		sc->base_y = malloc(params->n_ysensors * sizeof(*(sc->base_y)),
+		    M_USB, M_WAITOK);
+		if (sc->base_y == NULL) {
+			DPRINTF("mem for base_y\n");
+			return (ENXIO);
+		}
+
+		sc->cur_y = malloc(params->n_ysensors * sizeof(*(sc->cur_y)),
+		    M_USB, M_WAITOK);
+		if (sc->cur_y == NULL) {
+			DPRINTF("mem for cur_y\n");
+			return (ENXIO);
+		}
+
+		sc->pressure_y =
+			malloc(params->n_ysensors * sizeof(*(sc->pressure_y)),
+			    M_USB, M_WAITOK);
+		if (sc->pressure_y == NULL) {
+			DPRINTF("mem. for pressure_y\n");
+			return (ENXIO);
+		}
+	}
+
+	return (0);
+}
+
+/* Free dynamic memory allocated for some fields in softc. */
+static void
+atp_softc_unpopulate(struct atp_softc *sc)
+{
+	const struct atp_dev_params *params = sc->sc_params;
+
+	if (params == NULL) {
+		return;
+	}
+	if (params->n_xsensors != 0) {
+		if (sc->base_x != NULL) {
+			free(sc->base_x, M_USB);
+			sc->base_x = NULL;
+		}
+
+		if (sc->cur_x != NULL) {
+			free(sc->cur_x, M_USB);
+			sc->cur_x = NULL;
+		}
+
+		if (sc->pressure_x != NULL) {
+			free(sc->pressure_x, M_USB);
+			sc->pressure_x = NULL;
+		}
+	}
+	if (params->n_ysensors != 0) {
+		if (sc->base_y != NULL) {
+			free(sc->base_y, M_USB);
+			sc->base_y = NULL;
+		}
+
+		if (sc->cur_y != NULL) {
+			free(sc->cur_y, M_USB);
+			sc->cur_y = NULL;
+		}
+
+		if (sc->pressure_y != NULL) {
+			free(sc->pressure_y, M_USB);
+			sc->pressure_y = NULL;
+		}
+	}
+	if (sc->sensor_data != NULL) {
+		free(sc->sensor_data, M_USB);
+		sc->sensor_data = NULL;
+	}
+}
+
+/*
+ * Interpret the data from the X and Y pressure sensors. This function
+ * is called separately for the X and Y sensor arrays. The data in the
+ * USB packet is laid out in the following manner:
+ *
+ * sensor_data:
+ *            --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4
+ *  indices:   0  1  2  3  4  5  6  7  8 ...  15  ... 20 21 22 23 24
+ *
+ * '--' (in the above) indicates that the value is unimportant.
+ *
+ * Information about the above layout was obtained from the
+ * implementation of the AppleTouch driver in Linux.
+ *
+ * parameters:
+ *   sensor_data
+ *       raw sensor data from the USB packet.
+ *   num
+ *       The number of elements in the array 'arr'.
+ *   di_start
+ *       The index of the first data element to be interpreted for
+ *       this sensor array--i.e. when called to interpret the Y
+ *       sensors, di_start passed in as 2, which is the index of Y1 in
+ *       the raw data.
+ *   arr
+ *       The array to be initialized with the readings.
+ */
+static __inline void
+atp_interpret_sensor_data(const int8_t *sensor_data, u_int num, u_int di_start,
+    int	*arr)
+{
+	u_int i;
+	u_int di;   /* index into sensor data */
+
+	for (i = 0, di = di_start; i < num; /* empty */ ) {
+		arr[i++] = sensor_data[di++];
+		arr[i++] = sensor_data[di++];
+		di++;
+	}
+}
+
+static __inline void
+atp_get_pressures(int *p, const int *cur, const int *base, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++) {
+		p[i] = cur[i] - base[i];
+		if (p[i] > 127)
+			p[i] -= 256;
+		if (p[i] < -127)
+			p[i] += 256;
+		if (p[i] < 0)
+			p[i] = 0;
+
+		/*
+		 * Shave off pressures below the noise-pressure
+		 * threshold; this will reduce the contribution from
+		 * lower pressure readings.
+		 */
+		if (p[i] <= atp_sensor_noise_threshold)
+			p[i] = 0; /* filter away noise */
+		else
+			p[i] -= atp_sensor_noise_threshold;
+	}
+}
+
+static void
+atp_detect_pspans(int *p, u_int num_sensors,
+    u_int       max_spans, /* max # of pspans permitted */
+    atp_pspan  *spans,     /* finger spans */
+    u_int      *nspans_p)  /* num spans detected */
+{
+	u_int i;
+	int   maxp;             /* max pressure seen within a span */
+	u_int num_spans = 0;
+
+	enum atp_pspan_state {
+		ATP_PSPAN_INACTIVE,
+		ATP_PSPAN_INCREASING,
+		ATP_PSPAN_DECREASING,
+	} state; /* state of the pressure span */
+
+	/*
+	 * The following is a simple state machine to track
+	 * the phase of the pressure span.
+	 */
+	memset(spans, 0, max_spans * sizeof(atp_pspan));
+	maxp = 0;
+	state = ATP_PSPAN_INACTIVE;
+	for (i = 0; i < num_sensors; i++) {
+		if (num_spans >= max_spans)
+			break;
+
+		if (p[i] == 0) {
+			if (state == ATP_PSPAN_INACTIVE) {
+				/*
+				 * There is no pressure information for this
+				 * sensor, and we aren't tracking a finger.
+				 */
+				continue;
+			} else {
+				state = ATP_PSPAN_INACTIVE;
+				maxp = 0;
+				num_spans++;
+			}
+		} else {
+			switch (state) {
+			case ATP_PSPAN_INACTIVE:
+				state = ATP_PSPAN_INCREASING;
+				maxp  = p[i];
+				break;
+
+			case ATP_PSPAN_INCREASING:
+				if (p[i] > maxp)
+					maxp = p[i];
+				else if (p[i] <= (maxp >> 1))
+					state = ATP_PSPAN_DECREASING;
+				break;
+
+			case ATP_PSPAN_DECREASING:
+				if (p[i] > p[i - 1]) {
+					/*
+					 * This is the beginning of
+					 * another span; change state
+					 * to give the appearance that
+					 * we're starting from an
+					 * inactive span, and then
+					 * re-process this reading in
+					 * the next iteration.
+					 */
+					num_spans++;
+					state = ATP_PSPAN_INACTIVE;
+					maxp  = 0;
+					i--;
+					continue;
+				}
+				break;
+			}
+
+			/* Update the finger span with this reading. */
+			spans[num_spans].width++;
+			spans[num_spans].cum += p[i];
+			spans[num_spans].cog += p[i] * (i + 1);
+		}
+	}
+	if (state != ATP_PSPAN_INACTIVE)
+		num_spans++;    /* close the last finger span */
+
+	/* post-process the spans */
+	for (i = 0; i < num_spans; i++) {
+		/* filter away unwanted pressure spans */
+		if ((spans[i].cum < atp_pspan_min_cum_pressure) ||
+		    (spans[i].width > atp_pspan_max_width)) {
+			if ((i + 1) < num_spans) {
+				memcpy(&spans[i], &spans[i + 1],
+				    (num_spans - i - 1) * sizeof(atp_pspan));
+				i--;
+			}
+			num_spans--;
+			continue;
+		}
+
+		/* compute this span's representative location */
+		spans[i].loc = spans[i].cog * atp_mickeys_scale_factor /
+			spans[i].cum;
+
+		spans[i].matched = FALSE; /* not yet matched against a stroke */
+	}
+
+	*nspans_p = num_spans;
+}
+
+/*
+ * Match a pressure-span against a stroke-component. If there is a
+ * match, update the component's state and return TRUE.
+ */
+static boolean_t
+atp_match_stroke_component(atp_stroke_component *component,
+    const atp_pspan *pspan)
+{
+	int delta_mickeys = pspan->loc - component->loc;
+
+	if (abs(delta_mickeys) > atp_max_delta_mickeys)
+		return (FALSE); /* the finger span is too far out; no match */
+
+	component->loc          = pspan->loc;
+	component->cum_pressure = pspan->cum;
+	if (pspan->cum > component->max_cum_pressure)
+		component->max_cum_pressure = pspan->cum;
+
+	/*
+	 * If the cumulative pressure drops below a quarter of the max,
+	 * then disregard the component's movement.
+	 */
+	if (component->cum_pressure < (component->max_cum_pressure >> 2))
+		delta_mickeys = 0;
+
+	component->delta_mickeys = delta_mickeys;
+	return (TRUE);
+}
+
+static void
+atp_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
+    atp_pspan *pspans, u_int n_pspans, u_int repeat_count)
+{
+	u_int i, j;
+	u_int repeat_index = 0;
+
+	/* Determine the index of the multi-span. */
+	if (repeat_count) {
+		u_int cum = 0;
+		for (i = 0; i < n_pspans; i++) {
+			if (pspans[i].cum > cum) {
+				repeat_index = i;
+				cum = pspans[i].cum;
+			}
+		}
+	}
+
+	for (i = 0; i < sc->sc_n_strokes; i++) {
+		atp_stroke *stroke  = &sc->sc_strokes[i];
+		if (stroke->components[axis].matched)
+			continue; /* skip matched components */
+
+		for (j = 0; j < n_pspans; j++) {
+			if (pspans[j].matched)
+				continue; /* skip matched pspans */
+
+			if (atp_match_stroke_component(
+				    &stroke->components[axis], &pspans[j])) {
+				/* There is a match. */
+				stroke->components[axis].matched = TRUE;
+
+				/* Take care to repeat at the multi-span. */
+				if ((repeat_count > 0) && (j == repeat_index))
+					repeat_count--;
+				else
+					pspans[j].matched = TRUE;
+
+				break; /* skip to the next stroke */
+			}
+		} /* loop over pspans */
+	} /* loop over strokes */
+}
+
+/*
+ * Update strokes by matching against current pressure-spans.
+ * Return TRUE if any movement is detected.
+ */
+static boolean_t
+atp_update_strokes(struct atp_softc *sc, atp_pspan *pspans_x,
+    u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans)
+{
+	u_int       i, j;
+	atp_stroke *stroke;
+	boolean_t   movement = FALSE;
+	u_int       repeat_count = 0;
+
+	/* Reset X and Y components of all strokes as unmatched. */
+	for (i = 0; i < sc->sc_n_strokes; i++) {
+		stroke = &sc->sc_strokes[i];
+		stroke->components[X].matched = FALSE;
+		stroke->components[Y].matched = FALSE;
+	}
+
+	/*
+	 * Usually, the X and Y pspans come in pairs (the common case
+	 * being a single pair). It is possible, however, that
+	 * multiple contacts resolve to a single pspan along an
+	 * axis, as illustrated in the following:
+	 *
+	 *   F = finger-contact
+	 *
+	 *                pspan  pspan
+	 *        +-----------------------+
+	 *        |         .      .      |
+	 *        |         .      .      |
+	 *        |         .      .      |
+	 *        |         .      .      |
+	 *  pspan |.........F......F      |
+	 *        |                       |
+	 *        |                       |
+	 *        |                       |
+	 *        +-----------------------+
+	 *
+	 *
+	 * The above case can be detected by a difference in the
+	 * number of X and Y pspans. When this happens, X and Y pspans
+	 * aren't easy to pair or match against strokes.
+	 *
+	 * When X and Y pspans differ in number, the axis with the
+	 * smaller number of pspans is regarded as having a repeating
+	 * pspan (or a multi-pspan)--in the above illustration, the
+	 * Y-axis has a repeating pspan. Our approach is to try to
+	 * match the multi-pspan repeatedly against strokes. The
+	 * difference between the number of X and Y pspans gives us a
+	 * crude repeat_count for matching multi-pspans--i.e. the
+	 * multi-pspan along the Y axis (above) has a repeat_count of 1.
+	 */
+	repeat_count = abs(n_xpspans - n_ypspans);
+
+	atp_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
+	    (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
+		repeat_count : 0));
+	atp_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
+	    (((repeat_count != 0) && (n_ypspans < n_xpspans)) ?
+		repeat_count : 0));
+
+	/* Update the state of strokes based on the above pspan matches. */
+	for (i = 0; i < sc->sc_n_strokes; i++) {
+		stroke = &sc->sc_strokes[i];
+		if (stroke->components[X].matched &&
+		    stroke->components[Y].matched) {
+			atp_advance_stroke_state(sc, stroke, &movement);
+		} else {
+			/*
+			 * At least one component of this stroke
+			 * didn't match against current pspans;
+			 * terminate it.
+			 */
+			atp_terminate_stroke(sc, i);
+		}
+	}
+
+	/* Add new strokes for pairs of unmatched pspans */
+	for (i = 0; i < n_xpspans; i++) {
+		if (pspans_x[i].matched == FALSE) break;
+	}
+	for (j = 0; j < n_ypspans; j++) {
+		if (pspans_y[j].matched == FALSE) break;
+	}
+	if ((i < n_xpspans) && (j < n_ypspans)) {
+#if USB_DEBUG
+		if (atp_debug >= ATP_LLEVEL_INFO) {
+			printf("unmatched pspans:");
+			for (; i < n_xpspans; i++) {
+				if (pspans_x[i].matched)
+					continue;
+				printf(" X:[loc:%u,cum:%u]",
+				    pspans_x[i].loc, pspans_x[i].cum);
+			}
+			for (; j < n_ypspans; j++) {
+				if (pspans_y[j].matched)
+					continue;
+				printf(" Y:[loc:%u,cum:%u]",
+				    pspans_y[j].loc, pspans_y[j].cum);
+			}
+			printf("\n");
+		}
+#endif /* #if USB_DEBUG */
+		if ((n_xpspans == 1) && (n_ypspans == 1))
+			/* The common case of a single pair of new pspans. */
+			atp_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
+		else
+			atp_add_new_strokes(sc,
+			    pspans_x, n_xpspans,
+			    pspans_y, n_ypspans);
+	}
+
+#if USB_DEBUG
+	if (atp_debug >= ATP_LLEVEL_INFO) {
+		for (i = 0; i < sc->sc_n_strokes; i++) {
+			atp_stroke *stroke = &sc->sc_strokes[i];
+
+			printf(" %s%clc:%u,dm:%d,pnd:%d,mv:%d%c"
+			    ",%clc:%u,dm:%d,pnd:%d,mv:%d%c",
+			    (stroke->flags & ATSF_ZOMBIE) ? "zomb:" : "",
+			    (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<',
+			    stroke->components[X].loc,
+			    stroke->components[X].delta_mickeys,
+			    stroke->components[X].pending,
+			    stroke->components[X].movement,
+			    (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>',
+			    (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<',
+			    stroke->components[Y].loc,
+			    stroke->components[Y].delta_mickeys,
+			    stroke->components[Y].pending,
+			    stroke->components[Y].movement,
+			    (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>');
+		}

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list