root/sys/dev/adb/adb_ms.c
/*      $NetBSD: adb_ms.c,v 1.27 2025/06/27 21:36:24 andvar Exp $       */

/*
 * Copyright (C) 1998   Colin Wood
 * Copyright (C) 2006, 2007 Michael Lorenz
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Colin Wood.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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>
__KERNEL_RCSID(0, "$NetBSD: adb_ms.c,v 1.27 2025/06/27 21:36:24 andvar Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <sys/condvar.h>

#include <machine/autoconf.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsmousevar.h>

#include <dev/adb/adbvar.h>

#include "adbdebug.h"

#ifdef ADBMS_DEBUG
#define DPRINTF printf
#else
#define DPRINTF while (0) printf
#endif

/*
 * State info, per mouse instance.
 */
struct adbms_softc {
        device_t        sc_dev;
        struct adb_device *sc_adbdev;
        struct adb_bus_accessops *sc_ops;

        /* Extended Mouse Protocol info, faked for non-EMP mice */
        u_int8_t        sc_class;       /* mouse class (mouse, trackball) */
        u_int8_t        sc_buttons;     /* number of buttons */
        u_int32_t       sc_res;         /* mouse resolution (dpi) */
        char            sc_devid[5];    /* device identifier */
        uint8_t         sc_us;          /* cmd to watch for */
        int             sc_mb;          /* current button state */
        device_t        sc_wsmousedev;
        /* helpers for trackpads */
        int             sc_down;
        /*
         * trackpad protocol variant. Known so far:
         * 2 buttons - PowerBook 3400, single events on button 3 and 4 indicate
         *             finger down and up
         * 4 buttons - iBook G4, button 6 indicates finger down, button 4 is
         *             always down
         */
        int             sc_x, sc_y;
        int             sc_tapping;
        /* buffers */
        int             sc_poll;
        int             sc_msg_len;
        kcondvar_t      sc_event;
        kmutex_t        sc_interlock;
        uint8_t         sc_buffer[16];
};

/*
 * Function declarations.
 */
static int      adbms_match(device_t, cfdata_t, void *);
static void     adbms_attach(device_t, device_t, void *);
static void     ems_init(struct adbms_softc *);
static void     init_trackpad(struct adbms_softc *);
static void     adbms_init_mouse(struct adbms_softc *);
static void     adbms_init_turbo(struct adbms_softc *);
static void     adbms_init_uspeed(struct adbms_softc *);
static void     adbms_process_event(struct adbms_softc *, int, uint8_t *);
static int      adbms_send_sync(struct adbms_softc *, uint8_t, int, uint8_t *);

/* Driver definition. */
CFATTACH_DECL_NEW(adbms, sizeof(struct adbms_softc),
    adbms_match, adbms_attach, NULL, NULL);

static int adbms_enable(void *);
static int adbms_ioctl(void *, u_long, void *, int, struct lwp *);
static void adbms_disable(void *);

/*
 * handle tapping the trackpad
 * different pads report different button counts and use slightly different
 * protocols
 */
static void adbms_mangle_2(struct adbms_softc *, int);
static void adbms_mangle_4(struct adbms_softc *, int);
static void adbms_handler(void *, int, uint8_t *);
static int  adbms_wait(struct adbms_softc *, int);
static int  sysctl_adbms_tap(SYSCTLFN_ARGS);

const struct wsmouse_accessops adbms_accessops = {
        adbms_enable,
        adbms_ioctl,
        adbms_disable,
};

static int
adbms_match(device_t parent, cfdata_t cf, void *aux)
{
        struct adb_attach_args *aaa = aux;

        if (aaa->dev->original_addr == ADBADDR_MS)
                return 1;
        else
                return 0;
}

