/*
 * cc_pal.c - CC32xx Host Driver Implementation
 *
 * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com/
 *
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/******************************************************************************
 * 	cc_pal.c
 *
 *   SimpleLink Wi-Fi abstraction file for CC3220
 ******************************************************************************/

/* Board includes */
#include <ti/drivers/net/wifi/simplelink.h>
#include <ti/drivers/net/wifi/porting/cc_pal.h>
#include <ti/drivers/dpl/HwiP.h>
#include <ti/drivers/SPI.h>

#include <ti/devices/cc32xx/inc/hw_ints.h>
#include <ti/devices/cc32xx/inc/hw_udma.h>
#include <ti/devices/cc32xx/inc/hw_types.h>
#include <ti/devices/cc32xx/inc/hw_memmap.h>
#include <ti/devices/cc32xx/inc/hw_mcspi.h>
#include <ti/devices/cc32xx/inc/hw_common_reg.h>
#include <ti/devices/cc32xx/inc/hw_ocp_shared.h>
#include <ti/devices/cc32xx/inc/hw_apps_rcm.h>
#include <ti/devices/cc32xx/inc/hw_gprcm.h>
#include <ti/devices/cc32xx/inc/hw_hib1p2.h>
#include "ti/devices/cc32xx/driverlib/rom.h"
#include "ti/devices/cc32xx/driverlib/rom_map.h"
#include <ti/devices/cc32xx/driverlib/interrupt.h>
#include <ti/devices/cc32xx/driverlib/prcm.h>
#include <ti/devices/cc32xx/driverlib/spi.h>
#include <ti/devices/cc32xx/driverlib/timer.h>
#include <ti/devices/cc32xx/driverlib/udma.h>
#include <ti/drivers/net/wifi/source/driver.h>
#include <ti/drivers/Power.h>
#include <ti/drivers/power/PowerCC32XX.h>

#ifdef USE_FREERTOS
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#endif

/* NWP_SPARE_REG_5 - (OCP_SHARED_BASE + OCP_SHARED_O_SPARE_REG_5)
	- Bits 31:02 - Reserved
	- Bits 01	 - SLSTOP1 - NWP in Reset, Power Domain Down
	- Bits 00    - Reserved
 */
#define NWP_SPARE_REG_5	                (OCP_SHARED_BASE + OCP_SHARED_O_SPARE_REG_5)
#define NWP_SPARE_REG_5_SLSTOP          (0x00000002)

/* ANA_DCDC_PARAMS0 - (HIB1P2_BASE + HIB1P2_O_ANA_DCDC_PARAMETERS0)
	- Bits 31:28 - Reserved
	- Bits 27    - Override PWM mode (==> PFM)
	- Bits 26:00 - Reserved
 */
#define ANA_DCDC_PARAMS0                (HIB1P2_BASE + HIB1P2_O_ANA_DCDC_PARAMETERS0)
#define ANA_DCDC_PARAMS0_PWMOVERRIDE    (0x08000000)

/* WAKENWP - (ARCM_BASE + APPS_RCM_O_APPS_TO_NWP_WAKE_REQUEST)
	- Bits 31:01 - Reserved
	- Bits 00    - Wake Request to NWP
 */
#define WAKENWP                         (ARCM_BASE + APPS_RCM_O_APPS_TO_NWP_WAKE_REQUEST)
#define WAKENWP_WAKEREQ                 (APPS_RCM_APPS_TO_NWP_WAKE_REQUEST_APPS_TO_NWP_WAKEUP_REQUEST)

/* NWP_PWR_STATE - (GPRCM_BASE + GPRCM_O_NWP_PWR_STATE)
	- Bits 31:12 - Reserved
	- Bits 11:08 - Active (0x3)
	- Bits 07:00 - Reserved
 */
#define NWP_PWR_STATE                   (GPRCM_BASE + GPRCM_O_NWP_PWR_STATE)
#define NWP_PWR_STATE_PWRMASK           (0x00000F00)
#define NWP_PWR_STATE_PWRACTIVE         (0x00000300)

/* NWP_LPDS_WAKEUPCFG - (GPRCM_BASE + GPRCM_O_NWP_LPDS_WAKEUP_CFG)
	- Bits 31:08 - Reserved
	- Bits 07:00 - WakeUp Config AppsToNwp Wake (0x20) - reset condition
 */
#define NWP_LPDS_WAKEUPCFG              (GPRCM_BASE + GPRCM_O_NWP_LPDS_WAKEUP_CFG)
#define NWP_LPDS_WAKEUPCFG_APPS2NWP     (0x00000020)
#define NWP_LPDS_WAKEUPCFG_TIMEOUT_MSEC (600)

