/******************************************************************************
*     $Date: 2004/11/19 20:19:52 $
*     $RCSfile: usb_hpi_test.c,v $
*******************************************************************************

*******************************************************************************
*
*     XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS"
*     SOLELY FOR USE IN DEVELOPING PROGRAMS AND SOLUTIONS FOR
*     XILINX DEVICES.  BY PROVIDING THIS DESIGN, CODE, OR INFORMATION
*     AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, APPLICATION
*     OR STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS
*     IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT,
*     AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE
*     FOR YOUR IMPLEMENTATION.  XILINX EXPRESSLY DISCLAIMS ANY
*     WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE
*     IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR
*     REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF
*     INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*     FOR A PARTICULAR PURPOSE.
*     
*     (c) Copyright 2004 Xilinx, Inc.
*     All rights reserved.
*
******************************************************************************/
#include <sysace_stdio.h>
#include "xparameters.h"
#include "xuartns550_l.h"
#include "xio.h"
#include "sleep.h"
#include "util.h"
#include "xromlcd.h"

// UART Constants
#define UART_BASEADDR  XPAR_OPB_UART16550_0_BASEADDR
#define UART_CLOCK     XPAR_XUARTNS550_CLOCK_HZ
#define UART_BAUDRATE  9600

// USB GPIO Register
#define USB_GPIO       0x9000000C
#define USB_RESET      0x00002000

// USB HPI Registers
#define USB_DATA       0xA5000000
#define USB_MAILBOX    0xA5000004
#define USB_ADDRESS    0xA5000008
#define USB_STATUS     0xA500000C

// USB HPI Addresses
#define COMM_CODE_ADDR 0x01BC
#define COMM_INT_NUM   0x01C2
#define COMM_R0        0x01C4
#define COMM_R1        0x01C6
#define COMM_R2        0x01C8

// USB HPI Commands
#define COMM_JUMP2CODE 0xCE00
#define COMM_EXEC_INT  0xCE01
#define COMM_RESET     0xFA50

// USB User Code Parameters
#define USB_BASE_ADDR  0x04A4
#define USB_BIN_FILE   "a:\\demo.bin"

static char uppermap[] = {
    0,  0,  0,  0,'A','B','C','D',
  'E','F','G','H','I','J','K','L',
  'M','N','O','P','Q','R','S','T',
  'U','V','W','X','Y','Z','!','@',
  '#','$','%','^','&','*','(',')',
  '\n',27,'\b','\t',' ','_','+','{',
  '}','|','|',':','"','~','<','>',
  '?'
};

static char lowermap[] = {
    0,  0,  0,  0,'a','b','c','d',
  'e','f','g','h','i','j','k','l',
  'm','n','o','p','q','r','s','t',
  'u','v','w','x','y','z','1','2',
  '3','4','5','6','7','8','9','0',
  '\n',27,'\b','\t',' ','-','=','[',
  ']','\\','\\',';','\'','`',',','.',
  '/'
};

static int oldlower = 0;
static int LCD_ptr  = 16;

/* custom LCD line output function */
void LCDPrintLine(char * line1) {
  
  int i=0;
  
  XromLCDClear();

  for(i=0; i<16; i++){
    if(line1[i]){
      XromWriteData(((line1[i] >> 4) & 0x0000000F), (line1[i] & 0x0000000F));
    }
    else break;
  }	
  
  while(i<40){
    XromWriteData(2, 0); //fill in spaces until next line reached
    i++;
  }
}

/* custom LCD character output function */
void LCDPrintChar(char c) {
  if (LCD_ptr == 16) {
    LCDPrintLine("USBKeyboard Test");
    LCD_ptr = 0;
  }
  if (c != '\b') {
    XromWriteData(((c >> 4) & 0x0000000F), (c & 0x0000000F));
    LCD_ptr++;
  } else if (c == '\b' && LCD_ptr != 0) {
    XromMoveCursorLeft();
    LCD_ptr--;
  }
}

/* writes a value to the USB chip */
void usb_write(Xuint16 addr, Xuint16 data)
{
  XIo_Out16(USB_ADDRESS, addr);
  XIo_Out16(USB_DATA, data);
}

/* reads a value from the USB chip */
Xuint16 usb_read(Xuint16 addr)
{
  XIo_Out16(USB_ADDRESS, addr);
  return XIo_In16(USB_DATA);
}

