/*
 * Copyright © 1997 C. Scott Ananian
 * Copyright © 1998-2000 Bruce Kalk
 * Copyright © 2001 Stefan Gmeiner
 * Copyright © 2002 Linuxcare Inc. David Kennedy
 * Copyright © 2003 Fred Hucht <fred@thp.Uni-Duisburg.de>
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of Red Hat
 * not be used in advertising or publicity pertaining to distribution
 * of the software without specific, written prior permission.  Red
 * Hat makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:
 *      Stefan Gmeiner (riddlebox@freesurf.ch)
 *      C. Scott Ananian (cananian@alumni.priceton.edu)
 *      Bruce Kalk (kall@compass.com)
 *      Linuxcare Inc. David Kennedy (dkennedy@linuxcare.com)
 *      Fred Hucht (fred@thp.Uni-Duisburg.de)
 */
#include "config.h"

#include <xorg-server.h>
#include "synproto.h"
#include "synapticsstr.h"
#include "ps2comm.h"
#include <xf86.h>

#define MAX_UNSYNC_PACKETS 10   /* i.e. 10 to 60 bytes */
/*
 * The x/y limits are taken from the Synaptics TouchPad interfacing Guide,
 * section 2.3.2, which says that they should be valid regardless of the
 * actual size of the sensor.
 */
#define XMIN_NOMINAL 1472
#define XMAX_NOMINAL 5472
#define YMIN_NOMINAL 1408
#define YMAX_NOMINAL 4448

#define XMAX_VALID 6143

/* synaptics queries */
#define SYN_QUE_IDENTIFY		0x00
#define SYN_QUE_MODES			0x01
#define SYN_QUE_CAPABILITIES		0x02
#define SYN_QUE_MODEL			0x03
#define SYN_QUE_SERIAL_NUMBER_PREFIX	0x06
#define SYN_QUE_SERIAL_NUMBER_SUFFIX	0x07
#define SYN_QUE_RESOLUTION		0x08
#define SYN_QUE_EXT_CAPAB		0x09

/* status request response bits (PS2_CMD_STATUS_REQUEST) */
#define PS2_RES_REMOTE(r)	((r) & (1 << 22))
#define PS2_RES_ENABLE(r)	((r) & (1 << 21))
#define PS2_RES_SCALING(r)	((r) & (1 << 20))
#define PS2_RES_LEFT(r)		((r) & (1 << 18))
#define PS2_RES_MIDDLE(r)	((r) & (1 << 17))
#define PS2_RES_RIGHT(r)	((r) & (1 << 16))
#define PS2_RES_RESOLUTION(r)	(((r) >> 8) & 0x03)
#define PS2_RES_SAMPLE_RATE(r)	((r) & 0xff)

#ifdef DEBUG
#define PS2DBG(...) ErrorF(__VA_ARGS__)
#else
#define PS2DBG(...) do { } while (0)
#endif

/*****************************************************************************
 *	PS/2 Utility functions.
 *     Many parts adapted from tpconfig.c by C. Scott Ananian
 ****************************************************************************/

/*
 * Read a byte from the ps/2 port
 */
static Bool
ps2_getbyte(int fd, byte * b)
{
    if (xf86WaitForInput(fd, 50000) > 0) {
        if (xf86ReadSerial(fd, b, 1) != 1) {
            PS2DBG("ps2_getbyte: No byte read\n");
            return FALSE;
        }
        PS2DBG("ps2_getbyte: byte %02X read\n", *b);
        return TRUE;
    }
    PS2DBG("ps2_getbyte: timeout xf86WaitForInput\n");
    return FALSE;
}

/*
 * Write a byte to the ps/2 port, wait for ACK
 */
Bool
ps2_putbyte(int fd, byte b)
{
    byte ack;

    if (xf86WriteSerial(fd, &b, 1) != 1) {
        PS2DBG("ps2_putbyte: error xf86WriteSerial\n");
        return FALSE;
    }
    PS2DBG("ps2_putbyte: byte %02X send\n", b);
    /* wait for an ACK */
    if (!ps2_getbyte(fd, &ack)) {
        return FALSE;
    }
    if (ack != PS2_ACK) {
        PS2DBG("ps2_putbyte: wrong acknowledge 0x%02x\n", ack);
        return FALSE;
    }
    return TRUE;
}