/* N2A_INT_MASK_SET -                   (COMMON_REG_BASE + COMMON_REG_O_NW_INT_MASK_SET)  */
#define N2A_INT_MASK_SET                (COMMON_REG_BASE + COMMON_REG_O_NW_INT_MASK_SET)
/* N2A_INT_MASK_CLR -                   (COMMON_REG_BASE + COMMON_REG_O_NW_INT_MASK_CLR)  */
#define N2A_INT_MASK_CLR                (COMMON_REG_BASE + COMMON_REG_O_NW_INT_MASK_CLR)
/* N2A_INT_ACK -                        (COMMON_REG_BASE + COMMON_REG_O_NW_INT_ACK)       */
#define N2A_INT_ACK                     (COMMON_REG_BASE + COMMON_REG_O_NW_INT_ACK)
#define NWP_N2A_INT_ACK_TIMEOUT_MSEC    (3000)

/* A2N_INT_STS_CLR -                    (COMMON_REG_BASE + COMMON_REG_O_APPS_INT_STS_CLR)  */
#define A2N_INT_STS_CLR                 (COMMON_REG_BASE + COMMON_REG_O_APPS_INT_STS_CLR)
/* A2N_INT_TRIG -                       (COMMON_REG_BASE + COMMON_REG_O_APPS_INT_TRIG)     */
#define A2N_INT_TRIG                    (COMMON_REG_BASE + COMMON_REG_O_APPS_INT_TRIG)
/* A2N_INT_STS_RAW -                    (COMMON_REG_BASE + COMMON_REG_O_APPS_INT_STS_RAW)  */
#define A2N_INT_STS_RAW                 (COMMON_REG_BASE + COMMON_REG_O_APPS_INT_STS_RAW)

#define uSEC_DELAY(x)                   (ROM_UtilsDelayDirect(x*80/3))
#define MAX_DMA_RECV_TRANSACTION_SIZE   (4096)
#define SPI_RATE_20M                    (20000000)

HwiP_Handle g_intHandle = 0;

//****************************************************************************
//                          LOCAL FUNCTIONS
//****************************************************************************

Fd_t spi_Open_new(char *ifName, unsigned long flags)
{
    void *lspi_hndl;
    unsigned int lspi_index;
    SPI_Params SPI_Config;
    SPI_Params_init(&SPI_Config);

    /* configure the SPI settings */
    SPI_Config.transferMode = SPI_MODE_BLOCKING;
    SPI_Config.mode = SPI_MASTER;
    SPI_Config.bitRate = SPI_RATE_20M;
    SPI_Config.dataSize = 32;
    SPI_Config.frameFormat = SPI_POL0_PHA0;

    /* index of the link SPI initialization configuration in the SPI_Config table */
    lspi_index = 0;
    lspi_hndl = SPI_open(lspi_index, &SPI_Config);
    if(NULL == lspi_hndl)
    {
        return -1;
    }
    else
    {
        return (Fd_t)lspi_hndl;
    }
}


int spi_Close_new(Fd_t fd)
{
    SPI_close((void *)fd);
    return 0;
}

bool SPICC32XXDMA_transfer(SPI_Handle handle, SPI_Transaction *transaction);
bool SPICC32XXDMA_write32(SPI_Handle handle, SPI_Transaction *transaction);

int spi_Read_new(Fd_t fd, unsigned char *pBuff, int len)
{
    SPI_Transaction transact_details;
    int read_size = 0;

    /* check if the link SPI has been initialized successfully */
    if(fd < 0)
    {
        return -1;
    }

    transact_details.txBuf = NULL;
    transact_details.arg = NULL;
    while(len > 0)
    {
        /* DMA can transfer upto a maximum of 1024 words in one go. So, if
		   the data to be read is more than 1024 words, it will be done in
		   parts */
        /* length is received in bytes, should be specified in words for the
         * SPI driver.
         */
        if(len > MAX_DMA_RECV_TRANSACTION_SIZE)
        {
            transact_details.count = (MAX_DMA_RECV_TRANSACTION_SIZE +3)>>2;
            transact_details.rxBuf = (void*)(pBuff + read_size);
            if(SPICC32XXDMA_transfer((SPI_Handle)fd, &transact_details))//SPI_transfer((SPI_Handle)fd, &transact_details))
            {
                read_size += MAX_DMA_RECV_TRANSACTION_SIZE;
                len = len - MAX_DMA_RECV_TRANSACTION_SIZE;
            }
            else
            {
                return -1;
            }

        }
        else
        {
            transact_details.count = (len+3)>>2;
            transact_details.rxBuf = (void*)(pBuff + read_size);
            if(SPICC32XXDMA_transfer((SPI_Handle)fd, &transact_details))//SPI_transfer((SPI_Handle)fd, &transact_details))
            {
                read_size += len;
                len = 0;
                return read_size;
            }
            else
            {
                return -1;
            }
        }
    }

    return(read_size);
}


