// 16bit code to handle mouse events.
//
// Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2002  MandrakeSoft S.A.
//
// This file may be distributed under the terms of the GNU LGPLv3 license.

#include "biosvar.h" // GET_EBDA
#include "util.h" // debug_isr
#include "pic.h" // eoi_pic2
#include "bregs.h" // struct bregs
#include "ps2port.h" // aux_command

void
mouse_setup()
{
    if (! CONFIG_PS2_MOUSE)
        return;
    dprintf(3, "init mouse\n");
    // pointing device installed
    SETBITS_BDA(equipment_list_flags, 0x04);
    enable_hwirq(12, entry_74);
}

#define RET_SUCCESS      0x00
#define RET_EINVFUNCTION 0x01
#define RET_EINVINPUT    0x02
#define RET_EINTERFACE   0x03
#define RET_ENEEDRESEND  0x04
#define RET_ENOHANDLER   0x05

static int
disable_mouse(u16 ebda_seg)
{
    u8 ps2ctr = GET_EBDA2(ebda_seg, ps2ctr);
    ps2ctr |= I8042_CTR_AUXDIS;
    ps2ctr &= ~I8042_CTR_AUXINT;
    SET_EBDA2(ebda_seg, ps2ctr, ps2ctr);

    return aux_command(PSMOUSE_CMD_DISABLE, NULL);
}

// Disable Mouse
static void
mouse_15c20000(struct bregs *regs)
{
    u16 ebda_seg = get_ebda_seg();
    int ret = disable_mouse(ebda_seg);
    if (ret)
        set_code_fail(regs, RET_ENEEDRESEND);
    else
        set_code_success(regs);
}

// Enable Mouse
static void
mouse_15c20001(struct bregs *regs)
{
    u16 ebda_seg = get_ebda_seg();
    u8 mouse_flags_2 = GET_EBDA2(ebda_seg, mouse_flag2);
    if ((mouse_flags_2 & 0x80) == 0) {
        set_code_fail(regs, RET_ENOHANDLER);
        return;
    }

    u8 ps2ctr = GET_EBDA2(ebda_seg, ps2ctr);
    ps2ctr &= ~I8042_CTR_AUXDIS;
    ps2ctr |= I8042_CTR_AUXINT;
    SET_EBDA2(ebda_seg, ps2ctr, ps2ctr);

    int ret = aux_command(PSMOUSE_CMD_ENABLE, NULL);
    if (ret)
        set_code_fail(regs, RET_ENEEDRESEND);
    else
        set_code_success(regs);
}

static void
mouse_15c200XX(struct bregs *regs)
{
    set_code_fail(regs, RET_EINVFUNCTION);
}

// Disable/Enable Mouse
static void
mouse_15c200(struct bregs *regs)
{
    switch (regs->bh) {
    case 0x00: mouse_15c20000(regs); break;
    case 0x01: mouse_15c20001(regs); break;
    default:   mouse_15c200XX(regs); break;
    }
}

// Reset Mouse
static void
mouse_15c201(struct bregs *regs)
{
    u8 param[2];
    int ret = aux_command(PSMOUSE_CMD_RESET_BAT, param);
    if (ret) {
        set_code_fail(regs, RET_ENEEDRESEND);
        return;
    }
    regs->bl = param[0];
    regs->bh = param[1];
    set_code_success(regs);
}

// Set Sample Rate
static void
mouse_15c202(struct bregs *regs)
{
    static u8 VAR16 sample_rates[7] = {10, 20, 40, 60, 80, 100, 200};
    if (regs->bh >= ARRAY_SIZE(sample_rates)) {
        set_code_fail(regs, RET_EINVINPUT);
        return;
    }
    u8 mouse_data1 = GET_GLOBAL(sample_rates[regs->bh]);
    int ret = aux_command(PSMOUSE_CMD_SETRATE, &mouse_data1);
    if (ret)
        set_code_fail(regs, RET_ENEEDRESEND);
    else
        set_code_success(regs);
}

// Set Resolution
static void
mouse_15c203(struct bregs *regs)
{
    // BH:
    //      0 =  25 dpi, 1 count  per millimeter
    //      1 =  50 dpi, 2 counts per millimeter
    //      2 = 100 dpi, 4 counts per millimeter
    //      3 = 200 dpi, 8 counts per millimeter
    if (regs->bh >= 4) {
        set_code_fail(regs, RET_EINVINPUT);
        return;
    }
    u8 param = regs->bh;
    int ret = aux_command(PSMOUSE_CMD_SETRES, &param);
    if (ret)
        set_code_fail(regs, RET_ENEEDRESEND);
    else
        set_code_success(regs);
}

// Get Device ID
static void
mouse_15c204(struct bregs *regs)
{
    u8 param[2];
    int ret = aux_command(PSMOUSE_CMD_GETID, param);
    if (ret) {
        set_code_fail(regs, RET_ENEEDRESEND);
        return;
    }
    regs->bh = param[0];
    set_code_success(regs);
}

// Initialize Mouse
static void
mouse_15c205(struct bregs *regs)
{
    if (regs->bh != 3) {
        set_code_fail(regs, RET_EINTERFACE);
        return;
    }
    u16 ebda_seg = get_ebda_seg();
    SET_EBDA2(ebda_seg, mouse_flag1, 0x00);
    SET_EBDA2(ebda_seg, mouse_flag2, regs->bh);

    // Reset Mouse
    mouse_15c201(regs);
}

