#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"
#include "inc/hw_ssi.h"
#include "inc/hw_sysctl.h"
#include "inc/hw_udma.h"
#include "inc/hw_ints.h"
#include "inc/hw_nvic.h"
#include "inc/hw_uart.h"
#include "driverlib/debug.h"
#include "driverlib/udma.h"
#include "driverlib/gpio.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/interrupt.h"
#include "driverlib/pin_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/ssi.h"
#include "driverlib/uart.h"
#include "utils/uartstdio.h"

//*****************************************************************************
//
// Number of bytes to send and receive.
//
//*****************************************************************************
#define NUM_SSI_DATA            3
volatile uint32_t 	gui32SysClockFreq;
volatile bool		gbComplete;

//*****************************************************************************
//
// The control table used by the uDMA controller.  This table must be aligned
// to a 1024 byte boundary.
//
//*****************************************************************************
#if defined(ewarm)
#pragma data_alignment=1024
uint8_t pui8ControlTable[1024];
#elif defined(ccs)
#pragma DATA_ALIGN(pui8ControlTable, 1024)
uint8_t pui8ControlTable[1024];
#else
uint8_t pui8ControlTable[1024] __attribute__ ((aligned(1024)));
#endif

//*****************************************************************************
//
// This function sets up UART0 to be used for a console to display information
// as the example is running.
//
//*****************************************************************************
void
InitConsole(void)
{
    //
    // Enable GPIO port A which is used for UART0 pins.
    // TODO: change this to whichever GPIO port you are using.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    //
    // Configure the pin muxing for UART0 functions on port A0 and A1.
    // This step is not necessary if your part does not support pin muxing.
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinConfigure(GPIO_PA0_U0RX);
    GPIOPinConfigure(GPIO_PA1_U0TX);

    //
    // Enable UART0 so that we can configure the clock.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);

    //
    // Use the internal 16MHz oscillator as the UART clock source.
    //
    UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);

    //
    // Select the alternate (UART) function for these pins.
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

    //
    // Initialize the UART for console I/O.
    //
    UARTStdioConfig(0, 115200, 16000000);
}

//
// AKA: Added the new function for uDMA Initialization
//
void UDMA_Init(void)
{
    //
    // Enable the uDMA controller.
    //
    uDMAEnable();

    //
    // Point at the control table to use for channel control structures.
    //
    uDMAControlBaseSet(pui8ControlTable);

}

void SSI_Init(void)
{
	SSIConfigSetExpClk( SSI3_BASE,
						gui32SysClockFreq,
						SSI_FRF_MOTO_MODE_0,
						SSI_MODE_MASTER,
						10000000,
						8);

	//Enable the SSI
	SSIEnable(SSI3_BASE);

	// disable all interrupt
	SSIIntDisable(SSI3_BASE,SSI_TXEOT |	//transmit fifo is empty
							SSI_DMATX |	//DMA Transmit complete
							SSI_DMARX |	//DMA Receive complete
							SSI_TXFF  |	//TX FIFO half full or less
							SSI_RXFF  |	//RX FIFO half full or more
							SSI_RXTO  |	//rx timeout
							SSI_RXOR);	//rx error

	// AKA : Enable the Interrupt from the SSI-3 to be registered by NVIC
	IntEnable(INT_SSI3);
}

void SSIFlash_uDMA_Start(void){

	// Configure the DMA channel
	uDMAChannelAssign(UDMA_CH14_SSI3RX);
	uDMAChannelAssign(UDMA_CH15_SSI3TX);
	uDMAChannelAttributeDisable(UDMA_CH14_SSI3RX,
								UDMA_ATTR_ALL);
	uDMAChannelAttributeDisable(UDMA_CH15_SSI3TX,
								UDMA_ATTR_ALL);

	// Disable the transmit uDMA channel
	uDMAChannelDisable(UDMA_CH14_SSI3RX);
	uDMAChannelDisable(UDMA_CH15_SSI3TX);

	// Configure the DMA TX channel
	uDMAChannelAttributeEnable(	UDMA_CH14_SSI3RX,
								UDMA_ATTR_USEBURST);
	uDMAChannelControlSet(		UDMA_CH14_SSI3RX,
								UDMA_SIZE_8         |
								UDMA_SRC_INC_NONE   |
								UDMA_DST_INC_8      |
								UDMA_ARB_4);

	uDMAChannelAttributeEnable(	UDMA_CH15_SSI3TX,
								UDMA_ATTR_USEBURST);
	uDMAChannelControlSet(		UDMA_CH15_SSI3TX,
								UDMA_SIZE_8         |
								UDMA_SRC_INC_NONE   |
								UDMA_DST_INC_NONE   |
								UDMA_ARB_4);
}

