/*
 * @brief LPC11u6x SSP Registers and control functions
 *
 * @note
 * Copyright(C) NXP Semiconductors, 2013
 * All rights reserved.
 *
 * @par
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * LPC products.  This software is supplied "AS IS" without any warranties of
 * any kind, and NXP Semiconductors and its licensor disclaim any and
 * all warranties, express or implied, including all implied warranties of
 * merchantability, fitness for a particular purpose and non-infringement of
 * intellectual property rights.  NXP Semiconductors assumes no responsibility
 * or liability for the use of the software, conveys no license or rights under any
 * patent, copyright, mask work right, or any other intellectual property rights in
 * or to any products. NXP Semiconductors reserves the right to make changes
 * in the software without notification. NXP Semiconductors also makes no
 * representation or warranty that such application will be suitable for the
 * specified use without further testing or modification.
 *
 * @par
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, under NXP Semiconductors' and its
 * licensor's relevant copyrights in the software, without fee, provided that it
 * is used in conjunction with NXP Semiconductors microcontrollers.  This
 * copyright, permission, and disclaimer notice must appear in all copies of
 * this code.
 */

#include "chip.h"

/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/

/*****************************************************************************
 * Public types/enumerations/variables
 ****************************************************************************/

/*****************************************************************************
 * Private functions
 ****************************************************************************/

STATIC void SSP_Write2BFifo(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	if (xf_setup->tx_data) {
		Chip_SSP_SendFrame(pSSP, (*(uint16_t *) ((uint32_t) xf_setup->tx_data +
												 xf_setup->tx_cnt)));
		xf_setup->tx_cnt += 2;
	}
	else {
		Chip_SSP_SendFrame(pSSP, 0xFFFF);
	}
}

/** SSP macro: write 1 bytes to FIFO buffer */
STATIC void SSP_Write1BFifo(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	if (xf_setup->tx_data) {
		Chip_SSP_SendFrame(pSSP, (*(uint8_t *) ((uint32_t) xf_setup->tx_data + xf_setup->tx_cnt)));
	}
	else {
		Chip_SSP_SendFrame(pSSP, 0xFF);
	}

	xf_setup->tx_cnt++;
}

/** SSP macro: read 1 bytes from FIFO buffer */
STATIC void SSP_Read2BFifo(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	uint16_t rDat;

	while ((Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET) &&
		   (xf_setup->rx_cnt < xf_setup->length)) {
		rDat = Chip_SSP_ReceiveFrame(pSSP);
		if (xf_setup->rx_data) {
			*(uint16_t *) ((uint32_t) xf_setup->rx_data + xf_setup->rx_cnt) = rDat;
			xf_setup->rx_cnt += 2;
		}
	}
}

/** SSP macro: read 2 bytes from FIFO buffer */
STATIC void SSP_Read1BFifo(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	uint16_t rDat;

	while ((Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET) &&
		   (xf_setup->rx_cnt < xf_setup->length)) {
		rDat = Chip_SSP_ReceiveFrame(pSSP);
		if (xf_setup->rx_data) {
			*(uint8_t *) ((uint32_t) xf_setup->rx_data + xf_setup->rx_cnt) = rDat;
		}

		xf_setup->rx_cnt++;
	}
}

/* Returns clock for the peripheral block */
STATIC CHIP_SYSCTL_CLOCK_T Chip_SSP_GetClockIndex(LPC_SSP_T *pSSP)
{
	CHIP_SYSCTL_CLOCK_T clkSSP;

	if (pSSP == LPC_SSP0) {
		clkSSP = SYSCTL_CLOCK_SSP0;
	}
	else {
		clkSSP = SYSCTL_CLOCK_SSP1;
	}

	return clkSSP;
}

/* Returns reset ID for the peripheral block */
STATIC CHIP_SYSCTL_PERIPH_RESET_T Chip_SSP_GetResetIndex(LPC_SSP_T *pSSP)
{
	CHIP_SYSCTL_PERIPH_RESET_T resetSSP;

	if (pSSP == LPC_SSP0) {
		resetSSP = RESET_SSP0;
	}
	else {
		resetSSP = RESET_SSP1;
	}

	return resetSSP;
}

/* Returns reset ID for the peripheral block */
STATIC void Chip_SSP_SetSSPClkDivider(LPC_SSP_T *pSSP, uint32_t div)
{
	if (pSSP == LPC_SSP0) {
		Chip_Clock_SetSSP0ClockDiv(div);
	}
	else {
		Chip_Clock_SetSSP1ClockDiv(div);
	}
}

/* Returns SSP peripheral clock for the peripheral block */
STATIC uint32_t Chip_SSP_GetPCLKkRate(LPC_SSP_T *pSSP)
{
	uint32_t sspCLK = Chip_Clock_GetMainClockRate();

	if (pSSP == LPC_SSP0) {
		sspCLK /= Chip_Clock_GetSSP0ClockDiv();
	}
	else {
		sspCLK /= Chip_Clock_GetSSP1ClockDiv();
	}

	return sspCLK;
}