// Return Status
static void
mouse_15c20600(struct bregs *regs)
{
    u8 param[3];
    int ret = aux_command(PSMOUSE_CMD_GETINFO, param);
    if (ret) {
        set_code_fail(regs, RET_ENEEDRESEND);
        return;
    }
    regs->bl = param[0];
    regs->cl = param[1];
    regs->dl = param[2];
    set_code_success(regs);
}

// Set Scaling Factor to 1:1
static void
mouse_15c20601(struct bregs *regs)
{
    int ret = aux_command(PSMOUSE_CMD_SETSCALE11, NULL);
    if (ret)
        set_code_fail(regs, RET_ENEEDRESEND);
    else
        set_code_success(regs);
}

// Set Scaling Factor to 2:1
static void
mouse_15c20602(struct bregs *regs)
{
    int ret = aux_command(PSMOUSE_CMD_SETSCALE21, NULL);
    if (ret)
        set_code_fail(regs, RET_ENEEDRESEND);
    else
        set_code_success(regs);
}

static void
mouse_15c206XX(struct bregs *regs)
{
    set_code_fail(regs, RET_EINVFUNCTION);
}

// Return Status & Set Scaling Factor...
static void
mouse_15c206(struct bregs *regs)
{
    switch (regs->bh) {
    case 0x00: mouse_15c20600(regs); break;
    case 0x01: mouse_15c20601(regs); break;
    case 0x02: mouse_15c20602(regs); break;
    default:   mouse_15c206XX(regs); break;
    }
}

// Set Mouse Handler Address
static void
mouse_15c207(struct bregs *regs)
{
    u32 farptr = (regs->es << 16) | regs->bx;
    u16 ebda_seg = get_ebda_seg();
    u8 mouse_flags_2 = GET_EBDA2(ebda_seg, mouse_flag2);
    if (! farptr) {
        /* remove handler */
        if ((mouse_flags_2 & 0x80) != 0) {
            mouse_flags_2 &= ~0x80;
            disable_mouse(ebda_seg);
        }
    } else {
        /* install handler */
        mouse_flags_2 |= 0x80;
    }
    SET_EBDA2(ebda_seg, mouse_flag2, mouse_flags_2);
    SET_EBDA2(ebda_seg, far_call_pointer, farptr);
    set_code_success(regs);
}

static void
mouse_15c2XX(struct bregs *regs)
{
    set_code_fail(regs, RET_EINVFUNCTION);
}

void
handle_15c2(struct bregs *regs)
{
    //debug_stub(regs);

    if (! CONFIG_PS2_MOUSE) {
        set_code_fail(regs, RET_EUNSUPPORTED);
        return;
    }

    irq_enable();

    switch (regs->al) {
    case 0x00: mouse_15c200(regs); break;
    case 0x01: mouse_15c201(regs); break;
    case 0x02: mouse_15c202(regs); break;
    case 0x03: mouse_15c203(regs); break;
    case 0x04: mouse_15c204(regs); break;
    case 0x05: mouse_15c205(regs); break;
    case 0x06: mouse_15c206(regs); break;
    case 0x07: mouse_15c207(regs); break;
    default:   mouse_15c2XX(regs); break;
    }
}

static void
process_mouse(u8 data)
{
    u16 ebda_seg = get_ebda_seg();
    u8 mouse_flags_1 = GET_EBDA2(ebda_seg, mouse_flag1);
    u8 mouse_flags_2 = GET_EBDA2(ebda_seg, mouse_flag2);

    if (! (mouse_flags_2 & 0x80))
        // far call handler not installed
        return;

    u8 package_count = mouse_flags_2 & 0x07;
    u8 index = mouse_flags_1 & 0x07;
    SET_EBDA2(ebda_seg, mouse_data[index], data);

    if ((index+1) < package_count) {
        mouse_flags_1++;
        SET_EBDA2(ebda_seg, mouse_flag1, mouse_flags_1);
        return;
    }

    //BX_DEBUG_INT74("int74_function: make_farcall=1\n");
    u16 status = GET_EBDA2(ebda_seg, mouse_data[0]);
    u16 X      = GET_EBDA2(ebda_seg, mouse_data[1]);
    u16 Y      = GET_EBDA2(ebda_seg, mouse_data[2]);
    SET_EBDA2(ebda_seg, mouse_flag1, 0);

    u32 func = GET_EBDA2(ebda_seg, far_call_pointer);

    asm volatile(
        "sti\n"

        "pushl %0\n"
        "pushw %w1\n"  // status
        "pushw %w2\n"  // X
        "pushw %w3\n"  // Y
        "pushw $0\n"   // Z
        "lcallw *8(%%esp)\n"
        "addl $12, %%esp\n"

        "cli\n"
        "cld\n"
        :
        : "r"(func), "r"(status), "r"(X), "r"(Y)
        : "cc"
        );
}

// INT74h : PS/2 mouse hardware interrupt
void VISIBLE16
handle_74()
{
    debug_isr(DEBUG_ISR_74);
    if (! CONFIG_PS2_MOUSE)
        goto done;

    u8 v = inb(PORT_PS2_STATUS);
    if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
        != (I8042_STR_OBF|I8042_STR_AUXDATA)) {
        dprintf(1, "mouse irq but no mouse data.\n");
        goto done;
    }
    v = inb(PORT_PS2_DATA);

    process_mouse(v);

done:
    eoi_pic2();
}
