/*
 * Copyright © 2001 Stefan Gmeiner
 * Copyright © 2003 Neil Brown
 * Copyright © 2003-2005,2007 Peter Osterlund
 *
 * 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)
 *      Neil Brown (neilb@cse.unsw.edu.au)
 *      Peter Osterlund (petero2@telia.com)
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

/* Wait for the channel to go silent, which means we're in sync */
static void
ALPS_sync(int fd)
{
    byte buffer[64];

    while (xf86WaitForInput(fd, 250000) > 0) {
        xf86ReadSerial(fd, &buffer, 64);
    }
}

/*
 * send the ALPS init sequence, ie 4 consecutive "disable"s before the "enable"
 * This "magic knock" is performed both for the trackpad and for the pointing
 * stick. Not all models have a pointing stick, but trying to initialize it
 * anyway doesn't seem to hurt.
 */
static void
ALPS_initialize(int fd)
{
    xf86FlushInput(fd);
    ps2_putbyte(fd, PS2_CMD_SET_DEFAULT);
    ps2_putbyte(fd, PS2_CMD_SET_SCALING_2_1);
    ps2_putbyte(fd, PS2_CMD_SET_SCALING_2_1);
    ps2_putbyte(fd, PS2_CMD_SET_SCALING_2_1);
    ps2_putbyte(fd, PS2_CMD_DISABLE);

    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_ENABLE);

    ps2_putbyte(fd, PS2_CMD_SET_SCALING_1_1);
    ps2_putbyte(fd, PS2_CMD_SET_SCALING_1_1);
    ps2_putbyte(fd, PS2_CMD_SET_SCALING_1_1);
    ps2_putbyte(fd, PS2_CMD_DISABLE);

    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_DISABLE);
    ps2_putbyte(fd, PS2_CMD_ENABLE);

    ALPS_sync(fd);
}

static Bool
ALPSQueryHardware(InputInfoPtr pInfo)
{
    ALPS_initialize(pInfo->fd);
    return TRUE;
}

static Bool
ALPS_packet_ok(struct CommData *comm)
{
    /* ALPS absolute mode packets start with 0b11111mrl */
    if ((comm->protoBuf[0] & 0xf8) == 0xf8)
        return TRUE;
    return FALSE;
}

static Bool
ALPS_get_packet(struct CommData *comm, InputInfoPtr pInfo)
{
    int c;

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

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

        if (comm->protoBufTail == 3) {  /* PS/2 packet received? */
            if ((comm->protoBuf[0] & 0xc8) == 0x08) {
                comm->protoBufTail = 0;
                return TRUE;
            }
        }

        if (comm->protoBufTail >= 6) {  /* Full packet received */
            comm->protoBufTail = 0;
            if (ALPS_packet_ok(comm))
                return TRUE;
            while ((c = XisbRead(comm->buffer)) >= 0);  /* If packet is invalid, re-sync */
        }
    }

    return FALSE;
}

/*
 * ALPS abolute Mode
 * byte 0: 1 1 1 1 1 mid0 rig0 lef0
 * byte 1: 0 x6 x5 x4 x3 x2 x1 x0
 * byte 2: 0 x10 x9 x8 x7 up1 fin ges
 * byte 3: 0 y9 y8 y7 1 mid1 rig1 lef1
 * byte 4: 0 y6 y5 y4 y3 y2 y1 y0
 * byte 5: 0 z6 z5 z4 z3 z2 z1 z0
 *
 * On a dualpoint, {mid,rig,lef}0 are the stick, 1 are the pad.
 * We just 'or' them together for now.
 *
 * The touchpad on an 'Acer Aspire' has 4 buttons:
 *   left,right,up,down.
 * This device always sets {mid,rig,lef}0 to 1 and
 * reflects left,right,down,up in lef1,rig1,mid1,up1.
 */
static void
ALPS_process_packet(unsigned char *packet, struct SynapticsHwState *hw)
{
    int x = 0, y = 0, z = 0;
    int left = 0, right = 0, middle = 0;
    int i;

    hw->millis = GetTimeInMillis();

    x = (packet[1] & 0x7f) | ((packet[2] & 0x78) << (7 - 3));
    y = (packet[4] & 0x7f) | ((packet[3] & 0x70) << (7 - 4));
    z = packet[5];

    if (z == 127) {             /* DualPoint stick is relative, not absolute */
        hw->left = packet[3] & 1;
        hw->right = (packet[3] >> 1) & 1;
        return;
    }

    /* 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 (i = 0; i < 8; i++)
        hw->multi[i] = FALSE;

    if (z > 0) {
        hw->x = x;
        hw->y = y;
    }
    hw->z = z;
    hw->numFingers = (z > 0) ? 1 : 0;
    hw->fingerWidth = 5;

    left |= (packet[2]) & 1;
    left |= (packet[3]) & 1;
    right |= (packet[3] >> 1) & 1;
    if (packet[0] == 0xff) {
        int back = (packet[3] >> 2) & 1;
        int forward = (packet[2] >> 2) & 1;

        if (back && forward) {
            middle = 1;
            back = 0;
            forward = 0;
        }
        hw->down = back;
        hw->up = forward;
    }
    else {
        left |= (packet[0]) & 1;
        right |= (packet[0] >> 1) & 1;
        middle |= (packet[0] >> 2) & 1;
        middle |= (packet[3] >> 2) & 1;
    }

    hw->left = left;
    hw->right = right;
    hw->middle = middle;
}

static Bool
ALPSReadHwState(InputInfoPtr pInfo,
                struct CommData *comm, struct SynapticsHwState *hwRet)
{
    unsigned char *buf = comm->protoBuf;
    struct SynapticsHwState *hw = comm->hwState;

    if (!ALPS_get_packet(comm, pInfo))
        return FALSE;

    ALPS_process_packet(buf, hw);

    SynapticsCopyHwState(hwRet, hw);
    return TRUE;
}

struct SynapticsProtocolOperations alps_proto_operations = {
    NULL,
    NULL,
    ALPSQueryHardware,
    ALPSReadHwState,
    NULL,
    NULL
};
