#include <common.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <verify.h>
#include <interrupt.h>
#include <soc_C6748.h>
#include <hw_types.h>
#include <hw_syscfg0_C6748.h>
#include <spi.h>
#include <gpio.h>
#include <edma.h>
#include <edma_event.h>
#include <hw_edma3cc.h>
#include <hw_edma3tc.h>

#include "debug.h"
#include "frame.h"
#include "msgb.h"
#include "core.h"
#include "cirbuf.h"
#include "tran_spi.h"
#include "task.h"

/************************************************************************/
/*                                                                      */
/************************************************************************/
static comu_spi_t *cur_comu_spi = NULL;
static volatile unsigned char state = 0;

#ifdef USE_SPI_EDMA
static void (*cb_Fxn[EDMA3_NUM_TCC])(unsigned int tcc, unsigned int status);
static unsigned short rx_len;
static volatile unsigned char dummy = 0;
static volatile unsigned char dma_rx[MAX_PACK_LEN];
#endif

/************************************************************************/
/*                                                                      */
/************************************************************************/
static void spi_send_io_init(void)
{
     unsigned int savePinmux = 0;

     /*
     ** Clearing the bit in context and retaining the other bit values
     ** in PINMUX10 register.
     */
     savePinmux = (HWREG(SOC_SYSCFG_0_REGS + SYSCFG0_PINMUX(3)) &
                  ~(SYSCFG_PINMUX3_PINMUX3_27_24));

     /* Setting the pins corresponding to GP4[0] in PINMUX10 register.*/
     HWREG(SOC_SYSCFG_0_REGS + SYSCFG0_PINMUX(3)) =
          (PINMUX3_GPIO8_2_ENABLE | savePinmux);

	GPIODirModeSet(SOC_GPIO_0_REGS, SPI_SLAVE_SEND_REQGPIO, GPIO_DIR_OUTPUT);
	GPIOPinWrite(SOC_GPIO_0_REGS, SPI_SLAVE_SEND_REQGPIO, GPIO_PIN_HIGH);
}

static void spi_send_ready(void)
{
	GPIOPinWrite(SOC_GPIO_0_REGS, SPI_SLAVE_SEND_REQGPIO, GPIO_PIN_LOW);
}

static void spi_send_stop(void)
{
	GPIOPinWrite(SOC_GPIO_0_REGS, SPI_SLAVE_SEND_REQGPIO, GPIO_PIN_HIGH);
}

/************************************************************************/
/*                                                                      */
/************************************************************************/
/*
** This function allocates EDMA3 channels to SPI0 for trasmisssion and
** reception purposes.
*/
static void spi_edma_ch_req(void)
{
    /* Request DMA Channel and TCC for SPI Transmit*/
    EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, \
                        EDMA3_CHA_SPI0_TX, EDMA3_CHA_SPI0_TX, 0);

    /* Request DMA Channel and TCC for SPI Receive*/
    EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, \
                        EDMA3_CHA_SPI0_RX, EDMA3_CHA_SPI0_RX, 0);
}