static void
adbms_attach(device_t parent, device_t self, void *aux)
{
        struct adbms_softc *sc = device_private(self);
        struct adb_attach_args *aaa = aux;
        struct wsmousedev_attach_args a;

        sc->sc_dev = self;
        sc->sc_ops = aaa->ops;
        sc->sc_adbdev = aaa->dev;
        sc->sc_adbdev->cookie = sc;
        sc->sc_adbdev->handler = adbms_handler;
        mutex_init(&sc->sc_interlock, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&sc->sc_event, "adbms");
        sc->sc_us = ADBTALK(sc->sc_adbdev->current_addr, 0);
        printf(" addr %d: ", sc->sc_adbdev->current_addr);

        sc->sc_class = MSCLASS_MOUSE;
        sc->sc_buttons = 1;
        sc->sc_res = 100;
        sc->sc_devid[0] = 0;
        sc->sc_devid[4] = 0;
        sc->sc_poll = 0;
        sc->sc_msg_len = 0;
        sc->sc_tapping = 1;

        ems_init(sc);

        /* print out the type of mouse we have */
        switch (sc->sc_adbdev->handler_id) {
        case ADBMS_100DPI:
                printf("%d-button, %u dpi mouse\n", sc->sc_buttons,
                    sc->sc_res);
                break;
        case ADBMS_200DPI:
                sc->sc_res = 200;
                printf("%d-button, %u dpi mouse\n", sc->sc_buttons,
                    sc->sc_res);
                break;
        case ADBMS_MSA3:
                printf("Mouse Systems A3 mouse, %d-button, %u dpi\n",
                    sc->sc_buttons, sc->sc_res);
                break;
        case ADBMS_USPEED:
                printf("MicroSpeed mouse, default parameters\n");
                break;
        case ADBMS_UCONTOUR:
                printf("Contour mouse, default parameters\n");
                break;
        case ADBMS_TURBO:
                printf("Kensington Turbo Mouse\n");
                break;
        case ADBMS_EXTENDED:
                if (sc->sc_devid[0] == '\0') {
                        printf("Logitech ");
                        switch (sc->sc_class) {
                        case MSCLASS_MOUSE:
                                printf("MouseMan (non-EMP) mouse");
                                break;
                        case MSCLASS_TRACKBALL:
                                printf("TrackMan (non-EMP) trackball");
                                break;
                        default:
                                printf("non-EMP relative positioning device");
                                break;
                        }
                        printf("\n");
                } else {
                        printf("EMP ");
                        switch (sc->sc_class) {
                        case MSCLASS_TABLET:
                                printf("tablet");
                                break;
                        case MSCLASS_MOUSE:
                                printf("mouse");
                                break;
                        case MSCLASS_TRACKBALL:
                                printf("trackball");
                                break;
                        case MSCLASS_TRACKPAD:
                                printf("trackpad");
                                init_trackpad(sc);
                                break;
                        default:
                                printf("unknown device");
                                break;
                        }
                        printf(" <%s> %d-button, %u dpi\n", sc->sc_devid,
                            sc->sc_buttons, sc->sc_res);
                }
                break;
        default:
                printf("relative positioning device (mouse?) (%d)\n",
                        sc->sc_adbdev->handler_id);
                break;
        }

        a.accessops = &adbms_accessops;
        a.accesscookie = sc;
        sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint, CFARGS_NONE);
}

/*
 * Initialize extended mouse support -- probes devices as described
 * in Inside Macintosh: Devices, Chapter 5 "ADB Manager".
 *
 * Extended Mouse Protocol is documented in TechNote HW1:
 *      "ADB - The Untold Story:  Space Aliens Ate My Mouse"
 *
 * Supports: Extended Mouse Protocol, MicroSpeed Mouse Deluxe,
 *           Mouse Systems A^3 Mouse, Logitech non-EMP MouseMan
 */
void
ems_init(struct adbms_softc *sc)
{
        
        DPRINTF("ems_init %x\n", sc->sc_adbdev->handler_id);

        switch (sc->sc_adbdev->handler_id) {
                case ADBMS_USPEED:
                case ADBMS_UCONTOUR:
                        adbms_init_uspeed(sc);
                        return;
                case ADBMS_TURBO:
                        adbms_init_turbo(sc);
                        return;
                case ADBMS_100DPI:
                case ADBMS_200DPI:
                        adbms_init_mouse(sc);
        }
}