void SSIFlash_uDMA_Read(uint32_t u32Address, uint32_t u32Length, uint8_t *pu8Dst)
{
	uint8_t u8Tmp = 0;

	// Enable uDMA transmit complete
	SSIIntClear (SSI3_BASE, SSI_DMATX | SSI_DMARX);
	SSIIntEnable(SSI3_BASE, SSI_DMATX | SSI_DMARX);

	//start uDMA
	uDMAChannelTransferSet(	UDMA_CH14_SSI3RX,
							UDMA_MODE_BASIC,
							(void *)(SSI0_BASE + SSI_O_DR),
							(void *)pu8Dst,
							u32Length);
	uDMAChannelTransferSet(	UDMA_CH15_SSI3TX,
							UDMA_MODE_BASIC,
							(void *) &u8Tmp,
							(void *)(SSI0_BASE + SSI_O_DR),
							u32Length);
	uDMAChannelEnable(UDMA_CH15_SSI3TX | UDMA_CH14_SSI3RX);
	// AKA
	SSIDMAEnable(SSI3_BASE, SSI_DMA_TX | SSI_DMA_RX);
}
void SSIFlash_uDMA_Write(uint32_t u32Address, uint32_t u32Length, uint8_t *pu8Src)
{

	// Enable uDMA transmit complete
	SSIIntClear (SSI3_BASE, SSI_DMATX);
	SSIIntEnable(SSI3_BASE, SSI_DMATX);

	//start uDMA
	uDMAChannelTransferSet(	UDMA_CH15_SSI3TX,
							UDMA_MODE_BASIC,
							(void *) pu8Src,
							(void *)(SSI3_BASE + SSI_O_DR),
							u32Length);
	uDMAChannelEnable(UDMA_CH15_SSI3TX);
	// AKA
	SSIDMAEnable(SSI3_BASE, SSI_DMA_TX);
}

void SSIFlash_uDMA_Stop(void){

	//disable uDMA
	SSIDMADisable(SSI3_BASE,SSI_DMA_TX |
							SSI_DMA_RX);
	//disable interrupt
	SSIIntDisable(SSI3_BASE,SSI_TXEOT |	//transmit fifo is empty
							SSI_DMATX |	//DMA Transmit complete
							SSI_DMARX |	//DMA Receive complete
							SSI_TXFF  |	//TX FIFO half full or less
							SSI_RXFF  |	//RX FIFO half full or more
							SSI_RXTO  |	//rx timeout
							SSI_RXOR);	//rx error
}

void GPIO_Init(void) {

    //--------------------------------------------------------------------
    //Enable the clock to the appropriate GPIO modulel
    //--------------------------------------------------------------------
	SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
	SysCtlPeripheralReset(SYSCTL_PERIPH_UDMA);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI3);		//Flash2
	SysCtlPeripheralReset(SYSCTL_PERIPH_SSI3);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
	SysCtlPeripheralReset(SYSCTL_PERIPH_GPIOC);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
	SysCtlPeripheralReset(SYSCTL_PERIPH_GPIOF);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOQ);
	SysCtlPeripheralReset(SYSCTL_PERIPH_GPIOQ);


	//---------------------------------------------------------------
	//configure JTAG pins
	//---------------------------------------------------------------
	GPIOPinConfigure(GPIO_PC0_TCK);	 					//JTAG TCK
	GPIOPinConfigure(GPIO_PC1_TMS);	 					//JTAG TMS
	GPIOPinConfigure(GPIO_PC2_TDI);	 					//JTAG TDI
	GPIOPinConfigure(GPIO_PC3_TDO);	 					//JTAG TDO

	//---------------------------------------------------------------
	//configure the SSI pins for Flash2(SSI3)
	//---------------------------------------------------------------
	GPIOPinConfigure(GPIO_PQ0_SSI3CLK);  				//SSI3 CLK	(Flash 2)
	GPIOPinConfigure(GPIO_PQ2_SSI3XDAT0);				//SSI3 RX
	GPIOPinConfigure(GPIO_PF0_SSI3XDAT1);				//SSI3 TX

	//---------------------------------------------------------------
	//for each function pin, select the type
	//---------------------------------------------------------------
	GPIOPinTypeSSI(GPIO_PORTQ_BASE, GPIO_PIN_0 |		//SSI3 Flash2 CLK
									GPIO_PIN_2);		//SSI3 Flash2 RX
	GPIOPinTypeSSI(GPIO_PORTF_BASE,	GPIO_PIN_0);		//SSI3 Flash2 TX

	//---------------------------------------------------------------
	//for each output GPIO pin, select the type
	//---------------------------------------------------------------
	GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_5 |	//SSI3 FLASH2 HOLD
										   GPIO_PIN_4);	//SSI3 FLASH2 WP
	GPIOPinTypeGPIOOutput(GPIO_PORTQ_BASE, GPIO_PIN_1);	//SSI3 FLASH2 CS
}