/*
** This function is used to set the PaRAM entries of EDMA3 for the Transmit
** Channel of SPI0. The corresponding EDMA3 channel is also enabled for
** transmission.
*/
static void spi_txedma_par_set(unsigned int tcc, unsigned int ch, volatile unsigned char *buf, unsigned int len, unsigned int sbflag)
{
    EDMA3CCPaRAMEntry paramset;

    /* srcAddr holds address of memory location buffer. */
    paramset.srcAddr = (unsigned int)buf;

    /* destAddr holds address of SPIDAT1 register. */
    paramset.destAddr = (unsigned int)(SOC_SPI_0_REGS + SPI_SPIDAT1);

    /* aCnt holds the number of bytes in an array. */
    paramset.aCnt = (unsigned short)1;

    /* bCnt holds the number of such arrays to be transferred. */
    paramset.bCnt = (unsigned short)len;

    /* cCnt holds the number of frames of aCnt*bBcnt bytes to be transferred. */
    paramset.cCnt = (unsigned short)1;

    /*
    ** The srcBidx should be incremented by aCnt number of bytes since the
    ** source used here is  memory.
    */
    paramset.destBIdx = 0;
    if(TRUE == sbflag)
    {
       paramset.srcBIdx = (short)1;
    }
    else
    {
    	paramset.srcBIdx = (short)0;
    }

    /* A sync Transfer Mode is set in OPT.*/
    /* srCIdx and destCIdx set to zero since ASYNC Mode is used. */
    paramset.srcCIdx = (short)0;
    paramset.destCIdx = 0;

    /* Linking transfers in EDMA3 are not used. */
    paramset.linkAddr = (unsigned short)0xFFFF;
    paramset.bCntReload = (unsigned short)0;

    paramset.opt = 0;
    /* Set TCC field in OPT with the tcc. */
    paramset.opt |= ((tcc << EDMA3CC_OPT_TCC_SHIFT) & EDMA3CC_OPT_TCC);

    /* EDMA3 Interrupt is enabled and Intermediate Interrupt Disabled.*/
    paramset.opt |= (1 << EDMA3CC_OPT_TCINTEN_SHIFT);

    paramset.rsvd = 0;
    /* Now write the PaRam Set to EDMA3.*/
    EDMA3SetPaRAM(SOC_EDMA30CC_0_REGS, ch, &paramset);

    /* EDMA3 Transfer is Enabled. */
    EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, ch, EDMA3_TRIG_MODE_EVENT);
}

/*
** This function is used to set the PaRAM entries of EDMA3 for the Receive
** Channel of SPI0. The corresponding EDMA3 channel is also enabled for
** reception.
*/
static void spi_rxedma_par_set(unsigned int tcc, unsigned int ch, volatile unsigned char *buf, unsigned int len, unsigned int dbflag)
{
    EDMA3CCPaRAMEntry paramset;

    /* srcAddr holds address of SPI Rx FIFO. */
     paramset.srcAddr = (unsigned int)(SOC_SPI_0_REGS + SPI_SPIBUF);

    /* destAddr is address of memory location named buffer. */
     paramset.destAddr = (unsigned int)buf;

    /* aCnt holds the number of bytes in an array. */
     paramset.aCnt = (unsigned short)1;

    /* bCnt holds the number of such arrays to be transferred. */
     paramset.bCnt = (unsigned short)len;

    /* cCnt holds the number of frames of aCnt*bBcnt bytes to be transferred. */
     paramset.cCnt = (unsigned short)1;

    /* The srcBidx should not be incremented since it is a h/w register. */
     paramset.srcBIdx = 0;

    if(TRUE == dbflag)
    {
        /* The destBidx should be incremented for every byte. */
    	paramset.destBIdx = 1;
    }
    else
    {
        /* The destBidx should not be incremented. */
    	paramset.destBIdx = 0;
    }

    /* A sync Transfer Mode. */
    /* srCIdx and destCIdx set to zero since ASYNC Mode is used. */
    paramset.srcCIdx = 0;
    paramset.destCIdx = 0;

    /* Linking transfers in EDMA3 are not used. */
    paramset.linkAddr = (unsigned short)0xFFFF;
    paramset.bCntReload = 0;

    paramset.opt = 0;
    /* Set TCC field in OPT with the tcc. */
    paramset.opt |= ((tcc << EDMA3CC_OPT_TCC_SHIFT) & EDMA3CC_OPT_TCC);

    /* EDMA3 Interrupt is enabled and Intermediate Interrupt Disabled.*/
    paramset.opt |= (1 << EDMA3CC_OPT_TCINTEN_SHIFT);

    paramset.rsvd = 0;
    /* Now write the PaRam Set to EDMA3.*/
    EDMA3SetPaRAM(SOC_EDMA30CC_0_REGS, ch, &paramset);

    /* EDMA3 Transfer is Enabled. */
    EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, ch, EDMA3_TRIG_MODE_EVENT);
}

/*
** EDMA3 completion Interrupt Service Routine(ISR).
*/