int spi_Write_new(Fd_t fd, unsigned char *pBuff, int len)
{
    SPI_Transaction transact_details;
    int write_size = 0;

    /* check if the link SPI has been initialized successfully */
    if(fd < 0)
    {
        return -1;
    }

    transact_details.rxBuf = NULL;

    while(len > 0)
    {
        /* configure the transaction details.
         * length is received in bytes, should be specified in words for the SPI
         * driver.
         */
        if(len > MAX_DMA_RECV_TRANSACTION_SIZE)
        {
            transact_details.count = (MAX_DMA_RECV_TRANSACTION_SIZE +3)>>2;
            transact_details.txBuf = (void*)(pBuff + write_size);
            if(SPICC32XXDMA_write32((SPI_Handle)fd, &transact_details))//SPI_transfer((SPI_Handle)fd, &transact_details))
            {
                write_size += MAX_DMA_RECV_TRANSACTION_SIZE;
                len = len - MAX_DMA_RECV_TRANSACTION_SIZE;
            }
            else
            {
                return -1;
            }
        }
        else
        {
            transact_details.count = (len+3)>>2;
            transact_details.txBuf = (void*)(pBuff + write_size);
            if(SPICC32XXDMA_write32((SPI_Handle)fd, &transact_details))//SPI_transfer((SPI_Handle)fd, &transact_details))
            {
                write_size += len;
                len = 0;
                return write_size;
            }
            else
            {
                return -1;
            }
        }
    }

    return(write_size);
}


int NwpRegisterInterruptHandler(P_EVENT_HANDLER InterruptHdl , void* pValue)
{

    HwiP_Params nwp_iParams;

    HwiP_Params_init(&nwp_iParams);

    HwiP_clearInterrupt(INT_NWPIC);

    if(!InterruptHdl)
    {
        HwiP_delete(g_intHandle);
        return OS_OK;
    }
    else
    {
        nwp_iParams.priority = INT_PRIORITY_LVL_1 ;

    }
    g_intHandle = HwiP_create(INT_NWPIC , (HwiP_Fxn)(InterruptHdl) , &nwp_iParams);

    if(!g_intHandle)
    {
        return -1;
    }
    else
    {
        return OS_OK ;
    }
}


void NwpMaskInterrupt()
{
    (*(unsigned long *)N2A_INT_MASK_SET) = 0x1;
}


void NwpUnMaskInterrupt()
{
    (*(unsigned long *)N2A_INT_MASK_CLR) = 0x1;
}


void NwpPowerOn(void)
{
    /* bring the 1.32 eco out of reset */
    HWREG(NWP_SPARE_REG_5) &= ~NWP_SPARE_REG_5_SLSTOP;

    /* Clear host IRQ indication */
    HWREG(N2A_INT_ACK) = 1;

    /* NWP Wake-up */
    HWREG(WAKENWP) = WAKENWP_WAKEREQ;

    //UnMask Host Interrupt
    NwpUnMaskInterrupt();
}


