/*
 * @brief Secondary loader SPI host interface handler
 *
 * @note
 * Copyright(C) NXP Semiconductors, 2014
 * 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"
#include "sl_common.h"

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

static LPC_SPI_T *savedPort;
static LPC_SPI_T *pSPIArray[2] = {
	LPC_SPI0, LPC_SPI1
};

const DMA_CHID_T dmaTxChannel[2] = {
	DMAREQ_SPI0_TX, DMAREQ_SPI1_TX
};
const DMA_CHID_T dmaRxChannel[2] = {
	DMAREQ_SPI0_RX, DMAREQ_SPI1_RX
};
DMA_CHID_T SpiDmaTxChannel;
DMA_CHID_T SpiDmaRxChannel;

/* Possible function selections for SPI mapped pins in IOCON */
#define SPIPURP_SCK         0
#define SPIPURP_MOSI        1
#define SPIPURP_MISO        2
#define SPIPURP_SSEL        3
typedef struct {
	uint8_t     spiNum;			/* SPin number, 0 or 1 */
	uint8_t     port;			/* Port */
	uint8_t     pin;			/* Pin */
	uint8_t     func;			/* IOCON function number */
	uint8_t     purpose;		/* 0 = SCK, 1 = MOSI, 2 = MISO, 3 = SSEL */
} SL_SPIFNUCMAP_T;
#define SPIPURP_MAPMAPPINS  17
static const SL_SPIFNUCMAP_T spiMappedFuncs[SPIPURP_MAPMAPPINS] = {
	//  sawyer
//	/* SCK */
//	{0, 0, 11, IOCON_FUNC1, SPIPURP_SCK},			/* PIO0_11 SPI0_SCK (1) */
//	{0, 1, 3,  IOCON_FUNC5, SPIPURP_SCK},			/* PIO1_3  SPI0_SCK (5) */
//	{1, 1, 6,  IOCON_FUNC2, SPIPURP_SCK},			/* PIO1_6  SPI1_SCK (2) */
//	{1, 1, 12, IOCON_FUNC4, SPIPURP_SCK},			/* PIO1_12 SPI1_SCK (4) */

//	/* MOSI */
//	{0, 0, 12, IOCON_FUNC1, SPIPURP_MOSI},			/* PIO0_12 SPI0_MOSI (1) */
//	{0, 1, 9,  IOCON_FUNC2, SPIPURP_MOSI},			/* PIO1_9  SPI0_MOSI (2) */
//	{1, 1, 7,  IOCON_FUNC2, SPIPURP_MOSI},			/* PIO1_7  SPI1_MOSI (2) */
//	{1, 1, 13, IOCON_FUNC4, SPIPURP_MOSI},			/* PIO1_13 SPI1_MOSI (4) */

//	/* MISO */
//	{0, 0, 13, IOCON_FUNC1, SPIPURP_MISO},			/* PIO0_13 SPI0_MISO (1) */
//	{0, 1, 4,  IOCON_FUNC5, SPIPURP_MISO},			/* PIO1_4  SPI0_MISO (5) */
//	{1, 1, 8,  IOCON_FUNC2, SPIPURP_MISO},			/* PIO1_8  SPI1_MISO (2) */
//	{1, 1, 14, IOCON_FUNC4, SPIPURP_MISO},			/* PIO1_14 SPI1_MISO (4) */

//	/* SSEL */
//	{0, 0, 0,  IOCON_FUNC2, SPIPURP_SSEL},			/* PIO0_0  SPI0_SSEL0 (2) */
//	{0, 0, 9,  IOCON_FUNC5, SPIPURP_SSEL},			/* PIO0_9  SPI0_SSEL0 (5) */
//	{0, 0, 14, IOCON_FUNC1, SPIPURP_SSEL},			/* PIO0_14 SPI0_SSEL0 (1) */
//	{1, 1, 5,  IOCON_FUNC2, SPIPURP_SSEL},			/* PIO1_5  SPI1_SSEL0 (2) */
//	{1, 1, 15, IOCON_FUNC4, SPIPURP_SSEL},			/* PIO1_15 SPI1_SSEL0 (4) */
};

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

SPIS_XFER_T spiSlaveXfer;
extern int buffXferPending;
extern uint32_t g_spiTxPending;

