#include "archinc.h"
#include "pll.h"
#include "eventq.h"
#include "cli.h"
#include "clichar.h"
#include "uartregs.h"
#include "uart0.h"

#include <stdio.h>
#include <limits.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 maxRxLen = 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 maxTxLen = 0;
static int txErrors = 0;
static int txCharsDropped = 0;

static int maxIsrPassCount = 0;
static int spuriousInts = 0;
static int lineStatusInts = 0;

static boolean txIntEnable = false;

/* ISR and deferred handler declaration */
DECLARE_ISR(uart0Isr);

/* baudrate list */
static const 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;
}


static int uart0StatHandler(int argc, char *argv[], int flags)
{
  int bufLen;
  char * pOutBuf;
  unsigned char putData;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  sprintf(pOutBuf, "%s: maxTx[%d] maxRx[%d] maxIsrPass[%d]\r\n",argv[0], maxTxLen, maxRxLen, maxIsrPassCount);
  clicharSendOutputBuffer(pOutBuf, bufLen);
  sprintf(pOutBuf, "%s: txDrop[%d] rxDrop[%d] spurInt[%d] lsrInt[%d]\r\n",argv[0], txCharsDropped, rxCharsDropped, spuriousInts, lineStatusInts);
  clicharSendOutputBuffer(pOutBuf, bufLen);

  return 0;
}

int uart0Init(unsigned int baudrate) 
{
  int i;
  unsigned long dummy;
  int retVal = -1;

  // disable interrupts at the peripheral
  U0IER = (unsigned long)0;

  dummy = U0IIR; // clear Interrupt ID
  dummy = U0RBR; // clear RX data buffer
  dummy = U0LSR; // clear line status flags

  // setup the baudrate
  //if (setBaudrate(baudrate) != 0)
  //  return retVal;

  setBaudrate(baudrate);

  // Setup FIFO: FIFO's automatically cleared :-) by setting LSB
  U0FCR = FIFO_ENABLE; // 1 byte only for best response

  // 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(4, LPC21XX_IRQVEC_UART0, uart0Isr) == 0) {
    // enable interrupts at the VIC
    armvicIntEnable(LPC21XX_IRQVEC_UART0);
    U0IER = IER_RX_DATA;
    retVal = 0;
    }

  return retVal;
}

int uart0Init2(void)
{
  cliRegisterCommand("u0stat", uart0StatHandler, "u0stat - display uart status info");
}

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 (txIntEnable == false) {
    if (txLen < CONFIG_UART0_TX_QUEUE_SIZE) {
      txQueue[txHead] = txCh;
      circInc(txHead, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
      txLen++;
      maxTxLen = ((txLen > maxTxLen) ? txLen : maxTxLen);
      retVal = 0;
      }
    else {
      return -1;
      }

    /*
     * 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.
     *
     */

#if 0
    while (((U0LSR) & (LSR_TX_EMPTY | LSR_THR_EMPTY)) == 0)
      ;  // wait for THR to become empty
#else
    while (((U0LSR) & LSR_THR_EMPTY) == 0)
      ;  // wait for THR to become empty
#endif

    U0THR = (unsigned long)txQueue[txTail];
    circInc(txTail, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
    txLen--;
    txIntEnable = true;
    U0IER |= IER_THRE;
    }
  else if (txLen < CONFIG_UART0_TX_QUEUE_SIZE) {
    IRQ_DISABLE();
    txQueue[txHead] = txCh;
    circInc(txHead, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
    txLen++;
    IRQ_ENABLE();
    maxTxLen = ((txLen > maxTxLen) ? txLen : maxTxLen);
    retVal = 0;
    }

  return retVal;
}

int uart0RxOne(char *pRxCh)
{
  if ((rxLen == 0) || (pRxCh == NULL))
    return -1;

  // update maxRxLen
  maxRxLen = ((rxLen > maxRxLen) ? rxLen : maxRxLen);

  *pRxCh = rxQueue[rxTail];
  circInc(rxTail, 0, (CONFIG_UART0_RX_QUEUE_SIZE-1));
  rxLen--;

  return 0;
}

static char dummy;
static unsigned long iirReg;
static unsigned long lsrReg;

static int isrPassCount;

ISR_FUNCTION(uart0Isr)
{
  ISR_ENTER()

  isrPassCount = 0;

  while ((((iirReg = (U0IIR)) & IIR_NO_IRQ) == 0) && (isrPassCount < 128)) {
    lsrReg = U0LSR;
    switch (iirReg & IIR_MASK) {
      case IIR_THRE:
        /* transmitter available */
        if (txLen > 0) {
          // if (lsrReg & (LSR_THR_EMPTY | LSR_TX_EMPTY)) {
          if (lsrReg & LSR_THR_EMPTY) {
            U0THR = (unsigned long)txQueue[txTail];
            circInc(txTail, 0, (CONFIG_UART0_TX_QUEUE_SIZE-1));
            txLen--;
            }
          }
        else {
          // we've transmitted all the characters, so turn the IRQ off
          // U0IER = IER_RX_DATA;
          U0IER &= ~IER_THRE;
          txIntEnable = false;
          }
        break;
      case IIR_RX_DATA:
      case IIR_CH_TMOUT:
        /* received data available */
        while (lsrReg & LSR_RX_DATA) {
          if (rxLen < CONFIG_UART0_RX_QUEUE_SIZE) {
            rxQueue[rxHead] = U0RBR;
            circInc(rxHead, 0, (CONFIG_UART0_RX_QUEUE_SIZE-1));
            rxLen++;
            }
          else {
            rxCharsDropped++;
            }
          eventqPut(CONSOLE_RXCHAR);
          lsrReg = U0LSR;
          }
        break;
      case IIR_RX_LSR:
        lineStatusInts++;
        break;
      default:
        spuriousInts++;
        break;
        }
      isrPassCount++;
    }

  maxIsrPassCount = ((isrPassCount > maxIsrPassCount) ? isrPassCount : maxIsrPassCount);
  
  VICIrqDone;
  ISR_LEAVE()
}

#ifdef CONFIG_TOOLCHAIN_IAR
void __write(int ch)
#endif
#ifdef CONFIG_TOOLCHAIN_ROWLEY
void __putchar(int ch)
#endif
{
  if (ch == '\n')
    uart0TxOne('\r');
  uart0TxOne(ch);
}


#endif /* CONFIG_INCLUDE_LPC210X_UART0 */

