#include "archinc.h"
#include "pll.h"
#include "eventq.h"
#include "uartregs.h"
#include "uart0.h"

#ifdef CONFIG_INCLUDE_LPC210X_UART0

static char rxQueue[CONFIG_UART0_RX_QUEUE_SIZE];
static int rxHead = 0;
static int rxTail = 0;
static int rxLen = 0;
static int rxErrors = 0;
static int rxCharsDropped = 0;

static char txQueue[CONFIG_UART0_TX_QUEUE_SIZE];
static int txHead = 0;
static int txTail = 0;
static int txLen = 0;
static int txErrors = 0;
static int txCharsDropped = 0;

static boolean txIntEnable = false;

/* ISR and deferred handler declaration */
DECLARE_ISR(uart0Isr);

/* baudrate list */
static unsigned int validBaudrates[] = { 300, 1200, 2400, 9600, 19200, 38400, 57600, 115200 };

static int setBaudrate(unsigned int baudrate)
{
  int i;
  unsigned int divisor;
  int retVal = -1;

  for (i=0; i<SIZEOF_ARRAY(validBaudrates); i++) {
    if (baudrate == validBaudrates[i]) {
      retVal = 0;
      break;
      }
    }

  if (retVal == -1)
    return -1;

  /* calculate peripheral clock divisor using 16x oversampling */

  divisor = (pllGetPeriphFreq() / (16 * baudrate));

  U0LCR |= LCR_DLAB_ENABLE; /* Enable DLAB */
  U0DLL = (unsigned char)(divisor & 0xFF);
  U0DLM = (unsigned char)((divisor >> 8) & 0xFF);
  U0LCR &= ~(LCR_DLAB_ENABLE); /* Disable DLAB */

  return retVal;
}

int uart0Init(unsigned int baudrate) 
{
  int i;
  int retVal = -1;

  // disable interrupts at the peripheral
  U0IER = 0;

  // initialize rx queue
  for(i=0; i<CONFIG_UART0_RX_QUEUE_SIZE; i++)
    rxQueue[i] = 0x00;

  // initialize tx queue
  for(i=0; i<CONFIG_UART0_TX_QUEUE_SIZE; i++)
    txQueue[i] = 0x00;

  // setup the baudrate
  if (setBaudrate(baudrate) != 0)
    return retVal;

  // Setup FIFO: FIFO's automatically cleared :-) by setting LSB
  U0FCR = FIFO_ENABLE;

  // setup data format: 8 data bits, 1 stop bit, no parity */
  U0LCR = LCR_WORDLEN_8BITS; 

  /* enable receiver only, only enable tx when we're ready to send */

  // setup interrupts at the VIC
  if (armvicIntConnect(LPC210X_IRQVEC_UART0, uart0Isr) == 0) {
    // enable interrupts at the VIC
    armvicIntEnable(LPC210X_IRQVEC_UART0);
    U0IER |= IER_RX_DATA;
    retVal = 0;
    }

  return retVal;
}

int uart0Puts(char * putStr)
{
  int retVal = 0;

  if (putStr == NULL)
    return -1;

  while ((*putStr != '\0') && (retVal != -1))
    retVal = uart0TxOne(*putStr++);

  return retVal;
}

int uart0TxOne(char txCh)
{
  int retVal = -1;

  if (txLen != CONFIG_UART0_TX_QUEUE_SIZE) {
    txQueue[txHead] = txCh;
    circInc(txHead, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
    IRQ_DISABLE();
    txLen++;
    IRQ_ENABLE();
    retVal = 0;
    }
  else {
    txCharsDropped++;
    }

  if (txIntEnable == false) {
    /*
     * send the first char in the queue right away and enable tx irq 
     * for subsequent chars. If there are no more chars to be sent, 
     * the tx ISR will turn off the transmitter available interrupt, 
     * otherwise the tx queue will be drained by the ISR.
     *
     */

    while ((U0LSR & LSR_THR_EMPTY) == 0);  // wait for THR to become empty
    U0THR = txQueue[txTail];
    circInc(txTail, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
    IRQ_DISABLE();
    txLen--;
    IRQ_ENABLE();
    txIntEnable = true;
    U0IER |= IER_THRE;
    }

  return retVal;
}

int uart0RxOne(char *pRxCh)
{
  if ((rxLen == 0) || (pRxCh == NULL))
    return -1;

  *pRxCh = rxQueue[rxTail];
  circInc(rxTail, 0, (CONFIG_UART0_RX_QUEUE_SIZE-1));
  IRQ_DISABLE();
  rxLen--;
  IRQ_ENABLE();

  return 0;
}

static char dummy;
static lpc210x_reg iirReg;
static lpc210x_reg lsrReg;

ISR_FUNCTION(uart0Isr)
{
  ISR_ENTER()

  while (((iirReg = U0IIR) & IIR_NO_IRQ) == 0) {
    switch (iirReg & IIR_MASK) {
      case IIR_THRE:
        /* transmitter available */
        if (txLen != 0) {
          while ((U0LSR & LSR_THR_EMPTY) == 0); // wait for THR to empty if needbe
          U0THR = txQueue[txTail];
          circInc(txTail, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
          txLen--;
          }
        else {
          txIntEnable = false;
          U0IER &= ~IER_THRE;
          }
        break;
      case IIR_RX_DATA:
      case IIR_CH_TMOUT:
        /* received data available */
        if (rxLen != CONFIG_UART0_RX_QUEUE_SIZE) {
          rxQueue[rxHead] = U0RBR;
          circInc(rxHead, 0, (CONFIG_UART0_RX_QUEUE_SIZE-1));
          rxLen++;
          }
        eventqPut(CONSOLE_RXCHAR);
        break;
      case IIR_RX_LSR:
        /* read LSR to clear int for now,  */
        lsrReg = U0LSR;
        break;
      default:
        break;
      }
    }

  VICIrqDone;
  ISR_LEAVE()
}

void __putchar(int ch)
{
  if (ch == '\n')
    uart0TxOne('\r');
  uart0TxOne(ch);
}

#endif /* CONFIG_INCLUDE_LPC210X_UART0 */