static void
adbms_init_uspeed(struct adbms_softc *sc)
{
        uint8_t cmd, addr, buffer[4];

        addr = sc->sc_adbdev->current_addr;


        /* Found MicroSpeed Mouse Deluxe Mac or Contour Mouse */
        cmd = ADBLISTEN(addr, 1);

        /*
         * To setup the MicroSpeed or the Contour, it appears
         * that we can send the following command to the mouse
         * and then expect data back in the form:
         *  buffer[0] = 4 (bytes)
         *  buffer[1], buffer[2] as std. mouse
         *  buffer[3] = buffer[4] = 0xff when no buttons
         *   are down.  When button N down, bit N is clear.
         * buffer[4]'s locking mask enables a
         * click to toggle the button down state--sort of
         * like the "Easy Access" shift/control/etc. keys.
         * buffer[3]'s alternative speed mask enables using
         * different speed when the corr. button is down
         */
        buffer[0] = 0x20;       /* Alternative speed */
        buffer[1] = 0x00;       /* speed = maximum */
        buffer[2] = 0x10;       /* enable extended protocol,
                                 * lower bits = alt. speed mask
                                 *            = 0000b
                                 */
        buffer[3] = 0x07;       /* Locking mask = 0000b,
                                 * enable buttons = 0111b
                                 */
        adbms_send_sync(sc, cmd, 4, buffer);

        sc->sc_buttons = 3;

        adbms_send_sync(sc, ADBTALK(addr, 1), 0, NULL);
#ifdef ADBMS_DEBUG
                int i;
                printf("reg *");
                for (i = 0; i < sc->sc_msg_len; i++)
                        printf(" %02x", sc->sc_buffer[i]);
                printf("\n");
#endif
        sc->sc_res = 200;

        if (sc->sc_msg_len == 0) {
                DPRINTF("found ancient MacTrac, middle button can't be programmed\n");
                sc->sc_res = 100;
        }
}

static int
adbms_turbo_csum(uint8_t *d)
{
        int i = 0, sum = 0;

        for (i = 0; i < 7; i++)
                sum ^= d[i];
        return (sum ^ 0xff);
}

static void
adbms_init_turbo(struct adbms_softc *sc)
{
        uint8_t addr;

        /* Found Kensington Turbo Mouse */

/*
 * byte 1 assigns what which button does
 - 0x08 - button 1 - 1, button 2 - nothing
 - 0x09 - both buttons - 1
 - 0x0a - button 1 - 1, button 2 - toggle 1
 - 0x0b - button 1 - 1, button 2 - nothing
 - 0x0c - button 1 - 1, button 2 - 2
 - 0x0e - button 1 - 1, button 2 - 3
 - 0x0f - button 1 - 1, button 2 - toggle 3
 - 0x10 - button 1 toggle 1, button 2 nothing
 - 0x11 - button 1 - toggle 1, button 2 - 1
 - 0x12 - both toggle 1
 - 0x14 - button 1 toggle 1, button 2 - 2
 - 0x21 - button 1 - 2, button 2 - 1
 - 0x31 - button 1 - 3, button 2 - 1
 * byte 4 programs a delay for button presses, apparently in 1/100 seconds
 * byte 7 is a simple XOR checksum, writes will only stick if it's valid
          as in, b[7] = (b[0] ^ b[1] ^ ... ^ b[6]) ^ 0xff
 */
 
        /* this seems to be the most reasonable default */
        static u_char data[] =
                { 0xa5, 0x0e, 0, 0, 1, 0xff, 0xff, 0/*0x55*/ };

        addr = sc->sc_adbdev->current_addr;

#ifdef ADBMS_DEBUG
        {
                int i;
                adbms_send_sync(sc, ADBTALK(addr, 2), 0, NULL);
                printf("reg *");
                for (i = 0; i < sc->sc_msg_len; i++)
                        printf(" %02x", sc->sc_buffer[i]);
                printf("\n");
        }
#endif

        adbms_send_sync(sc, ADBFLUSH(addr), 0, NULL);
        data[7] = adbms_turbo_csum(data);
        adbms_send_sync(sc, ADBLISTEN(addr, 2), 8, data);


#ifdef ADBMS_DEBUG
        int i, reg;
        for (reg = 1; reg < 4; reg++) {
                adbms_send_sync(sc, ADBTALK(addr, reg), 0, NULL);
                printf("reg %d", reg);
                for (i = 0; i < sc->sc_msg_len; i++)
                        printf(" %02x", sc->sc_buffer[i]);
                printf("\n");
        }
#endif
}

