#include "LPC17xx.h"                              /* LPC17xx definitions    */
#include "spi.h"

/* bit definitions for register SSPCR0. */
#define SSPCR0_DSS      0
#define SSPCR0_CPOL     6
#define SSPCR0_CPHA     7
#define SSPCR0_SCR      8
/* bit definitions for register SSPCR1. */
#define SSPCR1_SSE      1
/* bit definitions for register SSPSR. */
#define SSPSR_TFE       0
#define SSPSR_TNF       1
#define SSPSR_RNE       2
#define SSPSR_RFF       3
#define SSPSR_BSY       4

/* Local functions */
static uint8_t LPC17xx_SPI_SendRecvByte (uint8_t byte_s);

/* Initialize the SSP0, SSP0_PCLK=CCLK=72MHz */
void LPC17xx_SPI_Init (void)
{
	uint32_t dummy;

	dummy = dummy; // avoid warning

#if 0
	/* Initialize and enable the SSP0 Interface module. */
	LPC_SC->PCONP |= (1 << 21);          /* Enable power to SSPI0 block  */

	/* SSEL is GPIO, output set to high. */
	LPC_GPIO0->FIODIR  |=  (1<<16);             /* P0.16 is output */
	LPC_PINCON->PINSEL1 &= ~(3<<0);             /* P0.16 SSEL (used as GPIO) */
	LPC17xx_SPI_DeSelect ();					/* set P0.16 high (SSEL inactiv) */

	/* SCK, MISO, MOSI are SSP pins. */
	LPC_PINCON->PINSEL0 &= ~(3UL<<30);          /* P0.15 cleared */
	LPC_PINCON->PINSEL0 |=  (2UL<<30);          /* P0.15 SCK0 */
	LPC_PINCON->PINSEL1 &= ~((3<<2) | (3<<4));  /* P0.17, P0.18 cleared */
	LPC_PINCON->PINSEL1 |=  ((2<<2) | (2<<4));  /* P0.17 MISO0, P0.18 MOSI0 */
#else
	LPC_SC->PCONP       |= (1 << 21);           /* Enable power to SSPI0 block */

	/* SSEL is GPIO, output set to high. */
	LPC_GPIO1->FIODIR   |=  (1<<21);            /* P1.21 is output             */
	LPC_GPIO1->FIOPIN   |=  (1<<21);            /* set P1.21 high (SSEL inact.)*/
	LPC_PINCON->PINSEL3 &= ~(0<<10);             /* P1.21 SSEL (used as GPIO)   */

	/* P3.26 is SD Card Power Supply Enable Pin */
	LPC_GPIO3->FIODIR   |=  (1<<26);            /* P3.26 is output             */
	LPC_GPIO3->FIOPIN   &= ~(1<<26);            /* set P3.26 low(enable power) */

	/* SCK, MISO, MOSI are SSP pins. */
	LPC_PINCON->PINSEL3 &= ~(3UL<<8);          /* P1.20 cleared               */
	LPC_PINCON->PINSEL3 |=  (3UL<<8);          /* P1.20 SCK0                  */
	LPC_PINCON->PINSEL3 &= ~((3<<14) | (3<<16));  /* P1.23, P1.24 cleared        */
	LPC_PINCON->PINSEL3 |=  ((3<<14) | (3<<16));  /* P1.23 MISO0, P1.24 MOSI0    */
#endif

	/* PCLK_SSP0=CCLK */
	LPC_SC->PCLKSEL1 &= ~(3<<10);               /* PCLKSP0 = CCLK/4 (18MHz) */
	LPC_SC->PCLKSEL1 |=  (1<<10);               /* PCLKSP0 = CCLK   (72MHz) */

	LPC_SSP0->CR0  = 0x0007;                    /* 8Bit, CPOL=0, CPHA=0         */
	LPC_SSP0->CR1  = 0x0002;                    /* SSP0 enable, master          */

	LPC17xx_SPI_SetSpeed (SPI_SPEED_400kHz);

	/* wait for busy gone */
	while( LPC_SSP0->SR & ( 1 << SSPSR_BSY ) );

	/* drain SPI RX FIFO */
	while( LPC_SSP0->SR & ( 1 << SSPSR_RNE ) )
	{
		dummy = LPC_SSP0->DR;
	}
}

/* Close SSP0 */
void LPC17xx_SPI_DeInit( void )
{
	// disable SPI
	LPC_SSP0->CR1  = 0;

#if 0
	// Pins to GPIO
	LPC_PINCON->PINSEL0 &= ~(3UL<<30);
	LPC_PINCON->PINSEL1 &= ~((3<<2) | (3<<4));
#else
	LPC_PINCON->PINSEL3 &= ~(3UL<<8);          /* P1.20 cleared               */
	LPC_PINCON->PINSEL3 &= ~((3<<14) | (3<<16));  /* P1.23, P1.24 cleared        */
#endif

	// disable SSP power
	LPC_SC->PCONP &= ~(1 << 21);
}

