root/usr/src/uts/common/io/vuidmice/vuidps2.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 *                      2/3/5 Button PS/2 Mouse Protocol
 *
 * This module dynamically determines the number of buttons on the mouse.
 */

#include <sys/param.h>
#include <sys/stream.h>
#include <sys/vuid_event.h>
#include "vuidmice.h"
#include <sys/vuid_wheel.h>
#include <sys/mouse.h>
#include <sys/strsun.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

/*
 * BUT(1)               LEFT   BUTTON
 * BUT(2)               MIDDLE BUTTON (if present)
 * BUT(3)               RIGHT  BUTTON
 */

#define PS2_BUTTONMASK          7               /* mask byte zero with this */

#define PS2_BUTTON_L            (uchar_t)0x01   /* Left button pressed */
#define PS2_BUTTON_R            (uchar_t)0x02   /* Right button pressed */
#define PS2_BUTTON_M            (uchar_t)0x04   /* Middle button pressed */
#define PS2_DATA_XSIGN          (uchar_t)0x10   /* X data sign bit */
#define PS2_DATA_YSIGN          (uchar_t)0x20   /* Y data sign bit */

#define PS2_START                       0       /* Beginning of packet  */
#define PS2_BUTTON                      1       /* Got button status    */
#define PS2_MAYBE_REATTACH              2       /* Got button status    */
#define PS2_DELTA_Y                     3       /* Got delta X          */
#define PS2_WHEEL_DELTA_Z               4
#define PS2_WHEEL5_DELTA_Z              5
#define PS2_WAIT_RESET_COMPLETE         6
#define PS2_WAIT_FOR_AA                 7
#define PS2_WAIT_FOR_00                 8
#define PS2_WAIT_SETRES0_ACK1           9
#define PS2_WAIT_SETRES0_ACK2           10      /* -+ must be consecutive */
#define PS2_WAIT_SCALE1_1_ACK           11      /*  | */
#define PS2_WAIT_SCALE1_2_ACK           12      /*  | */
#define PS2_WAIT_SCALE1_3_ACK           13      /* -+ */
#define PS2_WAIT_STATREQ_ACK            14      /* -+ */
#define PS2_WAIT_STATUS_1               15      /*  | */
#define PS2_WAIT_STATUS_BUTTONS         16      /*  | must stay in this order */
#define PS2_WAIT_STATUS_REV             17      /* -+ */
#define PS2_WAIT_STATUS_3               18
#define PS2_WAIT_WHEEL_SMPL1_CMD_ACK    19      /* Set the sample rate to 200 */
#define PS2_WAIT_WHEEL_SMPL1_RATE_ACK   20
#define PS2_WAIT_WHEEL_SMPL2_CMD_ACK    21      /* Set the sample rate to 200 */
#define PS2_WAIT_WHEEL_SMPL2_RATE_ACK   22
#define PS2_WAIT_WHEEL_SMPL3_CMD_ACK    23      /* Set the sample rate to 80 */
#define PS2_WAIT_WHEEL_SMPL3_RATE_ACK   24
#define PS2_WAIT_WHEEL_DEV_CMD          25
#define PS2_WAIT_WHEEL_DEV_ACK          26      /* Detected wheel mouse */
#define PS2_WAIT_WHEEL5_SMPL1_CMD_ACK   27      /* Set the sample rate to 200 */
#define PS2_WAIT_WHEEL5_SMPL1_RATE_ACK  28
#define PS2_WAIT_WHEEL5_SMPL2_CMD_ACK   29      /* Set the sample rate to 200 */
#define PS2_WAIT_WHEEL5_SMPL2_RATE_ACK  30
#define PS2_WAIT_WHEEL5_SMPL3_CMD_ACK   31      /* Set the sample rate to 100 */
#define PS2_WAIT_WHEEL5_SMPL3_RATE_ACK  32
#define PS2_WAIT_WHEEL5_DEV_CMD         33
#define PS2_WAIT_WHEEL5_DEV_ACK         34      /* Detected 5 button mouse */
#define PS2_WAIT_SETRES3_CMD            35
#define PS2_WAIT_SETRES3_ACK1           36
#define PS2_WAIT_SETRES3_ACK2           37
#define PS2_WAIT_STREAM_ACK             38
#define PS2_WAIT_ON_ACK                 39

