/**
 * @file	: lpc17xx_spi.c
 * @brief	: Contains all functions support for SPI firmware library on LPC17xx
 * @version	: 1.0
 * @date	: 3. April. 2009
 * @author	: HieuNguyen
 **************************************************************************
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * products. This software is supplied "AS IS" without any warranties.
 * NXP Semiconductors assumes no responsibility or liability for the
 * use of the software, conveys no license or title under any patent,
 * copyright, or mask work right to the product. NXP Semiconductors
 * reserves the right to make changes in the software without
 * notification. NXP Semiconductors also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 **********************************************************************/

/* Peripheral group ----------------------------------------------------------- */
/** @addtogroup SPI
 * @{
 */

/* Includes ------------------------------------------------------------------- */
#include "lpc17xx_spi.h"
#include "lpc17xx_clkpwr.h"

/* If this source file built with example, the LPC17xx FW library configuration
 * file in each example directory ("lpc17xx_libcfg.h") must be included,
 * otherwise the default FW library configuration file must be included instead
 */
#ifdef __BUILD_WITH_EXAMPLE__
#include "lpc17xx_libcfg.h"
#else
#include "lpc17xx_libcfg_default.h"
#endif /* __BUILD_WITH_EXAMPLE__ */

#ifdef _SPI

/* Private Types -------------------------------------------------------------- */
/** @defgroup SPI_Private_Types
 * @{
 */

/** @brief SPI device configuration structure type */
typedef struct
{
	int32_t 	dataword;				/* Current data word: 0 - 8 bit; 1 - 16 bit */
	uint32_t    txrx_setup; 			/* Transmission setup */
	void		(*inthandler)(void);   	/* Transmission interrupt handler */
} SPI_CFG_T;

/**
 * @}
 */


/* Private Variables ---------------------------------------------------------- */
/* SPI configuration data */
static SPI_CFG_T spidat;


/* Private Functions ---------------------------------------------------------- */
/** @defgroup SPI_Private_Functions
 * @{
 */

/*********************************************************************//**
 * @brief 		Standard Private SPI Interrupt handler
 * @param[in]	None
 * @return 		None
 ***********************************************************************/
void SPI_IntHandler(void)
{
	SPI_DATA_SETUP_Type *xf_setup;
    uint16_t tmp;

    xf_setup = (SPI_DATA_SETUP_Type *)spidat.txrx_setup;

    /* Dummy read to clear SPI interrupt flag */
    if (LPC_SPI->SPINT & SPI_SPINT_INTFLAG){
    	LPC_SPI->SPINT = SPI_SPINT_INTFLAG;
    }

    // save status
    tmp = LPC_SPI->SPSR;
    xf_setup->status = tmp;
    // Check for error
    if (tmp & (SPI_SPSR_ABRT | SPI_SPSR_MODF | SPI_SPSR_ROVR | SPI_SPSR_WCOL)){
    	xf_setup->status |= SPI_STAT_ERROR;
    	// Disable Interrupt and call call-back
    	SPI_IntCmd(LPC_SPI, DISABLE);
    	if (xf_setup->callback != NULL){
    		xf_setup->callback();
    	}
    	return;
    }

    /* Check SPI complete flag */
    if (tmp & SPI_SPSR_SPIF){
	   // Read data from SPI data
		tmp = SPI_ReceiveData(LPC_SPI);
		if (xf_setup->rx_data != NULL)
		{
//			if (spidat.dataword == 0){
//				*(uint8_t *)(xf_setup->rx_data + xf_setup->counter) = (uint8_t) tmp;
//			} else {
//				*(uint16_t *)(xf_setup->rx_data + xf_setup->counter) = (uint8_t) tmp;
//			}
			if (spidat.dataword == 0){
				*(uint8_t *)((uint8_t *)(xf_setup->rx_data) + xf_setup->counter) = (uint8_t) tmp;
			} else {
				*(uint16_t *)((uint8_t *)(xf_setup->rx_data) + xf_setup->counter) = (uint8_t) tmp;
			}
		}
		// Increase counter
		if (spidat.dataword == 0){
			xf_setup->counter++;
		} else {
			xf_setup->counter += 2;
		}
    }

    if (xf_setup->counter < xf_setup->length){
		// Write data to buffer
		if(xf_setup->tx_data == NULL){
			if (spidat.dataword == 0){
				SPI_SendData(LPC_SPI, 0xFF);
			} else {
				SPI_SendData(LPC_SPI, 0xFFFF);
			}
		} else {
//			if (spidat.dataword == 0){
//				SPI_SendData(SPI, (*(uint8_t *)(xf_setup->tx_data + xf_setup->counter)));
//			} else {
//				SPI_SendData(SPI, (*(uint16_t *)(xf_setup->tx_data + xf_setup->counter)));
//			}
			if (spidat.dataword == 0){
				SPI_SendData(LPC_SPI, (*(uint8_t *)((uint8_t *)(xf_setup->tx_data) + xf_setup->counter)));
			} else {
				SPI_SendData(LPC_SPI, (*(uint16_t *)((uint8_t *)(xf_setup->tx_data) + xf_setup->counter)));
			}
		}
    }
    // No more data to send
	else {
    	xf_setup->status |= SPI_STAT_DONE;
    	// Disable Interrupt and call call-back
    	SPI_IntCmd(LPC_SPI, DISABLE);
    	if (xf_setup->callback != NULL){
    		xf_setup->callback();
    	}
	}
}


