#include "archinc.h"
#include "eventq.h"
#include "cli.h"
#include "clichar.h"
#include "ssp.h"
#include "sspregs.h"

#include <stdio.h>
#include <stdlib.h>

/*
 * LPC213x SSP driver 
 */
 
#ifdef CONFIG_INCLUDE_LPC213X_SSP

typedef enum {idle, master, slave} spiMode;

static spiMode interfaceMode = idle;
static boolean useMasterMode = false;

static boolean doLoopback = false;

/*
 * Anomaly counters
 */

static unsigned int slaveAborts = 0;
static unsigned int modeFaults = 0;
static unsigned int readOverruns = 0;
static unsigned int writeCollisions = 0;

/*
 * Queues
 */

static char rxQueue[CONFIG_SSP_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_SSP_TX_QUEUE_SIZE];
static int txHead = 0;
static int txTail = 0;
static int txLen = 0;
static int txErrors = 0;
static int txCharsDropped = 0;

static int sspLoopbackChars = 0;

/* 
 * ISR declaration
 */

DECLARE_ISR(sspIsr);

int sspPut(unsigned char sendData)
{
  if (txLen == CONFIG_SSP_TX_QUEUE_SIZE)
    return -1;

  if (interfaceMode == idle) {
    SPDR = sendData;
    interfaceMode = ((useMasterMode == true) ? master : slave);
    }
  else {
    txQueue[txHead] = sendData;
    IRQ_DISABLE();
    circInc(txHead, 0, (CONFIG_SPI_TX_QUEUE_SIZE-1));
    txLen++;
    IRQ_ENABLE();
    }

  // this is OK: spiMasterSendBegin() will return for slave-only operation

  return 0;
}

int spiGet(unsigned char *pRcvData)
{
  if (rxLen == 0)
    return -1;

  *pRcvData = rxQueue[rxTail];
  IRQ_DISABLE();
  circInc(rxTail, 0, (CONFIG_SPI_RX_QUEUE_SIZE-1));
  rxLen--;
  IRQ_ENABLE();

  return 0;
}


/*
 * Interrupt handling
 *
 * error handling: start by counting each error and implement more 
 * sophisticated error handling later.
 * 
 * STATUS_SLAVE_ABRT: slave abort
 * STATUS_MODE_FAULT: mode fault
 * STATUS_RD_OVERRUN: read overrun
 * STATUS_WRITE_COL: write collision (re-send data)
 * STATUS_XFER_DONE: transaction completed without error
 */
 
static volatile unsigned long dummy;
static volatile unsigned long statusReg;

ISR_FUNCTION(spiSPIFIsr)
{
  ISR_ENTER()

  // read the status reg first
  statusReg = SPSR;

  if (statusReg & STATUS_XFER_DONE) {
    if (doLoopback == true) {
      dummy = SPDR;
      spiLoopbackChars++;
      SPDR = dummy;
      }
    else { 
      // first, queue the received data
    
      if (rxLen != CONFIG_SPI_RX_QUEUE_SIZE) {
        rxQueue[rxHead] = SPDR;
        circInc(rxHead, 0, CONFIG_SPI_RX_QUEUE_SIZE);
        rxLen++;
        }

      // next, get the next byte to go out ready
      if (txLen != 0) {
        SPDR = txQueue[txTail];
        circInc(txTail, 0, CONFIG_SPI_TX_QUEUE_SIZE);
        txLen--;
        }
      else {
        // send out zeroes if no data to send
        SPDR = (unsigned long)0;
        }
      eventqPut(SSP_RX_DATA);
      } // doLoopback == false
    } // STATUS_XFER_DONE

  if (statusReg & STATUS_SLAVE_ABRT) {
    slaveAborts++;
    }

  if (statusReg & STATUS_MODE_FAULT) {
    modeFaults++;
    }

  if (statusReg & STATUS_RD_OVERRUN) {
    readOverruns++;
    }

  if (statusReg & STATUS_WRITE_COL) {
    writeCollisions++;
    }
  
  // acknowledge the interrupt at the SPI controller  
  SPINT = IRQ_ACKNOWLEDGE;

  // acknowledge the interrupt at the VIC
  VICIrqDone;

  ISR_LEAVE()
}


static int spiPutHandler(int argc, char *argv[], int flags)
{
  int bufLen;
  char * pOutBuf;
  unsigned char putData;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  putData = (unsigned char)strtol(argv[1], (char **)NULL, 16);
  return spiPut(putData);
}

static int spiLoopbackHandler(int argc, char *argv[], int flags)
{
  int retVal = -1;
  char ch = *argv[1];

  switch(ch) {
    case 'y':
    case 'Y':
    case '1':
      doLoopback = true;
      retVal = 0;
      break;
    case 'n':
    case 'N':
    case '0':
      doLoopback = false;
      retVal = 0;
      break;
    default:
      break;
    }

  return retVal;
}

static int spiStatHandler(int argc, char *argv[], int flags)
{
  int bufLen;
  char * pOutBuf;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  sprintf(pOutBuf, "%s: loopback %c chars [%u] \r\n", argv[0], ((doLoopback == true) ? 'Y' : 'N'), spiLoopbackChars);
  clicharSendOutputBuffer(pOutBuf, bufLen);    

  sprintf(pOutBuf, "%s: RX queue[%u]  TX queue[%u]\r\n", argv[0], rxLen, txLen);
  clicharSendOutputBuffer(pOutBuf, bufLen);    

  return 0;
}

int spiInit(boolean enableMasterMode) 
{
  int retStat;
    
  interfaceMode = idle;
  useMasterMode = enableMasterMode;

  SPCR = 0;

  if (useMasterMode == true) {
    SPCR |= CTRL_MASTER_MODE;
    }

  // connect the interrupt vector
  if ((retStat = armvicIntConnect(6, LPC21XX_IRQVEC_SPI, spiSPIFIsr)) != 0)
    return retStat;

  // enable the interrupt at the VIC
  if ((retStat = armvicIntEnable(LPC21XX_IRQVEC_SPI)) != 0)
    return retStat;

  // enable interrupt at transceiver
  SPCR |= CTRL_IRQ_ENABLE;

  cliRegisterCommand("spiput", spiPutHandler, "spiput [byte] - queue a byte for xmit on the SPI bus");
  cliRegisterCommand("spiloop", spiLoopbackHandler, "spiloop [{y,1} | {n,0}] - put the SPI interface into loopback");
  cliRegisterCommand("spistat", spiStatHandler, "spistat - show SPI interface statistics");

  return 0;
}

#endif /* CONFIG_INCLUDE_LPC213X_SSP */
