/*
 * @brief UART DMA example
 *
 * @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 <string.h>
#include "board.h"

/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/
#ifdef __ICCARM__
#define ALIGNSTR(x) # x
#define ALIGN(x) _Pragma(ALIGNSTR(data_alignment = ## x))
#else
#define ALIGN(x) __attribute__ ((aligned(x)))
#endif

/* DMA send string arrays. DMA buffers must remain in memory during the DMA
   transfer. */
#define DMASENDSTRCNT   6
static char dmaSendStr[DMASENDSTRCNT][32];

/* Number of UART TX descriptors used for DMA */
#define UARTTXDESC 8

/* Next available UART TX DMA descriptor and use counter */
static volatile int nextTXDesc, countTXDescUsed;

/* Number of UART RX descriptors used for DMA */
#define UARTRXDESC 4

/* Maximum size of each UART RX receive buffer */
#define UARTRXBUFFSIZE  8

/* UART RX receive buffers */
static uint8_t dmaRXBuffs[UARTRXDESC][UARTRXBUFFSIZE];

/* UART receive buffer that is available and availa flag */
static volatile int uartRXBuff;
static volatile bool uartRxAvail;

/* DMA descriptors must be aligned to 16 bytes */

ALIGN(16) static DMA_CHDESC_T dmaTXDesc[UARTTXDESC];
ALIGN(16) static DMA_CHDESC_T dmaRXDesc[UARTRXDESC];

/* Set to 1 to use vcom port (0 to use UART0).  Additionally SJ9 must be
   moved to pins 2-3 to enable RX from vcom port.  This will remove P0_18
   from the Arduino header J2 */
#define USE_VCOM 0

#if (!USE_VCOM)
#define UART_TX_PIN 4
#define UART_RX_PIN 0
#else
#define UART_TX_PIN 7
#define UART_RX_PIN 18
#endif

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

/*****************************************************************************
 * Private functions
 ****************************************************************************/
static void Init_UART_PinMux(void)
{
	/* Enable the clock to the Switch Matrix */
	Chip_Clock_EnablePeriphClock(SYSCTL_CLOCK_SWM);

	Chip_Clock_SetUARTClockDiv(1);	/* divided by 1 */

#if defined(BOARD_NXP_LPCXPRESSO_824)
	/* Disable the fixed pins assigned to 0 and 18 */
	Chip_SWM_DisableFixedPin(SWM_FIXED_ACMP_I1);
	Chip_SWM_DisableFixedPin(SWM_FIXED_ADC8);

	/* Disable debug USART */
#if (USE_VCOM)
	Chip_SWM_MovablePinAssign(SWM_U1_TXD_O, 0xff);
	Chip_SWM_MovablePinAssign(SWM_U1_RXD_I, 0xff);
#endif
	/* Connect the U0_TXD_O and U0_RXD_I signals to port pins (see define above) */
	Chip_SWM_MovablePinAssign(SWM_U0_TXD_O, UART_TX_PIN);
	Chip_SWM_MovablePinAssign(SWM_U0_RXD_I, UART_RX_PIN);

#else
#error "No UART setup defined"
#endif

	/* Disable the clock to the Switch Matrix to save power */
	Chip_Clock_DisablePeriphClock(SYSCTL_CLOCK_SWM);
}

/* Setup DMA UART TX support, but do not queue descriptors yet */
static void dmaTXSetup(void)
{
	/* Setup UART 0 TX channel for the following configuration:
	   - Peripheral DMA request (UART 0 TX channel)
	   - Single transfer
	   - Low channel priority */
	Chip_DMA_EnableChannel(LPC_DMA, DMAREQ_USART0_TX);
	Chip_DMA_EnableIntChannel(LPC_DMA, DMAREQ_USART0_TX);
	Chip_DMA_SetupChannelConfig(LPC_DMA, DMAREQ_USART0_TX,
								(DMA_CFG_PERIPHREQEN | DMA_CFG_TRIGBURST_SNGL | DMA_CFG_CHPRIORITY(3)));

	countTXDescUsed = 0;
}

/* Setup DMA UART RX support, but do not queue descriptors yet */
static void dmaRXSetup(void)
{
	/* Setup UART 0 RX channel for the following configuration:
	   - Peripheral DMA request (UART 0 RX channel)
	   - Single transfer
	   - Low channel priority */
	Chip_DMA_EnableChannel(LPC_DMA, DMAREQ_USART0_RX);
	Chip_DMA_EnableIntChannel(LPC_DMA, DMAREQ_USART0_RX);
	Chip_DMA_SetupChannelConfig(LPC_DMA, DMAREQ_USART0_RX,
								(DMA_CFG_PERIPHREQEN | DMA_CFG_TRIGBURST_SNGL | DMA_CFG_CHPRIORITY(3)));
}