/*****************************************************************************
 * Public functions
 ****************************************************************************/

/*Set up output clocks per bit for SSP bus*/
void Chip_SSP_SetClockRate(LPC_SSP_T *pSSP, uint32_t clk_rate, uint32_t prescale)
{
	uint32_t temp;
	temp = pSSP->CR0 & (~(SSP_CR0_SCR(0xFF)));
	pSSP->CR0 = temp | (SSP_CR0_SCR(clk_rate));
	pSSP->CPSR = prescale;
}

/* SSP Polling Read/Write in blocking mode */
uint32_t Chip_SSP_RWFrames_Blocking(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	/* Clear all remaining frames in RX FIFO */
	while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE)) {
		Chip_SSP_ReceiveFrame(pSSP);
	}

	/* Clear status */
	Chip_SSP_ClearIntPending(pSSP, SSP_INT_CLEAR_BITMASK);

	if (Chip_SSP_GetDataSize(pSSP) > SSP_BITS_8) {
		while (xf_setup->rx_cnt < xf_setup->length || xf_setup->tx_cnt < xf_setup->length) {
			/* write data to buffer */
			if (( Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && ( xf_setup->tx_cnt < xf_setup->length) ) {
				SSP_Write2BFifo(pSSP, xf_setup);
			}

			/* Check overrun error */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/* Check for any data available in RX FIFO */
			SSP_Read2BFifo(pSSP, xf_setup);
		}
	}
	else {
		while (xf_setup->rx_cnt < xf_setup->length || xf_setup->tx_cnt < xf_setup->length) {
			/* write data to buffer */
			if (( Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && ( xf_setup->tx_cnt < xf_setup->length) ) {
				SSP_Write1BFifo(pSSP, xf_setup);
			}

			/* Check overrun error */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/* Check for any data available in RX FIFO */
			SSP_Read1BFifo(pSSP, xf_setup);
		}
	}
	if (xf_setup->tx_data) {
		return xf_setup->tx_cnt;
	}
	else if (xf_setup->rx_data) {
		return xf_setup->rx_cnt;
	}

	return 0;
}

/* SSP Polling Write in blocking mode */
uint32_t Chip_SSP_WriteFrames_Blocking(LPC_SSP_T *pSSP, uint8_t *buffer, uint32_t buffer_len)
{
	uint32_t tx_cnt = 0, rx_cnt = 0;

	/* Clear all remaining frames in RX FIFO */
	while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE)) {
		Chip_SSP_ReceiveFrame(pSSP);
	}

	/* Clear status */
	Chip_SSP_ClearIntPending(pSSP, SSP_INT_CLEAR_BITMASK);

	if (Chip_SSP_GetDataSize(pSSP) > SSP_BITS_8) {
		uint16_t *wdata16;

		wdata16 = (uint16_t *) buffer;

		while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
			/* write data to buffer */
			if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
				Chip_SSP_SendFrame(pSSP, *wdata16);
				wdata16++;
				tx_cnt += 2;
			}

			/* Check overrun error */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/* Check for any data available in RX FIFO */
			while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET) {
				Chip_SSP_ReceiveFrame(pSSP);	/* read dummy data */
				rx_cnt += 2;
			}
		}
	}
	else {
		uint8_t *wdata8;

		wdata8 = buffer;

		while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
			/* write data to buffer */
			if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
				Chip_SSP_SendFrame(pSSP, *wdata8);
				wdata8++;
				tx_cnt++;
			}

			/* Check overrun error */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/* Check for any data available in RX FIFO */
			while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET && rx_cnt < buffer_len) {
				Chip_SSP_ReceiveFrame(pSSP);	/* read dummy data */
				rx_cnt++;
			}
		}
	}

	return tx_cnt;

}

/* SSP Polling Read in blocking mode */
uint32_t Chip_SSP_ReadFrames_Blocking(LPC_SSP_T *pSSP, uint8_t *buffer, uint32_t buffer_len)
{
	uint32_t rx_cnt = 0, tx_cnt = 0;

	/* Clear all remaining frames in RX FIFO */
	while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE)) {
		Chip_SSP_ReceiveFrame(pSSP);
	}

	/* Clear status */
	Chip_SSP_ClearIntPending(pSSP, SSP_INT_CLEAR_BITMASK);

	if (Chip_SSP_GetDataSize(pSSP) > SSP_BITS_8) {
		uint16_t *rdata16;

		rdata16 = (uint16_t *) buffer;

		while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
			/* write data to buffer */
			if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
				Chip_SSP_SendFrame(pSSP, 0xFFFF);	/* just send dummy data */
				tx_cnt += 2;
			}

			/* Check overrun error */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/* Check for any data available in RX FIFO */
			while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET && rx_cnt < buffer_len) {
				*rdata16 = Chip_SSP_ReceiveFrame(pSSP);
				rdata16++;
				rx_cnt += 2;
			}
		}
	}
	else {
		uint8_t *rdata8;

		rdata8 = buffer;

		while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
			/* write data to buffer */
			if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
				Chip_SSP_SendFrame(pSSP, 0xFF);	/* just send dummy data		 */
				tx_cnt++;
			}

			/* Check overrun error */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/* Check for any data available in RX FIFO */
			while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET && rx_cnt < buffer_len) {
				*rdata8 = Chip_SSP_ReceiveFrame(pSSP);
				rdata8++;
				rx_cnt++;
			}
		}
	}

	return rx_cnt;

}