static void
adbms_init_mouse(struct adbms_softc *sc)
{
        int len;
        uint8_t cmd, addr, buffer[16];

        addr = sc->sc_adbdev->current_addr;
        /* found a mouse */
        cmd = ADBTALK(addr, 3);
        if (!adbms_send_sync(sc, cmd, 0, NULL)) {
#ifdef ADBMS_DEBUG
                printf("adb: ems_init timed out\n");
#endif
                return;
        }

        /* Attempt to initialize Extended Mouse Protocol */
        len = sc->sc_msg_len;
        memcpy(buffer, sc->sc_buffer, len);
        DPRINTF("buffer: %02x %02x\n", buffer[0], buffer[1]);
        buffer[1] = 4; /* make handler ID 4 */
        cmd = ADBLISTEN(addr, 3);
        if (!adbms_send_sync(sc, cmd, len, buffer)) {
#ifdef ADBMS_DEBUG
                printf("adb: ems_init timed out\n");
#endif
                return;
        }

        /*
         * Check to see if successful, if not
         * try to initialize it as other types
         */
        cmd = ADBTALK(addr, 3);
        if (!adbms_send_sync(sc, cmd, 0, NULL)) {
                DPRINTF("timeout checking for EMP switch\n");
                return;
        }
        DPRINTF("new handler ID: %02x\n", sc->sc_buffer[1]);
        if (sc->sc_buffer[1] == ADBMS_EXTENDED) {
                sc->sc_adbdev->handler_id = ADBMS_EXTENDED;
                cmd = ADBTALK(addr, 1);
                if(!adbms_send_sync(sc, cmd, 0, NULL)) {
                        DPRINTF("adb: ems_init timed out\n");
                        return;
                }

                len = sc->sc_msg_len;
                memcpy(buffer, sc->sc_buffer, len);

                if (sc->sc_msg_len == 8) {
                        uint16_t res;
                        /* we have a true EMP device */
#ifdef ADB_PRINT_EMP
                
                        printf("EMP: %02x %02x %02x %02x %02x %02x %02x %02x\n",
                            buffer[0], buffer[1], buffer[2], buffer[3],
                            buffer[4], buffer[5], buffer[6], buffer[7]);
#endif
                        memcpy(sc->sc_devid, &buffer[0], 4);
                        memcpy(&res, &buffer[4], sizeof(res));
                        sc->sc_res = res;
                        sc->sc_class = buffer[6];
                        sc->sc_buttons = buffer[7];
                } else if (buffer[0] == 0x9a &&
                    ((buffer[1] == 0x20) || (buffer[1] == 0x21))) {
                        /*
                         * Set up non-EMP Mouseman/Trackman to put
                         * button bits in 3rd byte instead of sending
                         * via pseudo keyboard device.
                         */
                        if (buffer[1] == 0x21)
                                sc->sc_class = MSCLASS_TRACKBALL;
                        else
                                sc->sc_class = MSCLASS_MOUSE;

                        cmd = ADBLISTEN(addr, 1);
                        buffer[0]=0x00;
                        buffer[1]=0x81;
                        adbms_send_sync(sc, cmd, 2, buffer);

                        cmd = ADBLISTEN(addr, 1);
                        buffer[0]=0x01;
                        buffer[1]=0x81;
                        adbms_send_sync(sc, cmd, 2, buffer);

                        cmd = ADBLISTEN(addr, 1);
                        buffer[0]=0x02;
                        buffer[1]=0x81;
                        adbms_send_sync(sc, cmd, 2, buffer);

                        cmd = ADBLISTEN(addr, 1);
                        buffer[0]=0x03;
                        buffer[1]=0x38;
                        adbms_send_sync(sc, cmd, 2, buffer);

                        sc->sc_buttons = 3;
                        sc->sc_res = 400;
                }
        } else {
                /* Attempt to initialize as an A3 mouse */
                buffer[1] = 0x03; /* make handler ID 3 */
                cmd = ADBLISTEN(addr, 3);
                if (!adbms_send_sync(sc, cmd, len, buffer)) {
#ifdef ADBMS_DEBUG
                        printf("adb: ems_init timed out\n");
#endif
                        return;
                }

                /*
                 * Check to see if successful, if not
                 * try to initialize it as other types
                 */
                cmd = ADBTALK(addr, 3);
                if(adbms_send_sync(sc, cmd, 0, NULL)) {
                        len = sc->sc_msg_len;
                        memcpy(buffer, sc->sc_buffer, len);
                        if (buffer[1] == ADBMS_MSA3) {
                                sc->sc_adbdev->handler_id = ADBMS_MSA3;
                                /* Initialize as above */
                                cmd = ADBLISTEN(addr, 2);
                                /* listen 2 */
                                buffer[0] = 0x00;
                                /* Irrelevant, buffer has 0x77 */
                                buffer[2] = 0x07;
                                /*
                                 * enable 3 button mode = 0111b,
                                 * speed = normal
                                 */
                                adbms_send_sync(sc, cmd, 3, buffer);
                                sc->sc_buttons = 3;
                                sc->sc_res = 300;
                        }
                }
        }
}