void SSI3_Handler(void) {

	uint32_t IntStatus;
	uint32_t uDMAModeRx;
	uint32_t uDMAModeTx;

	// Read and Clear the interrupt.
	IntStatus = SSIIntStatus(SSI3_BASE, true);
	SSIIntClear(SSI3_BASE, IntStatus);

	//check for Rx done
	if(IntStatus & SSI_DMARX) {
		uDMAModeRx = uDMAChannelModeGet(UDMA_CH14_SSI3RX);

		//clear the uDMA Transmit complete bit
		SSIDMADisable(SSI3_BASE, SSI_DMA_TX | SSI_DMA_RX);
		SSIIntClear(  SSI3_BASE, SSI_DMATX  | SSI_DMARX);

		if(uDMAModeRx == UDMA_MODE_STOP) {
			//signal to the upper layer that the transmfer has finished
			gbComplete = true;
		}
	}
	//check for Tx done
	else if(IntStatus & SSI_DMATX) {
		uDMAModeTx = uDMAChannelModeGet(UDMA_CH15_SSI3TX);

		//clear the uDMA Transmit complete bit
		SSIDMADisable(SSI3_BASE, SSI_DMA_TX);
		SSIIntClear(  SSI3_BASE, SSI_DMATX);

		if(uDMAModeTx == UDMA_MODE_STOP) {
			//signal to the upper layer that the transmfer has finished
			gbComplete = true;
		}
	}
}

//*****************************************************************************
//
// Configure SSI3 in master Freescale (SPI) mode.  This example will send out
// 3 bytes of data, then wait for 3 bytes of data to come in.  This will all be
// done using the polling method.
//
//*****************************************************************************
int
main(void)
{
	uint8_t i;
	uint8_t ui8TxBuffer[16];
	uint8_t ui8RxBuffer[16];

    //
    // Set the clocking to run directly from the external crystal/oscillator.
    // TODO: The SYSCTL_XTAL_ value must be changed to match the value of the
    // crystal on your board.
    //
    gui32SysClockFreq = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                            SYSCTL_OSC_MAIN |
                                            SYSCTL_USE_PLL |
                                            SYSCTL_CFG_VCO_480), 120000000);
    //
    // Set up the serial console to use for displaying messages.  This is
    // just for this example program and is not needed for SSI operation.
    //
    InitConsole();

    //
    // Display the setup on the console.
    //
    UARTprintf("SSI ->\n");
    UARTprintf("  Mode: SPI\n");
    UARTprintf("  Data: 8-bit\n\n");

    //
    // Initialize GPIO, SSI and UDMA Clocks
    //
    GPIO_Init();
    SSI_Init();
    UDMA_Init();
    SSIFlash_uDMA_Start();

    while(1)
    {
		//set the tx and rx buffers
		for(i = 0; i < 16; i++) {
			ui8TxBuffer[i] = rand();
			ui8RxBuffer[i] = 0xBA;
		}

		//write the data and then read back
        gbComplete = false;
        SSIFlash_uDMA_Write(0x0,16,&ui8TxBuffer);
        SSIFlash_uDMA_Write(0x0,16,&ui8RxBuffer);
    	while(!gbComplete);
    	SSIFlash_uDMA_Stop();

		//compare the tx and rx data
    	for(i = 0; i < 16; i++)
    		if(ui8TxBuffer[i] != ui8RxBuffer[i])
    			UARTprintf("Error reading data from Flash\n");
    }

    //
    // Loop Here
    //
    while(1);
}
