root/sys/dev/hid/bcm5974.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2012 Huang Wen Hui
 * Copyright (c) 2021 Vladimir Kondratyev <wulf@FreeBSD.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.
 *
 * 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 "opt_hid.h"

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

#include <dev/evdev/input.h>
#include <dev/evdev/evdev.h>

#define HID_DEBUG_VAR   bcm5974_debug
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include <dev/hid/hidquirk.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbhid.h>
#include <dev/usb/usb_ioctl.h>

#include "usbdevs.h"

#define BCM5974_BUFFER_MAX      (246 * 2)       /* 2 Type4 SPI frames */
#define BCM5974_TLC_PAGE        HUP_GENERIC_DESKTOP
#define BCM5974_TLC_USAGE       HUG_MOUSE

/* magic to switch device from HID (default) mode into raw */
/* Type1 & Type2 trackpads */
#define BCM5974_USB_IFACE_INDEX 0
#define BCM5974_USB_REPORT_LEN  8
#define BCM5974_USB_REPORT_ID   0
#define BCM5974_USB_MODE_RAW    0x01
#define BCM5974_USB_MODE_HID    0x08
/* Type4 trackpads */
#define BCM5974_HID_REPORT_LEN  2
#define BCM5974_HID_REPORT_ID   2
#define BCM5974_HID_MODE_RAW    0x01
#define BCM5974_HID_MODE_HID    0x00

/* Tunables */
static  SYSCTL_NODE(_hw_hid, OID_AUTO, bcm5974, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
    "HID wellspring touchpad");

#ifdef HID_DEBUG
enum wsp_log_level {
        BCM5974_LLEVEL_DISABLED = 0,
        BCM5974_LLEVEL_ERROR,
        BCM5974_LLEVEL_DEBUG,           /* for troubleshooting */
        BCM5974_LLEVEL_INFO,            /* for diagnostics */
};
/* the default is to only log errors */
static int bcm5974_debug = BCM5974_LLEVEL_ERROR;

SYSCTL_INT(_hw_hid_bcm5974, OID_AUTO, debug, CTLFLAG_RWTUN,
    &bcm5974_debug, BCM5974_LLEVEL_ERROR, "BCM5974 debug level");
#endif                                  /* HID_DEBUG */

/*
 * Some tables, structures, definitions and constant values for the
 * touchpad protocol has been copied from Linux's
 * "drivers/input/mouse/bcm5974.c" which has the following copyright
 * holders under GPLv2. All device specific code in this driver has
 * been written from scratch. The decoding algorithm is based on
 * output from FreeBSD's usbdump.
 *
 * Copyright (C) 2008      Henrik Rydberg (rydberg@euromail.se)
 * Copyright (C) 2008      Scott Shawcroft (scott.shawcroft@gmail.com)
 * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
 * Copyright (C) 2005      Johannes Berg (johannes@sipsolutions.net)
 * Copyright (C) 2005      Stelian Pop (stelian@popies.net)
 * Copyright (C) 2005      Frank Arnold (frank@scirocco-5v-turbo.de)
 * Copyright (C) 2005      Peter Osterlund (petero2@telia.com)
 * Copyright (C) 2005      Michael Hanselmann (linux-kernel@hansmi.ch)
 * Copyright (C) 2006      Nicolas Boichat (nicolas@boichat.ch)
 */

/* trackpad header types */
enum tp_type {
        TYPE1,                  /* plain trackpad */
        TYPE2,                  /* button integrated in trackpad */
        TYPE3,                  /* additional header fields since June 2013 */
        TYPE4,                  /* additional header field for pressure data */
        TYPE_MT2U,                      /* Magic Trackpad 2 USB */
        TYPE_CNT
};

/* list of device capability bits */
#define HAS_INTEGRATED_BUTTON   1
#define USES_COMPACT_REPORT     2

struct tp_type_params {
        uint8_t caps;           /* device capability bitmask */
        uint8_t button;         /* offset to button data */
        uint8_t offset;         /* offset to trackpad finger data */
        uint8_t delta;          /* offset from header to finger struct */
} const static tp[TYPE_CNT] = {
        [TYPE1] = {
                .caps = 0,
                .button = 0,
                .offset = 13 * 2,
                .delta = 0,
        },
        [TYPE2] = {
                .caps = HAS_INTEGRATED_BUTTON,
                .button = 15,
                .offset = 15 * 2,
                .delta = 0,
        },
        [TYPE3] = {
                .caps = HAS_INTEGRATED_BUTTON,
                .button = 23,
                .offset = 19 * 2,
                .delta = 0,
        },
        [TYPE4] = {
                .caps = HAS_INTEGRATED_BUTTON,
                .button = 31,
                .offset = 23 * 2,
                .delta = 2,
        },
        [TYPE_MT2U] = {
                .caps = HAS_INTEGRATED_BUTTON | USES_COMPACT_REPORT,
                .button = 1,
                .offset = 12,
                .delta = 0,
        },
};