static void spi_edma3_com_isr(void)
{
    volatile unsigned int pendingIrqs;
    volatile unsigned int isipr = 0;
    volatile unsigned int indexl = 1;
    volatile unsigned int Cnt = 0;

    IntEventClear(SYS_INT_EDMA3_0_CC0_INT1);

    isipr = EDMA3GetIntrStatus(SOC_EDMA30CC_0_REGS);
    if(isipr)
    {
        while ((Cnt < EDMA3CC_COMPL_HANDLER_RETRY_COUNT)&& (indexl != 0))
        {
            indexl = 0;
            pendingIrqs = EDMA3GetIntrStatus(SOC_EDMA30CC_0_REGS);
            while (pendingIrqs)
            {
                if((pendingIrqs & 1) == TRUE)
                {
                    /* Here write to ICR to clear the corresponding IPR bits. */
                    EDMA3ClrIntr(SOC_EDMA30CC_0_REGS, indexl);
                    (*cb_Fxn[indexl])(indexl, EDMA3_XFER_COMPLETE);
                }
                ++indexl;
                pendingIrqs >>= 1;
            }
            Cnt++;
        }
    }
}

/*
** EDMA3 Error Interrupt Service Routine(ISR).
*/

static void spi_edma3_err_isr(void)
{
	volatile unsigned int pendingIrqs;
	unsigned int index;
	unsigned int Cnt = 0u;
	unsigned int evtqueNum = 0;  /* Event Queue Num */

	pendingIrqs = 0u;
	index = 1u;

	IntEventClear(SYS_INT_EDMA3_0_CC0_ERRINT);

	DL_ERR("edma err: emr=0x%08x qemr=0x%08x ccerr=0x%08x val=0x%08x errstat=0x%08x errdet=0x%08x errcmd=0x%08x\n", HWREG(SOC_EDMA30CC_0_REGS + EDMA3CC_EMR), \
			HWREG(SOC_EDMA30CC_0_REGS + EDMA3CC_QEMR), HWREG(SOC_EDMA30CC_0_REGS + EDMA3CC_CCERR), HWREG(SOC_EDMA30CC_0_REGS + EDMA3CC_EEVAL), \
			HWREG(SOC_EDMA30TC_0_REGS+EDMA3TC_ERRSTAT), HWREG(SOC_EDMA30TC_0_REGS+EDMA3TC_ERRDET), HWREG(SOC_EDMA30TC_0_REGS+EDMA3TC_ERRCMD));

	if((EDMA3GetErrIntrStatus(SOC_EDMA30CC_0_REGS) != 0 )
		|| (EDMA3QdmaGetErrIntrStatus(SOC_EDMA30CC_0_REGS) != 0)
		|| (EDMA3GetCCErrStatus(SOC_EDMA30CC_0_REGS) != 0))
	{
		/* Loop for EDMA3CC_ERR_HANDLER_RETRY_COUNT number of time,
		 * breaks when no pending interrupt is found
		 */
		while ((Cnt < EDMA3CC_ERR_HANDLER_RETRY_COUNT)
					&& (index != 0u))
		{
			index = 0u;
			pendingIrqs = EDMA3GetErrIntrStatus(SOC_EDMA30CC_0_REGS);
			while (pendingIrqs)
			{
				/*Process all the pending interrupts*/
				if(TRUE == (pendingIrqs & 1u))
				{
				/* Write to EMCR to clear the corresponding EMR bits. */
				/*Clear any SER*/
				EDMA3ClrMissEvt(SOC_EDMA30CC_0_REGS, index);
				}
				++index;
				pendingIrqs >>= 1u;
			}

			index = 0u;
			pendingIrqs = EDMA3QdmaGetErrIntrStatus(SOC_EDMA30CC_0_REGS);
			while (pendingIrqs)
			{
				/*Process all the pending interrupts*/
				if(TRUE == (pendingIrqs & 1u))
				{
					/* Here write to QEMCR to clear the corresponding QEMR bits*/
					/*Clear any QSER*/
					EDMA3QdmaClrMissEvt(SOC_EDMA30CC_0_REGS, index);
				}
				++index;
				pendingIrqs >>= 1u;
			}

			index = 0u;
			pendingIrqs = EDMA3GetCCErrStatus(SOC_EDMA30CC_0_REGS);
			if (pendingIrqs != 0u)
			{
			/* Process all the pending CC error interrupts. */
			/* Queue threshold error for different event queues.*/
			for (evtqueNum = 0u; evtqueNum < SOC_EDMA3_NUM_EVQUE; evtqueNum++)
			{
				if((pendingIrqs & (1u << evtqueNum)) != 0u)
				{
						/* Clear the error interrupt. */
						EDMA3ClrCCErr(SOC_EDMA30CC_0_REGS, (1u << evtqueNum));
				}
			}

			/* Transfer completion code error. */
			if ((pendingIrqs & (1 << EDMA3CC_CCERR_TCCERR_SHIFT)) != 0u)
			{
				EDMA3ClrCCErr(SOC_EDMA30CC_0_REGS, (0x01u << EDMA3CC_CCERR_TCCERR_SHIFT));
			}
			++index;
			}
			Cnt++;
		}
	}
}