void NwpPowerOff(void)
{

    volatile unsigned long apps_int_sts_raw;
    volatile unsigned long sl_stop_ind       = HWREG(NWP_SPARE_REG_5);
    volatile unsigned long nwp_lpds_wake_cfg = HWREG(NWP_LPDS_WAKEUPCFG);
    _SlTimeoutParams_t SlTimeoutInfo         = {0};

    if((nwp_lpds_wake_cfg != NWP_LPDS_WAKEUPCFG_APPS2NWP) &&     /* Check for NWP POR condition - APPS2NWP is reset condition */
            !(sl_stop_ind & NWP_SPARE_REG_5_SLSTOP))         /* Check if sl_stop was executed */
    {
        HWREG(0xE000E104) = 0x200;               /* Enable the out of band interrupt, this is not a wake-up source*/
        HWREG(A2N_INT_TRIG) = 0x1;               /* Trigger out of band interrupt  */
        HWREG(WAKENWP) = WAKENWP_WAKEREQ;        /* Wake-up the NWP */

        _SlDrvStartMeasureTimeout(&SlTimeoutInfo, NWP_N2A_INT_ACK_TIMEOUT_MSEC);

        /* Wait for the A2N_INT_TRIG to be cleared by the NWP to indicate it's awake and ready for shutdown.
         * poll until APPs->NWP interrupt is cleared or timeout :
         * for service pack 3.1.99.1 or higher, this condition is fulfilled in less than 1 mSec.
         * Otherwise, in some cases it may require up to 3000 mSec of waiting.  */

        apps_int_sts_raw = HWREG(A2N_INT_STS_RAW);
        while(!(apps_int_sts_raw & 0x1))
        {
            if(_SlDrvIsTimeoutExpired(&SlTimeoutInfo))
            {
                break;
            }
            apps_int_sts_raw = HWREG(A2N_INT_STS_RAW);
        }

        WAIT_NWP_SHUTDOWN_READY;
    }

    /* Clear Out of band interrupt, Acked by the NWP */
    HWREG(A2N_INT_STS_CLR) = 0x1;

    /* Mask Host Interrupt */
    NwpMaskInterrupt();

    /* Switch to PFM Mode */
    HWREG(ANA_DCDC_PARAMS0) &= ~ANA_DCDC_PARAMS0_PWMOVERRIDE;

    /* sl_stop ECO for PG1.32 devices */
    HWREG(NWP_SPARE_REG_5) |= NWP_SPARE_REG_5_SLSTOP;

    /* Wait for 20 uSec, which is the minimal time between on-off cycle */
    uSEC_DELAY(20);
}


int Semaphore_create_handle(SemaphoreP_Handle* pSemHandle)
{
    SemaphoreP_Params params;

    SemaphoreP_Params_init(&params);

    params.mode = SemaphoreP_Mode_BINARY;

#ifndef SL_PLATFORM_MULTI_THREADED
    params.callback = tiDriverSpawnCallback;
#endif
    (*(pSemHandle)) = SemaphoreP_create(1, &params);

    if(!(*(pSemHandle)))
    {
        return SemaphoreP_FAILURE ;
    }

    return SemaphoreP_OK;
}

int SemaphoreP_delete_handle(SemaphoreP_Handle* pSemHandle)
{
    SemaphoreP_delete(*(pSemHandle));
    return SemaphoreP_OK;
}

int SemaphoreP_post_handle(SemaphoreP_Handle* pSemHandle)
{
    SemaphoreP_post(*(pSemHandle));
    return SemaphoreP_OK;
}


int Mutex_create_handle(MutexP_Handle* pMutexHandle)
{
    MutexP_Params params;

    MutexP_Params_init(&params);
#ifndef SL_PLATFORM_MULTI_THREADED
    params.callback = tiDriverSpawnCallback;
#endif

    (*(pMutexHandle)) = MutexP_create(&params);

    if(!(*(pMutexHandle)))
    {
        return MutexP_FAILURE ;
    }

    return MutexP_OK;
}

int  MutexP_delete_handle(MutexP_Handle* pMutexHandle)
{
    MutexP_delete(*(pMutexHandle));
    return(MutexP_OK);
}

int Mutex_unlock(MutexP_Handle pMutexHandle)
{
    MutexP_unlock(pMutexHandle, 0);
    return(MutexP_OK);
}


int Mutex_lock(MutexP_Handle pMutexHandle)
{
    MutexP_lock(pMutexHandle);
    return(MutexP_OK);
}


unsigned long TimerGetCurrentTimestamp()
{
    return (ClockP_getSystemTicks());
}


void NwpWaitForShutDownInd()
{
    volatile unsigned long nwp_wakup_ind = HWREG(NWP_LPDS_WAKEUPCFG);
    _SlTimeoutParams_t SlTimeoutInfo = {0};

    _SlDrvStartMeasureTimeout(&SlTimeoutInfo, NWP_LPDS_WAKEUPCFG_TIMEOUT_MSEC);

    while(nwp_wakup_ind != NWP_LPDS_WAKEUPCFG_APPS2NWP)
    {
        if(_SlDrvIsTimeoutExpired(&SlTimeoutInfo))
        {
            return;
        }
        nwp_wakup_ind = HWREG(NWP_LPDS_WAKEUPCFG);
    }

    return ;
}

/*!
    \brief  DMA SPI interrupt handler

    \param          None

    \return         None

    \note           This function
                       1. Invoked when SPI Transaction Completes
    \warning
 */

#define DMA_BUFF_SIZE_MIN               100
#define MAX_NUM_CH                      64  //32*2 entries
#define UNUSED(x)                       x=x