/* trackpad finger structure - compact version for external "Magic" devices */
struct tp_finger_compact {
        uint32_t coords; /* not struct directly due to endian conversion */
        uint8_t touch_major;
        uint8_t touch_minor;
        uint8_t size;
        uint8_t pressure;
        uint8_t id_ori;
} __packed;

_Static_assert((sizeof(struct tp_finger_compact) == 9), "tp_finger struct size must be 9");

/* trackpad finger structure - little endian */
struct tp_finger {
        uint16_t        origin;         /* zero when switching track finger */
        uint16_t        abs_x;          /* absolute x coodinate */
        uint16_t        abs_y;          /* absolute y coodinate */
        uint16_t        rel_x;          /* relative x coodinate */
        uint16_t        rel_y;          /* relative y coodinate */
        uint16_t        tool_major;     /* tool area, major axis */
        uint16_t        tool_minor;     /* tool area, minor axis */
        uint16_t        orientation;    /* 16384 when point, else 15 bit angle */
        uint16_t        touch_major;    /* touch area, major axis */
        uint16_t        touch_minor;    /* touch area, minor axis */
        uint16_t        unused[2];      /* zeros */
        uint16_t        pressure;       /* pressure on forcetouch touchpad */
        uint16_t        multi;          /* one finger: varies, more fingers:
                                         * constant */
} __packed;

#define BCM5974_LE2H(x) ((int32_t)(int16_t)le16toh(x))

/* trackpad finger data size, empirically at least ten fingers */
#define MAX_FINGERS             MAX_MT_SLOTS

#define MAX_FINGER_ORIENTATION  16384

enum {
        BCM5974_FLAG_WELLSPRING1,
        BCM5974_FLAG_WELLSPRING2,
        BCM5974_FLAG_WELLSPRING3,
        BCM5974_FLAG_WELLSPRING4,
        BCM5974_FLAG_WELLSPRING4A,
        BCM5974_FLAG_WELLSPRING5,
        BCM5974_FLAG_WELLSPRING6A,
        BCM5974_FLAG_WELLSPRING6,
        BCM5974_FLAG_WELLSPRING5A,
        BCM5974_FLAG_WELLSPRING7,
        BCM5974_FLAG_WELLSPRING7A,
        BCM5974_FLAG_WELLSPRING8,
        BCM5974_FLAG_WELLSPRING9_MODEL3,
        BCM5974_FLAG_WELLSPRING9_MODEL4,
#define BCM5974_FLAG_WELLSPRING9_MODEL_SPI      BCM5974_FLAG_WELLSPRING9_MODEL4
        BCM5974_FLAG_WELLSPRING9_MODEL5,
        BCM5974_FLAG_WELLSPRING9_MODEL6,
        BCM5974_FLAG_MAGIC_TRACKPAD2_USB,
        BCM5974_FLAG_MAX,
};

/* device-specific parameters */
struct bcm5974_axis {
        int snratio;                    /* signal-to-noise ratio */
        int min;                        /* device minimum reading */
        int max;                        /* device maximum reading */
        int size;                       /* physical size, mm */
};

/* device-specific configuration */
struct bcm5974_dev_params {
        const struct tp_type_params* tp;
        struct bcm5974_axis p;          /* finger pressure limits */
        struct bcm5974_axis w;          /* finger width limits */
        struct bcm5974_axis x;          /* horizontal limits */
        struct bcm5974_axis y;          /* vertical limits */
        struct bcm5974_axis o;          /* orientation limits */
};

/* logical signal quality */
#define SN_PRESSURE     45              /* pressure signal-to-noise ratio */
#define SN_WIDTH        25              /* width signal-to-noise ratio */
#define SN_COORD        250             /* coordinate signal-to-noise ratio */
#define SN_ORIENT       10              /* orientation signal-to-noise ratio */