#define MOUSE_MODE_PLAIN        0       /* Normal PS/2 mouse - 3 byte msgs */
#define MOUSE_MODE_WHEEL        1       /* Wheel mouse - 4 byte msgs */
#define MOUSE_MODE_WHEEL5       2       /* Wheel + 5 btn mouse - 4 byte msgs */

#define PS2_FLAG_NO_EXTN        0x08    /* Mouse doesn't obey extended cmds */
#define PS2_FLAG_INIT_DONE      0x01    /* Mouse has been inited successfully */
#define PS2_FLAG_INIT_TIMEOUT   0x02    /* Mouse init timeout */

/*
 * The RESET command takes more time
 * before the PS/2 mouse is ready
 */
#define PS2_INIT_TMOUT_RESET    500000  /* 500ms for RESET command */
#define PS2_INIT_TMOUT_PER_CMD  200000  /* 200ms for each command-response */
#define PS2_INIT_TMOUT_PER_GROUP        500000 /* 500ms for group commands */

#define PS2_MAX_INIT_COUNT      5

static void vuidmice_send_wheel_event(queue_t *const, uchar_t,
                uchar_t, uchar_t, int);
extern void VUID_PUTNEXT(queue_t *const, uchar_t, uchar_t, uchar_t, int);
extern void uniqtime32(struct timeval32 *);
static void VUID_INIT_TIMEOUT(void *q);

/*
 * We apply timeout to nearly each command-response
 * during initialization:
 *
 * Set timeout for SET RESOLUTION
 * Set timeout for SET SCALE
 * Set timeout for SET SAMPLE RATE
 * Set timeout for STATUS REQUEST
 * Set timeout for GET DEV
 * Set timeout for SET STREAM MODE and ENABLE.
 *
 * But for simplicity, sometimes we just apply the timeout
 * to a function with group commands (e.g. wheel-mouse detection).
 *
 */
static void
vuid_set_timeout(queue_t *const qp, clock_t time)
{
        if (STATEP->init_tid != 0)
                (void) quntimeout(qp, STATEP->init_tid);
        STATEP->init_tid = qtimeout(qp, VUID_INIT_TIMEOUT,
            qp, drv_usectohz(time));
}

static void
vuid_cancel_timeout(queue_t *const qp)
{
        (void) quntimeout(qp, STATEP->init_tid);
        STATEP->init_tid = 0;
}

/*
 * vuidmice_send_wheel_event
 *      Convert wheel data to firm_events
 */
static void
vuidmice_send_wheel_event(queue_t *const qp, uchar_t event_id,
    uchar_t event_pair_type, uchar_t event_pair, int event_value)
{
        mblk_t          *bp;
        Firm_event      *fep;

        if ((bp = allocb((int)sizeof (Firm_event), BPRI_HI)) == NULL) {

                return;
        }

        fep = (void *)bp->b_wptr;
        fep->id = vuid_id_addr(vuid_first(VUID_WHEEL)) |
            vuid_id_offset(event_id);
        fep->pair_type = event_pair_type;
        fep->pair = event_pair;
        fep->value = event_value;
        uniqtime32(&fep->time);
        bp->b_wptr += sizeof (Firm_event);

        if (canput(qp->q_next)) {
                putnext(qp, bp);
        } else {
                (void) putbq(qp, bp); /* read side is blocked */
        }
}


static void
sendButtonEvent(queue_t *const qp)
{
        static int bmap[3] = {1, 3, 2};
        uint_t b;

        /* for each button, see if it has changed */
        for (b = 0; b < STATEP->nbuttons; b++) {
                uchar_t mask = 0x1 << b;

                if ((STATEP->buttons & mask) != (STATEP->oldbuttons & mask))
                        VUID_PUTNEXT(qp, (uchar_t)BUT(bmap[b]), FE_PAIR_NONE, 0,
                            (STATEP->buttons & mask ? 1 : 0));
        }
}

