#include "archinc.h"
#include "eventq.h"
#include "cli.h"
#include "clichar.h"
#include "spi.h"
#include "spiregs.h"

#include <stdio.h>
#include <stdlib.h>

/*
 * LPC210x SPI driver (Master/slave mode)
 *
 * From Philips errata sheet for LPC2106...
 *
 * Functional Deviations of LPC2106
 * SPI.1 Unintentional clearing of SPI interrupt flag
 *
 * Introduction: The SPI interrupt flag is set by the SPI interface to 
 * generate an interrupt. It is cleared by writing a 1 to this bit.
 *
 * Problem: A write to any register associated with the SPI peripheral 
 * will clear the SPI interrupt register.
 *
 * Work around: Avoid writing to SPI registers while transmissions are 
 * in progress or while SPI interrupts are pending.
 */
 
/*
SSCS is actually always an input to the LPC21XX... it is used by some
external SPI master to select the LPC21XX spi hardware as the target
slave. So if it's not de-asserted for the duration of SPI transfers
originating from the LPC21XX, you get what they call a mode fault.
That's why it's safe to simply pull it high via a resistor if the
LPC21XX will never be a SPI slave.

As for the clocking... do you have the PLL set up to run CCLK at 60 MHz?
Do you have the PCLK divider set to 1 (I think it defaults to 4)? You
might also check the SPICLK period... 12,500 sixteen-bit samples per
second would imply that you're only running the SPI at 200 KHz, or a
PCLK of 1.6 MHz with SPCCR=8. You can definitely get much faster than that.

Of course the delay could be coming from something besides the SPI
hardware (i.e. the software...). This would show up if you see a fast
SPICLK, but long durations between samples.
 */
#ifdef CONFIG_INCLUDE_LPC210X_SPI_SLAVE

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_SPI_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_SPI_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 spiLoopbackChars = 0;

static int spiMasterSendBegin(void);

/* 
 * ISR declaration
 */

DECLARE_ISR(spiSPIFIsr);
DECLARE_ISR(spiMODFIsr);

#ifndef SPDR
#define SPDR (*(volatile unsigned long *)/* TODO: put address here */)
#endif

int spiPut(unsigned char sendData)
{
  if (txLen == CONFIG_SPI_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(SPI_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_LPC210X_SPI */
