/*
 * Copyright (c) 2013 - 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2023 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_spi.h"
#include "fsl_inputmux.h"
#include "fsl_iopctl.h"
#include "fsl_dma.h"
/*
 * Pins used in this application:
 *
 * P0_6  [D:O] -  J30.2: GPO - SYNCHRO indicator (active high)
 * P0_15 [D:O] -  JS6.2: GPO - TEST indicator (active high)
 * P0_16 [D:O] -  JS5.2: GPO - AUX0 indicator (test parameters indicator)
 *
 * P1_3  [D:O] - JP26.4: SPI5_CLK
 * P1_4  [D:I] - JP26.3: SPI5_MISO
 * P1_5  [D:O] - JP26.2: SPI5_MOSI
 * P1_6  [D:O] - JP26.1: SPI5_SS0
 *
 * P2_29 [D:O] - J18.1 : CLKOUT, main clock/1
 *
 * P2_15 [D:O] - J47.10: GPO - error indicator (active high)
 *
 */

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define IOCON_IN_NOPUPD(x)	\
	(IOPCTL_PIO_FSEL(x) 	|	\
	 IOPCTL_PIO_PUPDENA(0)	| IOPCTL_PIO_IBENA(1)	| IOPCTL_PIO_FULLDRIVE(0)	| \
     IOPCTL_PIO_AMENA(0)	| IOPCTL_PIO_ODENA(0)	| IOPCTL_PIO_IIENA(0))

// FC5 SPI
#define SPI_SS_PORT		1
#define SPI_SS_PIN		6
#define	SPI_SS_FUNC		1

#define SPI_CLK_PORT	1
#define SPI_CLK_PIN		3
#define	SPI_CLK_FUNC	1

#define SPI_MOSI_PORT	1
#define SPI_MOSI_PIN	5
#define	SPI_MOSI_FUNC	1

#define SPI_MISO_PORT	1
#define SPI_MISO_PIN	4
#define	SPI_MISO_FUNC	1

#define CLKOUT_PORT		2
#define	CLKOUT_PIN		29
#define	CLKOUT_FUNC		5

//#define DEBUG_MODE
#ifdef DEBUG_MODE
gpio_pin_config_t gpio_config =
 {
	kGPIO_DigitalOutput,
	0,
};

/* SYNCHRO indicates when the the program starts.
 * TEST indicates when a test starts/stops.
 * AUX0 not used in this example.
 * ERROR indicates when data received are different to data sent.
 */

#define	SYNCHRO_PORT	0
#define	SYNCHRO_PIN		6
#define SYNCHRO_FUNC	0
#define	TEST_PORT		0
#define	TEST_PIN		15
#define TEST_FUNC		0
#define	AUX0_PORT		0
#define	AUX0_PIN		16
#define AUX0_FUNC		0
#define ERROR_PORT		2
#define	ERROR_PIN		15
#define ERROR_FUNC		0
#endif

// DMA section
//============
#define	DESTINATION_SRAM	0
#define DESTINATION_PORT	1
#define	TEST_DESTINATION	DESTINATION_PORT

#define	DESC_ADDON				2
#define RT500_DMA_CH_PERI_REQ	37
#define DEMO_DMA_CH_PERI_REQ	(RT500_DMA_CH_PERI_REQ + DESC_ADDON)
enum demo_dma_descriptor_enum
{
	dma_desc_spi_rx_main	= 10,
	dma_desc_spi_tx_main	= 11,

	dma_desc_spi_tx_add1	= RT500_DMA_CH_PERI_REQ,
	dma_desc_spi_rx_add1	= dma_desc_spi_tx_add1+1
};

struct DEMO_DESCRIPTOR_TYPE_T
{
	uint32_t xfercfg;
	uint32_t src_addr;
	uint32_t des_addr;
	uint32_t link;
};
__attribute__((aligned(1024))) volatile struct DEMO_DESCRIPTOR_TYPE_T demo_dma_descriptor[DEMO_DMA_CH_PERI_REQ];