/**
 * @}
 */

/* Public Functions ----------------------------------------------------------- */
/** @addtogroup SPI_Public_Functions
 * @{
 */

/*********************************************************************//**
 * @brief 		Setup clock rate for SPI device
 * @param[in] 	SPIx	SPI peripheral definition, should be SPI
 * @param[in]	target_clock : clock of SPI (Hz)
 * @return 		None
 ***********************************************************************/
void SPI_SetClock (LPC_SPI_TypeDef *SPIx, uint32_t target_clock)
{
	uint32_t spi_pclk;
	uint32_t prescale, temp;

	CHECK_PARAM(PARAM_SPIx(SPIx));

	if (SPIx == LPC_SPI){
		spi_pclk =  CLKPWR_GetPCLK (CLKPWR_PCLKSEL_SPI);
	} else {
		return;
	}

	prescale = 8;
	// Find closest clock to target clock
	while (1){
		temp = target_clock * prescale;
		if (temp >= spi_pclk){
			break;
		}
		prescale += 2;
		if(prescale >= 254){
			break;
		}
	}

	// Write to register
	SPIx->SPCCR = SPI_SPCCR_COUNTER(prescale);
}


/*********************************************************************//**
 * @brief		De-initializes the SPIx peripheral registers to their
*                  default reset values.
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @return 		None
 **********************************************************************/
void SPI_DeInit(LPC_SPI_TypeDef *SPIx)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));

	if (SPIx == LPC_SPI){
		/* Set up clock and power for SPI module */
		CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCSPI, DISABLE);
	}
}



/********************************************************************//**
 * @brief		Initializes the SPIx peripheral according to the specified
*               parameters in the UART_ConfigStruct.
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @param[in]	SPI_ConfigStruct Pointer to a SPI_CFG_Type structure
*                    that contains the configuration information for the
*                    specified SPI peripheral.
 * @return 		None
 *********************************************************************/
void SPI_Init(LPC_SPI_TypeDef *SPIx, SPI_CFG_Type *SPI_ConfigStruct)
{
	uint32_t tmp;

	CHECK_PARAM(PARAM_SPIx(SPIx));

	if(SPIx == LPC_SPI){
		/* Set up clock and power for UART module */
		CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCSPI, ENABLE);
	} else {
		return;
	}

	// Configure SPI, interrupt is disable as default
	tmp = ((SPI_ConfigStruct->CPHA) | (SPI_ConfigStruct->CPOL) \
		| (SPI_ConfigStruct->DataOrder) | (SPI_ConfigStruct->Databit) \
		| (SPI_ConfigStruct->Mode) | SPI_SPCR_BIT_EN) & SPI_SPCR_BITMASK;
	// write back to SPI control register
	SPIx->SPCR = tmp;

	if (SPI_ConfigStruct->Databit > SPI_DATABIT_8){
		spidat.dataword = 1;
	} else {
		spidat.dataword = 0;
	}

	// Set clock rate for SPI peripheral
	SPI_SetClock(SPIx, SPI_ConfigStruct->ClockRate);

	// If interrupt flag is set, Write '1' to Clear interrupt flag
	if (SPIx->SPINT & SPI_SPINT_INTFLAG){
		SPIx->SPINT = SPI_SPINT_INTFLAG;
	}
}



/*****************************************************************************//**
* @brief		Fills each SPI_InitStruct member with its default value:
* 				- CPHA = SPI_CPHA_FIRST
* 				- CPOL = SPI_CPOL_HI
* 				- ClockRate = 1000000
* 				- DataOrder = SPI_DATA_MSB_FIRST
* 				- Databit = SPI_DATABIT_8
* 				- Mode = SPI_MASTER_MODE
* @param[in]	SPI_InitStruct Pointer to a SPI_CFG_Type structure
*                    which will be initialized.
* @return		None
*******************************************************************************/
void SPI_ConfigStructInit(SPI_CFG_Type *SPI_InitStruct)
{
	SPI_InitStruct->CPHA = SPI_CPHA_FIRST;
	SPI_InitStruct->CPOL = SPI_CPOL_HI;
	SPI_InitStruct->ClockRate = 1000000;
	SPI_InitStruct->DataOrder = SPI_DATA_MSB_FIRST;
	SPI_InitStruct->Databit = SPI_DATABIT_8;
	SPI_InitStruct->Mode = SPI_MASTER_MODE;
}