static void
adbms_handler(void *cookie, int len, uint8_t *data)
{
        struct adbms_softc *sc = cookie;

#ifdef ADBMS_DEBUG
        int i;
        printf("%s: %02x - ", device_xname(sc->sc_dev), sc->sc_us);
        for (i = 0; i < len; i++) {
                printf(" %02x", data[i]);
        }
        printf("\n");
#endif
        if (len >= 2) {
                memcpy(sc->sc_buffer, &data[2], len - 2);
                sc->sc_msg_len = len - 2;
                if ((data[1] == sc->sc_us) && (len > 2)) {
                        /* make sense of the mouse message */
                        adbms_process_event(sc, sc->sc_msg_len, sc->sc_buffer);
                        return;
                }
                cv_signal(&sc->sc_event);
        } else {
                DPRINTF("bogus message\n");
        }
}

static void
adbms_process_event(struct adbms_softc *sc, int len, uint8_t *buffer)
{
        int buttons = 0, mask, dx, dy, i;
        int button_bit = 1;

        if ((sc->sc_adbdev->handler_id == ADBMS_EXTENDED) && (sc->sc_devid[0] == 0)) {
                /* massage the data to look like EMP data */
                if ((buffer[2] & 0x04) == 0x04)
                        buffer[0] &= 0x7f;
                else
                        buffer[0] |= 0x80;
                if ((buffer[2] & 0x02) == 0x02)
                        buffer[1] &= 0x7f;
                else
                        buffer[1] |= 0x80;
                if ((buffer[2] & 0x01) == 0x01)
                        buffer[2] = 0x00;
                else
                        buffer[2] = 0x80;
        }

        switch (sc->sc_adbdev->handler_id) {
                case ADBMS_USPEED:
                case ADBMS_UCONTOUR:
                        /* MicroSpeed mouse and Contour mouse */
                        if (len == 4)
                                buttons = (~buffer[3]) & 0xff;
                        else {
                                /*
                                 * deal with the strange way old MacTracs report
                                 * button events:
                                 * 0x8080 - all up
                                 * 0x0000 - right button down
                                 * ox0080 - left button down
                                 * 0x8000 - both buttons down
                                 * the middle button seems to do wome weird
                                 * click lock thing
                                 */
                                int bt = ((buffer[0] & 0x80) >> 6) |
                                         ((buffer[1] & 0x80) >> 7);
                                int bttrans[] = {4, 1, 5, 0};
                                buttons = bttrans[bt];
                                DPRINTF("microspeed buttons %x\n", buttons);
                        }
                        break;
                case ADBMS_MSA3:
                        /* Mouse Systems A3 mouse */
                        if (len == 3)
                                buttons = (~buffer[2]) & 0x07;
                        else
                                buttons = (buffer[0] & 0x80) ? 0 : 1;
                        break;
                default:        
                        /* Classic Mouse Protocol (up to 2 buttons) */
                        for (i = 0; i < 2; i++, button_bit <<= 1)
                                /* 0 when button down */
                                if (!(buffer[i] & 0x80))
                                        buttons |= button_bit;
                                else
                                        buttons &= ~button_bit;
                        /* Extended Protocol (up to 6 more buttons) */
                        for (mask = 0x80; i < len;
                             i += (mask == 0x80), button_bit <<= 1) {
                                /* 0 when button down */
                                if (!(buffer[i] & mask))
                                        buttons |= button_bit;
                                else
                                        buttons &= ~button_bit;
                                mask = ((mask >> 4) & 0xf)
                                        | ((mask & 0xf) << 4);
                        }                               
                        break;
        }

        if ((sc->sc_adbdev->handler_id != ADBMS_EXTENDED) &&
            (sc->sc_adbdev->handler_id != ADBMS_TURBO)) {
                dx = ((int)(buffer[1] & 0x3f)) - ((buffer[1] & 0x40) ? 64 : 0);
                dy = ((int)(buffer[0] & 0x3f)) - ((buffer[0] & 0x40) ? 64 : 0);
        } else {
                /* EMP crap, additional motion bits */
                int shift = 7, ddx, ddy, sign, smask;

#ifdef ADBMS_DEBUG
                printf("EMP packet:");
                for (i = 0; i < len; i++)
                        printf(" %02x", buffer[i]);
                printf("\n");
#endif
                dx = (int)buffer[1] & 0x7f;
                dy = (int)buffer[0] & 0x7f;
                for (i = 2; i < len; i++) {
                        ddx = (buffer[i] & 0x07);
                        ddy = (buffer[i] & 0x70) >> 4;
                        dx |= (ddx << shift);
                        dy |= (ddy << shift);
                        shift += 3;
                }
                sign = 1 << (shift - 1);
                smask = 0xffffffff << shift;
                if (dx & sign)
                        dx |= smask;
                if (dy & sign)
                        dy |= smask;
#ifdef ADBMS_DEBUG
                printf("%d %d %08x %d\n", dx, dy, smask, shift);
#endif
        }

        if (sc->sc_class == MSCLASS_TRACKPAD) {

                if (sc->sc_tapping == 1) {
                        if (sc->sc_down) {
                                /* finger is down - collect motion data */
                                sc->sc_x += dx;
                                sc->sc_y += dy;
                        }
                        DPRINTF("buttons: %02x\n", buttons);
                        switch (sc->sc_buttons) {
                                case 2:
                                        buttons |= ((buttons & 2) >> 1);
                                        adbms_mangle_2(sc, buttons);
                                        break;
                                case 4:
                                        adbms_mangle_4(sc, buttons);
                                        break;
                        }
                }
                /* filter the pseudo-buttons out */
                buttons &= 1;
        }

        if (sc->sc_wsmousedev)
                wsmouse_input(sc->sc_wsmousedev, sc->sc_mb | buttons,
                              dx, -dy, 0, 0,
                              WSMOUSE_INPUT_DELTA);
#if NAED > 0
        aed_input(&new_event);
#endif
}