/*
 * Use the Synaptics extended ps/2 syntax to write a special command byte. Needed by
 * ps2_send_cmd and ps2_set_mode.
 * special command: 0xE8 rr 0xE8 ss 0xE8 tt 0xE8 uu where (rr*64)+(ss*16)+(tt*4)+uu
 *                  is the command. A 0xF3 or 0xE9 must follow (see ps2_send_cmd, ps2_set_mode)
 */
static Bool
ps2_special_cmd(int fd, byte cmd)
{
    /* initialize with 'inert' command */
    if (!ps2_putbyte(fd, PS2_CMD_SET_SCALING_1_1))
        return FALSE;

    /* send 4x 2-bits with set resolution command */
    for (int i = 0; i < 4; i++) {
        if (!ps2_putbyte(fd, PS2_CMD_SET_RESOLUTION) ||
            !ps2_putbyte(fd, (cmd >> 6) & 0x3))
            return FALSE;
        cmd <<= 2;
    }
    return TRUE;
}

/*
 * Send a command to the synpatics touchpad by special commands
 */
static Bool
ps2_send_cmd(int fd, byte c)
{
    PS2DBG("send command: 0x%02X\n", c);
    return (ps2_special_cmd(fd, c) && ps2_putbyte(fd, PS2_CMD_STATUS_REQUEST));
}

/*****************************************************************************
 *	Synaptics communications functions
 ****************************************************************************/

/*
 * Set the synaptics touchpad mode byte by special commands
 */
static Bool
ps2_synaptics_set_mode(int fd, byte mode)
{
    PS2DBG("set mode byte to: 0x%02X\n", mode);
    return (ps2_special_cmd(fd, mode) &&
            ps2_putbyte(fd, PS2_CMD_SET_SAMPLE_RATE) && ps2_putbyte(fd, 0x14));
}

/*
 * reset the touchpad
 */
static Bool
ps2_synaptics_reset(int fd)
{
    byte r[2];

    xf86FlushInput(fd);
    PS2DBG("Reset the Touchpad...\n");
    if (!ps2_putbyte(fd, PS2_CMD_RESET)) {
        PS2DBG("...failed\n");
        return FALSE;
    }
    xf86WaitForInput(fd, 4000000);
    if (ps2_getbyte(fd, &r[0]) && ps2_getbyte(fd, &r[1])) {
        if (r[0] == 0xAA && r[1] == 0x00) {
            PS2DBG("...done\n");
            return TRUE;
        }
        else {
            PS2DBG("...failed. Wrong reset ack 0x%02x, 0x%02x\n", r[0], r[1]);
            return FALSE;
        }
    }
    PS2DBG("...failed\n");
    return FALSE;
}

/*
 * Read the model-id bytes from the touchpad
 * see also SYN_MODEL_* macros
 */
static Bool
ps2_synaptics_model_id(int fd, struct PS2SynapticsHwInfo *synhw)
{
    byte mi[3];

    PS2DBG("Read mode id...\n");

    synhw->model_id = 0;
    if (ps2_send_cmd(fd, SYN_QUE_MODEL) &&
        ps2_getbyte(fd, &mi[0]) &&
        ps2_getbyte(fd, &mi[1]) && ps2_getbyte(fd, &mi[2])) {
        synhw->model_id = (mi[0] << 16) | (mi[1] << 8) | mi[2];
        PS2DBG("model-id %06X\n", synhw->model_id);
        PS2DBG("...done.\n");
        return TRUE;
    }
    PS2DBG("...failed.\n");
    return FALSE;
}

/*
 * Read the capability-bits from the touchpad
 * see also the SYN_CAP_* macros
 */