/*********************************************************************//**
 * @brief		Transmit a single data through SPIx peripheral
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @param[in]	Data	Data to transmit (must be 16 or 8-bit long,
 * 						this depend on SPI data bit number configured)
 * @return 		none
 **********************************************************************/
void SPI_SendData(LPC_SPI_TypeDef* SPIx, uint16_t Data)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));

	SPIx->SPDR = Data & SPI_SPDR_BITMASK;
}



/*********************************************************************//**
 * @brief		Receive a single data from SPIx peripheral
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @return 		Data received (16-bit long)
 **********************************************************************/
uint16_t SPI_ReceiveData(LPC_SPI_TypeDef* SPIx)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));

	return ((uint16_t) (SPIx->SPDR & SPI_SPDR_BITMASK));
}

/*********************************************************************//**
 * @brief 		SPI 	Read write data function
 * @param[in]	SPIx 	Pointer to SPI peripheral, should be SPI
 * @param[in]	dataCfg	Pointer to a SPI_DATA_SETUP_Type structure that
 * 						contains specified information about transmit
 * 						data configuration.
 * @param[in]	xfType	Transfer type, should be:
 * 						- SPI_TRANSFER_POLLING: Polling mode
 * 						- SPI_TRANSFER_INTERRUPT: Interrupt mode
 * @return 		Actual Data length has been transferred in polling mode.
 * 				In interrupt mode, always return (0)
 * 				Return (-1) if error.
 * Note: This function can be used in both master and slave mode.
 ***********************************************************************/
int32_t SPI_ReadWrite (LPC_SPI_TypeDef *SPIx, SPI_DATA_SETUP_Type *dataCfg, \
						SPI_TRANSFER_Type xfType)
{
	uint8_t *rdata8;
    uint8_t *wdata8;
	uint16_t *rdata16;
    uint16_t *wdata16;
    uint32_t stat;
    uint32_t temp;

	//read for empty buffer
	temp = SPIx->SPDR;
	//dummy to clear status
	temp = SPIx->SPSR;
	dataCfg->counter = 0;
	dataCfg->status = 0;

	if (xfType == SPI_TRANSFER_POLLING){

		if (spidat.dataword == 0){
			rdata8 = (uint8_t *)dataCfg->rx_data;
			wdata8 = (uint8_t *)dataCfg->tx_data;
		} else {
			rdata16 = (uint16_t *)dataCfg->rx_data;
			wdata16 = (uint16_t *)dataCfg->tx_data;
		}

		while(dataCfg->counter < dataCfg->length)
		{
			// Write data to buffer
			if(dataCfg->tx_data == NULL){
				if (spidat.dataword == 0){
					SPI_SendData(SPIx, 0xFF);
				} else {
					SPI_SendData(SPIx, 0xFFFF);
				}
			} else {
				if (spidat.dataword == 0){
					SPI_SendData(SPIx, *wdata8);
					wdata8++;
				} else {
					SPI_SendData(SPIx, *wdata16);
					wdata16++;
				}
			}
			// Wait for transfer complete
			while (!((stat = SPIx->SPSR) & SPI_SPSR_SPIF));
			// Check for error
			if (stat & (SPI_SPSR_ABRT | SPI_SPSR_MODF | SPI_SPSR_ROVR | SPI_SPSR_WCOL)){
				// save status
				dataCfg->status = stat | SPI_STAT_ERROR;
				return (dataCfg->counter);
			}
			// Read data from SPI dat
			temp = (uint32_t) SPI_ReceiveData(SPIx);

			// Store data to destination
			if (dataCfg->rx_data != NULL)
			{
				if (spidat.dataword == 0){
					*(rdata8) = (uint8_t) temp;
					rdata8++;
				} else {
					*(rdata16) = (uint16_t) temp;
					rdata16++;
				}
			}
			// Increase counter
			if (spidat.dataword == 0){
				dataCfg->counter++;
			} else {
				dataCfg->counter += 2;
			}
		}

		// Return length of actual data transferred
		// save status
		dataCfg->status = stat | SPI_STAT_DONE;
		return (dataCfg->counter);
	}
	// Interrupt mode
	else {
		spidat.txrx_setup = (uint32_t)dataCfg;
		spidat.inthandler = SPI_IntHandler;

		// Check if interrupt flag is already set
		if(SPIx->SPINT & SPI_SPINT_INTFLAG){
			SPIx->SPINT = SPI_SPINT_INTFLAG;
		}
		if (dataCfg->counter < dataCfg->length){
			// Write data to buffer
			if(dataCfg->tx_data == NULL){
				if (spidat.dataword == 0){
					SPI_SendData(SPIx, 0xFF);
				} else {
					SPI_SendData(SPIx, 0xFFFF);
				}
			} else {
				if (spidat.dataword == 0){
					SPI_SendData(SPIx, (*(uint8_t *)dataCfg->tx_data));
				} else {
					SPI_SendData(SPIx, (*(uint16_t *)dataCfg->tx_data));
				}
			}
			SPI_IntCmd(SPIx, ENABLE);
		} else {
			// Save status
			dataCfg->status = SPI_STAT_DONE;
		}
		return (0);
	}
	return (0);
}