/* Send data via the UART */
static bool dmaTXSend(uint8_t *data, int bytes)
{
	/* Disable the DMA IRQ to prevent race conditions with shared data */
	NVIC_DisableIRQ(DMA_IRQn);

	/* This is a limited example, limit descriptor and byte count */
	if ((countTXDescUsed >= UARTTXDESC) || (bytes > 1024)) {
		/* Re-enable the DMA IRQ */
		NVIC_EnableIRQ(DMA_IRQn);

		/* All DMA descriptors are used, so just exit */
		return false;
	}
	else if (countTXDescUsed == 0) {
		/* No descriptors are currently used, so take the first one */
		nextTXDesc = 0;
	}

	/* Create a descriptor for the data */
	dmaTXDesc[countTXDescUsed].source = DMA_ADDR(data + bytes - 1);	/* Last address here */
	dmaTXDesc[countTXDescUsed].dest = DMA_ADDR(&LPC_USART0->TXDATA);	/* Byte aligned */

	/* If there are multiple buffers with non-contiguous addresses, they can be chained
	   together here (it is recommended to only use the DMA_XFERCFG_SETINTA on the
	   last chained descriptor). If another TX buffer needs to be sent, the DMA
	   IRQ handler will re-queue and send the buffer there without using chaining. */
	dmaTXDesc[countTXDescUsed].next = DMA_ADDR(0);

	/* Setup transfer configuration */
	dmaTXDesc[countTXDescUsed].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(bytes);

	/* If a transfer is currently in progress, then stop here and let the DMA
	   handler re-queue the next transfer. Otherwise, start the transfer here. */
	if (countTXDescUsed == 0) {
		/* Setup transfer descriptor and validate it */
		Chip_DMA_SetupTranChannel(LPC_DMA, DMAREQ_USART0_TX, &dmaTXDesc[countTXDescUsed]);

		/* Setup data transfer */
		Chip_DMA_SetupChannelTransfer(LPC_DMA, DMAREQ_USART0_TX,
									  dmaTXDesc[countTXDescUsed].xfercfg);

		Chip_DMA_SetValidChannel(LPC_DMA, DMAREQ_USART0_TX);
	}

	/* Update used descriptor count */
	countTXDescUsed++;

	/* Re-enable the DMA IRQ */
	NVIC_EnableIRQ(DMA_IRQn);

	return true;
}

/* Queue up DMA descriptors and buffers for UART RX */
static void dmaRXQueue(void)
{
	int i;

	/* Linked list of descriptors that map to the 3 receive buffers */
	for (i = 0; i < UARTRXDESC; i++) {
		/* Setup next descriptor */
		if (i == (UARTRXDESC - 1)) {
			/* Wrap descriptors */
			dmaRXDesc[i].next = DMA_ADDR(&dmaRXDesc[0]);
		}
		else {
			dmaRXDesc[i].next = DMA_ADDR(&dmaRXDesc[i + 1]);
		}

		/* Create a descriptor for the data */
		dmaRXDesc[i].source = DMA_ADDR(&LPC_USART0->RXDATA) + 0;	/* Byte aligned */
		dmaRXDesc[i].dest = DMA_ADDR(&dmaRXBuffs[i][0] + UARTRXBUFFSIZE - 1);

		/* Setup transfer configuration */
		dmaRXDesc[i].xfercfg = DMA_XFERCFG_CFGVALID | DMA_XFERCFG_SETINTA |
							   DMA_XFERCFG_WIDTH_8 | DMA_XFERCFG_SRCINC_0 |
							   DMA_XFERCFG_DSTINC_1 | DMA_XFERCFG_RELOAD |
							   DMA_XFERCFG_XFERCOUNT(UARTRXBUFFSIZE);
	}

	/* Setup transfer descriptor and validate it */
	Chip_DMA_SetupTranChannel(LPC_DMA, DMAREQ_USART0_RX, &dmaRXDesc[0]);

	/* Setup data transfer */
	Chip_DMA_SetupChannelTransfer(LPC_DMA, DMAREQ_USART0_RX,
								  dmaRXDesc[0].xfercfg);
	Chip_DMA_SetValidChannel(LPC_DMA, DMAREQ_USART0_RX);
	Chip_DMA_SWTriggerChannel(LPC_DMA, DMAREQ_USART0_RX);
}

/* Check and return UART RX data if it exists */
static int checkRxData(uint8_t *buff)
{
	int bytesRec = 0;

	if (uartRxAvail) {
		uartRxAvail = false;

		memcpy(buff, dmaRXBuffs[uartRXBuff], UARTRXBUFFSIZE);
		uartRXBuff++;
		if (uartRXBuff >= UARTRXDESC) {
			uartRXBuff = 0;
		}
		bytesRec = UARTRXBUFFSIZE;
	}

	return bytesRec;
}

/* Clear an error on a 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);
}

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

/**
 * @brief	DMA Interrupt Handler
 * @return	None
 */