static void spi_edma_init(void)
{
	IntRegister(C674X_MASK_INT4, spi_edma3_com_isr);
	IntRegister(C674X_MASK_INT5, spi_edma3_err_isr);

	IntEventMap(C674X_MASK_INT4, SYS_INT_EDMA3_0_CC0_INT1);
	IntEventMap(C674X_MASK_INT5, SYS_INT_EDMA3_0_CC0_ERRINT);

	IntEnable(C674X_MASK_INT4);
	IntEnable(C674X_MASK_INT5);
}

static void spi_edma_tx(unsigned char *buf, unsigned int len)
{
	unsigned char *tx_buf;
	unsigned int sbflag;

	if(buf == NULL)
	{
		dummy = 0x00;
		tx_buf = (unsigned char *)&dummy;
		sbflag = FALSE;
	}
	else
	{
		tx_buf = buf;
		sbflag = TRUE;
	}

	spi_txedma_par_set(EDMA3_CHA_SPI0_TX, EDMA3_CHA_SPI0_TX, tx_buf, len, sbflag);
}

static void spi_edma_rx(unsigned char *buf, unsigned int len)
{
	unsigned char *rx_buf;
	unsigned int dbflag;

	if(buf == NULL)
	{
		rx_buf = (unsigned char *)&dummy;
		dbflag = FALSE;
	}
	else
	{
		rx_buf = buf;
		dbflag = TRUE;
	}

	/* Configure the PaRAM registers in EDMA for Reception.*/
	spi_rxedma_par_set(EDMA3_CHA_SPI0_RX, EDMA3_CHA_SPI0_RX, rx_buf, len, dbflag);
}

static void spi_edma_start(struct spi *spi)
{
    /* Enable SPI controller to generate DMA events */
	spi_int_enable(spi, SPI_DMA_REQUEST_ENA_INT);
}

static void spi_edma_stop(struct spi *spi)
{
    /* Enable SPI controller to generate DMA events */
	spi_int_disable(spi, SPI_DMA_REQUEST_ENA_INT);
}

/*
** This function is used as a callback from EDMA3 Completion Handler.
*/
static void spi_edma_callback(unsigned int tccnum, unsigned int status)
{

	if(tccnum == EDMA3_CHA_SPI0_TX)
    {
		EDMA3DisableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_TX, EDMA3_TRIG_MODE_EVENT);
		spi_edma_rx((unsigned char *)dma_rx, 8);

    }
    else if(tccnum == EDMA3_CHA_SPI0_RX)
    {
    	
    	EDMA3DisableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_RX, EDMA3_TRIG_MODE_EVENT);
		spi_edma_tx(data, 8);


    }
}