static const struct bcm5974_dev_params bcm5974_dev_params[BCM5974_FLAG_MAX] = {
        [BCM5974_FLAG_WELLSPRING1] = {
                .tp = tp + TYPE1,
                .p = { SN_PRESSURE, 0, 256, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4824, 5342, 105 },
                .y = { SN_COORD, -172, 5820, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING2] = {
                .tp = tp + TYPE1,
                .p = { SN_PRESSURE, 0, 256, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4824, 4824, 105 },
                .y = { SN_COORD, -172, 4290, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING3] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4460, 5166, 105 },
                .y = { SN_COORD, -75, 6700, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING4] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4620, 5140, 105 },
                .y = { SN_COORD, -150, 6600, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING4A] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4616, 5112, 105 },
                .y = { SN_COORD, -142, 5234, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING5] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4415, 5050, 105 },
                .y = { SN_COORD, -55, 6680, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING6] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4620, 5140, 105 },
                .y = { SN_COORD, -150, 6600, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING5A] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4750, 5280, 105 },
                .y = { SN_COORD, -150, 6730, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING6A] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4620, 5140, 105 },
                .y = { SN_COORD, -150, 6600, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING7] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4750, 5280, 105 },
                .y = { SN_COORD, -150, 6730, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING7A] = {
                .tp = tp + TYPE2,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4750, 5280, 105 },
                .y = { SN_COORD, -150, 6730, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING8] = {
                .tp = tp + TYPE3,
                .p = { SN_PRESSURE, 0, 300, 0 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4620, 5140, 105 },
                .y = { SN_COORD, -150, 6600, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        /*
         * NOTE: Actually force-sensitive. Pressure has a "size" equal to the max
         * so that the "resolution" is 1 (i.e. values will be interpreted as grams).
         * No scientific measurements have been done :) but a really hard press
         * results in a value around 3500 on model 4.
         */
        [BCM5974_FLAG_WELLSPRING9_MODEL3] = {
                .tp = tp + TYPE4,
                .p = { SN_PRESSURE, 0, 4096, 4096 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -4828, 5345, 105 },
                .y = { SN_COORD, -203, 6803, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING9_MODEL4] = {
                .tp = tp + TYPE4,
                .p = { SN_PRESSURE, 0, 4096, 4096 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -5087, 5579, 105 },
                .y = { SN_COORD, -182, 6089, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING9_MODEL5] = {
                .tp = tp + TYPE4,
                .p = { SN_PRESSURE, 0, 4096, 4096 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -6243, 6749, 105 },
                .y = { SN_COORD, -170, 7685, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_WELLSPRING9_MODEL6] = {
                .tp = tp + TYPE4,
                .p = { SN_PRESSURE, 0, 4096, 4096 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -7456, 7976, 105 },
                .y = { SN_COORD, -163, 9283, 75 },
                .o = { SN_ORIENT,
                    -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
        },
        [BCM5974_FLAG_MAGIC_TRACKPAD2_USB] = {
                .tp = tp + TYPE_MT2U,
                .p = { SN_PRESSURE, 0, 256, 256 },
                .w = { SN_WIDTH, 0, 2048, 0 },
                .x = { SN_COORD, -3678, 3934, 157 },
                .y = { SN_COORD, -2478, 2587, 107 },
                .o = { SN_ORIENT, -3, 4, 0 },
        },
};

#define BCM5974_DEV(v,p,i)      {                                       \
        HID_BVPI(BUS_USB, USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i),    \
        HID_TLC(BCM5974_TLC_PAGE, BCM5974_TLC_USAGE),                   \
}

#define APPLE_HID       "APP000D"
#define BCM5974_DEV_SPI(hid, i) {                                       \
        HID_BUS(BUS_SPI), HID_PNP(hid), HID_DRIVER_INFO(i),             \
        HID_TLC(BCM5974_TLC_PAGE, BCM5974_TLC_USAGE),                   \
}

static const struct hid_device_id bcm5974_devs[] = {
        /* MacbookAir1.1 */
        BCM5974_DEV(APPLE, WELLSPRING_ANSI, BCM5974_FLAG_WELLSPRING1),
        BCM5974_DEV(APPLE, WELLSPRING_ISO, BCM5974_FLAG_WELLSPRING1),
        BCM5974_DEV(APPLE, WELLSPRING_JIS, BCM5974_FLAG_WELLSPRING1),

        /* MacbookProPenryn, aka wellspring2 */
        BCM5974_DEV(APPLE, WELLSPRING2_ANSI, BCM5974_FLAG_WELLSPRING2),
        BCM5974_DEV(APPLE, WELLSPRING2_ISO, BCM5974_FLAG_WELLSPRING2),
        BCM5974_DEV(APPLE, WELLSPRING2_JIS, BCM5974_FLAG_WELLSPRING2),

        /* Macbook5,1 (unibody), aka wellspring3 */
        BCM5974_DEV(APPLE, WELLSPRING3_ANSI, BCM5974_FLAG_WELLSPRING3),
        BCM5974_DEV(APPLE, WELLSPRING3_ISO, BCM5974_FLAG_WELLSPRING3),
        BCM5974_DEV(APPLE, WELLSPRING3_JIS, BCM5974_FLAG_WELLSPRING3),

        /* MacbookAir3,2 (unibody), aka wellspring4 */
        BCM5974_DEV(APPLE, WELLSPRING4_ANSI, BCM5974_FLAG_WELLSPRING4),
        BCM5974_DEV(APPLE, WELLSPRING4_ISO, BCM5974_FLAG_WELLSPRING4),
        BCM5974_DEV(APPLE, WELLSPRING4_JIS, BCM5974_FLAG_WELLSPRING4),

        /* MacbookAir3,1 (unibody), aka wellspring4 */
        BCM5974_DEV(APPLE, WELLSPRING4A_ANSI, BCM5974_FLAG_WELLSPRING4A),
        BCM5974_DEV(APPLE, WELLSPRING4A_ISO, BCM5974_FLAG_WELLSPRING4A),
        BCM5974_DEV(APPLE, WELLSPRING4A_JIS, BCM5974_FLAG_WELLSPRING4A),

        /* Macbook8 (unibody, March 2011) */
        BCM5974_DEV(APPLE, WELLSPRING5_ANSI, BCM5974_FLAG_WELLSPRING5),
        BCM5974_DEV(APPLE, WELLSPRING5_ISO, BCM5974_FLAG_WELLSPRING5),
        BCM5974_DEV(APPLE, WELLSPRING5_JIS, BCM5974_FLAG_WELLSPRING5),

        /* MacbookAir4,1 (unibody, July 2011) */
        BCM5974_DEV(APPLE, WELLSPRING6A_ANSI, BCM5974_FLAG_WELLSPRING6A),
        BCM5974_DEV(APPLE, WELLSPRING6A_ISO, BCM5974_FLAG_WELLSPRING6A),
        BCM5974_DEV(APPLE, WELLSPRING6A_JIS, BCM5974_FLAG_WELLSPRING6A),

        /* MacbookAir4,2 (unibody, July 2011) */
        BCM5974_DEV(APPLE, WELLSPRING6_ANSI, BCM5974_FLAG_WELLSPRING6),
        BCM5974_DEV(APPLE, WELLSPRING6_ISO, BCM5974_FLAG_WELLSPRING6),
        BCM5974_DEV(APPLE, WELLSPRING6_JIS, BCM5974_FLAG_WELLSPRING6),

        /* Macbook8,2 (unibody) */
        BCM5974_DEV(APPLE, WELLSPRING5A_ANSI, BCM5974_FLAG_WELLSPRING5A),
        BCM5974_DEV(APPLE, WELLSPRING5A_ISO, BCM5974_FLAG_WELLSPRING5A),
        BCM5974_DEV(APPLE, WELLSPRING5A_JIS, BCM5974_FLAG_WELLSPRING5A),

        /* MacbookPro10,1 (unibody, June 2012) */
        /* MacbookPro11,1-3 (unibody, June 2013) */
        BCM5974_DEV(APPLE, WELLSPRING7_ANSI, BCM5974_FLAG_WELLSPRING7),
        BCM5974_DEV(APPLE, WELLSPRING7_ISO, BCM5974_FLAG_WELLSPRING7),
        BCM5974_DEV(APPLE, WELLSPRING7_JIS, BCM5974_FLAG_WELLSPRING7),

        /* MacbookPro10,2 (unibody, October 2012) */
        BCM5974_DEV(APPLE, WELLSPRING7A_ANSI, BCM5974_FLAG_WELLSPRING7A),
        BCM5974_DEV(APPLE, WELLSPRING7A_ISO, BCM5974_FLAG_WELLSPRING7A),
        BCM5974_DEV(APPLE, WELLSPRING7A_JIS, BCM5974_FLAG_WELLSPRING7A),

        /* MacbookAir6,2 (unibody, June 2013) */
        BCM5974_DEV(APPLE, WELLSPRING8_ANSI, BCM5974_FLAG_WELLSPRING8),
        BCM5974_DEV(APPLE, WELLSPRING8_ISO, BCM5974_FLAG_WELLSPRING8),
        BCM5974_DEV(APPLE, WELLSPRING8_JIS, BCM5974_FLAG_WELLSPRING8),

        /* MacbookPro12,1 MacbookPro11,4 */
        BCM5974_DEV(APPLE, WELLSPRING9_ANSI, BCM5974_FLAG_WELLSPRING9_MODEL3),
        BCM5974_DEV(APPLE, WELLSPRING9_ISO, BCM5974_FLAG_WELLSPRING9_MODEL3),
        BCM5974_DEV(APPLE, WELLSPRING9_JIS, BCM5974_FLAG_WELLSPRING9_MODEL3),

        /* Generic SPI device */
        BCM5974_DEV_SPI(APPLE_HID, BCM5974_FLAG_WELLSPRING9_MODEL_SPI),

        /* External "Magic" devices */
        BCM5974_DEV(APPLE, MAGIC_TRACKPAD2, BCM5974_FLAG_MAGIC_TRACKPAD2_USB),
};

#define BCM5974_WELLSPRING9_RDESC_SIZE          110
#define BCM5974_WELLSPRING9_MODEL_OFFSET        106

struct bcm5974_softc {
        device_t sc_dev;
        struct evdev_dev *sc_evdev;
        /* device configuration */
        const struct bcm5974_dev_params *sc_params;
        bool sc_saved_mode;
};

static const uint8_t bcm5974_rdesc[] = {
        0x05, BCM5974_TLC_PAGE, /* Usage Page (BCM5974_TLC_PAGE)        */
        0x09, BCM5974_TLC_USAGE,/* Usage (BCM5974_TLC_USAGE)            */
        0xA1, 0x01,             /* Collection (Application)             */
        0x06, 0x00, 0xFF,       /*   Usage Page (Vendor Defined 0xFF00) */
        0x09, 0x01,             /*   Usage (0x01)                       */
        0x15, 0x00,             /*   Logical Minimum (0)                */
        0x26, 0xFF, 0x00,       /*   Logical Maximum (255)              */
        0x75, 0x08,             /*   Report Size (8)                    */
        0x96,                   /*   Report Count (BCM5974_BUFFER_MAX)  */
        BCM5974_BUFFER_MAX & 0xFF,
        BCM5974_BUFFER_MAX >> 8 & 0xFF,
        0x81, 0x02,             /*   Input (Data,Var,Abs)               */
        0xC0,                   /* End Collection                       */
};

/*
 * function prototypes
 */
static evdev_open_t     bcm5974_ev_open;
static evdev_close_t    bcm5974_ev_close;
static const struct evdev_methods bcm5974_evdev_methods = {
        .ev_open =      &bcm5974_ev_open,
        .ev_close =     &bcm5974_ev_close,
};
static hid_intr_t       bcm5974_intr;

/* Device methods. */
static device_identify_t bcm5974_identify;
static device_probe_t   bcm5974_probe;
static device_attach_t  bcm5974_attach;
static device_detach_t  bcm5974_detach;

/*
 * Type1 and Type2 touchpads use keyboard USB interface to switch from HID to
 * RAW mode. Although it is possible to extend hkbd driver to support such a
 * mode change requests, it's not wanted due to cross device tree dependencies.
 * So, find lowest common denominator (struct usb_device of grandparent usbhid
 * driver) of touchpad and keyboard drivers and issue direct USB requests.
 */
static int
bcm5974_set_device_mode_usb(struct bcm5974_softc *sc, bool on)
{
        uint8_t mode_bytes[BCM5974_USB_REPORT_LEN];
        struct usb_ctl_request ucr;
        int err;

        ucr.ucr_request.bmRequestType = UT_READ_CLASS_INTERFACE;
        ucr.ucr_request.bRequest = UR_GET_REPORT;
        USETW2(ucr.ucr_request.wValue,
            UHID_FEATURE_REPORT, BCM5974_USB_REPORT_ID);
        ucr.ucr_request.wIndex[0] = BCM5974_USB_IFACE_INDEX;
        ucr.ucr_request.wIndex[1] = 0;
        USETW(ucr.ucr_request.wLength, BCM5974_USB_REPORT_LEN);
        ucr.ucr_data = mode_bytes;

        err = hid_ioctl(sc->sc_dev, USB_REQUEST, (uintptr_t)&ucr);
        if (err != 0) {
                DPRINTF("Failed to read device mode (%d)\n", err);
                return (EIO);
        }
#if 0
        /*
         * XXX Need to wait at least 250ms for hardware to get
         * ready. The device mode handling appears to be handled
         * asynchronously and we should not issue these commands too
         * quickly.
         */
        pause("WHW", hz / 4);
#endif
        mode_bytes[0] = on ? BCM5974_USB_MODE_RAW : BCM5974_USB_MODE_HID;
        ucr.ucr_request.bmRequestType = UT_WRITE_CLASS_INTERFACE;
        ucr.ucr_request.bRequest = UR_SET_REPORT;

        err = hid_ioctl(sc->sc_dev, USB_REQUEST, (uintptr_t)&ucr);
        if (err != 0) {
                DPRINTF("Failed to write device mode (%d)\n", err);
                return (EIO);
        }

        return (0);
}

static int
bcm5974_set_device_mode_hid(struct bcm5974_softc *sc, bool on)
{
        uint8_t mode_bytes[BCM5974_HID_REPORT_LEN] = {
                BCM5974_HID_REPORT_ID,
                on ? BCM5974_HID_MODE_RAW : BCM5974_HID_MODE_HID,
        };
#if 0
        int err;

        err = hid_get_report(sc->sc_dev, mode_bytes, BCM5974_HID_REPORT_LEN,
            NULL, HID_FEATURE_REPORT, BCM5974_HID_REPORT_ID);
        if (err != 0) {
                DPRINTF("Failed to read device mode (%d)\n", err);
                return (err);
        }
        /*
         * XXX Need to wait at least 250ms for hardware to get
         * ready. The device mode handling appears to be handled
         * asynchronously and we should not issue these commands too
         * quickly.
         */
        pause("WHW", hz / 4);
        mode_bytes[1] = on ? BCM5974_HID_MODE_RAW : BCM5974_HID_MODE_HID;
#endif
        return (hid_set_report(sc->sc_dev, mode_bytes, BCM5974_HID_REPORT_LEN,
            HID_FEATURE_REPORT, BCM5974_HID_REPORT_ID));
}

static int
bcm5974_set_device_mode(struct bcm5974_softc *sc, bool on)
{
        int err = 0;

        switch (sc->sc_params->tp - tp) {
        case TYPE1:
        case TYPE2:
                err = bcm5974_set_device_mode_usb(sc, on);
                break;
        case TYPE3:     /* Type 3 does not require a mode switch */
                break;
        case TYPE4:
        case TYPE_MT2U:
                err = bcm5974_set_device_mode_hid(sc, on);
                break;
        default:
                KASSERT(0 == 1, ("Unknown trackpad type"));
        }

        if (!err)
                sc->sc_saved_mode = on;

        return (err);
}

static uintptr_t
bcm5974_get_wsp9_model(device_t dev)
{
        const struct hid_device_info *hw = hid_get_device_info(dev);
        static uint8_t rdesc[BCM5974_WELLSPRING9_RDESC_SIZE];
        uint8_t model_byte = 0;

        bus_topo_assert();

        if (hw->rdescsize == sizeof(rdesc) &&
            hid_get_rdesc(dev, rdesc, sizeof(rdesc)) == 0) {
                model_byte = rdesc[BCM5974_WELLSPRING9_MODEL_OFFSET];
                switch (model_byte) {
                case 3:
                        /* MacbookPro12,1 MacbookPro11,4 */
                        return (BCM5974_FLAG_WELLSPRING9_MODEL3);
                case 4:
                        /* Macbook8,1 Macbook9,1 Macbook10,1 */
                        return (BCM5974_FLAG_WELLSPRING9_MODEL4);
                case 5:
                        /*
                         * MacbookPro13,1 MacbookPro13,2
                         * MacbookPro14,1 MacbookPro14,2
                         */
                        return (BCM5974_FLAG_WELLSPRING9_MODEL5);
                case 6:
                        /* MacbookPro13,3 MacbookPro14,3 */
                        return (BCM5974_FLAG_WELLSPRING9_MODEL6);
                }
        }

        device_printf(dev, "Unexpected trackpad descriptor len=%u model_byte="
            "%u, not extracting model\n", hw->rdescsize, model_byte);

        /* Fallback for unknown SPI versions */
        return (BCM5974_FLAG_WELLSPRING9_MODEL_SPI);
}

static void
bcm5974_identify(driver_t *driver, device_t parent)
{
        void *d_ptr;
        hid_size_t d_len;

        /*
         * The bcm5974 touchpad has no stable RAW mode TLC in its report
         * descriptor.  So replace existing HID mode mouse TLC with dummy one
         * to set proper transport layer buffer sizes, make driver probe
         * simpler and prevent unwanted hms driver attachment.
         */
        if (HIDBUS_LOOKUP_ID(parent, bcm5974_devs) != NULL &&
            hid_get_report_descr(parent, &d_ptr, &d_len) == 0 &&
            hid_is_mouse(d_ptr, d_len))
                hid_set_report_descr(parent, bcm5974_rdesc,
                    sizeof(bcm5974_rdesc));
}

static int
bcm5974_probe(device_t dev)
{
        int err;

        err = HIDBUS_LOOKUP_DRIVER_INFO(dev, bcm5974_devs);
        if (err != 0)
                return (err);

        hidbus_set_desc(dev, "Touchpad");

        return (BUS_PROBE_DEFAULT);
}

static int
bcm5974_attach(device_t dev)
{
        struct bcm5974_softc *sc = device_get_softc(dev);
        const struct hid_device_info *hw = hid_get_device_info(dev);
        uintptr_t drv_info;
        int err;

        DPRINTFN(BCM5974_LLEVEL_INFO, "sc=%p\n", sc);

        sc->sc_dev = dev;

        /* get device specific configuration */
        drv_info = hidbus_get_driver_info(dev);
        if (drv_info == BCM5974_FLAG_WELLSPRING9_MODEL_SPI)
                drv_info = bcm5974_get_wsp9_model(dev);
        sc->sc_params = bcm5974_dev_params + drv_info;

        sc->sc_evdev = evdev_alloc();
        evdev_set_name(sc->sc_evdev, device_get_desc(dev));
        evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
        evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct,
            hw->idVersion);
        evdev_set_serial(sc->sc_evdev, hw->serial);
        evdev_set_methods(sc->sc_evdev, sc, &bcm5974_evdev_methods);
        evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER);
        evdev_support_event(sc->sc_evdev, EV_SYN);
        evdev_support_event(sc->sc_evdev, EV_ABS);
        evdev_support_event(sc->sc_evdev, EV_KEY);
        evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */

#define BCM5974_ABS(evdev, code, param)                                 \
        evdev_support_abs((evdev), (code), (param).min, (param).max,    \
        ((param).max - (param).min) / (param).snratio, 0,               \
        (param).size != 0 ? ((param).max - (param).min) / (param).size : 0);

        /* finger position */
        BCM5974_ABS(sc->sc_evdev, ABS_MT_POSITION_X, sc->sc_params->x);
        BCM5974_ABS(sc->sc_evdev, ABS_MT_POSITION_Y, sc->sc_params->y);
        /* finger pressure */
        BCM5974_ABS(sc->sc_evdev, ABS_MT_PRESSURE, sc->sc_params->p);
        /* finger touch area */
        BCM5974_ABS(sc->sc_evdev, ABS_MT_TOUCH_MAJOR, sc->sc_params->w);
        BCM5974_ABS(sc->sc_evdev, ABS_MT_TOUCH_MINOR, sc->sc_params->w);
        /* finger approach area */
        if ((sc->sc_params->tp->caps & USES_COMPACT_REPORT) == 0) {
                BCM5974_ABS(sc->sc_evdev, ABS_MT_WIDTH_MAJOR, sc->sc_params->w);
                BCM5974_ABS(sc->sc_evdev, ABS_MT_WIDTH_MINOR, sc->sc_params->w);
        }
        /* finger orientation */
        BCM5974_ABS(sc->sc_evdev, ABS_MT_ORIENTATION, sc->sc_params->o);
        /* button properties */
        evdev_support_key(sc->sc_evdev, BTN_LEFT);
        if ((sc->sc_params->tp->caps & HAS_INTEGRATED_BUTTON) != 0)
                evdev_support_prop(sc->sc_evdev, INPUT_PROP_BUTTONPAD);
        /* Enable automatic touch assignment for type B MT protocol */
        evdev_support_abs(sc->sc_evdev, ABS_MT_SLOT,
            0, MAX_FINGERS - 1, 0, 0, 0);
        evdev_support_abs(sc->sc_evdev, ABS_MT_TRACKING_ID,
            -1, MAX_FINGERS - 1, 0, 0, 0);
        if ((sc->sc_params->tp->caps & USES_COMPACT_REPORT) == 0)
                evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_TRACK);
        evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_AUTOREL);
        /* Synaptics compatibility events */
        evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_STCOMPAT);

        err = evdev_register(sc->sc_evdev);
        if (err)
                goto detach;

        hidbus_set_intr(dev, bcm5974_intr, sc);

        return (0);