static Bool
ps2_synaptics_capability(int fd, struct PS2SynapticsHwInfo *synhw)
{
    byte cap[3];

    PS2DBG("Read capabilites...\n");

    synhw->capabilities = 0;
    synhw->ext_cap = 0;
    if (ps2_send_cmd(fd, SYN_QUE_CAPABILITIES) &&
        ps2_getbyte(fd, &cap[0]) &&
        ps2_getbyte(fd, &cap[1]) && ps2_getbyte(fd, &cap[2])) {
        synhw->capabilities = (cap[0] << 16) | (cap[1] << 8) | cap[2];
        PS2DBG("capabilities %06X\n", synhw->capabilities);
        if (SYN_CAP_VALID(synhw)) {
            if (SYN_EXT_CAP_REQUESTS(synhw)) {
                if (ps2_send_cmd(fd, SYN_QUE_EXT_CAPAB) &&
                    ps2_getbyte(fd, &cap[0]) &&
                    ps2_getbyte(fd, &cap[1]) && ps2_getbyte(fd, &cap[2])) {
                    synhw->ext_cap = (cap[0] << 16) | (cap[1] << 8) | cap[2];
                    PS2DBG("ext-capability %06X\n", synhw->ext_cap);
                }
                else {
                    PS2DBG("synaptics says, that it has extended-capabilities, "
                           "but I cannot read them.");
                }
            }
            PS2DBG("...done.\n");
            return TRUE;
        }
    }
    PS2DBG("...failed.\n");
    return FALSE;
}

/*
 * Identify Touchpad
 * See also the SYN_ID_* macros
 */
static Bool
ps2_synaptics_identify(int fd, struct PS2SynapticsHwInfo *synhw)
{
    byte id[3];

    PS2DBG("Identify Touchpad...\n");

    synhw->identity = 0;
    if (ps2_send_cmd(fd, SYN_QUE_IDENTIFY) &&
        ps2_getbyte(fd, &id[0]) &&
        ps2_getbyte(fd, &id[1]) && ps2_getbyte(fd, &id[2])) {
        synhw->identity = (id[0] << 16) | (id[1] << 8) | id[2];
        PS2DBG("ident %06X\n", synhw->identity);
        if (SYN_ID_IS_SYNAPTICS(synhw)) {
            PS2DBG("...done.\n");
            return TRUE;
        }
    }
    PS2DBG("...failed.\n");
    return FALSE;
}

static Bool
ps2_synaptics_enable_device(int fd)
{
    return ps2_putbyte(fd, PS2_CMD_ENABLE);
}

static Bool
ps2_synaptics_disable_device(int fd)
{
    xf86FlushInput(fd);
    return ps2_putbyte(fd, PS2_CMD_DISABLE);
}

static Bool
ps2_query_is_synaptics(InputInfoPtr pInfo, int fd,
                       struct PS2SynapticsHwInfo *synhw)
{
    for (int i = 0; i < 3; i++) {
        if (ps2_synaptics_disable_device(fd))
            break;
    }

    xf86WaitForInput(fd, 20000);
    xf86FlushInput(fd);
    if (ps2_synaptics_identify(fd, synhw)) {
        return TRUE;
    }
    else {
        xf86IDrvMsg(pInfo, X_ERROR, "Query no Synaptics: %06X\n",
                    synhw->identity);
        return FALSE;
    }
}