void
put1(queue_t *const qp, int c)
{
        mblk_t *bp;

        if (bp = allocb(1, BPRI_MED)) {
                *bp->b_wptr++ = (char)c;
                putnext(qp, bp);
        }
}

static void
vuidmice_start_wdc_or_setres(queue_t *qp)
{
        /* Set timeout for set res or sample rate */
        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_GROUP);

        /*
         * Start the wheel-mouse detection code.  First, we look
         * for standard wheel mice.  If we set the sample rate
         * to 200, 100, and then 80 and finally request the
         * device ID, a wheel mouse will return an ID of 0x03.
         * After that, we'll try for the wheel+5 variety.  The
         * incantation in this case is 200, 200, and 80.  We'll
         * get 0x04 back in that case.
         */
        if (STATEP->inited & PS2_FLAG_NO_EXTN) {
                STATEP->state = PS2_WAIT_SETRES3_ACK1;
                put1(WR(qp), MSESETRES);
        } else {
                STATEP->state = PS2_WAIT_WHEEL_SMPL1_CMD_ACK;
                put1(WR(qp), MSECHGMOD);
        }
}

int
VUID_OPEN(queue_t *const qp)
{
        STATEP->format = VUID_FIRM_EVENT;
        STATEP->vuid_mouse_mode = MOUSE_MODE_PLAIN;
        STATEP->inited = 0;
        STATEP->nbuttons = 3;

        STATEP->state = PS2_WAIT_RESET_COMPLETE;

        put1(WR(qp), MSERESET);

        while ((STATEP->state != PS2_START) &&
            !(STATEP->inited & PS2_FLAG_INIT_TIMEOUT)) {
                if (qwait_sig(qp) == 0)
                        break;
        }

        /*
         * Later the PS/2 mouse maybe re-attach, so here
         * clear the init_count.
         */
        STATEP->init_count = 0;

        return (0);
}

void
VUID_CLOSE(queue_t *const qp)
{
        if (STATEP->init_tid != 0)
                vuid_cancel_timeout(qp);
}

static void
VUID_INIT_TIMEOUT(void *q)
{
        queue_t *qp = q;

        STATEP->init_tid = 0;

        /*
         * Some mice do not even send an error in response to
         * the wheel mouse sample commands, so if we're in any of
         * the PS2_WAIT_WHEEL_SMPL* states, and there has been
         * a timeout, assume the mouse cannot handle the extended
         * (wheel mouse) commands.
         */
        if ((STATEP->state == PS2_WAIT_WHEEL_SMPL1_CMD_ACK) ||
            (STATEP->state == PS2_WAIT_WHEEL_SMPL1_RATE_ACK) ||
            (STATEP->state == PS2_WAIT_WHEEL_SMPL2_RATE_ACK) ||
            (STATEP->state == PS2_WAIT_WHEEL_SMPL3_RATE_ACK)) {
                /*
                 * We overload 'inited' to mark the PS/2 mouse
                 * as one which doesn't respond to extended commands.
                 */

                STATEP->inited |= PS2_FLAG_NO_EXTN;
        }


        /*
         * If the Logitech button detection sequence timed out at some point
         * in the sequence, ignore it and skip to the next step in
         * initialization.  Do NOT count this as a timeout, so do NOT
         * increment init_count.
         */
        if (STATEP->state >= PS2_WAIT_STATREQ_ACK &&
            STATEP->state <= PS2_WAIT_STATUS_REV) {

                /* See the comment under the PS2_WAIT_STATUS_BUTTONS case */
#if     defined(VUID3PS2)
                STATEP->nbuttons = 3;
#else
                STATEP->nbuttons = 2;
#endif
                vuidmice_start_wdc_or_setres(qp);
                return;
        }

        /*
         * If the initialization process has timed out too many times, even if
         * a subset of the process was successful, stop trying and put the
         * mouse in the only state from which it can recover -- waiting for an
         * 0xAA 0x00 response (i.e. like one we get on resume from mouse8042
         * or from a replug).
         */
        if (++STATEP->init_count >= PS2_MAX_INIT_COUNT) {
                STATEP->inited |= PS2_FLAG_INIT_TIMEOUT;
                STATEP->state = PS2_WAIT_FOR_AA;
        } else {

                STATEP->state = PS2_WAIT_RESET_COMPLETE;

                /* Try to reset the mouse again */
                put1(WR(qp), MSERESET);
        }
}