//
// GLOBAL VARIABLES -- Start
//
volatile Fd_t g_SpiFd =0;
P_EVENT_HANDLER g_pHostIntHdl  = NULL;
unsigned long g_ucDinDout[20];
unsigned char g_ucDMAEnabled = 0;
SemaphoreHandle_t DMAMsgQ;
#if defined(SL_PLATFORM_MULTI_THREADED)

char g_cDummy[4];
#else //SL_PLATFORM_MULTI_THREADED
volatile char g_cDummy;
#endif //SL_PLATFORM_MULTI_THREADED

/*!
     \brief     Does the actual Memory transfer

     \param ulChannel - the DMA channel to be selected

     \param ulMode

     \param ulItemCount

     \param ulItemSize

     \param ulArbSize

     \param pvSrcBuf

     \param ulSrcInc

     \param pvDstBuf

     \param ulDstInc

     \return None.
 */
static void cc_SetupTransfer(
        unsigned long ulChannel,
        unsigned long ulMode,
        unsigned long ulItemCount,
        unsigned long ulItemSize,
        unsigned long ulArbSize,
        void *pvSrcBuf,
        unsigned long ulSrcInc,
        void *pvDstBuf,
        unsigned long ulDstInc)
{

    MAP_uDMAChannelControlSet(ulChannel,
            ulItemSize | ulSrcInc | ulDstInc | ulArbSize);

    MAP_uDMAChannelAttributeEnable(ulChannel,UDMA_ATTR_USEBURST);

    MAP_uDMAChannelTransferSet(ulChannel, ulMode,
            pvSrcBuf, pvDstBuf, ulItemCount);

    MAP_uDMAChannelEnable(ulChannel);

}

void DmaSpiSwIntHandler()
{
    MAP_SPIIntClear(LSPI_BASE,SPI_INT_EOW);
    MAP_SPICSDisable(LSPI_BASE);

#if defined(SL_PLATFORM_MULTI_THREADED)
    xSemaphoreGiveFromISR(DMAMsgQ, NULL);
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
#else
    g_cDummy = 0x1;
#endif
}


/*!
    \brief  This function is used to set DMA receive on th SPI

    \param          ucBuff  -   points to the first location of the receive buffer

    \param          len     - Length of the data to be received

    \sa             SetupDMASend

    \return         None

    \warning
 */

void SetupDMAReceive(unsigned char *ucBuff,int len)
{

    cc_SetupTransfer(UDMA_CH13_LSPI_TX,
            UDMA_MODE_BASIC,
            len/4,
            UDMA_SIZE_32,
            UDMA_ARB_1,
            &g_ucDinDout[5],
            UDMA_SRC_INC_NONE,
            (void *)(LSPI_BASE+MCSPI_O_TX0),
            UDMA_DST_INC_NONE);

    cc_SetupTransfer(UDMA_CH12_LSPI_RX,
            UDMA_MODE_BASIC,
            len/4,
            UDMA_SIZE_32,
            UDMA_ARB_1,
            (void *)(LSPI_BASE+MCSPI_O_RX0),
            UDMA_SRC_INC_NONE,
            (void *)ucBuff,
            UDMA_DST_INC_32);

    MAP_SPIWordCountSet(LSPI_BASE,len/4);

}


/*!
    \brief  This function is used to set DMA send on th SPI

    \param          ucBuff  -   points to the first location of the send buffer

    \param          len     - Length of the data to be sent

    \sa             SetupDMAReceive

    \return         None

    \warning
 */

void SetupDMASend(unsigned char *ucBuff,int len)
{

    cc_SetupTransfer(UDMA_CH13_LSPI_TX,
            UDMA_MODE_BASIC,
            len/4,
            UDMA_SIZE_32,
            UDMA_ARB_1,
            (void *)ucBuff,
            UDMA_SRC_INC_32,
            (void *)(LSPI_BASE+MCSPI_O_TX0),
            UDMA_DST_INC_NONE);


    cc_SetupTransfer(UDMA_CH12_LSPI_RX,
            UDMA_MODE_BASIC,
            len/4,
            UDMA_SIZE_32,
            UDMA_ARB_1,
            (void *)(LSPI_BASE+MCSPI_O_RX0),
            UDMA_SRC_INC_NONE,
            &g_ucDinDout[15],
            UDMA_DST_INC_NONE);

    MAP_SPIWordCountSet(LSPI_BASE,len/4);


}

/*!
     \brief     Configures the uDMA channel

     \param uiChannel - the DMA channel to be selected

     \param pfpAppCb - the application callback to be invoked on transfer

     \return None.
 */