/* Set a SSP0 clock speed to desired value. */
void LPC17xx_SPI_SetSpeed (uint8_t speed)
{
	speed &= 0xFE;
	if ( speed < 2  ) {
		speed = 2 ;
	}
	LPC_SSP0->CPSR = speed;
}

/* SSEL: low */
void LPC17xx_SPI_Select ()
{
#if 0
	LPC_GPIO0->FIOPIN &= ~(1<<16);
#else
	LPC_GPIO1->FIOPIN &= ~(1<<21);            /* SSEL is GPIO, set to high.  */
#endif
}

/* SSEL: high */
void LPC17xx_SPI_DeSelect ()
{
#if 0
	LPC_GPIO0->FIOPIN |= (1<<16);
#else
	LPC_GPIO1->FIOPIN |= (1<<21);             /* SSEL is GPIO, set to high.  */
#endif
}

/* Send one byte then recv one byte of response. */
static uint8_t LPC17xx_SPI_SendRecvByte (uint8_t byte_s)
{
	uint8_t byte_r;

	LPC_SSP0->DR = byte_s;
	while (LPC_SSP0->SR & (1 << SSPSR_BSY) /*BSY*/); 	/* Wait for transfer to finish */
	byte_r = LPC_SSP0->DR;

	return byte_r;                      /* Return received value */
}

/* Send one byte */
void LPC17xx_SPI_SendByte (uint8_t data)
{
	LPC17xx_SPI_SendRecvByte (data);
}

/* Recv one byte */
uint8_t LPC17xx_SPI_RecvByte ()
{
	return LPC17xx_SPI_SendRecvByte (0xFF);
}

/* Release SSP0 */
void LPC17xx_SPI_Release (void)
{
	LPC17xx_SPI_DeSelect ();
	LPC17xx_SPI_RecvByte ();
}


#if USE_FIFO
/* on LPC17xx the FIFOs have 8 elements which each can hold up to 16 bits */
#define FIFO_ELEM 8

/* Receive btr (must be multiple of 4) bytes of data and store in buff. */
void LPC17xx_SPI_RecvBlock_FIFO (uint8_t *buff,	uint32_t btr)
{
	uint32_t hwtr, startcnt, i, rec;

	hwtr = btr/2;  /* byte number in unit of short */
	if ( btr < FIFO_ELEM ) {
		startcnt = hwtr;
	} else {
		startcnt = FIFO_ELEM;
	}

	LPC_SSP0 -> CR0 |= 0x0f;  /* DSS to 16 bit */

	for ( i = startcnt; i; i-- ) {
		LPC_SSP0 -> DR = 0xffff;  /* fill TX FIFO, prepare clk for receive */
	}

	do {
		while ( !(LPC_SSP0->SR & ( 1 << SSPSR_RNE ) ) ) {
			// wait for data in RX FIFO (RNE set)
		}
		rec = LPC_SSP0->DR;
		if ( i < ( hwtr - startcnt ) ) {
			LPC_SSP0->DR = 0xffff;	/* fill TX FIFO, prepare clk for receive */
		}
		*buff++ = (uint8_t)(rec>>8);
		*buff++ = (uint8_t)(rec);
		i++;
	} while ( i < hwtr );

	LPC_SSP0->CR0 &= ~0x08;  /* DSS to 8 bit */
}

/* Send 512 bytes of data block (stored in buff). */
void LPC17xx_SPI_SendBlock_FIFO (const uint8_t *buff)
{
	uint32_t cnt;
	uint16_t data;

	LPC_SSP0->CR0 |= 0x0f;  /* DSS to 16 bit */

	/* fill the FIFO unless it is full */
	for ( cnt = 0; cnt < ( 512 / 2 ); cnt++ )
	{
		/* wait for TX FIFO not full (TNF) */
		while ( !( LPC_SSP0->SR & ( 1 << SSPSR_TNF ) ) );

		data  = (*buff++) << 8;
		data |= *buff++;
		LPC_SSP0->DR = data;
	}

	/* wait for BSY gone */
	while ( LPC_SSP0->SR & ( 1 << SSPSR_BSY ) );

	/* drain receive FIFO */
	while ( LPC_SSP0->SR & ( 1 << SSPSR_RNE ) ) {
		data = LPC_SSP0->DR;
	}

	LPC_SSP0->CR0 &= ~0x08;  /* DSS to 8 bit */
}
#endif /* USE_FIFO */