/************************************************************************/
/*                                                                      */
/************************************************************************/
static int spi_init(struct spi *spi)
{
	unsigned int  val = SIMO_SOMI_CLK_CS;
	unsigned char cs_set;
	unsigned char dcs_set;

	dcs_set = cs_set = (1 << spi->cs);

	/* Performing the Pin Multiplexing for SPI. */
	SPIPinMuxSetup(spi->bus);
	SPI0CSPinMuxSetup(spi->cs);

	SPIReset(spi->baseadd);
	SPIOutOfReset(spi->baseadd);

	switch(spi->mode)
	{
	case SPI_MASTER_MODE:
	{
		SPIModeConfigure(spi->baseadd, SPI_MASTER_MODE);
		SPIClkConfigure(spi->baseadd, C6748_SPI_PLL_IN, spi->rate, spi->data_fmt);
	}
	break;
	case SPI_SLAVE_MODE:
	{
		SPIModeConfigure(spi->baseadd, SPI_SLAVE_MODE);

		SPIModeConfigure(spi->baseadd, SPI_SPIGCR1_LOOPBACK);
	}
	break;
	default:
		ERR("Unknown SPI mode\n");
		return -1;
	}

	SPIPinControl(spi->baseadd, spi->bus, 0, &val);

	SPIDefaultCSSet(spi->baseadd, dcs_set);

    /* Configures the polarity and phase of SPI clock */
    SPIConfigClkFormat(spi->baseadd, (SPI_CLK_POL_HIGH | SPI_CLK_INPHASE), spi->data_fmt);

    /* Configures SPI to transmit MSB bit First during data transfer */
    SPIShiftMsbFirst(spi->baseadd, spi->data_fmt);

    /* Sets the Charcter length */
    SPICharLengthSet(spi->baseadd, C6748_TRN_BITLEN, spi->data_fmt);

	 /* Selects the SPI Data format register to used and Sets CSHOLD
	  * to assert CS pin(line)
	  */
	SPIDat1Config(spi->baseadd, (SPI_CSHOLD | spi->data_fmt), cs_set);

	/* Enable SPI communication */
	SPIEnable(spi->baseadd);

	return 0;
}

struct spi *spi_setup(unsigned int bus, unsigned int cs, unsigned int mode, void (*userisr)(void))
{
	struct spi *spim;

	spim = (struct spi *)malloc(sizeof(struct spi));
	if (!spim)
		return NULL;

	switch (bus)
	{
#ifdef SOC_SPI_0_REGS
	case 0:
	{
		spim->baseadd = SOC_SPI_0_REGS;
		spim->sys_int = SYS_INT_SPI0_INT;
	}
	break;
#endif
#ifdef SOC_SPI_1_REGS
	case 1:
	{
		spim->baseadd = SOC_SPI_1_REGS;
		spim->sys_int = SYS_INT_SPI1_INT;
	}
	break;
#endif
	default:
		goto err;
	}

	spim->bus      = bus;
	spim->cs       = cs;
	spim->data_fmt = mode;
	spim->mode     = SPI_SLAVE_MODE;

	if(spi_init(spim) < 0)
	{
		ERR("SPI init failed!\n");
		goto err;
	}
	if(userisr != NULL)
	{
		spi_int_init(spim, userisr);
	}

	return spim;

err:
   free(spim);
   return NULL;
}


static struct spi *spi_bus_init(void)
{
	struct spi *spi;

	spi = spi_setup(DEFAULT_SPI_BUS, DEFAULT_SPI_CS, DEFAULT_SPI_MODE, NULL);
	if (spi == NULL)
	{
		DL_ERR("Init spi failed!\n");

		return NULL;
	}

	spi_edma_init();
	spi_edma_ch_req();

	/* Registering Callback Function for Transmission. */
	cb_Fxn[EDMA3_CHA_SPI0_TX] = &spi_edma_callback;
	/* Registering Callback Function for Reception. */
	cb_Fxn[EDMA3_CHA_SPI0_RX] = &spi_edma_callback;

	spi_edma_rx((unsigned char *)dma_rx, 8);

	spi_edma_start(spi);

	return spi;
}

