#include <AT91SAM7S64.h>
#include <stdint.h>
#include <dev/spi.h>
#include <sam7s-spi.h>

/* Prevents interrupts using SPI at inappropriate times */
unsigned char spi_busy = 0;

#define SPI_SPEED 1000000  /* 1MHz clock*/
#define SPI_DLYBCT 1
#define SPI_DLYBS 20

#define SPI_TRANSFER (AT91C_PA12_MISO | AT91C_PA13_MOSI | AT91C_PA14_SPCK)

#define SPI_CS (AT91C_PA11_NPCS0 | AT91C_PA31_NPCS1)

void
spi_init()
{
  static uint8_t initialised = 0;
  if (!initialised) {
    *AT91C_SPI_CR = AT91C_SPI_SPIDIS | AT91C_SPI_SWRST;
    *AT91C_PMC_PCER = (1 << AT91C_ID_SPI);
    *AT91C_PIOA_ASR = SPI_TRANSFER | SPI_CS;
    *AT91C_PIOA_PDR = SPI_TRANSFER | SPI_CS;
    *AT91C_PIOA_PPUER = AT91C_PA12_MISO | SPI_CS;
    *AT91C_SPI_MR = (AT91C_SPI_MSTR | AT91C_SPI_PS_FIXED
		     | AT91C_SPI_MODFDIS | AT91C_SPI_PCS);

    /* It seems necessary to set the clock speed for chip select 0
       even if it's not used. */
    AT91C_SPI_CSR[0] = (MCK/SPI_SPEED)<<8;

    *AT91C_SPI_CR = AT91C_SPI_SPIEN;
    initialised = 1;
  }
}

void
spi_init_chip_select(unsigned int chip, unsigned int speed,
		     unsigned int dlybct,
		     unsigned int dlybs, unsigned int phase,
		     unsigned int polarity)
{
  spi_init();

  AT91C_SPI_CSR[chip] =
    ((dlybct<<24) | (dlybs<<16) | (((MCK+speed/2)/speed)<<8)
     | (phase?AT91C_SPI_NCPHA:0) | (polarity?AT91C_SPI_CPOL:0)
     | AT91C_SPI_BITS_8 | AT91C_SPI_CSAAT);
}

#if 0
#define DBG_SEND dbg_blocking_putchar('>');
#define DBG_RECV dbg_blocking_putchar('<');
#else
#define DBG_SEND
#define DBG_RECV
#endif

void
spi_transfer(unsigned int chip, const struct spi_block *block, unsigned int blocks)
{
  spi_busy = 1;
  while(!(*AT91C_SPI_SR & AT91C_SPI_TXEMPTY)); /* wait unti previous transfer is done */
  
  /* Clear any data left in the receiver */
  (void)*AT91C_SPI_RDR;
  (void)*AT91C_SPI_RDR;

  /* Select chip */
  *AT91C_SPI_MR = ((*AT91C_SPI_MR & ~AT91C_SPI_PCS)
		   | ((~(1<<chip) & 0x0f) << 16));
  
  while(blocks-- > 0) {
    struct spi_block current = *block++;
    if (current.send) {
      if (current.receive) {
	/* Send and receive */
	while(current.len-- > 0) {
	  while(!(*AT91C_SPI_SR & AT91C_SPI_TDRE));
	  *AT91C_SPI_TDR = *current.send++;
	  DBG_SEND;
	  while(!(*AT91C_SPI_SR & AT91C_SPI_RDRF));
	  *current.receive++ = *AT91C_SPI_RDR;
	  DBG_RECV;
	}
      } else {
	/* Send only */
	while(current.len-- > 0) {
	  while(!(*AT91C_SPI_SR & AT91C_SPI_TDRE));
	  *AT91C_SPI_TDR = *current.send++;
	  DBG_SEND;
	  while(!(*AT91C_SPI_SR & AT91C_SPI_RDRF));
	  (void)*AT91C_SPI_RDR;
	  DBG_RECV;
	}
      }
    } else {
      if (current.receive) {
	/* Receive only */
	while(current.len-- > 0) {
	  while(!(*AT91C_SPI_SR & AT91C_SPI_TDRE));
	  *AT91C_SPI_TDR = 0;
	  DBG_SEND;
	  while(!(*AT91C_SPI_SR & AT91C_SPI_RDRF));
	  *current.receive++ = *AT91C_SPI_RDR;
	  DBG_RECV;
	}
      } else {
	/* Clock only */
	while(current.len-- > 0) {
	  while(!(*AT91C_SPI_SR & AT91C_SPI_TDRE));
	  *AT91C_SPI_TDR = 0;
	  DBG_SEND;
	  while(!(*AT91C_SPI_SR & AT91C_SPI_RDRF));
	  (void)*AT91C_SPI_RDR;
	  DBG_RECV;
	}
      }
    } 
  }
  *AT91C_SPI_CR = AT91C_SPI_LASTXFER;
    
  spi_busy = 0;
}