void cc_UDMAChannelSelect(unsigned int uiChannel)
{
    MAP_uDMAChannelAssign(uiChannel);
    MAP_uDMAChannelAttributeDisable(uiChannel,UDMA_ATTR_ALTSELECT);
}

typedef void (*P_OSI_INTR_ENTRY)(void);
void osi_InterruptRegister(int iIntrNum,P_OSI_INTR_ENTRY pEntry,unsigned char ucPriority)
{
    MAP_IntRegister(iIntrNum,(void(*)(void))pEntry);
    MAP_IntPrioritySet(iIntrNum, ucPriority);
    MAP_IntEnable(iIntrNum);
}
void Message(const char *str);
/*!
    \brief open spi communication port to be used for communicating with a SimpleLink device

    Given an interface name and option flags, this function opens the spi communication port
    and creates a file descriptor. This file descriptor can be used afterwards to read and
    write data from and to this specific spi channel.
    The SPI speed, clock polarity, clock phase, chip select and all other attributes are all
    set to hardcoded values in this function.

    \param          ifName      -   points to the interface name/path. The interface name is an
                                    optional attributes that the simple link driver receives
                                    on opening the device. in systems that the spi channel is
                                    not implemented as part of the os device drivers, this
                                    parameter could be NULL.
    \param          flags       -   option flags

    \return         upon successful completion, the function shall open the spi channel and return
                    a non-negative integer representing the file descriptor.
                    Otherwise, -1 shall be returned

    \sa             spi_Close , spi_Read , spi_Write
    \note
    \warning
 */

Fd_t spi_Open(char *ifName, unsigned long flags)
{
    unsigned long ulBase;
    unsigned long ulSpiBitRate;

    //NWP master interface
    ulBase = LSPI_BASE;

    //Enable MCSPIA2
    MAP_PRCMPeripheralClkEnable(PRCM_LSPI,PRCM_RUN_MODE_CLK|PRCM_SLP_MODE_CLK);

    //Disable Chip Select
    MAP_SPICSDisable(ulBase);

    //Disable SPI Channel
    MAP_SPIDisable(ulBase);

    // Reset SPI
    MAP_SPIReset(ulBase);

    //
    // Configure SPI interface
    //
    ulSpiBitRate = SPI_RATE_20M;

    MAP_SPIConfigSetExpClk(ulBase,MAP_PRCMPeripheralClockGet(PRCM_LSPI),
            ulSpiBitRate,SPI_MODE_MASTER,SPI_SUB_MODE_0,
            (SPI_SW_CTRL_CS |
                    SPI_4PIN_MODE |
                    SPI_TURBO_OFF |
                    SPI_CS_ACTIVEHIGH |
                    SPI_WL_32));

    if(MAP_PRCMPeripheralStatusGet(PRCM_UDMA))
    {
        g_ucDMAEnabled = (HWREG(UDMA_BASE + UDMA_O_CTLBASE) != 0x0) ? 1 : 0;
    }
    else
    {
        g_ucDMAEnabled = 0;
    }
#ifdef SL_CPU_MODE
    g_ucDMAEnabled = 0;
#endif
    if(g_ucDMAEnabled)
    {
        //Message("g_ucDMAEnabled\r\n");
        memset(g_ucDinDout,0xFF,sizeof(g_ucDinDout));

        // Set DMA channel
        cc_UDMAChannelSelect(UDMA_CH12_LSPI_RX);
        cc_UDMAChannelSelect(UDMA_CH13_LSPI_TX);


        MAP_SPIFIFOEnable(ulBase,SPI_RX_FIFO);
        MAP_SPIFIFOEnable(ulBase,SPI_TX_FIFO);
        MAP_SPIDmaEnable(ulBase,SPI_RX_DMA);
        MAP_SPIDmaEnable(ulBase,SPI_TX_DMA);

        MAP_SPIFIFOLevelSet(ulBase,1,1);
#if defined(SL_PLATFORM_MULTI_THREADED)
        osi_InterruptRegister(INT_LSPI, (P_OSI_INTR_ENTRY)DmaSpiSwIntHandler,INT_PRIORITY_LVL_1);

        MAP_SPIIntEnable(ulBase,SPI_INT_EOW);

        if((DMAMsgQ = xSemaphoreCreateBinary()) == NULL)
        {
            Message("!DMAMsgQ\r\n");
            while(1){

            }
        }

#else

        MAP_IntRegister(INT_LSPI,(void(*)(void))DmaSpiSwIntHandler);
        MAP_IntPrioritySet(INT_LSPI, INT_PRIORITY_LVL_1);
        MAP_IntEnable(INT_LSPI);

        MAP_SPIIntEnable(ulBase,SPI_INT_EOW);


        g_cDummy = 0x0;
#endif

    }
    MAP_SPIEnable(ulBase);
    Power_setConstraint(PowerCC32XX_DISALLOW_LPDS);
    g_SpiFd = 1;
    return g_SpiFd;

}
/*!
    \brief closes an opened spi communication port

    \param          fd          -   file descriptor of an opened SPI channel

    \return         upon successful completion, the function shall return 0.
                    Otherwise, -1 shall be returned

    \sa             spi_Open
    \note
    \warning
 */