static void
adbms_mangle_2(struct adbms_softc *sc, int buttons)
{

        if (buttons & 4) {
                /* finger down on pad */
                if (sc->sc_down == 0) {
                        sc->sc_down = 1;
                        sc->sc_x = 0;
                        sc->sc_y = 0;
                }
        }
        if (buttons & 8) {
                /* finger up */
                if (sc->sc_down) {
                        if (((sc->sc_x * sc->sc_x + 
                            sc->sc_y * sc->sc_y) < 3) && 
                            (sc->sc_wsmousedev)) {
                                /* 
                                 * if there wasn't much movement between
                                 * finger down and up again we assume
                                 * someone tapped the pad and we just
                                 * send a mouse button event
                                 */
                                wsmouse_input(sc->sc_wsmousedev,
                                    1, 0, 0, 0, 0, WSMOUSE_INPUT_DELTA);
                        }
                        sc->sc_down = 0;
                }
        }
}

static void
adbms_mangle_4(struct adbms_softc *sc, int buttons)
{

        if (buttons & 0x20) {
                /* finger down on pad */
                if (sc->sc_down == 0) {
                        sc->sc_down = 1;
                        sc->sc_x = 0;
                        sc->sc_y = 0;
                }
        }
        if ((buttons & 0x20) == 0) {
                /* finger up */
                if (sc->sc_down) {
                        if (((sc->sc_x * sc->sc_x + 
                            sc->sc_y * sc->sc_y) < 3) && 
                            (sc->sc_wsmousedev)) {
                                /* 
                                 * if there wasn't much movement between
                                 * finger down and up again we assume
                                 * someone tapped the pad and we just
                                 * send a mouse button event
                                 */
                                wsmouse_input(sc->sc_wsmousedev,
                                    1, 0, 0, 0, 0, WSMOUSE_INPUT_DELTA);
                        }
                        sc->sc_down = 0;
                }
        }
}