#define SPI_DATA_SIZE	8
#define	SPI_DATA_MASK	((1<<SPI_DATA_SIZE)-1)

#define TEST_CPOL		0
#define TEST_CPHA		0

#define	DMACH_RX_PRIO	2
#define	DMACH_TX_PRIO	3

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
volatile uint32_t sw_gate, sw_gate_count;
volatile uint32_t main_loop_count;
volatile uint32_t dummy32bit;

#define TEST_ARRAY_SIZE	32
volatile uint8_t  spi_tx_array_8bit[TEST_ARRAY_SIZE], spi_rx_array_8bit[TEST_ARRAY_SIZE];
volatile uint32_t spi_tx_array_tail[4];
volatile uint32_t tx_body_len, tx_tail_len, rx_body_len, rx_tail_len;

volatile uint32_t i_aux;
volatile uint32_t i_fclk2sclk;

void delay_sw(uint32_t count);
void init_mcu(void);
void init_spi(void);
void init_dma(void);
void update_spi(uint32_t spi_div);
void prepare_arrays(uint32_t key, uint32_t array_len,
					uint32_t* pnt_tx_body_len, uint32_t* pnt_tx_tail_len,
					uint32_t* pnt_rx_body_len, uint32_t* pnt_rx_tail_len);
void prepare_dma(void);
void verify_received_data(void);

volatile uint32_t spi_fifowr_ctrl, spi_fifowr_last_ctrl_data;
volatile uint32_t i_key;

const uint32_t fclk2sclk_array[] = {3, 100, 50, 10, 5, 4, 3, 2, 1, 0};
volatile uint32_t dma_txrx_data_len;

spi_master_config_t masterConfig;
/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Main function
 */
int main(void)
{
    /* Init board hardware. */
    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();

    init_mcu();
    init_spi();
    init_dma();

#ifdef DEBUG_MODE
    // synchro pulse - use this to trigger the scope/logic
    GPIO_PortClear(GPIO, SYNCHRO_PORT, 1 << SYNCHRO_PIN);
    delay_sw(200);
    GPIO_PortSet(GPIO, SYNCHRO_PORT, 1 << SYNCHRO_PIN);
    delay_sw(200);
    GPIO_PortClear(GPIO, SYNCHRO_PORT, 1 << SYNCHRO_PIN);
    delay_sw(200);
#endif

	spi_fifowr_ctrl =
		SPI_FIFOWR_LEN(SPI_DATA_SIZE-1) |	// len
	    SPI_FIFOWR_TXIGNORE(0)			|	// TXIGNORE
	    SPI_FIFOWR_RXIGNORE(0)			|	// RXIGNORE
	    SPI_FIFOWR_EOF(1)				|	// EOF
	    SPI_FIFOWR_EOT(0)				|	// EOT
	    SPI_FIFOWR_TXSSEL3_N(1)			|	// TXSSEL3
	    SPI_FIFOWR_TXSSEL2_N(1)			|	// TXSSEL2
	    SPI_FIFOWR_TXSSEL1_N(1)			|	// TXSSEL1
	    SPI_FIFOWR_TXSSEL0_N(0);	// TXSSEL0

	dummy32bit = 0;
    i_key = 0;
    i_fclk2sclk = 0;
	while(fclk2sclk_array[i_fclk2sclk] != 0)
	{
		for (dma_txrx_data_len = (TEST_ARRAY_SIZE-16); dma_txrx_data_len != (TEST_ARRAY_SIZE-12); dma_txrx_data_len++)
		{
#ifdef DEBUG_MODE
			// indicate test start
			GPIO_PortSet(GPIO, TEST_PORT, 1 << TEST_PIN);
			delay_sw(50);
#endif
			update_spi(fclk2sclk_array[i_fclk2sclk]);

			i_key = (i_key+1) & 0x7;

			prepare_arrays(i_key, dma_txrx_data_len,
								(uint32_t*) &tx_body_len, (uint32_t*) &tx_tail_len,
								(uint32_t*) &rx_body_len, (uint32_t*) &rx_tail_len);


			// prepare SPI FIFOWR control bits
			*((uint16_t*)(((uint32_t)&SPI5->FIFOWR)+0x2)) = (uint16_t)(spi_fifowr_ctrl>>16);

			// make sure SSD @ STAT is 0
			SPI5->STAT = 1<<5;

			prepare_dma();

			main_loop_count = 0;
			while((SPI5->STAT & (1<<5)) == 0)
			{
				main_loop_count++;
			}

#if TEST_DESTINATION ==	DESTINATION_SRAM
			verify_received_data();
#endif
#ifdef DEBUG_MODE
			// indicate test stop
			GPIO_PortClear(GPIO, TEST_PORT, 1 << TEST_PIN);
#endif
			dummy32bit++;

		}// end of dma_txrx_data_len loop

		delay_sw(2000);

		// update fclk2sclk_array index
		i_fclk2sclk++;

	}// end of FCLK2SCLK loop

    main_loop_count = 0;
    while(1)
    {
    	main_loop_count++;
    }
}


