Edit

IABSD.fr/src/sys/dev/usb/ubcmtp.c

Branch :

  • Show log

    Commit

  • Author : mglocker
    Date : 2020-08-21 17:42:45
    Hash : b8d97572
    Message : ubcmtp(4) doesn't attach to uhidev(4) but to uhub(4). ok jcs@

  • sys/dev/usb/ubcmtp.c
  • /*	$OpenBSD: ubcmtp.c,v 1.22 2020/08/21 17:42:45 mglocker Exp $ */
    
    /*
     * Copyright (c) 2013-2014, joshua stein <jcs@openbsd.org>
     * 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.
     * 3. The name of the copyright holder may not be used to endorse or
     *    promote products derived from this software without specific
     *    prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``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 COPYRIGHT OWNER 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.
     */
    
    /*
     * Apple USB multitouch trackpad (Broadcom BCM5974) driver
     *
     * Protocol info/magic from bcm5974 Linux driver by Henrik Rydberg, et al.
     */
    
    #include <sys/param.h>
    #include <sys/device.h>
    #include <sys/errno.h>
    #include <sys/malloc.h>
    
    #include <sys/ioctl.h>
    #include <sys/systm.h>
    #include <sys/tty.h>
    
    #include <dev/usb/usb.h>
    #include <dev/usb/usbdi.h>
    #include <dev/usb/usbdevs.h>
    #include <dev/usb/usbhid.h>
    
    #include <dev/wscons/wsconsio.h>
    #include <dev/wscons/wsmousevar.h>
    
    /* #define UBCMTP_DEBUG */
    
    #ifdef UBCMTP_DEBUG
    #define DPRINTF(x...)	do { printf(x); } while (0);
    #else
    #define DPRINTF(x...)
    #endif
    
    /* magic to switch device from HID (default) mode into raw */
    #define UBCMTP_WELLSPRING_MODE_RAW	0x01
    #define UBCMTP_WELLSPRING_MODE_HID	0x08
    #define UBCMTP_WELLSPRING_MODE_LEN	8
    #define UBCMTP_WELLSPRING9_MODE_RAW	0x01
    #define UBCMTP_WELLSPRING9_MODE_HID	0x00
    #define UBCMTP_WELLSPRING9_MODE_LEN	2
    
    struct ubcmtp_button {
    	uint8_t		unused;
    	uint8_t		button;
    	uint8_t		rel_x;
    	uint8_t		rel_y;
    };
    
    struct ubcmtp_finger {
    	uint16_t	origin;
    	uint16_t	abs_x;
    	uint16_t	abs_y;
    	uint16_t	rel_x;
    	uint16_t	rel_y;
    	uint16_t	tool_major;
    	uint16_t	tool_minor;
    	uint16_t	orientation;
    	uint16_t	touch_major;
    	uint16_t	touch_minor;
    	uint16_t	unused[2];
    	uint16_t	pressure;
    	uint16_t	multi;
    } __packed __attribute((aligned(2)));
    
    #define UBCMTP_MAX_FINGERS	16
    #define UBCMTP_ALL_FINGER_SIZE	(UBCMTP_MAX_FINGERS * sizeof(struct ubcmtp_finger))
    
    #define UBCMTP_TYPE1		1
    #define UBCMTP_TYPE1_TPOFF	(13 * sizeof(uint16_t))
    #define UBCMTP_TYPE1_TPLEN	UBCMTP_TYPE1_TPOFF + UBCMTP_ALL_FINGER_SIZE
    #define UBCMTP_TYPE1_TPIFACE	1
    #define UBCMTP_TYPE1_BTIFACE	2
    
    #define UBCMTP_TYPE2		2
    #define UBCMTP_TYPE2_TPOFF	(15 * sizeof(uint16_t))
    #define UBCMTP_TYPE2_TPLEN	UBCMTP_TYPE2_TPOFF + UBCMTP_ALL_FINGER_SIZE
    #define UBCMTP_TYPE2_TPIFACE	1
    #define UBCMTP_TYPE2_BTOFF	15
    
    #define UBCMTP_TYPE3		3
    #define UBCMTP_TYPE3_TPOFF	(19 * sizeof(uint16_t))
    #define UBCMTP_TYPE3_TPLEN	UBCMTP_TYPE3_TPOFF + UBCMTP_ALL_FINGER_SIZE
    #define UBCMTP_TYPE3_TPIFACE	2
    #define UBCMTP_TYPE3_BTOFF	23
    
    #define UBCMTP_TYPE4		4
    #define UBCMTP_TYPE4_TPOFF	(24 * sizeof(uint16_t))
    #define UBCMTP_TYPE4_TPLEN	UBCMTP_TYPE4_TPOFF + UBCMTP_ALL_FINGER_SIZE
    #define UBCMTP_TYPE4_TPIFACE	2
    #define UBCMTP_TYPE4_BTOFF	31
    #define UBCMTP_TYPE4_FINGERPAD	(1 * sizeof(uint16_t))
    
    #define UBCMTP_FINGER_ORIENT	16384
    #define UBCMTP_SN_PRESSURE	45
    #define UBCMTP_SN_WIDTH		25
    #define UBCMTP_SN_COORD		250
    #define UBCMTP_SN_ORIENT	10
    
    /* Identify clickpads in ubcmtp_configure. */
    #define IS_CLICKPAD(ubcmtp_type) (ubcmtp_type != UBCMTP_TYPE1)
    
    /* Use a constant, synaptics-compatible pressure value for now. */
    #define DEFAULT_PRESSURE	40
    
    struct ubcmtp_limit {
    	int limit;
    	int min;
    	int max;
    };
    
    struct ubcmtp_dev {
    	int vendor;			/* vendor */
    	int ansi, iso, jis;		/* 3 types of product */
    	int type;			/* 1 (normal) or 2 (integrated btn) */
    	struct ubcmtp_limit l_pressure;	/* finger pressure */
    	struct ubcmtp_limit l_width;	/* finger width */
    	struct ubcmtp_limit l_x;
    	struct ubcmtp_limit l_y;
    	struct ubcmtp_limit l_orientation;
    };
    
    static struct ubcmtp_dev ubcmtp_devices[] = {
    	/* type 1 devices with separate buttons */
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookAir */
    		USB_PRODUCT_APPLE_WELLSPRING_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING_JIS,
    		UBCMTP_TYPE1,
    		{ UBCMTP_SN_PRESSURE, 0, 256 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4824, 5342 },
    		{ UBCMTP_SN_COORD, -172, 5820 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookProPenryn */
    		USB_PRODUCT_APPLE_WELLSPRING2_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING2_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING2_JIS,
    		UBCMTP_TYPE1,
    		{ UBCMTP_SN_PRESSURE, 0, 256 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4824, 4824 },
    		{ UBCMTP_SN_COORD, -172, 4290 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	/* type 2 devices with integrated buttons */
    	{
    		USB_VENDOR_APPLE,
    		/* Macbook5,1 */
    		USB_PRODUCT_APPLE_WELLSPRING3_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING3_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING3_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4460, 5166 },
    		{ UBCMTP_SN_COORD, -75, 6700 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookAir3,1 */
    		USB_PRODUCT_APPLE_WELLSPRING4A_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING4A_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING4A_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4616, 5112 },
    		{ UBCMTP_SN_COORD, -142, 5234 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookAir3,2 */
    		USB_PRODUCT_APPLE_WELLSPRING4_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING4_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING4_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4620, 5140 },
    		{ UBCMTP_SN_COORD, -150, 6600 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* Macbook8 */
    		USB_PRODUCT_APPLE_WELLSPRING5_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING5_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING5_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4415, 5050 },
    		{ UBCMTP_SN_COORD, -55, 6680 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* Macbook8,2 */
    		USB_PRODUCT_APPLE_WELLSPRING5A_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING5A_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING5A_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4750, 5280 },
    		{ UBCMTP_SN_COORD, -150, 6730 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookAir4,2 */
    		USB_PRODUCT_APPLE_WELLSPRING6_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING6_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING6_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4620, 5140 },
    		{ UBCMTP_SN_COORD, -150, 6600 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookAir4,1 */
    		USB_PRODUCT_APPLE_WELLSPRING6A_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING6A_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING6A_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4620, 5140 },
    		{ UBCMTP_SN_COORD, -150, 6600 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookPro10,1 */
    		USB_PRODUCT_APPLE_WELLSPRING7_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING7_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING7_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4750, 5280 },
    		{ UBCMTP_SN_COORD, -150, 6730 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookPro10,2 */
    		USB_PRODUCT_APPLE_WELLSPRING7A_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING7A_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING7A_JIS,
    		UBCMTP_TYPE2,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4750, 5280 },
    		{ UBCMTP_SN_COORD, -150, 6730 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookAir6,1 */
    		USB_PRODUCT_APPLE_WELLSPRING8_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING8_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING8_JIS,
    		UBCMTP_TYPE3,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4620, 5140 },
    		{ UBCMTP_SN_COORD, -150, 6600 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    	{
    		USB_VENDOR_APPLE,
    		/* MacbookPro12,1 */
    		USB_PRODUCT_APPLE_WELLSPRING9_ANSI,
    		USB_PRODUCT_APPLE_WELLSPRING9_ISO,
    		USB_PRODUCT_APPLE_WELLSPRING9_JIS,
    		UBCMTP_TYPE4,
    		{ UBCMTP_SN_PRESSURE, 0, 300 },
    		{ UBCMTP_SN_WIDTH, 0, 2048 },
    		{ UBCMTP_SN_COORD, -4828, 5345 },
    		{ UBCMTP_SN_COORD, -203, 6803 },
    		{ UBCMTP_SN_ORIENT, -UBCMTP_FINGER_ORIENT, UBCMTP_FINGER_ORIENT },
    	},
    };
    
    struct ubcmtp_softc {
    	struct device		sc_dev;		/* base device */
    
    	struct ubcmtp_dev	*dev_type;
    
    	struct usbd_device	*sc_udev;
    	struct device		*sc_wsmousedev;
    
    	struct usbd_interface	*sc_tp_iface;	/* trackpad interface */
    	struct usbd_pipe	*sc_tp_pipe;	/* trackpad pipe */
    	int			sc_tp_epaddr;	/* endpoint addr */
    	int			tp_maxlen;	/* max size of tp data */
    	int			tp_offset;	/* finger offset into data */
    	int			tp_fingerpad;	/* padding between finger data */
    	uint8_t			*tp_pkt;
    
    	struct usbd_interface	*sc_bt_iface;	/* button interface */
    	struct usbd_pipe	*sc_bt_pipe;	/* button pipe */
    	int			sc_bt_epaddr;	/* endpoint addr */
    	int			bt_maxlen;	/* max size of button data */
    	uint8_t			*bt_pkt;
    
    	uint32_t		sc_status;
    #define UBCMTP_ENABLED		1
    
    	struct mtpoint		frame[UBCMTP_MAX_FINGERS];
    	int			contacts;
    	int			btn;
    };
    
    int	ubcmtp_enable(void *);
    void	ubcmtp_disable(void *);
    int	ubcmtp_ioctl(void *, unsigned long, caddr_t, int, struct proc *);
    int	ubcmtp_raw_mode(struct ubcmtp_softc *, int);
    int	ubcmtp_setup_pipes(struct ubcmtp_softc *);
    void	ubcmtp_bt_intr(struct usbd_xfer *, void *, usbd_status);
    void	ubcmtp_tp_intr(struct usbd_xfer *, void *, usbd_status);
    
    int	ubcmtp_match(struct device *, void *, void *);
    void	ubcmtp_attach(struct device *, struct device *, void *);
    int	ubcmtp_detach(struct device *, int);
    int	ubcmtp_activate(struct device *, int);
    int	ubcmtp_configure(struct ubcmtp_softc *);
    
    const struct wsmouse_accessops ubcmtp_accessops = {
    	ubcmtp_enable,
    	ubcmtp_ioctl,
    	ubcmtp_disable,
    };
    
    struct cfdriver ubcmtp_cd = {
    	NULL, "ubcmtp", DV_DULL
    };
    
    const struct cfattach ubcmtp_ca = {
    	sizeof(struct ubcmtp_softc), ubcmtp_match, ubcmtp_attach, ubcmtp_detach,
    	ubcmtp_activate,
    };
    
    int
    ubcmtp_match(struct device *parent, void *match, void *aux)
    {
    	struct usb_attach_arg *uaa = aux;
    	usb_interface_descriptor_t *id;
    	int i;
    
    	if (uaa->iface == NULL)
    		return (UMATCH_NONE);
    
    	for (i = 0; i < nitems(ubcmtp_devices); i++) {
    		if (uaa->vendor == ubcmtp_devices[i].vendor && (
    		    uaa->product == ubcmtp_devices[i].ansi ||
    		    uaa->product == ubcmtp_devices[i].iso ||
    		    uaa->product == ubcmtp_devices[i].jis)) {
    			if (uaa->nifaces < 2)
    				return (UMATCH_NONE);
    			if ((ubcmtp_devices[i].type != UBCMTP_TYPE2) &&
    			    (uaa->nifaces < 3))
    				return (UMATCH_NONE);
    
    			/*
    			 * The USB keyboard/mouse device will have one keyboard
    			 * HID and two mouse HIDs, though only one will have a
    			 * protocol of mouse -- we only want to take control of
    			 * that one.
    			 */
    			id = usbd_get_interface_descriptor(uaa->iface);
    			if (id->bInterfaceProtocol == UIPROTO_BOOT_MOUSE)
    				return (UMATCH_VENDOR_PRODUCT_CONF_IFACE);
    		}
    	}
    
    	return (UMATCH_NONE);
    }
    
    void
    ubcmtp_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct ubcmtp_softc *sc = (struct ubcmtp_softc *)self;
    	struct usb_attach_arg *uaa = aux;
    	struct usbd_device *dev = uaa->device;
    	struct wsmousedev_attach_args a;
    	usb_device_descriptor_t *udd;
    	int i;
    
    	sc->sc_udev = uaa->device;
    	sc->sc_status = 0;
    	sc->tp_fingerpad = 0;
    
    	if ((udd = usbd_get_device_descriptor(dev)) == NULL) {
    		printf("ubcmtp: failed getting device descriptor\n");
    		return;
    	}
    
    	for (i = 0; i < nitems(ubcmtp_devices); i++) {
    		if (uaa->vendor == ubcmtp_devices[i].vendor && (
    		    uaa->product == ubcmtp_devices[i].ansi ||
    		    uaa->product == ubcmtp_devices[i].iso ||
    		    uaa->product == ubcmtp_devices[i].jis)) {
    			sc->dev_type = &ubcmtp_devices[i];
    			DPRINTF("%s: attached to 0x%x/0x%x type %d\n",
    			    sc->sc_dev.dv_xname, uaa->vendor, uaa->product,
    			    sc->dev_type->type);
    			break;
    		}
    	}
    
    	if (sc->dev_type == NULL) {
    		/* how did we match then? */
    		printf("%s: failed looking up device in table\n",
    		    sc->sc_dev.dv_xname);
    		return;
    	}
    
    	switch (sc->dev_type->type) {
    	case UBCMTP_TYPE1:
    		sc->tp_maxlen = UBCMTP_TYPE1_TPLEN;
    		sc->tp_offset = UBCMTP_TYPE1_TPOFF;
    		sc->sc_tp_iface = uaa->ifaces[UBCMTP_TYPE1_TPIFACE];
    		usbd_claim_iface(sc->sc_udev, UBCMTP_TYPE1_TPIFACE);
    
    		/* button offsets */
    		sc->bt_maxlen = sizeof(struct ubcmtp_button);
    		sc->sc_bt_iface = uaa->ifaces[UBCMTP_TYPE1_BTIFACE];
    		usbd_claim_iface(sc->sc_udev, UBCMTP_TYPE1_BTIFACE);
    		break;
    
    	case UBCMTP_TYPE2:
    		sc->tp_maxlen = UBCMTP_TYPE2_TPLEN;
    		sc->tp_offset = UBCMTP_TYPE2_TPOFF;
    		sc->sc_tp_iface = uaa->ifaces[UBCMTP_TYPE2_TPIFACE];
    		usbd_claim_iface(sc->sc_udev, UBCMTP_TYPE2_TPIFACE);
    		break;
    
    	case UBCMTP_TYPE3:
    		sc->tp_maxlen = UBCMTP_TYPE3_TPLEN;
    		sc->tp_offset = UBCMTP_TYPE3_TPOFF;
    		sc->sc_tp_iface = uaa->ifaces[UBCMTP_TYPE3_TPIFACE];
    		usbd_claim_iface(sc->sc_udev, UBCMTP_TYPE3_TPIFACE);
    		break;
    
    	case UBCMTP_TYPE4:
    		sc->tp_maxlen = UBCMTP_TYPE4_TPLEN;
    		sc->tp_offset = UBCMTP_TYPE4_TPOFF;
    		sc->sc_tp_iface = uaa->ifaces[UBCMTP_TYPE4_TPIFACE];
    		sc->tp_fingerpad = UBCMTP_TYPE4_FINGERPAD;
    		usbd_claim_iface(sc->sc_udev, UBCMTP_TYPE4_TPIFACE);
    		break;
    	}
    
    	a.accessops = &ubcmtp_accessops;
    	a.accesscookie = sc;
    
    	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
    	if (sc->sc_wsmousedev != NULL && ubcmtp_configure(sc))
    		ubcmtp_disable(sc);
    }
    
    int
    ubcmtp_detach(struct device *self, int flags)
    {
    	struct ubcmtp_softc *sc = (struct ubcmtp_softc *)self;
    	int ret = 0;
    
    	if (sc->sc_wsmousedev != NULL)
    		ret = config_detach(sc->sc_wsmousedev, flags);
    
    	return (ret);
    }
    
    int
    ubcmtp_activate(struct device *self, int act)
    {
    	struct ubcmtp_softc *sc = (struct ubcmtp_softc *)self;
    	int rv = 0;
    
    	if (act == DVACT_DEACTIVATE) {
    		if (sc->sc_wsmousedev != NULL)
    			rv = config_deactivate(sc->sc_wsmousedev);
    		usbd_deactivate(sc->sc_udev);
    	}
    
    	return (rv);
    }
    
    int
    ubcmtp_configure(struct ubcmtp_softc *sc)
    {
    	struct wsmousehw *hw = wsmouse_get_hw(sc->sc_wsmousedev);
    
    	hw->type = WSMOUSE_TYPE_TOUCHPAD;
    	hw->hw_type = (IS_CLICKPAD(sc->dev_type->type)
    	    ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD);
    	hw->x_min = sc->dev_type->l_x.min;
    	hw->x_max = sc->dev_type->l_x.max;
    	hw->y_min = sc->dev_type->l_y.min;
    	hw->y_max = sc->dev_type->l_y.max;
    	hw->mt_slots = UBCMTP_MAX_FINGERS;
    	hw->flags = WSMOUSEHW_MT_TRACKING;
    
    	return wsmouse_configure(sc->sc_wsmousedev, NULL, 0);
    }
    
    int
    ubcmtp_enable(void *v)
    {
    	struct ubcmtp_softc *sc = v;
    
    	if (sc->sc_status & UBCMTP_ENABLED)
    		return (EBUSY);
    
    	if (usbd_is_dying(sc->sc_udev))
    		return (EIO);
    
    	if (ubcmtp_raw_mode(sc, 1) != 0) {
    		printf("%s: failed to enter raw mode\n", sc->sc_dev.dv_xname);
    		return (1);
    	}
    
    	if (ubcmtp_setup_pipes(sc) == 0) {
    		sc->sc_status |= UBCMTP_ENABLED;
    		return (0);
    	} else
    		return (1);
    }
    
    void
    ubcmtp_disable(void *v)
    {
    	struct ubcmtp_softc *sc = v;
    
    	if (usbd_is_dying(sc->sc_udev) || !(sc->sc_status & UBCMTP_ENABLED))
    		return;
    
    	sc->sc_status &= ~UBCMTP_ENABLED;
    
    	ubcmtp_raw_mode(sc, 0);
    
    	if (sc->sc_tp_pipe != NULL) {
    		usbd_close_pipe(sc->sc_tp_pipe);
    		sc->sc_tp_pipe = NULL;
    	}
    	if (sc->sc_bt_pipe != NULL) {
    		usbd_close_pipe(sc->sc_bt_pipe);
    		sc->sc_bt_pipe = NULL;
    	}
    
    	if (sc->tp_pkt != NULL) {
    		free(sc->tp_pkt, M_USBDEV, sc->tp_maxlen);
    		sc->tp_pkt = NULL;
    	}
    	if (sc->bt_pkt != NULL) {
    		free(sc->bt_pkt, M_USBDEV, sc->bt_maxlen);
    		sc->bt_pkt = NULL;
    	}
    }
    
    int
    ubcmtp_ioctl(void *v, unsigned long cmd, caddr_t data, int flag, struct proc *p)
    {
    	struct ubcmtp_softc *sc = v;
    	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
    	int wsmode;
    
    	DPRINTF("%s: in %s with cmd 0x%lx\n", sc->sc_dev.dv_xname, __func__,
    	    cmd);
    
    	switch (cmd) {
    	case WSMOUSEIO_GTYPE: {
    		struct wsmousehw *hw = wsmouse_get_hw(sc->sc_wsmousedev);
    		*(u_int *)data = hw->type;
    		break;
    	}
    
    	case WSMOUSEIO_GCALIBCOORDS:
    		wsmc->minx = sc->dev_type->l_x.min;
    		wsmc->maxx = sc->dev_type->l_x.max;
    		wsmc->miny = sc->dev_type->l_y.min;
    		wsmc->maxy = sc->dev_type->l_y.max;
    		wsmc->swapxy = 0;
    		wsmc->resx = 0;
    		wsmc->resy = 0;
    		break;
    
    	case WSMOUSEIO_SETMODE:
    		wsmode = *(u_int *)data;
    		if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) {
    			printf("%s: invalid mode %d\n", sc->sc_dev.dv_xname,
    			    wsmode);
    			return (EINVAL);
    		}
    		wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
    
    		DPRINTF("%s: changing mode to %s\n",
    		    sc->sc_dev.dv_xname, (wsmode == WSMOUSE_COMPAT ? "compat" :
    		    "native"));
    
    		break;
    
    	default:
    		return (-1);
    	}
    
    	return (0);
    }
    
    int
    ubcmtp_raw_mode(struct ubcmtp_softc *sc, int enable)
    {
    	usb_device_request_t r;
    	usbd_status err;
    	uint8_t buf[8];
    
    	/* type 3 has no raw mode */
    	if (sc->dev_type->type == UBCMTP_TYPE3)
    		return (0);
    
    	r.bRequest = UR_GET_REPORT;
    	r.bmRequestType = UT_READ_CLASS_INTERFACE;
    	if (sc->dev_type->type < UBCMTP_TYPE4) {
    		USETW2(r.wValue, UHID_FEATURE_REPORT, 0);
    		USETW(r.wIndex, 0);
    		USETW(r.wLength, UBCMTP_WELLSPRING_MODE_LEN);
    	} else {
    		USETW2(r.wValue, UHID_FEATURE_REPORT, 2);
    		USETW(r.wIndex, 2);
    		USETW(r.wLength, UBCMTP_WELLSPRING9_MODE_LEN);
    	}
    
    	err = usbd_do_request(sc->sc_udev, &r, buf);
    	if (err != USBD_NORMAL_COMPLETION) {
    		printf("%s: %s: failed to get feature report\n",
    		    sc->sc_dev.dv_xname, __func__);
    		return (err);
    	}
    
    	/* toggle magic byte and write everything back */
    	if (sc->dev_type->type < UBCMTP_TYPE4)
    		buf[0] = (enable ? UBCMTP_WELLSPRING_MODE_RAW :
    		    UBCMTP_WELLSPRING_MODE_HID);
    	else
    		buf[1] = (enable ? UBCMTP_WELLSPRING9_MODE_RAW :
    		    UBCMTP_WELLSPRING9_MODE_HID);
    
    	r.bRequest = UR_SET_REPORT;
    	r.bmRequestType = UT_WRITE_CLASS_INTERFACE;
    	if (sc->dev_type->type < UBCMTP_TYPE4) {
    		USETW2(r.wValue, UHID_FEATURE_REPORT, 0);
    		USETW(r.wIndex, 0);
    		USETW(r.wLength, UBCMTP_WELLSPRING_MODE_LEN);
    	} else {
    		USETW2(r.wValue, UHID_FEATURE_REPORT, 2);
    		USETW(r.wIndex, 2);
    		USETW(r.wLength, UBCMTP_WELLSPRING9_MODE_LEN);
    	}
    
    	err = usbd_do_request(sc->sc_udev, &r, buf);
    	if (err != USBD_NORMAL_COMPLETION) {
    		printf("%s: %s: failed to toggle raw mode\n",
    		    sc->sc_dev.dv_xname, __func__);
    		return (err);
    	}
    
    	return (0);
    }
    
    int
    ubcmtp_setup_pipes(struct ubcmtp_softc *sc)
    {
    	usbd_status err;
    	usb_endpoint_descriptor_t *ed;
    
    	if (sc->dev_type->type == UBCMTP_TYPE1) {
    		/* setup physical button pipe */
    
    		ed = usbd_interface2endpoint_descriptor(sc->sc_bt_iface, 0);
    		if (ed == NULL) {
    			printf("%s: failed getting button endpoint descriptor\n",
    			    sc->sc_dev.dv_xname);
    			goto fail1;
    		}
    		sc->sc_bt_epaddr = ed->bEndpointAddress;
    		sc->bt_pkt = malloc(sc->bt_maxlen, M_USBDEV, M_WAITOK);
    		if (sc->bt_pkt == NULL)
    			goto fail1;
    
    		DPRINTF("%s: button iface at 0x%x, max size %d\n",
    		    sc->sc_dev.dv_xname, sc->sc_bt_epaddr, sc->bt_maxlen);
    
    		err = usbd_open_pipe_intr(sc->sc_bt_iface, sc->sc_bt_epaddr,
    		    USBD_SHORT_XFER_OK, &sc->sc_bt_pipe, sc, sc->bt_pkt,
    		    sc->bt_maxlen, ubcmtp_bt_intr, USBD_DEFAULT_INTERVAL);
    		if (err != USBD_NORMAL_COMPLETION) {
    			printf("%s: failed opening button pipe\n",
    			    sc->sc_dev.dv_xname);
    			goto fail1;
    		}
    	}
    
    	/* setup trackpad data pipe */
    
    	ed = usbd_interface2endpoint_descriptor(sc->sc_tp_iface, 0);
    	if (ed == NULL) {
    		printf("%s: failed getting trackpad data endpoint descriptor\n",
    		    sc->sc_dev.dv_xname);
    		goto fail2;
    	}
    	sc->sc_tp_epaddr = ed->bEndpointAddress;
    	sc->tp_pkt = malloc(sc->tp_maxlen, M_USBDEV, M_WAITOK);
    	if (sc->tp_pkt == NULL)
    		goto fail2;
    
    	DPRINTF("%s: trackpad data iface at 0x%x, max size %d\n",
    	    sc->sc_dev.dv_xname, sc->sc_tp_epaddr, sc->tp_maxlen);
    
    	err = usbd_open_pipe_intr(sc->sc_tp_iface, sc->sc_tp_epaddr,
    	    USBD_SHORT_XFER_OK, &sc->sc_tp_pipe, sc, sc->tp_pkt, sc->tp_maxlen,
    	    ubcmtp_tp_intr, USBD_DEFAULT_INTERVAL);
    	if (err != USBD_NORMAL_COMPLETION) {
    		printf("%s: error opening trackpad data pipe\n",
    		    sc->sc_dev.dv_xname);
    		goto fail2;
    	}
    
    	return (0);
    
    fail2:
    	if (sc->sc_tp_pipe != NULL)
    		usbd_close_pipe(sc->sc_tp_pipe);
    	if (sc->tp_pkt != NULL)
    		free(sc->tp_pkt, M_USBDEV, sc->tp_maxlen);
    fail1:
    	if (sc->sc_bt_pipe != NULL)
    		usbd_close_pipe(sc->sc_bt_pipe);
    	if (sc->bt_pkt != NULL)
    		free(sc->bt_pkt, M_USBDEV, sc->bt_maxlen);
    
    	return (1);
    }
    
    void
    ubcmtp_tp_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
    {
    	struct ubcmtp_softc *sc = priv;
    	struct ubcmtp_finger *finger;
    	u_int32_t pktlen;
    	int off, s, btn, contacts = 0;
    
    	if (usbd_is_dying(sc->sc_udev) || !(sc->sc_status & UBCMTP_ENABLED))
    		return;
    
    	if (status != USBD_NORMAL_COMPLETION) {
    		DPRINTF("%s: %s with status 0x%x\n", sc->sc_dev.dv_xname,
    		    __func__, status);
    
    		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
    			return;
    		if (status == USBD_STALLED)
    			usbd_clear_endpoint_stall_async(sc->sc_tp_pipe);
    		return;
    	}
    
    	usbd_get_xfer_status(xfer, NULL, NULL, &pktlen, NULL);
    
    	if (sc->tp_pkt == NULL || pktlen < sc->tp_offset)
    		return;
    
    	contacts = 0;
    	for (off = sc->tp_offset; off < pktlen;
    	    off += (sizeof(struct ubcmtp_finger) + sc->tp_fingerpad)) {
    		finger = (struct ubcmtp_finger *)(sc->tp_pkt + off);
    
    		if ((int16_t)letoh16(finger->touch_major) == 0)
    			continue; /* finger lifted */
    
    		sc->frame[contacts].x = (int16_t)letoh16(finger->abs_x);
    		sc->frame[contacts].y = (int16_t)letoh16(finger->abs_y);
    		sc->frame[contacts].pressure = DEFAULT_PRESSURE;
    		contacts++;
    	}
    
    	btn = sc->btn;
    	if (sc->dev_type->type == UBCMTP_TYPE2)
    		sc->btn = !!((int16_t)letoh16(sc->tp_pkt[UBCMTP_TYPE2_BTOFF]));
    	else if (sc->dev_type->type == UBCMTP_TYPE3)
    		sc->btn = !!((int16_t)letoh16(sc->tp_pkt[UBCMTP_TYPE3_BTOFF]));
    	else if (sc->dev_type->type == UBCMTP_TYPE4)
    		sc->btn = !!((int16_t)letoh16(sc->tp_pkt[UBCMTP_TYPE4_BTOFF]));
    
    	if (contacts || sc->contacts || sc->btn != btn) {
    		sc->contacts = contacts;
    		s = spltty();
    		wsmouse_buttons(sc->sc_wsmousedev, sc->btn);
    		wsmouse_mtframe(sc->sc_wsmousedev, sc->frame, contacts);
    		wsmouse_input_sync(sc->sc_wsmousedev);
    		splx(s);
    	}
    }
    
    /* hardware button interrupt */
    void
    ubcmtp_bt_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
    {
    	struct ubcmtp_softc *sc = priv;
    	struct ubcmtp_button *pkt;
    	u_int32_t len;
    
    	if (usbd_is_dying(sc->sc_udev) || !(sc->sc_status & UBCMTP_ENABLED))
    		return;
    
    	if (status != USBD_NORMAL_COMPLETION) {
    		DPRINTF("%s: %s with status 0x%x\n", sc->sc_dev.dv_xname,
    		    __func__, status);
    		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
    			return;
    		if (status == USBD_STALLED)
    			usbd_clear_endpoint_stall_async(sc->sc_tp_pipe);
    		return;
    	}
    
    	usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL);
    
    	if (sc->bt_pkt == NULL || len < sizeof(struct ubcmtp_button))
    		return;
    
    	pkt = (struct ubcmtp_button *)(sc->bt_pkt);
    
    	DPRINTF("%s: button interrupt (%d, %d, %d, %d)", sc->sc_dev.dv_xname,
    	    pkt->unused, pkt->button, pkt->rel_x, pkt->rel_y);
    
    	if (pkt->button != sc->btn) {
    		sc->btn = pkt->button;
    		wsmouse_buttons(sc->sc_wsmousedev, sc->btn);
    		wsmouse_input_sync(sc->sc_wsmousedev);
    	}
    }