/* DMA descriptors must be aligned to 16 bytes */
#if defined(__CC_ARM)	/* Keil support */
__align(16) static DMA_CHDESC_T dmaSPISTxDesc[1];
__align(16) static DMA_CHDESC_T dmaSPISRxDesc[1];
#elif defined(__ICCARM__)	/* IAR support */
#pragma data_alignment=16
static DMA_CHDESC_T dmaSPISTxDesc[1];
#pragma data_alignment=16
static DMA_CHDESC_T dmaSPISRxDesc[1];
#elif defined( __GNUC__ )	/* GCC support */
static DMA_CHDESC_T dmaSPISTxDesc[1] __attribute__ ((aligned(16)));
static DMA_CHDESC_T dmaSPISRxDesc[1] __attribute__ ((aligned(16)));
#endif /* defined (__GNUC__) */

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

/* Reset DMA Channel */
static void dmaClearChannel(DMA_CHID_T ch) {
	Chip_DMA_DisableChannel(LPC_DMA, ch);
	while ((Chip_DMA_GetBusyChannels(LPC_DMA) & (1 << ch)) != 0) {}

	Chip_DMA_AbortChannel(LPC_DMA, ch);
	Chip_DMA_ClearErrorIntChannel(LPC_DMA, ch);
}

/* DMA uses a real transfer size of 1 greater than the SPI buffer size. Using
   this size, the DMA interrupt never fires (as long as SPI packets don't
   exceed SL_HOSTIFBUFFSIZE bytes). This makes DMA state handling and receive
   size computation a lot easier. */
#define SPI_READDMA_XFERSIZE    (SL_HOSTIFBUFFSIZE + 1)

/* Start a DMA slave write operation */
static void spis_dma_WriteStart(void)
{
#if 0
	/* Slave to master */
	dmaSPISTxDesc[0].source = DMA_ADDR(&spiSlaveXfer.pTXData8[0]) + SPI_READDMA_XFERSIZE - 1;
	dmaSPISTxDesc[0].dest = DMA_ADDR(&savedPort->TXDAT);
	dmaSPISTxDesc[0].next = DMA_ADDR(0);

	/* Note DMA transfer size is made +1 greater than the buffer size. As long as transfers
	   are at 512 bytes or less, the DMA interrupt won't fire and the SPI de-assertion handler
	   can close the DMA transfer and count the transfer size. */
	dmaSPISTxDesc[0].xfercfg = DMA_XFERCFG_CFGVALID | DMA_XFERCFG_SETINTA |
							   DMA_XFERCFG_SWTRIG | DMA_XFERCFG_WIDTH_8 | DMA_XFERCFG_SRCINC_1 |
							   DMA_XFERCFG_DSTINC_0 | DMA_XFERCFG_XFERCOUNT(SPI_READDMA_XFERSIZE);

	/* Reset DMA Tx Channel */
	dmaClearChannel(SpiDmaTxChannel);
	/* Setup transfer descriptor and validate it */
	while (!Chip_DMA_SetupTranChannel(LPC_DMA, SpiDmaTxChannel, &dmaSPISTxDesc[0])) {}
	/* Setup data transfer */
	Chip_DMA_SetupChannelTransfer(LPC_DMA, SpiDmaTxChannel, dmaSPISTxDesc[0].xfercfg);
	Chip_DMA_EnableChannel(LPC_DMA, SpiDmaTxChannel);
	Chip_DMA_SetValidChannel(LPC_DMA, SpiDmaTxChannel);
#endif
}

/* Start a DMA slave read operation */
static void spis_dma_ReadStart(void)
{
#if 0
	/* Slave to master */
	dmaSPISRxDesc[0].source = DMA_ADDR(&savedPort->RXDAT);
	dmaSPISRxDesc[0].dest = DMA_ADDR(&spiSlaveXfer.pRXData8[0]) + SPI_READDMA_XFERSIZE - 1;
	dmaSPISRxDesc[0].next = DMA_ADDR(0);

	/* Note DMA transfer size is made +1 greater than the buffer size. As long as transfers
	   are at 512 bytes or less, the DMA interrupt won't fire and the SPI de-assertion handler
	   can close the DMA transfer and count the transfer size. */
	dmaSPISRxDesc[0].xfercfg = DMA_XFERCFG_CFGVALID | DMA_XFERCFG_SETINTA |
							   DMA_XFERCFG_SWTRIG | DMA_XFERCFG_WIDTH_8 | DMA_XFERCFG_DSTINC_1 |
							   DMA_XFERCFG_SRCINC_0 | DMA_XFERCFG_XFERCOUNT(SPI_READDMA_XFERSIZE);

	/* Reset DMA Rx Channel */
	dmaClearChannel(SpiDmaRxChannel);
	/* Setup transfer descriptor and validate it */
	while (!Chip_DMA_SetupTranChannel(LPC_DMA, SpiDmaRxChannel, &dmaSPISRxDesc[0])) {}

	/* Setup data transfer */
	Chip_DMA_SetupChannelTransfer(LPC_DMA, SpiDmaRxChannel, dmaSPISRxDesc[0].xfercfg);
	Chip_DMA_EnableChannel(LPC_DMA, SpiDmaRxChannel);

	Chip_DMA_SetValidChannel(LPC_DMA, SpiDmaRxChannel);
#endif
}