detach:
        bcm5974_detach(dev);
        return (ENOMEM);
}

static int
bcm5974_detach(device_t dev)
{
        struct bcm5974_softc *sc = device_get_softc(dev);

        evdev_free(sc->sc_evdev);

        return (0);
}

static int
bcm5974_resume(device_t dev)
{
        struct bcm5974_softc *sc = device_get_softc(dev);

        bcm5974_set_device_mode(sc, sc->sc_saved_mode);

        return (0);
}

static void
bcm5974_intr(void *context, void *data, hid_size_t len)
{
        struct bcm5974_softc *sc = context;
        const struct bcm5974_dev_params *params = sc->sc_params;
        union evdev_mt_slot slot_data;
        struct tp_finger *f;
        struct tp_finger_compact *fc;
        int coords;
        int ntouch;                     /* the finger number in touch */
        int ibt;                        /* button status */
        int i;
        int slot;
        uint8_t id;
        uint8_t fsize = sizeof(struct tp_finger) + params->tp->delta;

        if ((params->tp->caps & USES_COMPACT_REPORT) != 0)
                fsize = sizeof(struct tp_finger_compact) + params->tp->delta;

        if ((len < params->tp->offset + fsize) ||
            ((len - params->tp->offset) % fsize) != 0) {
                DPRINTFN(BCM5974_LLEVEL_INFO, "Invalid length: %d, %x, %x\n",
                    len, params->tp->offset, fsize);
                return;
        }

        ibt = ((uint8_t *)data)[params->tp->button];
        ntouch = (len - params->tp->offset) / fsize;

        for (i = 0, slot = 0; i != ntouch; i++) {
                if ((params->tp->caps & USES_COMPACT_REPORT) != 0) {
                        fc = (struct tp_finger_compact *)(((uint8_t *)data) +
                             params->tp->offset + params->tp->delta + i * fsize);
                        coords = (int)le32toh(fc->coords);
                        id = fc->id_ori & 0x0f;
                        slot = evdev_mt_id_to_slot(sc->sc_evdev, id);
                        DPRINTFN(BCM5974_LLEVEL_INFO,
                            "[%d]ibt=%d, taps=%d, x=%5d, y=%5d, state=%4d, "
                            "tchmaj=%4d, tchmin=%4d, size=%4d, pressure=%4d, "
                            "ot=%4x, id=%4x, slot=%d\n",
                            i, ibt, ntouch, coords << 19 >> 19,
                            coords << 6 >> 19, (u_int)coords >> 30,
                            fc->touch_major, fc->touch_minor, fc->size,
                            fc->pressure, fc->id_ori >> 5, id, slot);
                        if (fc->touch_major == 0 || slot == -1)
                                continue;
                        slot_data = (union evdev_mt_slot) {
                                .id = id,
                                .x = coords << 19 >> 19,
                                .y = params->y.min + params->y.max -
                                    ((coords << 6) >> 19),
                                .p = fc->pressure,
                                .maj = fc->touch_major << 2,
                                .min = fc->touch_minor << 2,
                                .ori = (int)(fc->id_ori >> 5) - 4,
                        };
                        evdev_mt_push_slot(sc->sc_evdev, slot, &slot_data);
                        continue;
                }
                f = (struct tp_finger *)(((uint8_t *)data) +
                    params->tp->offset + params->tp->delta + i * fsize);
                DPRINTFN(BCM5974_LLEVEL_INFO,
                    "[%d]ibt=%d, taps=%d, o=%4d, ax=%5d, ay=%5d, "
                    "rx=%5d, ry=%5d, tlmaj=%4d, tlmin=%4d, ot=%4x, "
                    "tchmaj=%4d, tchmin=%4d, pressure=%4d, m=%4x\n",
                    i, ibt, ntouch, BCM5974_LE2H(f->origin),
                    BCM5974_LE2H(f->abs_x), BCM5974_LE2H(f->abs_y),
                    BCM5974_LE2H(f->rel_x), BCM5974_LE2H(f->rel_y),
                    BCM5974_LE2H(f->tool_major), BCM5974_LE2H(f->tool_minor),
                    BCM5974_LE2H(f->orientation), BCM5974_LE2H(f->touch_major),
                    BCM5974_LE2H(f->touch_minor), BCM5974_LE2H(f->pressure),
                    BCM5974_LE2H(f->multi));

                if (BCM5974_LE2H(f->touch_major) == 0)
                        continue;
                slot_data = (union evdev_mt_slot) {
                        .id = slot,
                        .x = BCM5974_LE2H(f->abs_x),
                        .y = params->y.min + params->y.max -
                             BCM5974_LE2H(f->abs_y),
                        .p = BCM5974_LE2H(f->pressure),
                        .maj = BCM5974_LE2H(f->touch_major) << 1,
                        .min = BCM5974_LE2H(f->touch_minor) << 1,
                        .w_maj = BCM5974_LE2H(f->tool_major) << 1,
                        .w_min = BCM5974_LE2H(f->tool_minor) << 1,
                        .ori = params->o.max - BCM5974_LE2H(f->orientation),
                };
                evdev_mt_push_slot(sc->sc_evdev, slot, &slot_data);
                slot++;
        }

        evdev_push_key(sc->sc_evdev, BTN_LEFT, ibt);
        evdev_sync(sc->sc_evdev);
}