/********************************************************************//**
 * @brief 		Enable or disable SPIx interrupt.
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @param[in]	NewState New state of specified UART interrupt type,
 * 				should be:
 * 				- ENALBE: Enable this SPI interrupt.
* 				- DISALBE: Disable this SPI interrupt.
 * @return 		None
 *********************************************************************/
void SPI_IntCmd(LPC_SPI_TypeDef *SPIx, FunctionalState NewState)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));
	CHECK_PARAM(PARAM_FUNCTIONALSTATE(NewState));

	if (NewState == ENABLE)
	{
		SPIx->SPCR |= SPI_SPCR_SPIE;
	}
	else
	{
		SPIx->SPCR &= (~SPI_SPCR_SPIE) & SPI_SPCR_BITMASK;
	}
}


/********************************************************************//**
 * @brief 		Checks whether the SPI interrupt flag is set or not.
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @return 		The new state of SPI Interrupt Flag (SET or RESET)
 *********************************************************************/
IntStatus SPI_GetIntStatus (LPC_SPI_TypeDef *SPIx)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));

	return ((SPIx->SPINT & SPI_SPINT_INTFLAG) ? SET : RESET);
}


/********************************************************************//**
 * @brief 		Clear SPI interrupt flag.
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @return 		None
 *********************************************************************/
void SPI_ClearIntPending(LPC_SPI_TypeDef *SPIx)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));

	SPIx->SPINT = SPI_SPINT_INTFLAG;
}


/********************************************************************//**
 * @brief 		Get current value of SPI Status register in SPIx peripheral.
 * @param[in]	SPIx	SPI peripheral selected, should be SPI
 * @return		Current value of SPI Status register in SPI peripheral.
 * Note:	The return value of this function must be used with
 * 			SPI_CheckStatus() to determine current flag status
 * 			corresponding to each SPI status type. Because some flags in
 * 			SPI Status register will be cleared after reading, the next reading
 * 			SPI Status register could not be correct. So this function used to
 * 			read SPI status register in one time only, then the return value
 * 			used to check all flags.
 *********************************************************************/
uint32_t SPI_GetStatus(LPC_SPI_TypeDef* SPIx)
{
	CHECK_PARAM(PARAM_SPIx(SPIx));

	return (SPIx->SPSR & SPI_SPSR_BITMASK);
}



/********************************************************************//**
 * @brief 		Checks whether the specified SPI Status flag is set or not
 * 				via inputSPIStatus parameter.
 * @param[in]	inputSPIStatus Value to check status of each flag type.
 * 				This value is the return value from SPI_GetStatus().
 * @param[in]	SPIStatus	Specifies the SPI status flag to check,
 * 				should be one of the following:
				- SPI_STAT_ABRT: Slave abort.
				- SPI_STAT_MODF: Mode fault.
				- SPI_STAT_ROVR: Read overrun.
				- SPI_STAT_WCOL: Write collision.
				- SPI_STAT_SPIF: SPI transfer complete.
 * @return 		The new state of SPIStatus (SET or RESET)
 *********************************************************************/
FlagStatus SPI_CheckStatus (uint32_t inputSPIStatus,  uint8_t SPIStatus)
{
	CHECK_PARAM(PARAM_SPI_STAT(SPIStatus));

	return ((inputSPIStatus & SPIStatus) ? SET : RESET);
}

/**
 * @brief		Standard SPI Interrupt handler
 * @param[in] 	None
 * @return		None
 */
void SPI_StdIntHandler(void)
{
	// Call relevant handler
	spidat.inthandler();
}


/**
 * @}
 */

#endif /* _SPI */

/**
 * @}
 */

/* --------------------------------- End Of File ------------------------------ */
