/*
* USB keyboard library
* Contains functions to read a generic USB keybaord from USB1 (SPI2)
* And writes the resulting characters to the HID fifo
*/

// uses stdlib.c
// uses hidfifo.c
// uses USBSCANCODES.c

#define USBKEYBOARD_CMD_SET_USB_SPEED        0x04
#define USBKEYBOARD_CMD_RESET_ALL            0x05
#define USBKEYBOARD_CMD_GET_STATUS           0x22
#define USBKEYBOARD_CMD_SET_USB_MODE         0x15
#define USBKEYBOARD_MODE_HOST_0              0x05
#define USBKEYBOARD_MODE_HOST_1              0x07
#define USBKEYBOARD_MODE_HOST_2              0x06
#define USBKEYBOARD_ANSW_USB_INT_CONNECT     0x15

#define USBKEYBOARD_POLLING_RATE 30
#define USBKEYBOARD_HOLD_COUNTS 20

// These are in stdlib.c already
// #define TIMER2_VAL 0xC0273B
// #define TIMER2_CTRL 0xC0273C

#define USBKEYBOARD_SPI2_INTERRUPT 0xC02730

#define USBKEYBOARD_DATA_OFFSET 3

/*
*   Global Variables
*/

word USBkeyboard_endp_mode = 0x80;
word USBkeyboard_buffer[8];
word USBkeyboard_buffer_parsed[8];
word USBkeyboard_buffer_prev[8];

word USBkeyboard_holdCounter = 0;
word USBkeyboard_holdButton = 0;
word USBkeyboard_holdButtonDataOrig = 0;


/*
*   Functions
*/

// Workaround for defines in ASM
void USBkeyboard_asmDefines()
{
    asm(
        "define USBkeyboard_SPI2_CS_ADDR = 0xC0272F ; address of SPI2_CS\n"
        "define USBkeyboard_SPI2_ADDR = 0xC0272E    ; address of SPI2\n"
        );
}

// Sets SPI2_CS low
void USBkeyboard_spiBeginTransfer()
{
    asm(
        "; backup regs\n"
        "push r1\n"
        "push r2\n"

        "load32 USBkeyboard_SPI2_CS_ADDR r2 ; r2 = USBkeyboard_SPI2_CS_ADDR\n"

        "load 0 r1                          ; r1 = 0 (enable)\n"
        "write 0 r2 r1                      ; write to SPI2_CS\n"

        "; restore regs\n"
        "pop r2\n"
        "pop r1\n"
        );
}

// Sets SPI2_CS high
void USBkeyboard_spiEndTransfer()
{
    asm(
        "; backup regs\n"
        "push r1\n"
        "push r2\n"

        "load32 USBkeyboard_SPI2_CS_ADDR r2 ; r2 = USBkeyboard_SPI2_CS_ADDR\n"

        "load 1 r1                          ; r1 = 1 (disable)\n"
        "write 0 r2 r1                      ; write to SPI2_CS\n"

        "; restore regs\n"
        "pop r2\n"
        "pop r1\n"
        );
}

// write dataByte and return read value
// write 0x00 for a read
// Writes byte over SPI2 to CH376
word USBkeyboard_spiTransfer(word dataByte)
{
    word retval = 0;
    asm(
        "load32 USBkeyboard_SPI2_ADDR r2    ; r2 = USBkeyboard_SPI2_ADDR\n"
        "write 0 r2 r4                      ; write r4 over SPI2\n"
        "read 0 r2 r2                       ; read return value\n"
        "write -4 r14 r2                    ; write to stack to return\n"
        );

    return retval;
}


// Get status after waiting for an interrupt
word USBkeyboard_WaitGetStatus() {
    word intValue = 1;
    while(intValue)
    {
        word *i = (word *) USBKEYBOARD_SPI2_INTERRUPT;
        intValue = *i;
    }
    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer(USBKEYBOARD_CMD_GET_STATUS);
    USBkeyboard_spiEndTransfer(); 
    //delay(1);

    USBkeyboard_spiBeginTransfer();
    word retval = USBkeyboard_spiTransfer(0x00);
    USBkeyboard_spiEndTransfer(); 

    return retval;
}

// Get status without using interrupts
word USBkeyboard_noWaitGetStatus() {
    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer(USBKEYBOARD_CMD_GET_STATUS);
    word retval = USBkeyboard_spiTransfer(0x00);
    USBkeyboard_spiEndTransfer(); 

    return retval;
}