void delay_sw(uint32_t count)
{
	while(count != 0)
	{
		count--;
	}

	return;
}


void init_mcu(void)
{
	CLOCK_EnableClock(kCLOCK_InputMux);
	RESET_PeripheralReset(kINPUTMUX_RST_SHIFT_RSTn);

	CLOCK_AttachClk(kMAIN_CLK_to_CLKOUT);
	CLOCK_SetClkDiv(kCLOCK_DivClockOut, 0);

	IOPCTL_PinMuxSet(IOPCTL, CLKOUT_PORT, CLKOUT_PIN, IOCON_IN_NOPUPD(CLKOUT_FUNC));

#ifdef DEBUG_MODE
    // prime the test indicators
    //==========================
	GPIO_PortInit(GPIO, SYNCHRO_PORT);
	GPIO_PortInit(GPIO, ERROR_PORT);

	GPIO_PinInit(GPIO, SYNCHRO_PORT, SYNCHRO_PIN, &gpio_config);
	IOPCTL_PinMuxSet(IOPCTL, SYNCHRO_PORT, SYNCHRO_PIN, IOCON_IN_NOPUPD(SYNCHRO_FUNC));

	GPIO_PinInit(GPIO, TEST_PORT, TEST_PIN, &gpio_config);
	IOPCTL_PinMuxSet(IOPCTL, TEST_PORT, TEST_PIN, IOCON_IN_NOPUPD(TEST_FUNC));

	GPIO_PinInit(GPIO, AUX0_PORT, AUX0_PIN, &gpio_config);
	IOPCTL_PinMuxSet(IOPCTL, AUX0_PORT, AUX0_PIN, IOCON_IN_NOPUPD(AUX0_FUNC));

	GPIO_PinInit(GPIO, ERROR_PORT, ERROR_PIN, &gpio_config);
	IOPCTL_PinMuxSet(IOPCTL, ERROR_PORT, ERROR_PIN, IOCON_IN_NOPUPD(ERROR_FUNC));
#endif

	return;
}


void init_spi(void)
{

	CLOCK_SetFRGClock(&(const clock_frg_clk_config_t){5, kCLOCK_FrgMainClk, 255, 0});
	CLOCK_AttachClk(kFRG_to_FLEXCOMM5);
	CLOCK_EnableClock(kCLOCK_Spi5);

	// select FC5 as SPI
	FLEXCOMM5->PSELID = FLEXCOMM_PSELID_PERSEL(2);

	// FC5_SCK @ ...
	IOPCTL_PinMuxSet(IOPCTL, SPI_SS_PORT, SPI_SS_PIN, IOCON_IN_NOPUPD(SPI_SS_FUNC));
	IOPCTL_PinMuxSet(IOPCTL, SPI_CLK_PORT, SPI_CLK_PIN, IOCON_IN_NOPUPD(SPI_CLK_FUNC));
	IOPCTL_PinMuxSet(IOPCTL, SPI_MOSI_PORT, SPI_MOSI_PIN, IOCON_IN_NOPUPD(SPI_MOSI_FUNC));
	IOPCTL_PinMuxSet(IOPCTL, SPI_MISO_PORT, SPI_MISO_PIN, IOCON_IN_NOPUPD(SPI_MISO_FUNC));

	SPI_MasterGetDefaultConfig(&masterConfig);
	SPI_MasterInit(SPI5, &masterConfig, CLOCK_GetFlexcommClkFreq(5));

	return;
}