/* opens and reads from CF a bin file for USB and puts it into the USB chip through HPI */
int read_bin(char FileName[], int baseaddr)
{
  Xuint8 readBuffer[1024];
  SYSACE_FILE *infile;
  int i, numread;

  infile = sysace_fopen(FileName, "r");
  
  if(infile) {
    xil_printf("Loading executable file for USB controller chip.\r\n\r\n");
    XromLCDPrint2Strings("USBKeyboard Test", "Reading file...");

    XIo_Out16(USB_ADDRESS, baseaddr);

    numread = sysace_fread(readBuffer, 1, 1024, infile);
    sleep(1);
    while (numread > 0) {
      for (i = 0; i < numread; i+=2) {
	XIo_Out16(USB_DATA, (readBuffer[i+1] << 8) | readBuffer[i]);
      }
      numread = sysace_fread(readBuffer, 1, 1024, infile);
    }

    sysace_fclose(infile);
    return 1;
  } else {
    return 0;
  }
}

/* resets USB chip */
void usb_reset()
{
  XIo_Out16(USB_MAILBOX, COMM_RESET);
}

/* sets PC of USB chip */
void set_pc(Xuint16 addr)
{
  Xuint16 temp;

  // Set COMM_CODE_ADDR to addr
  usb_write(COMM_CODE_ADDR, addr);

  // Set MailBox to execute command
  XIo_Out16(USB_MAILBOX, COMM_JUMP2CODE);
}

/* clears USB EEPROM (use with caution!) */
void clear_eeprom()
{
  Xuint16 temp;

  // Set COMM_INT_NUM to 65
  usb_write(COMM_INT_NUM, 65);

  // Set COMM_R0 to 1
  usb_write(COMM_R0, 1);

  // Set COMM_R1 to 0
  usb_write(COMM_R1, 0);

  // Set COMM_R2 to 0
  usb_write(COMM_R2, 0);

  // Set MailBox to execute interrupt
  XIo_Out16(USB_MAILBOX, COMM_EXEC_INT);

  temp = XIo_In16(USB_STATUS);
  xil_printf("Status: %x\r\n", temp);

  temp = XIo_In16(USB_MAILBOX);
  xil_printf("Mailbox: %x\r\n", temp);
}

/* reads USB chip's mailbox register and outputs keyboard presses to the UART terminal and LCD screen */
void read_mailbox() 
{
  Xuint16 status, temp, modifier;

  status = XIo_In16(USB_STATUS);
  //xil_printf("Status: %x\r\n", status);

  if (status & 0x001) {
    temp = XIo_In16(USB_MAILBOX);
    // xil_printf("Mailbox: %x\r\n", temp);

    modifier = temp >> 8;
    temp = temp & 0x00FF;
    if (oldlower != temp) {
      if (modifier & 0x0022) {
	if (temp >= 4 && temp <= 56) {
	  XUartNs550_SendByte(XPAR_OPB_UART16550_0_BASEADDR, uppermap[temp]);
	  LCDPrintChar(uppermap[temp]);
	}
	if (temp == 40) {
	  XUartNs550_SendByte(XPAR_OPB_UART16550_0_BASEADDR, '\r');
	  LCDPrintLine("USBKeyboard Demo");
	}
      } else {
	if (temp >= 4 && temp <= 56) {
	  XUartNs550_SendByte(XPAR_OPB_UART16550_0_BASEADDR, lowermap[temp]);
	  LCDPrintChar(lowermap[temp]);
	}
	if (temp == 40) {
	  XUartNs550_SendByte(XPAR_OPB_UART16550_0_BASEADDR, '\r');
	  LCDPrintLine("USBKeyboard Demo");
	}
      }
      oldlower = temp;
    }
  }

}

int main()
{

  int temp;

  /* setup 16550 Uart */
  XUartNs550_SetBaud(UART_BASEADDR, UART_CLOCK, UART_BAUDRATE);
  XUartNs550_mSetLineControlReg(UART_BASEADDR, XUN_LCR_8_DATA_BITS);

  /* initialize LCD */
  XromLCDInit();
  XromLCDOn();

  /* hard reset usb */
  temp = XIo_In32(USB_GPIO);
  XIo_Out32(USB_GPIO, temp | USB_RESET);
  XIo_Out32(USB_GPIO, temp);

  xil_printf("ML401 USB Keyboard Demo\r\n\r\n");

  read_bin(USB_BIN_FILE, USB_BASE_ADDR);

  usb_reset();
  while (XIo_In16(USB_MAILBOX) != 0x0FED) {;}

  set_pc(USB_BASE_ADDR);
  xil_printf("Please connect a USB keyboard to the ML401.\r\n");
  xil_printf("Keypresses will appear on the UART terminal and character LCD screen.\r\n");
  xil_printf("Note: USB Keyboard must not have a built-in hub.\r\n\r\n");
  XromLCDPrint2Strings("Connect USB key-", "board and type.");

  while (1) {
    read_mailbox();
  }

  return 0;
}