// Sets USB mode to mode, returns status code
// Which should be 0x51 when successful
word USBkeyboard_setUSBmode(word mode)
{
    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer(USBKEYBOARD_CMD_SET_USB_MODE);
    USBkeyboard_spiEndTransfer();

    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer(mode);
    USBkeyboard_spiEndTransfer();

    word i;
    for(i = 0; i < 20000; i++); // non-interrupt wait

    USBkeyboard_spiBeginTransfer();
    word status = USBkeyboard_spiTransfer(0x00);
    USBkeyboard_spiEndTransfer();

    return status;
}


// Sets USB speed (0 = USB2, 2 = USB1)
void USBkeyboard_setUSBspeed(word speed)
{
    USBkeyboard_spiBeginTransfer();   
    USBkeyboard_spiTransfer( USBKEYBOARD_CMD_SET_USB_SPEED );     
    USBkeyboard_spiTransfer( speed ); 
    USBkeyboard_spiEndTransfer(); 
}


// resets and initializes CH376
void USBkeyboard_init()
{
    USBkeyboard_asmDefines(); // workaround to prevent deletion by optimizer
    USBkeyboard_spiEndTransfer(); // start with cs high
    delay(10);
    
    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer(USBKEYBOARD_CMD_RESET_ALL);
    USBkeyboard_spiEndTransfer();
    delay(100); // wait after reset

    USBkeyboard_setUSBmode(USBKEYBOARD_MODE_HOST_0);

    // set polling interrupt
    // set timer
    word *p = (word *) TIMER2_VAL;
    *p = USBKEYBOARD_POLLING_RATE;
    // start timer
    word *q = (word *) TIMER2_CTRL;
    *q = 1;
    
}


// Sets USB mode to 2
// Checks again if the device connected in new USB mode
void USBkeyboard_connectDevice()
{
    USBkeyboard_setUSBmode(USBKEYBOARD_MODE_HOST_1);
    USBkeyboard_setUSBmode(USBKEYBOARD_MODE_HOST_2);

    while (USBkeyboard_WaitGetStatus() != USBKEYBOARD_ANSW_USB_INT_CONNECT); // Wait for reconnection of device
}


// I think this is required to start receiving data from USB
void USBkeyboard_toggle_recv()
{
  USBkeyboard_spiBeginTransfer();
  USBkeyboard_spiTransfer( 0x1C );
  USBkeyboard_spiTransfer( USBkeyboard_endp_mode );
  USBkeyboard_spiEndTransfer();
  USBkeyboard_endp_mode = USBkeyboard_endp_mode ^0x40;
}


// Set endpoint and pid
void USBkeyboard_issue_token(word endp_and_pid)
{
  USBkeyboard_spiBeginTransfer();
  USBkeyboard_spiTransfer( 0x4F );
  USBkeyboard_spiTransfer( endp_and_pid );  // Bit7~4 for EndPoint No, Bit3~0 for Token PID
  USBkeyboard_spiEndTransfer(); 
}


// Read data from USB, and do something with it
// First byte is the length, which should be 8 for an HID keyboard
// The other bytes are the data indicating which keys are pressed
void USBkeyboard_receive_data(char* usbBuf) 
{     
    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer(0x28); 
    word len = USBkeyboard_spiTransfer(0x00);

    // check for 8 bytes
    if (len != 8)
    {
        USBkeyboard_spiEndTransfer();
        return;
    }

    word i;
    for (i = 0; i < len; i++)
    {
        usbBuf[i] = USBkeyboard_spiTransfer(0x00);

        // also put the parsed button into a separate buffer
        // convert to ascii/code using table
        // add new ascii/code to fifo
        word *tableNomal = (word*) &DATA_USBSCANCODE_NORMAL;
        tableNomal += USBKEYBOARD_DATA_OFFSET; // set offset to data in function
        USBkeyboard_buffer_parsed[i] = *(tableNomal+usbBuf[i]);
    }

    USBkeyboard_spiEndTransfer();
}


void USBkeyboard_set_addr(word addr) {    

    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer( 0x45 );     
    USBkeyboard_spiTransfer( addr );  
    USBkeyboard_spiEndTransfer();   

    USBkeyboard_WaitGetStatus();

    USBkeyboard_spiBeginTransfer();
    USBkeyboard_spiTransfer( 0x13 ); 
    USBkeyboard_spiTransfer( addr ); 
    USBkeyboard_spiEndTransfer();   
}   


void USBkeyboard_set_config(word cfg) {  
    USBkeyboard_spiBeginTransfer();   
    USBkeyboard_spiTransfer( 0x49 );     
    USBkeyboard_spiTransfer( cfg ); 
    USBkeyboard_spiEndTransfer();   

    USBkeyboard_WaitGetStatus();
} 