void init_dma(void)
{
	// DMA, INPUTMUX
	CLOCK_EnableClock(kCLOCK_Dmac0);
	RESET_PeripheralReset(kDMAC0_RST_SHIFT_RSTn);

	CLOCK_EnableClock(kCLOCK_InputMux);
	RESET_PeripheralReset(kINPUTMUX_RST_SHIFT_RSTn);

    DMA0->SRAMBASE = (uint32_t)&demo_dma_descriptor[0];
    DMA0->CTRL = 1;

    // dma_desc_spi_rx_main: SPI Rx request, drive DMA out A, input trigger = DMA out A
    INPUTMUX_EnableSignal(INPUTMUX, kINPUTMUX_Flexcomm5RxToDmac0Ch10RequestEna, true);
    INPUTMUX_AttachSignal(INPUTMUX, 0, kINPUTMUX_Dma0OtrigChannel10ToTriginChannels);
    INPUTMUX_AttachSignal(INPUTMUX, dma_desc_spi_rx_main, kINPUTMUX_Dma0TrigOutAToDma0);

    // dma_desc_spi_tx_main: SPI tx request, input trigger = DMA out A
    INPUTMUX_EnableSignal(INPUTMUX, kINPUTMUX_Flexcomm5TxToDmac0Ch11RequestEna, true);
    INPUTMUX_AttachSignal(INPUTMUX, dma_desc_spi_tx_main, kINPUTMUX_Dma0TrigOutAToDma0);

	return;
}


void update_spi(uint32_t spi_div)
{
	// reset FC5/SPI5
	RESET_PeripheralReset(kFC5_RST_SHIFT_RSTn);

	// select FC5 as SPI
	FLEXCOMM5->PSELID = FLEXCOMM_PSELID_PERSEL(2);

	SPI_MasterGetDefaultConfig(&masterConfig);
	masterConfig.baudRate_Bps = (CLOCK_GetFlexcommClkFreq(5) / spi_div);
	SPI_MasterInit(SPI5, &masterConfig, CLOCK_GetFlexcommClkFreq(5));
	SPI_EnableTxDMA(SPI5, true);
	SPI_EnableRxDMA(SPI5, true);

	return;
}

void prepare_arrays(uint32_t key, uint32_t array_len,
					uint32_t* pnt_tx_body_len, uint32_t* pnt_tx_tail_len,
					uint32_t* pnt_rx_body_len, uint32_t* pnt_rx_tail_len)
{
	uint32_t i_loc;

	// rx array
	for (i_loc = 0; i_loc != TEST_ARRAY_SIZE; i_loc++)
	{
		spi_rx_array_8bit[i_loc] = 0xFF;
	}

	for (i_loc = 0; i_loc != array_len; i_loc++)
	{
		spi_tx_array_8bit[i_loc] = (uint8_t)(((key<<5) | i_loc+1) & SPI_DATA_MASK);
	}

	if ((array_len & 0x03) == 0)
	{	// data array len divisible by 4
		*pnt_tx_tail_len = 4;
	}
	else
	{	// data array len NOT divisible by 4
		*pnt_tx_tail_len = array_len & 0x3;
	}

	// the main rx descriptor must perform transfer 1 + 4k, the tail is added if needed
	*pnt_rx_tail_len = 0;
	while(((array_len - *pnt_rx_tail_len) & 0x3) != 0x1)
	{
		*pnt_rx_tail_len = *pnt_rx_tail_len + 1;
	}

	*pnt_rx_body_len = array_len - *pnt_rx_tail_len;
	*pnt_tx_body_len = array_len - *pnt_tx_tail_len;

	return;
}