void
ps2_print_ident(InputInfoPtr pInfo, const struct PS2SynapticsHwInfo *synhw)
{
    xf86IDrvMsg(pInfo, X_PROBED, " Synaptics Touchpad, model: %d\n",
                SYN_ID_MODEL(synhw));
    xf86IDrvMsg(pInfo, X_PROBED, " Firmware: %d.%d\n", SYN_ID_MAJOR(synhw),
                SYN_ID_MINOR(synhw));

    if (SYN_MODEL_ROT180(synhw))
        xf86IDrvMsg(pInfo, X_PROBED, " 180 degree mounted touchpad\n");
    if (SYN_MODEL_PORTRAIT(synhw))
        xf86IDrvMsg(pInfo, X_PROBED, " portrait touchpad\n");
    xf86IDrvMsg(pInfo, X_PROBED, " Sensor: %d\n", SYN_MODEL_SENSOR(synhw));
    if (SYN_MODEL_NEWABS(synhw))
        xf86IDrvMsg(pInfo, X_PROBED, " new absolute packet format\n");
    if (SYN_MODEL_PEN(synhw))
        xf86IDrvMsg(pInfo, X_PROBED, " pen detection\n");

    if (SYN_CAP_EXTENDED(synhw)) {
        xf86IDrvMsg(pInfo, X_PROBED,
                    " Touchpad has extended capability bits\n");
        if (SYN_CAP_MULTI_BUTTON_NO(synhw))
            xf86IDrvMsg(pInfo, X_PROBED,
                        " -> %d multi buttons, i.e. besides standard buttons\n",
                        (int) (SYN_CAP_MULTI_BUTTON_NO(synhw)));
        if (SYN_CAP_MIDDLE_BUTTON(synhw))
            xf86IDrvMsg(pInfo, X_PROBED, " -> middle button\n");
        if (SYN_CAP_FOUR_BUTTON(synhw))
            xf86IDrvMsg(pInfo, X_PROBED, " -> four buttons\n");
        if (SYN_CAP_MULTIFINGER(synhw))
            xf86IDrvMsg(pInfo, X_PROBED, " -> multifinger detection\n");
        if (SYN_CAP_PALMDETECT(synhw))
            xf86IDrvMsg(pInfo, X_PROBED, " -> palm detection\n");
        if (SYN_CAP_PASSTHROUGH(synhw))
            xf86IDrvMsg(pInfo, X_PROBED, " -> pass-through port\n");
    }
}

static Bool
PS2DeviceOffHook(InputInfoPtr pInfo)
{
    ps2_synaptics_reset(pInfo->fd);
    ps2_synaptics_enable_device(pInfo->fd);

    return TRUE;
}

static Bool
PS2QueryHardware(InputInfoPtr pInfo)
{
    int mode;
    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
    struct PS2SynapticsHwInfo *synhw;

    if (!priv->proto_data)
        priv->proto_data = calloc(1, sizeof(struct PS2SynapticsHwInfo));
    synhw = (struct PS2SynapticsHwInfo *) priv->proto_data;

    /* is the synaptics touchpad active? */
    if (!ps2_query_is_synaptics(pInfo, pInfo->fd, synhw))
        return FALSE;

    xf86IDrvMsg(pInfo, X_PROBED, "synaptics touchpad found\n");

    if (!ps2_synaptics_reset(pInfo->fd))
        xf86IDrvMsg(pInfo, X_ERROR, "reset failed\n");

    if (!ps2_synaptics_identify(pInfo->fd, synhw))
        return FALSE;

    if (!ps2_synaptics_model_id(pInfo->fd, synhw))
        return FALSE;

    if (!ps2_synaptics_capability(pInfo->fd, synhw))
        return FALSE;

    mode = SYN_BIT_ABSOLUTE_MODE | SYN_BIT_HIGH_RATE;
    if (SYN_ID_MAJOR(synhw) >= 4)
        mode |= SYN_BIT_DISABLE_GESTURE;
    if (SYN_CAP_EXTENDED(synhw))
        mode |= SYN_BIT_W_MODE;
    if (!ps2_synaptics_set_mode(pInfo->fd, mode))
        return FALSE;

    ps2_synaptics_enable_device(pInfo->fd);

    ps2_print_ident(pInfo, synhw);

    return TRUE;
}

/*
 * Decide if the current packet stored in priv->protoBuf is valid.
 */
static Bool
ps2_packet_ok(struct PS2SynapticsHwInfo *synhw, struct CommData *comm)
{
    unsigned char *buf = comm->protoBuf;
    int newabs = SYN_MODEL_NEWABS(synhw);

    if (newabs ? ((buf[0] & 0xC0) != 0x80) : ((buf[0] & 0xC0) != 0xC0)) {
        PS2DBG("Synaptics driver lost sync at 1st byte\n");
        return FALSE;
    }

    if (!newabs && ((buf[1] & 0x60) != 0x00)) {
        PS2DBG("Synaptics driver lost sync at 2nd byte\n");
        return FALSE;
    }

    if ((newabs ? ((buf[3] & 0xC0) != 0xC0) : ((buf[3] & 0xC0) != 0x80))) {
        PS2DBG("Synaptics driver lost sync at 4th byte\n");
        return FALSE;
    }

    if (!newabs && ((buf[4] & 0x60) != 0x00)) {
        PS2DBG("Synaptics driver lost sync at 5th byte\n");
        return FALSE;
    }

    return TRUE;
}