/* SPI slave select assertion callback function */
static void SPISlaveAssert(SPIS_XFER_T *pSlaveXfer)
{
	Hostif_DeAssertIRQ();
}

/* SPI slave send data callback function */
static void SPISlaveSendData(SPIS_XFER_T *pSlaveXfer)
{
	/* Do nothing */
}

/* SPI slave receive data callback function */
static void SPISlaveRecvData(SPIS_XFER_T *pSlaveXfer)
{
	/* Do nothing */
}

/* SPI slave select de-assertion callback function */
static void SPISlaveDeAssert(SPIS_XFER_T *pSlaveXfer)
{
	/* Abort DMA transfer on both channels */
	dmaAbort(SpiDmaTxChannel);
	dmaAbort(SpiDmaRxChannel);

	/* check if we sending response */
	if (g_spiTxPending) {
		g_spiTxPending = 0;
		startHostIfSPI();
	}
	else {
		/* TX size isn't important, but loader might beed to know the actual
		   receive size the DMA handled. Use DMA configured transfer size
		   instead of the SPI buffer size. */
		recvBuff.bytes = dmaDetermineSpiTransferSize(SpiDmaRxChannel,
													 SPI_READDMA_XFERSIZE, 1);
		//	recvBuff.bytes = spiSlaveXfer.dataRXferred;
	}

	buffXferPending = 1;
}

/* SPI slave driver callbacks */
static const SPIS_CALLBACKS_T spiSlaveCallbacks = {
	&SPISlaveAssert,
	&SPISlaveSendData,
	&SPISlaveRecvData,
	&SPISlaveDeAssert
};

/* Maps a pin's IOCON function to it's SPI number, port, pin, and purpose */
static void spiMapPin(int spiNum, SL_PORTPIN_T *pSpiPortPin, uint8_t purp)
{
	int i = 0;
	uint32_t func;
	bool mapped = false;

	while ((mapped == false) && (i < SPIPURP_MAPMAPPINS)) {
		if (spiMappedFuncs[i].spiNum == spiNum) {
			if (spiMappedFuncs[i].purpose == purp) {
				if (pSpiPortPin->port == spiMappedFuncs[i].port) {
					if (pSpiPortPin->pin == spiMappedFuncs[i].pin) {
						/* Matched function to pin */
						func = (uint32_t) spiMappedFuncs[i].func;
							//  sawyer
//						/* Setup IOCON */
//						Chip_IOCON_PinMuxSet(LPC_IOCON, spiMappedFuncs[i].port,
//											 spiMappedFuncs[i].pin, (func | IOCON_MODE_PULLUP | IOCON_DIGITAL_EN));
						break;
					}
				}
			}
		}

		i++;
	}
}

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

void setupMuxingSPI(int spiNum)
{
	/* Pin selection based on port. Note MISO is not setup here, as it is
	   setup later after A5 packet processing (also ok for app provided
	   MISO). */
	spiMapPin(spiNum, &slIfConfig.hostMosiPortPin, SPIPURP_MOSI);
	spiMapPin(spiNum, &slIfConfig.hostSselPortPin, SPIPURP_SSEL);
	spiMapPin(spiNum, &slIfConfig.hostSckPortPin, SPIPURP_SCK);
}

/* Sets up pinmuxing for an SPI MISO pin only */
void setupMuxingSPIMiso(int spiNum)
{
	spiMapPin(spiNum, &slIfConfig.hostMisoPortPin, SPIPURP_MISO);
}