static int
adbms_enable(void *v)
{
        return 0;
}

static int
adbms_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
{

        switch (cmd) {
        case WSMOUSEIO_GTYPE:
                *(u_int *)data = WSMOUSE_TYPE_ADB;
                break;

        default:
                return (EPASSTHROUGH);
        }
        return (0);
}

static void
adbms_disable(void *v)
{
}

static void
init_trackpad(struct adbms_softc *sc)
{
        const struct sysctlnode *me = NULL, *node = NULL;
        int cmd, addr, ret;
        uint8_t buffer[16];
        uint8_t b2[] = {0x99, 0x94, 0x19, 0xff, 0xb2, 0x8a, 0x1b, 0x50};
        
        addr = sc->sc_adbdev->current_addr;
        cmd = ADBTALK(addr, 1);
        if (!adbms_send_sync(sc, cmd, 0, NULL))
                return;

        if (sc->sc_msg_len != 8)
                return;

        memcpy(buffer, sc->sc_buffer, 8);

        /* now whack the pad */
        cmd = ADBLISTEN(addr, 1);
        buffer[6] = 0x0d;
        adbms_send_sync(sc, cmd, 8, buffer);

        delay(1000);
        cmd = ADBLISTEN(addr, 2);
        adbms_send_sync(sc, cmd, 8, b2);

        delay(1000);
        cmd = ADBLISTEN(addr, 1);
        buffer[6] = 0x03;
        adbms_send_sync(sc, cmd, 8, buffer);

        cmd = ADBFLUSH(addr);
        adbms_send_sync(sc, cmd, 0, NULL);
        delay(1000);

        /*
         * setup a sysctl node to control whether tapping the pad should
         * trigger mouse button events
         */

        sc->sc_tapping = 1;
        
        ret = sysctl_createv(NULL, 0, NULL, &me,
            CTLFLAG_READWRITE,
            CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
            NULL, 0, NULL, 0,
            CTL_MACHDEP, CTL_CREATE, CTL_EOL);

        ret = sysctl_createv(NULL, 0, NULL, &node,
            CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
            CTLTYPE_INT, "tapping", "tapping the pad causes button events",
            sysctl_adbms_tap, 1, (void *)sc, 0,
            CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL);

        (void)ret;
}

static int
adbms_wait(struct adbms_softc *sc, int timeout)
{
        int cnt = 0;
        
        if (sc->sc_poll) {
                while (sc->sc_msg_len == -1) {
                        sc->sc_ops->poll(sc->sc_ops->cookie);
                }
        } else {
                mutex_enter(&sc->sc_interlock);
                while ((sc->sc_msg_len == -1) && (cnt < timeout)) {
                        cv_timedwait(&sc->sc_event, &sc->sc_interlock, hz);
                        cnt++;
                }
                mutex_exit(&sc->sc_interlock);
        }
        return (sc->sc_msg_len > 0);
}

static int
adbms_send_sync(struct adbms_softc *sc, uint8_t cmd, int len, uint8_t *msg)
{
        int i;

        sc->sc_msg_len = -1;
        DPRINTF("send: %02x", cmd);
        for (i = 0; i < len; i++)
                DPRINTF(" %02x", msg[i]);
        DPRINTF("\n");
        sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, len, msg);
        adbms_wait(sc, 3);
        return (sc->sc_msg_len != -1);
}

static int
sysctl_adbms_tap(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct adbms_softc *sc = node.sysctl_data;

        node.sysctl_idata = sc->sc_tapping;

        if (newp) {

                /* we're asked to write */      
                node.sysctl_data = &sc->sc_tapping;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {

                        sc->sc_tapping = (*(int *)node.sysctl_data == 0) ? 0 : 1;
                        return 0;
                }
                return EINVAL;
        } else {

                node.sysctl_data = &sc->sc_tapping;
                node.sysctl_size = 4;
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }

        return 0;
}

SYSCTL_SETUP(sysctl_ams_setup, "sysctl ams subtree setup")
{

        sysctl_createv(NULL, 0, NULL, NULL,
                       CTLFLAG_PERMANENT,
                       CTLTYPE_NODE, "machdep", NULL,
                       NULL, 0, NULL, 0,
                       CTL_MACHDEP, CTL_EOL);
}