static int
bcm5974_ev_open(struct evdev_dev *evdev)
{
        struct bcm5974_softc *sc = evdev_get_softc(evdev);
        int err;

        /*
         * By default the touchpad behaves like a HID device, sending
         * packets with reportID = 8. Such reports contain only
         * limited information. They encode movement deltas and button
         * events, but do not include data from the pressure
         * sensors. The device input mode can be switched from HID
         * reports to raw sensor data using vendor-specific USB
         * control commands:
         */
        err = bcm5974_set_device_mode(sc, true);
        if (err != 0) {
                DPRINTF("failed to set mode to RAW MODE (%d)\n", err);
                return (err);
        }

        return (hid_intr_start(sc->sc_dev));
}

static int
bcm5974_ev_close(struct evdev_dev *evdev)
{
        struct bcm5974_softc *sc = evdev_get_softc(evdev);
        int err;

        err = hid_intr_stop(sc->sc_dev);
        if (err != 0)
                return (err);

        /*
         * During re-enumeration of the device we need to force the
         * device back into HID mode before switching it to RAW
         * mode. Else the device does not work like expected.
         */
        err = bcm5974_set_device_mode(sc, false);
        if (err != 0)
                DPRINTF("Failed to set mode to HID MODE (%d)\n", err);

        return (err);
}

static device_method_t bcm5974_methods[] = {
        /* Device interface */
        DEVMETHOD(device_identify,      bcm5974_identify),
        DEVMETHOD(device_probe,         bcm5974_probe),
        DEVMETHOD(device_attach,        bcm5974_attach),
        DEVMETHOD(device_detach,        bcm5974_detach),
        DEVMETHOD(device_resume,        bcm5974_resume),
        DEVMETHOD_END
};

static driver_t bcm5974_driver = {
        .name = "bcm5974",
        .methods = bcm5974_methods,
        .size = sizeof(struct bcm5974_softc)
};

DRIVER_MODULE(bcm5974, hidbus, bcm5974_driver, NULL, NULL);
MODULE_DEPEND(bcm5974, hidbus, 1, 1, 1);
MODULE_DEPEND(bcm5974, hid, 1, 1, 1);
MODULE_DEPEND(bcm5974, evdev, 1, 1, 1);
MODULE_VERSION(bcm5974, 1);
HID_PNP_INFO(bcm5974_devs);