void prepare_dma(void)
{
	uint32_t dma_desc_spi_rx_main_xfercfg_temp, i_loc;

	dma_desc_spi_rx_main_xfercfg_temp = 0;

    //
    // dma_desc_spi_rx_* begin
    //========================
	if (rx_tail_len != 0)
	{
		// dma_desc_spi_rx_add1
		demo_dma_descriptor[dma_desc_spi_rx_add1].xfercfg	=
			1<<0 	|	// valid configuration
			0<<1 	|	// link/reload disabled
			0<<2	|	// no sw trigger
			1<<3	|	// clear trigger at the end
			0<<4 	|	// no int A at the end
			0<<5	|	// no int B at the end
			0<<8 	|	// 8-bit transfers
			0<<12	|	// src: +0
			1<<14	|	// dst: +1
			(rx_tail_len-1)<<16;	// transfer count...;
		demo_dma_descriptor[dma_desc_spi_rx_add1].src_addr	= (uint32_t)&SPI5->FIFORD;
		demo_dma_descriptor[dma_desc_spi_rx_add1].des_addr	= (uint32_t)&spi_rx_array_8bit[dma_txrx_data_len-1];
		demo_dma_descriptor[dma_desc_spi_rx_add1].link		= 0;

		dma_desc_spi_rx_main_xfercfg_temp |= 1<<1;	//prime the main rx descriptor link/reload feature
	}

	// dma_desc_spi_rx_main
	demo_dma_descriptor[dma_desc_spi_rx_main].xfercfg	= 0;
	demo_dma_descriptor[dma_desc_spi_rx_main].src_addr	= (uint32_t)&SPI5->FIFORD;
	demo_dma_descriptor[dma_desc_spi_rx_main].des_addr	= (uint32_t)&spi_rx_array_8bit[rx_body_len-1];
	if (rx_tail_len == 0)
	{
		demo_dma_descriptor[dma_desc_spi_rx_main].link = 0;
	}
	else
	{
		demo_dma_descriptor[dma_desc_spi_rx_main].link = (uint32_t)&demo_dma_descriptor[dma_desc_spi_rx_add1];
	}

	DMA0->CHANNEL[dma_desc_spi_rx_main].CFG =
		1<<0	|	// peripheral req enable
		1<<1	|	// hw trigger enabled
		0<<4	|	// falling...
		0<<5	| 	// ... edge
		1<<6	|	// burst transfer(s)
		2<<8	|	// burst size = 2^2 = 4 transfer
		DMACH_RX_PRIO<<16;		// priority =...

	dma_desc_spi_rx_main_xfercfg_temp |=
		0<<0 	|	// not valid configuration yet!!!
		0<<1 	|	// link/reload disabled - adjusted by add1 if present
		0<<2	|	// no sw trigger
		1<<3	|	// clear trigger at the end
		0<<4 	|	// no int A at the end
		0<<5	|	// no int B at the end
		0<<8 	|	// 8-bit transfers
		0<<12	|	// src: +0
		1<<14	|	// dst: +1
		(rx_body_len-1)<<16;	// transfer count...;
	DMA0->CHANNEL[dma_desc_spi_rx_main].XFERCFG = dma_desc_spi_rx_main_xfercfg_temp;
    // dma_desc_spi_rx_* end
    //----------------------


    //
    // dma_desc_spi_tx_* begin
    //========================
	// dma_desc_spi_tx_add1
	demo_dma_descriptor[dma_desc_spi_tx_add1].xfercfg	=
		1<<0 	|	// valid configuration
		0<<1 	|	// link/reload disabled
		0<<2	|	// no sw trigger
		1<<3	|	// clear trigger at the end
		0<<4 	|	// no int A at the end
		0<<5	|	// no int B at the end
		2<<8 	|	// 32-bit transfers
		1<<12	|	// src: +1
		0<<14	|	// dst: +0
		(tx_tail_len-1)<<16;	// transfer count...;
	demo_dma_descriptor[dma_desc_spi_tx_add1].src_addr	= (uint32_t)&spi_tx_array_tail[tx_tail_len-1];
	demo_dma_descriptor[dma_desc_spi_tx_add1].des_addr	= (uint32_t)&SPI5->FIFOWR;
	demo_dma_descriptor[dma_desc_spi_tx_add1].link		= 0;

	// dma_desc_spi_tx_main
	demo_dma_descriptor[dma_desc_spi_tx_main].xfercfg	= 0;
	demo_dma_descriptor[dma_desc_spi_tx_main].src_addr	= (uint32_t)&spi_tx_array_8bit[tx_body_len-1];
	demo_dma_descriptor[dma_desc_spi_tx_main].des_addr	= (uint32_t)&SPI5->FIFOWR;
	demo_dma_descriptor[dma_desc_spi_tx_main].link		= (uint32_t)&demo_dma_descriptor[dma_desc_spi_tx_add1];

	DMA0->CHANNEL[dma_desc_spi_tx_main].CFG =
		0<<0	|	// peripheral req disable
		1<<1	|	// hw trigger enabled
		0<<4	|	// falling...
		0<<5	| 	// ... edge
		1<<6	|	// burst transfer(s)
		2<<8	| 	// burst size = 2^2 = 4 transfer
		DMACH_TX_PRIO<<16;	// priority =...

	DMA0->CHANNEL[dma_desc_spi_tx_main].XFERCFG =
		0<<0 	|	// not valid configuration yet
		1<<1 	|	// link/reload enabled
		0<<2	|	// no sw trigger
		1<<3	|	// clear trigger at the end
		0<<4 	|	// no int A at the end
		0<<5	|	// no int B at the end
		0<<8 	|	// 8-bit transfers
		1<<12	|	// src: +1
		0<<14	|	// dst: +0
		(tx_body_len-1)<<16;	// transfer count...;

	// prepare the tail array, add SSEL de-select for the last entry
	for (i_loc = 0; i_loc != tx_tail_len; i_loc++)
	{
		spi_tx_array_tail[i_loc] = spi_fifowr_ctrl | ((uint32_t)spi_tx_array_8bit[tx_body_len+i_loc]);
	}
	spi_tx_array_tail[tx_tail_len-1] |= 1<<20;
    // dma_desc_spi_tx_* end
    //----------------------

	DMA0->CHANNEL[dma_desc_spi_rx_main].XFERCFG = dma_desc_spi_rx_main_xfercfg_temp |
		1<<0 	|	// valid configuration
		1<<2;		// sw trigger

	DMA_EnableChannel(DMA0, dma_desc_spi_rx_main);

	DMA0->CHANNEL[dma_desc_spi_tx_main].XFERCFG =
		1<<0 	|	// valid configuration
		1<<1 	|	// link/reload enabled
		1<<2	|	// sw trigger
		1<<3	|	// clear trigger at the end
		0<<4 	|	// no int A at the end
		0<<5	|	// no int B at the end
		0<<8 	|	// 8-bit transfers
		1<<12	|	// src: +1
		0<<14	|	// dst: +0
		(tx_body_len-1)<<16;	// transfer count...;

	DMA_EnableChannel(DMA0, dma_desc_spi_tx_main);

	return;
}


void verify_received_data(void)
{
	uint32_t i_loc;
	uint32_t error_loc;

	error_loc = 0;
	for (i_loc = 0; i_loc != dma_txrx_data_len; i_loc++)
	{
		if (((spi_tx_array_8bit[i_loc]^spi_rx_array_8bit[i_loc]) & SPI_DATA_MASK) != 0)
		{
			error_loc++;
		}
	}

	if (error_loc != 0)
	{
		while(1)
		{
#ifdef DEBUG_MODE
			GPIO_PortSet(GPIO, ERROR_PORT, 1 << ERROR_PIN);
			delay_sw(50);
			GPIO_PortClear(GPIO, ERROR_PORT, 1 << ERROR_PIN);
			delay_sw(50);
#endif
		}
	}

	return;
}