/* Clean all data in RX FIFO of SSP */
void Chip_SSP_Int_FlushData(LPC_SSP_T *pSSP)
{
	if (Chip_SSP_GetStatus(pSSP, SSP_STAT_BSY)) {
		while (Chip_SSP_GetStatus(pSSP, SSP_STAT_BSY)) {}
	}

	/* Clear all remaining frames in RX FIFO */
	while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE)) {
		Chip_SSP_ReceiveFrame(pSSP);
	}

	/* Clear status */
	Chip_SSP_ClearIntPending(pSSP, SSP_INT_CLEAR_BITMASK);
}

/* SSP Interrupt Read/Write with 8-bit frame width */
Status Chip_SSP_Int_RWFrames8Bits(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	/* Check overrun error in RIS register */
	if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
		return ERROR;
	}

	if ((xf_setup->tx_cnt != xf_setup->length) || (xf_setup->rx_cnt != xf_setup->length)) {
		/* check if RX FIFO contains data */
		SSP_Read1BFifo(pSSP, xf_setup);

		while ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF)) && (xf_setup->tx_cnt != xf_setup->length)) {
			/* Write data to buffer */
			SSP_Write1BFifo(pSSP, xf_setup);

			/* Check overrun error in RIS register */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/*  Check for any data available in RX FIFO */
			SSP_Read1BFifo(pSSP, xf_setup);
		}

		return SUCCESS;
	}

	return ERROR;
}

/* SSP Interrupt Read/Write with 16-bit frame width */
Status Chip_SSP_Int_RWFrames16Bits(LPC_SSP_T *pSSP, Chip_SSP_DATA_SETUP_T *xf_setup)
{
	/* Check overrun error in RIS register */
	if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
		return ERROR;
	}

	if ((xf_setup->tx_cnt != xf_setup->length) || (xf_setup->rx_cnt != xf_setup->length)) {
		/* check if RX FIFO contains data */
		SSP_Read2BFifo(pSSP, xf_setup);

		while ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF)) && (xf_setup->tx_cnt != xf_setup->length)) {
			/* Write data to buffer */
			SSP_Write2BFifo(pSSP, xf_setup);

			/* Check overrun error in RIS register */
			if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
				return ERROR;
			}

			/*  Check for any data available in RX FIFO			 */
			SSP_Read2BFifo(pSSP, xf_setup);
		}

		return SUCCESS;
	}

	return ERROR;
}

/* Set the SSP operating modes, master or slave */
void Chip_SSP_SetMaster(LPC_SSP_T *pSSP, bool master)
{
	if (master) {
		Chip_SSP_Set_Mode(pSSP, SSP_MODE_MASTER);
	}
	else {
		Chip_SSP_Set_Mode(pSSP, SSP_MODE_SLAVE);
	}
}

/* Set the clock frequency for SSP interface */
void Chip_SSP_SetBitRate(LPC_SSP_T *pSSP, uint32_t bitRate)
{
	uint32_t ssp_clk, cr0_div, cmp_clk, prescale;

	ssp_clk = Chip_SSP_GetPCLKkRate(pSSP);

	cr0_div = 0;
	cmp_clk = 0xFFFFFFFF;
	prescale = 2;

	while (cmp_clk > bitRate) {
		cmp_clk = ssp_clk / ((cr0_div + 1) * prescale);
		if (cmp_clk > bitRate) {
			cr0_div++;
			if (cr0_div > 0xFF) {
				cr0_div = 0;
				prescale += 2;
			}
		}
	}

	Chip_SSP_SetClockRate(pSSP, cr0_div, prescale);
}

/* Initialize the SSP */
void Chip_SSP_Init(LPC_SSP_T *pSSP)
{
	Chip_Clock_EnablePeriphClock(Chip_SSP_GetClockIndex(pSSP));
	Chip_SSP_SetSSPClkDivider(pSSP, 1);
	Chip_SYSCTL_PeriphReset(Chip_SSP_GetResetIndex(pSSP));

	Chip_SSP_Set_Mode(pSSP, SSP_MODE_MASTER);
	Chip_SSP_SetFormat(pSSP, SSP_BITS_8, SSP_FRAMEFORMAT_SPI, SSP_CLOCK_CPHA0_CPOL0);
	Chip_SSP_SetBitRate(pSSP, 100000);
}

/* De-initializes the SSP peripheral */
void Chip_SSP_DeInit(LPC_SSP_T *pSSP)
{
	Chip_SSP_Disable(pSSP);

	Chip_Clock_DisablePeriphClock(Chip_SSP_GetClockIndex(pSSP));
	Chip_SSP_SetSSPClkDivider(pSSP, 0);
}