static Bool
ps2_synaptics_get_packet(InputInfoPtr pInfo, struct PS2SynapticsHwInfo *synhw,
                         struct SynapticsProtocolOperations *proto_ops,
                         struct CommData *comm)
{
    int count = 0;
    int c;

    while ((c = XisbRead(comm->buffer)) >= 0) {
        unsigned char u = (unsigned char) c;

        /* test if there is a reset sequence received */
        if ((c == 0x00) && (comm->lastByte == 0xAA)) {
            if (xf86WaitForInput(pInfo->fd, 50000) == 0) {
                PS2DBG("Reset received\n");
                proto_ops->QueryHardware(pInfo);
            }
            else
                PS2DBG("faked reset received\n");
        }
        comm->lastByte = u;

        /* to avoid endless loops */
        if (count++ > 30) {
            LogMessageVerbSigSafe(X_ERROR, 0,
                                  "Synaptics driver lost sync... got gigantic packet!\n");
            return FALSE;
        }

        comm->protoBuf[comm->protoBufTail++] = u;

        /* Check that we have a valid packet. If not, we are out of sync,
           so we throw away the first byte in the packet. */
        if (comm->protoBufTail >= 6) {
            if (!ps2_packet_ok(synhw, comm)) {
                for (int i = 0; i < comm->protoBufTail - 1; i++)
                    comm->protoBuf[i] = comm->protoBuf[i + 1];
                comm->protoBufTail--;
                comm->outOfSync++;
                if (comm->outOfSync > MAX_UNSYNC_PACKETS) {
                    comm->outOfSync = 0;
                    PS2DBG("Synaptics synchronization lost too long -> reset touchpad.\n");
                    proto_ops->QueryHardware(pInfo);    /* including a reset */
                    continue;
                }
            }
        }

        if (comm->protoBufTail >= 6) {  /* Full packet received */
            if (comm->outOfSync > 0) {
                comm->outOfSync = 0;
                PS2DBG("Synaptics driver resynced.\n");
            }
            comm->protoBufTail = 0;
            return TRUE;
        }
    }

    return FALSE;
}

Bool
PS2ReadHwStateProto(InputInfoPtr pInfo,
                    struct SynapticsProtocolOperations *proto_ops,
                    struct CommData *comm, struct SynapticsHwState *hwRet)
{
    unsigned char *buf = comm->protoBuf;
    struct SynapticsHwState *hw = comm->hwState;
    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
    SynapticsParameters *para = &priv->synpara;
    struct PS2SynapticsHwInfo *synhw;
    int newabs;
    int w;

    synhw = (struct PS2SynapticsHwInfo *) priv->proto_data;
    if (!synhw) {
        LogMessageVerbSigSafe(X_ERROR, 0,
                              "PS2ReadHwState, synhw is NULL. This is a bug.\n");
        return FALSE;
    }

    newabs = SYN_MODEL_NEWABS(synhw);

    if (!ps2_synaptics_get_packet(pInfo, synhw, proto_ops, comm))
        return FALSE;

    /* Handle normal packets */
    hw->x = hw->y = hw->z = hw->numFingers = hw->fingerWidth = 0;
    hw->left = hw->right = hw->up = hw->down = hw->middle = FALSE;
    for (int i = 0; i < 8; i++)
        hw->multi[i] = FALSE;