int spi_Close(Fd_t fd)
{
    unsigned long ulBase = LSPI_BASE;

    g_SpiFd = 0;
    if(g_ucDMAEnabled)
    {

#ifdef SL_PLATFORM_MULTI_THREADED
        HwiP_delete((HwiP_Handle)INT_LSPI);
        vSemaphoreDelete(DMAMsgQ );

#else
        MAP_SPIIntUnregister(ulBase);
        g_cDummy = 0;
#endif
        MAP_SPIFIFODisable(ulBase,SPI_RX_FIFO);
        MAP_SPIFIFODisable(ulBase,SPI_TX_FIFO);
        MAP_SPIDmaDisable(ulBase,SPI_RX_DMA);
        MAP_SPIDmaDisable(ulBase,SPI_TX_DMA);
    }
    //Disable Chip Select
    MAP_SPICSDisable(LSPI_BASE);


    //Disable SPI Channel
    MAP_SPIDisable(ulBase);

    // Reset SPI
    MAP_SPIReset(ulBase);

    // Enable SPI Peripheral
    MAP_PRCMPeripheralClkDisable(PRCM_LSPI,PRCM_RUN_MODE_CLK|PRCM_SLP_MODE_CLK);

    Power_releaseConstraint(PowerCC32XX_DISALLOW_LPDS);
    return 0;
}

/*!
    \brief attempts to read up to len bytes from SPI channel into a buffer starting at pBuff.

    \param          pBuff       -   points to first location to start writing the data

    \param          len         -   number of bytes to read from the SPI channel

    \return         upon successful completion, the function shall return Read Size.
                    Otherwise, -1 shall be returned

    \sa             spi_Read_CPU , spi_Write_CPU
    \note
    \warning
 */
static int spi_Read_CPU(unsigned char *pBuff, int len)
{
    unsigned long ulCnt;
    unsigned long ulStatusReg;
    unsigned long *ulDataIn;
    unsigned long ulTxReg;
    unsigned long ulRxReg;

    MAP_SPICSEnable(LSPI_BASE);

    //
    // Initialize local variable.
    //
    ulDataIn = (unsigned long *)pBuff;
    ulCnt = (len + 3) >> 2;
    ulStatusReg = LSPI_BASE+MCSPI_O_CH0STAT;
    ulTxReg = LSPI_BASE + MCSPI_O_TX0;
    ulRxReg = LSPI_BASE + MCSPI_O_RX0;

    //
    // Reading loop
    //
    while(ulCnt--)
    {
        while(!( HWREG(ulStatusReg)& MCSPI_CH0STAT_TXS ));
        HWREG(ulTxReg) = 0;
        while(!( HWREG(ulStatusReg)& MCSPI_CH0STAT_RXS ));
        *ulDataIn = HWREG(ulRxReg);
        ulDataIn++;
    }

    MAP_SPICSDisable(LSPI_BASE);

    return len;
}

/*!
    \brief attempts to write up to len bytes to the SPI channel

    \param          pBuff       -   points to first location to start getting the data from

    \param          len         -   number of bytes to write to the SPI channel

    \return         upon successful completion, the function shall return write size.
                    Otherwise, -1 shall be returned

    \sa             spi_Read_CPU , spi_Write_CPU
    \note           This function could be implemented as zero copy and return only upon successful completion
                    of writing the whole buffer, but in cases that memory allocation is not too tight, the
                    function could copy the data to internal buffer, return back and complete the write in
                    parallel to other activities as long as the other SPI activities would be blocked untill
                    the entire buffer write would be completed
    \warning
 */