void setupInterfaceSPI(int spiNum)
{
#if 0
	//  sawyer
	//	SPI_CFGSETUP_T spiSetup;

	savedPort = pSPIArray[spiNum];
	SpiDmaTxChannel = dmaTxChannel[spiNum];
	SpiDmaRxChannel = dmaRxChannel[spiNum];

	/* Setup DMA channels for SPI slave send and receive */
	/* Setup SPI slave channel for the following configuration:
	   - Peripheral DMA request
	   - Single transfer
	   - High channel priority */
	Chip_DMA_EnableChannel(LPC_DMA, SpiDmaTxChannel);
	Chip_DMA_EnableChannel(LPC_DMA, SpiDmaRxChannel);
	Chip_DMA_EnableIntChannel(LPC_DMA, SpiDmaTxChannel);
	Chip_DMA_EnableIntChannel(LPC_DMA, SpiDmaRxChannel);
	Chip_DMA_SetupChannelConfig(LPC_DMA, SpiDmaTxChannel,
								(DMA_CFG_PERIPHREQEN | DMA_CFG_TRIGBURST_SNGL | DMA_CFG_CHPRIORITY(0)));
	Chip_DMA_SetupChannelConfig(LPC_DMA, SpiDmaRxChannel,
								(DMA_CFG_PERIPHREQEN | DMA_CFG_TRIGBURST_SNGL | DMA_CFG_CHPRIORITY(0)));

	/* Initialize SPI controller */
	Chip_SPI_Init(savedPort);

	/* Call to initialize SPI controller for mode0, slave mode, MSB first */
	Chip_SPI_Enable(savedPort);
	//  sawyer
//	spiSetup.master = 0;
//	spiSetup.lsbFirst = 0;
//	spiSetup.mode = SPI_CLOCK_MODE0;
//	Chip_SPI_ConfigureSPI(savedPort, &spiSetup);

	/* Setup slave controller SSEL for active low select */
	Chip_SPI_SetCSPolLow(savedPort, 0);

	/* Setup slave controller for 8-bit transfers */
	Chip_SPI_SetXferSize(savedPort, 8);

	/* For the SPI controller configured in slave mode, enable SPI slave interrupts
	   for interrupt service. Do not enable SPI_INTENSET_TXDYEN. */
	Chip_SPI_EnableInts(savedPort, (SPI_INTENSET_RXDYEN | SPI_INTENSET_RXOVEN |
									SPI_INTENSET_TXUREN | SPI_INTENSET_SSAEN | SPI_INTENSET_SSDEN));

	/* Setup slave transfer callbacks in the transfer descriptor */
	spiSlaveXfer.pCB = &spiSlaveCallbacks;
#endif
}

void startHostIfSPI(void)
{
	/* Queue transfer */
	spiSlaveXfer.pTXData8 = &sendBuff.buffer[0];
	spiSlaveXfer.txCount = SL_HOSTIFBUFFSIZE;
	spiSlaveXfer.pRXData8 = &recvBuff.buffer[0];
	spiSlaveXfer.rxCount = SL_HOSTIFBUFFSIZE;
	spiSlaveXfer.dataRXferred = 0;
	spiSlaveXfer.dataTXferred = 0;

	/* Flush any pre loaded SPI data */
	Chip_SPI_FlushFifos(savedPort);

	/* Start DMA */
	spis_dma_WriteStart();
	spis_dma_ReadStart();

	Chip_SPIS_PreBuffSlave(savedPort, &spiSlaveXfer);
}

/* SPI host interface processing loop */
void loopHostIfSPI(int spiNum)
{
	uint32_t ints = Chip_SPI_GetPendingInts(savedPort);

	/* Handle SPI slave interrupts only */
	if (ints & SPI_INTENSET_SSAEN) {
		/* SPI Slave selection */
		Chip_SPI_ClearStatus(savedPort, SPI_STAT_SSA);
		SPISlaveAssert(&spiSlaveXfer);
	}
	else if (ints & SPI_INTENSET_SSDEN) {
		Chip_SPI_ClearStatus(savedPort, SPI_STAT_SSD);
		SPISlaveDeAssert(&spiSlaveXfer);
	}
}

/* Shut down a SPI slave interface */
void shutdownInterfaceSPI(int spiNum)
{
	Chip_SPI_Disable(savedPort);
	Chip_SPI_DeInit(savedPort);
}