void DMA_IRQHandler(void)
{
	uint32_t errors, pending;

	/* Get DMA error and interrupt channels */
	errors = Chip_DMA_GetErrorIntChannels(LPC_DMA);
	pending = Chip_DMA_GetActiveIntAChannels(LPC_DMA);

	/* Check DMA interrupts of UART 0 TX channel */
	if ((errors | pending) & (1 << DMAREQ_USART0_TX)) {
		/* Clear DMA interrupt for the channel */
		Chip_DMA_ClearActiveIntAChannel(LPC_DMA, DMAREQ_USART0_TX);

		/* Handle errors if needed */
		if (errors & (1 << DMAREQ_USART0_TX)) {
			/* DMA error, channel needs to be reset */
			dmaClearChannel(DMAREQ_USART0_TX);
			dmaTXSetup();
		}
		else {
			/* Descriptor is consumed */
			countTXDescUsed--;
		}

		/* Is another DMA descriptor waiting that was not chained? */
		if (countTXDescUsed > 0) {
			nextTXDesc++;

			/* Setup transfer descriptor and validate it */
			Chip_DMA_SetupTranChannel(LPC_DMA, DMAREQ_USART0_TX, &dmaTXDesc[nextTXDesc]);

			/* Setup data transfer */
			Chip_DMA_SetupChannelTransfer(LPC_DMA, DMAREQ_USART0_TX,
										  dmaTXDesc[nextTXDesc].xfercfg);

			Chip_DMA_SetValidChannel(LPC_DMA, DMAREQ_USART0_TX);
		}
	}

	/* Check DMA interrupts of UART 0 RX channel */
	if ((errors | pending) & (1 << DMAREQ_USART0_RX)) {
		/* Clear DMA interrupt for the channel */
		Chip_DMA_ClearActiveIntAChannel(LPC_DMA, DMAREQ_USART0_RX);

		/* Handle errors if needed */
		if (errors & (1 << DMAREQ_USART0_RX)) {
			/* DMA error, channel needs to be reset */
			dmaClearChannel(DMAREQ_USART0_RX);
			dmaRXSetup();
			dmaRXQueue();
		}
		else {
			uartRxAvail = true;
		}
	}
}

/**
 * @brief	Main UART/DMA program body
 * @return	Does not exit
 */
int main(void)
{
	int bytes, idx;
	uint8_t buff[UARTRXBUFFSIZE];

	SystemCoreClockUpdate();
	Board_Init();
	Init_UART_PinMux();
	Board_LED_Set(0, false);

	Chip_UART_Init(LPC_USART0);
	Chip_UART_ConfigData(LPC_USART0, UART_CFG_DATALEN_8 | UART_CFG_PARITY_NONE | UART_CFG_STOPLEN_1);
	Chip_Clock_SetUSARTNBaseClockRate((115200 * 16), true);
	Chip_UART_SetBaud(LPC_USART0, 115200);
	Chip_UART_Enable(LPC_USART0);
	Chip_UART_TXEnable(LPC_USART0);

	/* DMA initialization - enable DMA clocking and reset DMA if needed */
	Chip_DMA_Init(LPC_DMA);

	/* Enable DMA controller and use driver provided DMA table for current descriptors */
	Chip_DMA_Enable(LPC_DMA);
	Chip_DMA_SetSRAMBase(LPC_DMA, DMA_ADDR(Chip_DMA_Table));

	/* Setup UART 0 TX DMA support */
	dmaTXSetup();

	/* Setup UART 0 RX DMA support */
	dmaRXSetup();

	/* Enable the DMA IRQ */
	NVIC_EnableIRQ(DMA_IRQn);

	/* Enqueue a bunch of strings in DMA transmit descriptors and start
	   transmit. In this use of DMA, the descriptors aren't chained, so
	     the DMA restarts the next queued descriptor in the DMA interrupt
	     handler. */
	for (idx = 0; idx < DMASENDSTRCNT; idx++) {
		sprintf(dmaSendStr[idx], "DMA send string (unlinked) #%d\r\n", idx);
		dmaTXSend((uint8_t *) dmaSendStr[idx], strlen(dmaSendStr[idx]));
	}

	/* Wait for UART TX DMA channel to go inactive */
	while (1) {
		__WFI();
		if (Chip_DMA_GetActiveChannels(LPC_DMA) & (1 << DMAREQ_USART0_TX)) {
			break;
		}
	}

	/* Receive buffers are queued. The DMA interrupt will only trigger on a
	   full DMA buffer receive, so if the UART is idle, but the DMA is only
	   partially complete, the DMA interrupt won't fire. For UART data
	   receive where data is not continuous, a timeout method will be
	   required to flush the DMA when the DMA has pending data and no
	   data has been received on the UART in a specified timeout */
	dmaRXQueue();

	/* Get RX data via DMA and send it out on TX via DMA */
	while (1) {
		/* Sleep until something happens */
		__WFI();

		/* Did any data come in? */
		bytes = checkRxData(buff);
		if (bytes > 0) {
			/* RX data received, send it via TX DMA */
			dmaTXSend(buff, bytes);
		}
	}
}