static int spi_Write_CPU(unsigned char *pBuff, int len)
{


    unsigned long ulCnt;
    unsigned long ulStatusReg;
    unsigned long *ulDataOut;
    unsigned long ulDataIn;
    unsigned long ulTxReg;
    unsigned long ulRxReg;


    MAP_SPICSEnable(LSPI_BASE);

    //
    // Initialize local variable.
    //
    ulDataOut = (unsigned long *)pBuff;
    ulCnt = (len +3 ) >> 2;
    ulStatusReg = LSPI_BASE+MCSPI_O_CH0STAT;
    ulTxReg = LSPI_BASE + MCSPI_O_TX0;
    ulRxReg = LSPI_BASE + MCSPI_O_RX0;

    //
    // Writing Loop
    //
    while(ulCnt--)
    {
        while(!( HWREG(ulStatusReg)& MCSPI_CH0STAT_TXS ));
        HWREG(ulTxReg) = *ulDataOut;
        while(!( HWREG(ulStatusReg)& MCSPI_CH0STAT_RXS ));
        ulDataIn = HWREG(ulRxReg);
        ulDataOut++;
    }

    MAP_SPICSDisable(LSPI_BASE);

    UNUSED(ulDataIn);
    return len;

}

/*!
    \brief closes an opened spi communication port

    \param          fd          -   file descriptor of an opened SPI channel

    \return         upon successful completion, the function shall return 0.
                    Otherwise, -1 shall be returned

    \sa             spi_Open
    \note
    \warning
 */

int spi_Read(Fd_t fd, unsigned char *pBuff, int len)
{
    int read_size = 0;
    unsigned int pBuffAddr = (unsigned int)pBuff;
    if(fd!=1 || g_SpiFd == 0)
        return -1;

    if(len>DMA_BUFF_SIZE_MIN && g_ucDMAEnabled && ((pBuffAddr % 4) == 0))
    {

#if defined(SL_PLATFORM_MULTI_THREADED)
        char temp[4];
#endif
        while (len>0)
        {
            if( len < MAX_DMA_RECV_TRANSACTION_SIZE)
            {
                SetupDMAReceive(&pBuff[read_size],len);
                MAP_SPICSEnable(LSPI_BASE);
#if defined(SL_PLATFORM_MULTI_THREADED)
                xSemaphoreTake(DMAMsgQ, portMAX_DELAY);
#else
                while(g_cDummy != 0x1);
                g_cDummy = 0x0;
#endif
                read_size += len;
                len = 0;
            }
            else
            {
                SetupDMAReceive(&pBuff[read_size],MAX_DMA_RECV_TRANSACTION_SIZE);
                MAP_SPICSEnable(LSPI_BASE);
#if defined(SL_PLATFORM_MULTI_THREADED)
                xSemaphoreTake(DMAMsgQ, portMAX_DELAY);
#else
                while(g_cDummy != 0x1);
                g_cDummy = 0x0;
#endif
                read_size += MAX_DMA_RECV_TRANSACTION_SIZE;

                len -= MAX_DMA_RECV_TRANSACTION_SIZE;
            }
        }
    }
    else
    {
        read_size += spi_Read_CPU(pBuff,len);
    }
    return read_size;
}

/*!
    \brief attempts to write up to len bytes to the SPI channel

    \param          fd          -   file descriptor of an opened SPI channel

    \param          pBuff       -   points to first location to start getting the data from

    \param          len         -   number of bytes to write to the SPI channel

    \return         upon successful completion, the function shall return 0.
                    Otherwise, -1 shall be returned

    \sa             spi_Open , spi_Read
    \note           This function could be implemented as zero copy and return only upon successful completion
                    of writing the whole buffer, but in cases that memory allocation is not too tight, the
                    function could copy the data to internal buffer, return back and complete the write in
                    parallel to other activities as long as the other SPI activities would be blocked untill
                    the entire buffer write would be completed
    \warning
 */
int spi_Write(Fd_t fd, unsigned char *pBuff, int len)
{
    int write_size = 0;
    unsigned int pBuffAddr = (unsigned int)pBuff;

    if(fd!=1 || g_SpiFd == 0)
        return -1;

    if(len>DMA_BUFF_SIZE_MIN && g_ucDMAEnabled && ((pBuffAddr % 4) == 0))
    {
#if defined(SL_PLATFORM_MULTI_THREADED)
        char temp[4];
#endif
        SetupDMASend(pBuff,len);
        MAP_SPICSEnable(LSPI_BASE);

#if defined(SL_PLATFORM_MULTI_THREADED)
        xSemaphoreTake(DMAMsgQ, portMAX_DELAY);
#else
        while(g_cDummy != 0x1);
        g_cDummy = 0x0;
#endif
        write_size += len;
    }

    else
    {
        write_size += spi_Write_CPU(pBuff,len);
    }
    return write_size;
}