    if (newabs) {               /* newer protos... */
        PS2DBG("using new protocols\n");
        hw->x = (((buf[3] & 0x10) << 8) | ((buf[1] & 0x0f) << 8) | buf[4]);
        hw->y = (((buf[3] & 0x20) << 7) | ((buf[1] & 0xf0) << 4) | buf[5]);

        hw->z = buf[2];
        w = (((buf[0] & 0x30) >> 2) |
             ((buf[0] & 0x04) >> 1) | ((buf[3] & 0x04) >> 2));

        hw->left = (buf[0] & 0x01) ? 1 : 0;
        hw->right = (buf[0] & 0x02) ? 1 : 0;

        if (SYN_CAP_EXTENDED(synhw)) {
            if (SYN_CAP_MIDDLE_BUTTON(synhw)) {
                hw->middle = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0;
            }
            if (SYN_CAP_FOUR_BUTTON(synhw)) {
                hw->up = ((buf[3] & 0x01)) ? 1 : 0;
                if (hw->left)
                    hw->up = !hw->up;
                hw->down = ((buf[3] & 0x02)) ? 1 : 0;
                if (hw->right)
                    hw->down = !hw->down;
            }
            if (SYN_CAP_MULTI_BUTTON_NO(synhw)) {
                if ((buf[3] & 2) ? !hw->right : hw->right) {
                    switch (SYN_CAP_MULTI_BUTTON_NO(synhw) & ~0x01) {
                    default:
                        break;
                    case 8:
                        hw->multi[7] = ((buf[5] & 0x08)) ? 1 : 0;
                        hw->multi[6] = ((buf[4] & 0x08)) ? 1 : 0;
                        /* fallthrough */
                    case 6:
                        hw->multi[5] = ((buf[5] & 0x04)) ? 1 : 0;
                        hw->multi[4] = ((buf[4] & 0x04)) ? 1 : 0;
                        /* fallthrough */
                    case 4:
                        hw->multi[3] = ((buf[5] & 0x02)) ? 1 : 0;
                        hw->multi[2] = ((buf[4] & 0x02)) ? 1 : 0;
                        /* fallthrough */
                    case 2:
                        hw->multi[1] = ((buf[5] & 0x01)) ? 1 : 0;
                        hw->multi[0] = ((buf[4] & 0x01)) ? 1 : 0;
                    }
                }
            }
        }
    }
    else {                      /* old proto... */
        PS2DBG("using old protocol\n");
        hw->x = (((buf[1] & 0x1F) << 8) | buf[2]);
        hw->y = (((buf[4] & 0x1F) << 8) | buf[5]);

        hw->z = (((buf[0] & 0x30) << 2) | (buf[3] & 0x3F));
        w = (((buf[1] & 0x80) >> 4) | ((buf[0] & 0x04) >> 1));

        hw->left = (buf[0] & 0x01) ? 1 : 0;
        hw->right = (buf[0] & 0x02) ? 1 : 0;
    }

    hw->y = YMAX_NOMINAL + YMIN_NOMINAL - hw->y;

    if (hw->z >= para->finger_high) {
        int w_ok = 0;

        /*
         * Use capability bits to decide if the w value is valid.
         * If not, set it to 5, which corresponds to a finger of
         * normal width.
         */
        if (SYN_CAP_EXTENDED(synhw)) {
            if ((w >= 0) && (w <= 1)) {
                w_ok = SYN_CAP_MULTIFINGER(synhw);
            }
            else if (w == 2) {
                w_ok = SYN_MODEL_PEN(synhw);
            }
            else if ((w >= 4) && (w <= 15)) {
                w_ok = SYN_CAP_PALMDETECT(synhw);
            }
        }
        if (!w_ok)
            w = 5;

        switch (w) {
        case 0:
            hw->numFingers = 2;
            hw->fingerWidth = 5;
            break;
        case 1:
            hw->numFingers = 3;
            hw->fingerWidth = 5;
            break;
        default:
            hw->numFingers = 1;
            hw->fingerWidth = w;
            break;
        }
    }
    hw->millis = GetTimeInMillis();
    SynapticsCopyHwState(hwRet, hw);
    return TRUE;
}

static Bool
PS2ReadHwState(InputInfoPtr pInfo,
               struct CommData *comm, struct SynapticsHwState *hwRet)
{
    return PS2ReadHwStateProto(pInfo, &psaux_proto_operations, comm, hwRet);
}

struct SynapticsProtocolOperations psaux_proto_operations = {
    NULL,
    PS2DeviceOffHook,
    PS2QueryHardware,
    PS2ReadHwState,
    NULL,
    NULL
};