void USBkeyboard_sendToFifo(char usbCode, char* usbBuf)
{
    // check first byte for both shift key bits
    if ((usbBuf[0] & 0x2) || (usbBuf[0] & 0x20))
    {
        // convert to ascii/code using table
        // add new ascii/code to fifo
        word *tableShifted = (word*) &DATA_USBSCANCODE_SHIFTED;
        tableShifted += USBKEYBOARD_DATA_OFFSET; // set offset to data in function
        HID_FifoWrite(*(tableShifted+usbCode));
    }
    else // non-shifted
    {
        // convert to ascii/code using table
        // add new ascii/code to fifo
        word *tableNomal = (word*) &DATA_USBSCANCODE_NORMAL;
        tableNomal += USBKEYBOARD_DATA_OFFSET; // set offset to data in function
        HID_FifoWrite(*(tableNomal+usbCode));
    } 
}

// Returns if a button value is present in a USB buffer
word USBkeyboard_buttonInBuffer(char button, char* buffer)
{
    word i;
    for (i = 0; i < 8; i++)
    {
        if (button == buffer[i])
        {
            return 1;
        }
    }
    return 0;
}

// Parses USB keyboard buffer and writes result to HID FIFO
void USBkeyboard_parse_buffer(char* usbBuf, char* usbBufPrev)
{

    // detect which keys are pressed:
    // compare byte 2-7 with previous buffer
    word i;
    for (i = 2; i < 8; i++)
    {
        // if new byte != 0 and not in previous buffer, then new byte is new keypress
        if (usbBuf[i] != 0 && !USBkeyboard_buttonInBuffer(usbBuf[i], usbBufPrev))
        {
            //uprintlnDec(usbBuf[i]); // usefull for adding new keys
            USBkeyboard_holdButton = i;
            USBkeyboard_sendToFifo(usbBuf[i], usbBuf);
        }
    }
    
    // copy buffer to previous buffer
    memcpy(usbBufPrev, usbBuf, 8);
}

// Poll for USB
void USBkeyboard_poll()
{
    word status = USBkeyboard_noWaitGetStatus();
    if (status == 0x14)
    {
        USBkeyboard_receive_data(USBkeyboard_buffer);
        USBkeyboard_parse_buffer(USBkeyboard_buffer, USBkeyboard_buffer_prev);
        USBkeyboard_toggle_recv();
        USBkeyboard_issue_token(0x19); // result of endp 1 and pid 9 ( 1 << 4 ) | 0x09

    }
    // If reconnection TODO: move this to main loop, since we do not want to keep the interrupt occupied for so long
    else if (status == USBKEYBOARD_ANSW_USB_INT_CONNECT)
    {
        word x;
        for (x = 0; x < 100000; x++); // delay without interrupt, to make sure connection is stable
        USBkeyboard_connectDevice();
        USBkeyboard_setUSBspeed(0);
        USBkeyboard_set_addr(1); // endpoint 1 IN
        USBkeyboard_set_config(1);
        USBkeyboard_toggle_recv();
        USBkeyboard_issue_token(0x19); // result of endp 1 and pid 9 ( 1 << 4 ) | 0x09
    }

    char* usbBuf = USBkeyboard_buffer;

    // Button hold detection
    if (USBkeyboard_holdButton != 0)
    {
        if (usbBuf[USBkeyboard_holdButton] != 0)
        {
            if (USBkeyboard_holdCounter == 0)
            {
                USBkeyboard_holdButtonDataOrig = usbBuf[USBkeyboard_holdButton];
                USBkeyboard_holdCounter++;
            }
            else // check if it is still the same button that is being held
            {
                if (usbBuf[USBkeyboard_holdButton] == USBkeyboard_holdButtonDataOrig)
                {
                    if (USBkeyboard_holdCounter < USBKEYBOARD_HOLD_COUNTS)
                        USBkeyboard_holdCounter++;
                }
                else
                {
                    USBkeyboard_holdButton = 0;
                    USBkeyboard_holdCounter = 0;
                }
            }
        }
        else
        {
            USBkeyboard_holdButton = 0;
            USBkeyboard_holdCounter = 0;
        }

        // Send repetitive code to HID FIFO when holding a button for a certain amount of time
        if (USBkeyboard_holdCounter >= USBKEYBOARD_HOLD_COUNTS)
        {
            USBkeyboard_sendToFifo(usbBuf[USBkeyboard_holdButton], usbBuf);
        }
    }
}


void USBkeyboard_HandleInterrupt()
{
    // poll the USB keyboard
    USBkeyboard_poll();

    // set timer to polling rate
    word *p = (word *) TIMER2_VAL;
    *p = USBKEYBOARD_POLLING_RATE;
    // start timer
    word *q = (word *) TIMER2_CTRL;
    *q = 1;
}