void
VUID_QUEUE(queue_t *const qp, mblk_t *mp)
{
        int code, length;
        clock_t now;
        clock_t elapsed;
        clock_t mouse_timeout;

        mouse_timeout = drv_usectohz(250000);
        now = ddi_get_lbolt();
        elapsed = now - STATEP->last_event_lbolt;
        STATEP->last_event_lbolt = now;

        while (mp->b_rptr < mp->b_wptr) {
                length = MBLKL(mp);
                code = *mp->b_rptr++;

                switch (STATEP->state) {

                /*
                 * Start state. We stay here if the start code is not
                 * received thus forcing us back into sync. When we get a
                 * start code the button mask comes with it forcing us to
                 * to the next state.
                 */
restart:
                case PS2_START:

                        /*
                         * 3-byte packet format
                         *
                         * Bit   7   6    5     4       3   2   1       0
                         * Byte ---- ---- ----- ----- -- ------ ------ ------
                         * 1    Y_Ov X_Ov Y_Sgn X_Sgn  1 MdlBtn RgtBtn LftBtn
                         * 2    |<--------------X Movement----------------->|
                         * 3    |<--------------Y Movement----------------->|
                         *
                         * 4-byte wheel packet format
                         *
                         * Bit   7    6   5     4       3   2   1       0
                         * Byte ---- ---- ----- ----- -- ------ ------ ------
                         * 1    Y_Ov X_Ov Y_Sgn X_Sgn  1 MdlBtn RgtBtn LftBtn
                         * 2    |<--------------X Movement----------------->|
                         * 3    |<--------------Y Movement----------------->|
                         * 4    |<--------------Z Movement----------------->|
                         *
                         * 4-byte wheel+5 packet format
                         *
                         * Bit   7    6   5     4       3   2   1       0
                         * Byte ---- ---- ----- ----- -- ------ ------ ------
                         * 1    Y_Ov X_Ov Y_Sgn X_Sgn  1 MdlBtn RgtBtn LftBtn
                         * 2    |<--------------X Movement----------------->|
                         * 3    |<--------------Y Movement----------------->|
                         * 4    0    0   5_Btn 4_Btn Z3   Z2    Z1      Z0
                         */

                        if (!(STATEP->inited & PS2_FLAG_INIT_DONE)) {
                                STATEP->sync_byte = code & 0x8;
                                STATEP->inited |= PS2_FLAG_INIT_DONE;
                        }
                /*
                 * the PS/2 mouse data format doesn't have any sort of sync
                 * data to make sure we are in sync with the packet stream,
                 * but the Technical Reference manual states that bits 2 & 3
                 * of the first byte are reserved.  Logitech uses bit 2 for
                 * the middle button.  We HOPE that noone uses bit 3 though,
                 * and decide we're out of sync if bit 3 is not set here.
                 */

                        if ((code ^ STATEP->sync_byte) & 0x08) {
                                /* bit 3 not set */
                                STATEP->state = PS2_START;
                                break;                  /* toss the code */
                        }

                        /* get the button values */
                        STATEP->buttons = code & PS2_BUTTONMASK;
                        if (STATEP->buttons != STATEP->oldbuttons) {
                                sendButtonEvent(qp);
                                STATEP->oldbuttons = STATEP->buttons;
                        }

                        /* bit 5 indicates Y value is negative (the sign bit) */
                        if (code & PS2_DATA_YSIGN)
                                STATEP->deltay = -1 & ~0xff;
                        else
                                STATEP->deltay = 0;

                        /* bit 4 is X sign bit */
                        if (code & PS2_DATA_XSIGN)
                                STATEP->deltax = -1 & ~0xff;
                        else
                                STATEP->deltax = 0;

                        if (code == MSE_AA)
                                STATEP->state = PS2_MAYBE_REATTACH;
                        else
                                STATEP->state = PS2_BUTTON;

                        break;

                case PS2_WAIT_FOR_AA:
                        /*
                         * On Dell latitude D800, the initial MSE_ACK is
                         * received after the initialization sequence
                         * times out, so restart it here.
                         */
                        if (code == MSE_ACK) {
                                STATEP->inited &= ~PS2_FLAG_INIT_TIMEOUT;
                                STATEP->init_count = 0;
                                STATEP->state = PS2_WAIT_RESET_COMPLETE;
                                put1(WR(qp), MSERESET);
                                break;
                        }

                        if (code != MSE_AA)
                                break;

                        STATEP->state = PS2_WAIT_FOR_00;
                        break;

                case PS2_WAIT_FOR_00:
                        if (code != MSE_00)
                                break;

                        STATEP->inited &= ~PS2_FLAG_INIT_TIMEOUT;
                        STATEP->init_count = 0;
                        STATEP->state = PS2_WAIT_RESET_COMPLETE;
                        put1(WR(qp), MSERESET);
                        break;

                case PS2_MAYBE_REATTACH:
                        if (code == MSE_00) {
                                STATEP->state = PS2_WAIT_RESET_COMPLETE;
                                put1(WR(qp), MSERESET);
                                break;
                        }
                        /*FALLTHROUGH*/

                case PS2_BUTTON:
                        /*
                         * Now for the 7 bits of delta x.  "Or" in
                         * the sign bit and continue.  This is ac-
                         * tually a signed 9 bit number, but I just
                         * truncate it to a signed char in order to
                         * avoid changing and retesting all of the
                         * mouse-related modules for this patch.
                         */
                        if (elapsed > mouse_timeout)
                                goto restart;
                        STATEP->deltax |= code & 0xff;
                        STATEP->state = PS2_DELTA_Y;
                        break;

                case PS2_DELTA_Y:
                        /*
                         * This byte is delta Y.  If this is a plain mouse,
                         * we're done.  Wheel mice have two different flavors
                         * of fourth byte.
                         */

                        if (elapsed > mouse_timeout) {
                                goto restart;
                        }
                        STATEP->deltay |= code & 0xff;

                        if (STATEP->vuid_mouse_mode == MOUSE_MODE_WHEEL) {
                                STATEP->state = PS2_WHEEL_DELTA_Z;
                                break;
                        } else if (STATEP->vuid_mouse_mode ==
                            MOUSE_MODE_WHEEL5) {
                                STATEP->state = PS2_WHEEL5_DELTA_Z;
                                break;
                        }
                        goto packet_complete;

                case PS2_WHEEL5_DELTA_Z:
                        if (code & 0x10) {
                                /* fourth physical button */
                                VUID_PUTNEXT(qp, (uchar_t)BUT(4),
                                    FE_PAIR_NONE, 0, 1);
                                VUID_PUTNEXT(qp, (uchar_t)BUT(4),
                                    FE_PAIR_NONE, 0, 0);
                        } else if (code & 0x20) {
                                /* fifth physical button */
                                VUID_PUTNEXT(qp, (uchar_t)BUT(5),
                                    FE_PAIR_NONE, 0, 1);
                                VUID_PUTNEXT(qp, (uchar_t)BUT(5),
                                    FE_PAIR_NONE, 0, 0);
                        }
                        /*FALLTHROUGH*/

                case PS2_WHEEL_DELTA_Z:
                        /*
                         * Check whether reporting vertical wheel
                         * movements is enabled
                         */
                        code &= 0xf;

                        if (STATEP->wheel_state_bf & (1 <<
                            VUIDMICE_VERTICAL_WHEEL_ID)) {
                                /*
                                 * PS/2 mouse reports -ve values
                                 * when the wheel is scrolled up. So
                                 * we need to convert it into +ve as
                                 * X interprets a +ve value as wheel up event.
                                 * Same is true for the horizontal wheel also.
                                 * The mouse reports 0xf when scrolled up
                                 * and 0x1 when scrolled down. This observation
                                 * is based on Logitech, HCL,
                                 * Microsoft and Black Cat mouse only
                                 */
                                if (code == 0xf) {
                                        /* negative Z - wheel up */
                                        code |= 0xfffffff0;
                                        vuidmice_send_wheel_event(qp, 0,
                                            FE_PAIR_NONE, 0, -code);
                                } else if (code == 0x01) {
                                        /* positive Z - wheel down */
                                        vuidmice_send_wheel_event(qp, 0,
                                            FE_PAIR_NONE, 0, -code);
                                }
                        }

                        /*
                         * Check whether reporting horizontal wheel
                         * movements is enabled
                         */
                        if (STATEP->wheel_state_bf &
                            (1 << VUIDMICE_HORIZONTAL_WHEEL_ID)) {

                                /*
                                 * The mouse return -7 and +7 when it
                                 * is scrolled horizontally
                                 */
                                if (code == 0x09) {
                                        /* negative Z - wheel left */
                                        vuidmice_send_wheel_event(qp, 1,
                                            FE_PAIR_NONE, 0, 1);
                                } else if (code == 0x07) {
                                        /* positive Z - wheel right */
                                        vuidmice_send_wheel_event(qp, 1,
                                            FE_PAIR_NONE, 0, -1);
                                }
                        }

packet_complete:
                        STATEP->state = PS2_START;
                        /*
                         * If we can peek at the next mouse character, and
                         * its not the start of the next packet, don't use
                         * this packet.
                         */
                        if (mp->b_wptr > mp->b_rptr &&
                            ((mp->b_rptr[0] ^ STATEP->sync_byte) & 0x08)) {
                                /*
                                 * bit 3 not set
                                 */
                                break;
                        }

                        /*
                         * send the info to the next level --
                         * need to send multiple events if we have both
                         * a delta *AND* button event(s)
                         */

                        /* motion has occurred ... */
                        if (STATEP->deltax)
                                VUID_PUTNEXT(qp, (uchar_t)LOC_X_DELTA,
                                    FE_PAIR_ABSOLUTE, (uchar_t)LOC_X_ABSOLUTE,
                                    STATEP->deltax);

                        if (STATEP->deltay)
                                VUID_PUTNEXT(qp, (uchar_t)LOC_Y_DELTA,
                                    FE_PAIR_ABSOLUTE, (uchar_t)LOC_Y_ABSOLUTE,
                                    STATEP->deltay);

                        STATEP->deltax = STATEP->deltay = 0;
                        break;

                case PS2_WAIT_RESET_COMPLETE:

                        /*
                         * If length is 1, code holds the data from the message.
                         * for lengths > 1, we look at *(mp->b_rptr + offset)
                         * for the rest of the data.
                         */
                        if (length == 1) {
                                /*
                                 * A response with length 1 from the mouse
                                 * driver can be either an ACK (the first part
                                 * of the reset reply) or either MSEERROR or
                                 * MSERESEND.  Issue another reset if either
                                 * of the latter are received.  For mice that
                                 * are not connected, MSERESEND is received
                                 * quickly.
                                 */

                                if (code == MSE_ACK)
                                        break;

                                if (++STATEP->init_count >=
                                    PS2_MAX_INIT_COUNT) {
                                        STATEP->inited |= PS2_FLAG_INIT_TIMEOUT;
                                        STATEP->state = PS2_WAIT_FOR_AA;
                                } else {
                                        put1(WR(qp), MSERESET);
                                }

                                break;

                        } else if (length != 2) {
                                break;
                        }

                        /*
                         * The only possible 2-byte reply from mouse8042 is
                         * 0xAA 0x00.  If the mouse doesn't send that, mouse8042
                         * will send a 1-byte error message, handled above by
                         * resetting the mouse.
                         */

                        /* Skip past the 0x00 (since `code' contains 0xAA) */
                        mp->b_rptr += 1;

                        /* Reset completed successfully */

                        STATEP->state = PS2_WAIT_SETRES0_ACK1;

                        /* Set timeout for set res */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_GROUP);

                        /* Begin Logitech autodetect sequence */
                        put1(WR(qp), MSESETRES);
                        break;

                case PS2_WAIT_SETRES0_ACK1:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_SETRES0_ACK2;
                        put1(WR(qp), 0);
                        break;

                case PS2_WAIT_SETRES0_ACK2:
                case PS2_WAIT_SCALE1_1_ACK:
                case PS2_WAIT_SCALE1_2_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state++;
                        put1(WR(qp), MSESCALE1);
                        break;

                case PS2_WAIT_SCALE1_3_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }

                        /* Set res and scale have been ok */
                        vuid_cancel_timeout(qp);

                        STATEP->state = PS2_WAIT_STATREQ_ACK;

                        /* Set timeout for status request */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_GROUP);

                        put1(WR(qp), MSESTATREQ);

                        break;

                case PS2_WAIT_STATREQ_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_STATUS_1;
                        break;

                case PS2_WAIT_STATUS_1:
                        STATEP->state = PS2_WAIT_STATUS_BUTTONS;
                        break;

                case PS2_WAIT_STATUS_BUTTONS:
                        if (code != 0) {
                                STATEP->nbuttons = (uchar_t)code;
                                STATEP->state = (uchar_t)PS2_WAIT_STATUS_REV;
                        } else {
#if     defined(VUID3PS2)
                                /*
                                 * It seems that there are some 3-button mice
                                 * that don't play the Logitech autodetect
                                 * game.  One is a Mouse Systems mouse OEM'ed
                                 * by Intergraph.
                                 *
                                 * Until we find out how to autodetect these
                                 * mice, we'll assume that if we're being
                                 * compiled as vuid3ps2 and the mouse doesn't
                                 * play the autodetect game, it's a 3-button
                                 * mouse.  This effectively disables
                                 * autodetect for mice using vuid3ps2, but
                                 * since vuid3ps2 is used only on x86 where
                                 * we currently assume manual configuration,
                                 * this shouldn't be a problem.  At some point
                                 * in the future when we *do* start using
                                 * autodetect on x86, we should probably define
                                 * VUIDPS2 instead of VUID3PS2.  Even then,
                                 * we could leave this code so that *some*
                                 * mice could use autodetect and others not.
                                 */
                                STATEP->nbuttons = 3;
#else
                                STATEP->nbuttons = 2;
#endif
                                STATEP->state = PS2_WAIT_STATUS_3;
                        }
                        break;

                case PS2_WAIT_STATUS_REV:
                        /*FALLTHROUGH*/

                case PS2_WAIT_STATUS_3:

                        /* Status request completed successfully */
                        vuid_cancel_timeout(qp);

                        vuidmice_start_wdc_or_setres(qp);
                        break;

                case PS2_WAIT_WHEEL_SMPL1_CMD_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL_SMPL1_RATE_ACK;
                        put1(WR(qp), 200);
                        break;
                case PS2_WAIT_WHEEL_SMPL1_RATE_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL_SMPL2_CMD_ACK;
                        put1(WR(qp), MSECHGMOD);
                        break;

                case PS2_WAIT_WHEEL_SMPL2_CMD_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL_SMPL2_RATE_ACK;
                        put1(WR(qp), 100);
                        break;

                case PS2_WAIT_WHEEL_SMPL2_RATE_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL_SMPL3_CMD_ACK;
                        put1(WR(qp), MSECHGMOD);
                        break;

                case PS2_WAIT_WHEEL_SMPL3_CMD_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL_SMPL3_RATE_ACK;
                        put1(WR(qp), 80);
                        break;

                case PS2_WAIT_WHEEL_SMPL3_RATE_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }

                        /* Set sample rate completed successfully */
                        vuid_cancel_timeout(qp);

                        STATEP->state = PS2_WAIT_WHEEL_DEV_CMD;

                        /* Set timeout for get dev */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_CMD);

                        put1(WR(qp), MSEGETDEV);
                        break;

                case PS2_WAIT_WHEEL_DEV_CMD:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL_DEV_ACK;
                        break;

                case PS2_WAIT_WHEEL_DEV_ACK:

                        /* Get dev completed successfully */
                        vuid_cancel_timeout(qp);

                        if (code != 0x03) {
                                STATEP->state = PS2_WAIT_SETRES3_ACK1;

                                /* Set timeout for set res */
                                vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_CMD);

                                put1(WR(qp), MSESETRES);

                                break;
                        }

                        STATEP->vuid_mouse_mode = MOUSE_MODE_WHEEL;

                        /*
                         * Found wheel. By default enable the wheel.
                         */
                        STATEP->wheel_state_bf |= VUID_WHEEL_STATE_ENABLED;

                        STATEP->state = PS2_WAIT_WHEEL5_SMPL1_CMD_ACK;

                        /* Set timeout for set sample rate */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_GROUP);

                        /* We're on a roll - try for wheel+5 */
                        put1(WR(qp), MSECHGMOD);

                        break;

                case PS2_WAIT_WHEEL5_SMPL1_CMD_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL5_SMPL1_RATE_ACK;
                        put1(WR(qp), 200);
                        break;

                case PS2_WAIT_WHEEL5_SMPL1_RATE_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL5_SMPL2_CMD_ACK;
                        put1(WR(qp), MSECHGMOD);
                        break;

                case PS2_WAIT_WHEEL5_SMPL2_CMD_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL5_SMPL2_RATE_ACK;
                        put1(WR(qp), 200);
                        break;

                case PS2_WAIT_WHEEL5_SMPL2_RATE_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL5_SMPL3_CMD_ACK;
                        put1(WR(qp), MSECHGMOD);
                        break;

                case PS2_WAIT_WHEEL5_SMPL3_CMD_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL5_SMPL3_RATE_ACK;
                        put1(WR(qp), 80);
                        break;

                case PS2_WAIT_WHEEL5_SMPL3_RATE_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }

                        /* Set sample rate completed successfully */
                        vuid_cancel_timeout(qp);

                        STATEP->state = PS2_WAIT_WHEEL5_DEV_CMD;

                        /* Set timeout for wheel5 get dev */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_CMD);

                        put1(WR(qp), MSEGETDEV);

                        break;

                case PS2_WAIT_WHEEL5_DEV_CMD:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_WHEEL5_DEV_ACK;
                        break;

                case PS2_WAIT_WHEEL5_DEV_ACK:
                        if (code == 0x04) {
                                STATEP->vuid_mouse_mode = MOUSE_MODE_WHEEL5;
                                STATEP->nbuttons        = 5;

                                /*
                                 * Found wheel. By default enable the wheel.
                                 */
                                STATEP->wheel_state_bf |=
                                    VUID_WHEEL_STATE_ENABLED <<
                                    MOUSE_MODE_WHEEL;
                        }

                        /* Wheel5 get dev completed successfully */
                        vuid_cancel_timeout(qp);

                        /* FALLTHROUGH */

                case PS2_WAIT_SETRES3_CMD:
                        STATEP->state = PS2_WAIT_SETRES3_ACK1;

                        /* Set timeout for set res */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_CMD);

                        put1(WR(qp), MSESETRES);

                        break;

                case PS2_WAIT_SETRES3_ACK1:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_SETRES3_ACK2;
                        put1(WR(qp), 3);
                        break;

                case PS2_WAIT_SETRES3_ACK2:
                        if (code != MSE_ACK) {
                                break;
                        }

                        /* Set res completed successfully */
                        vuid_cancel_timeout(qp);

                        STATEP->state = PS2_WAIT_STREAM_ACK;

                        /* Set timeout for enable */
                        vuid_set_timeout(qp, PS2_INIT_TMOUT_PER_CMD);

                        put1(WR(qp), MSESTREAM);

                        break;

                case PS2_WAIT_STREAM_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }
                        STATEP->state = PS2_WAIT_ON_ACK;
                        put1(WR(qp), MSEON);
                        break;

                case PS2_WAIT_ON_ACK:
                        if (code != MSE_ACK) {
                                break;
                        }

                        /* Enable completed successfully */

                        /*
                         * The entire initialization sequence
                         * is complete.  Now, we can clear the
                         * init_count retry counter.
                         */
                        STATEP->init_count = 0;
                        vuid_cancel_timeout(qp);


                        STATEP->state = PS2_START;
                        break;
                }
        }
        freemsg(mp);
}