#include <xdc/runtime/Memory.h>

#include <ti/csl/csl_chip.h>
#include <ti/csl/csl_semAux.h>
#include <ti/csl/cslr_device.h>

#include "ti/drv/qmss/qmss_drv.h"
#include "ti/drv/cppi/cppi_drv.h"
#include "ti/drv/rm/test/rm_test.h"

#define NUM_HOST_DESC               32
#define SIZE_HOST_DESC              64
#define NUM_MONOLITHIC_DESC         32

/* Accumulator channel to use */
#define		TEST_ACC_CHANNEL_NUM			0u

#define		RM_BASE_QUEUE_NUM			0u

#define      RM_BASE_LOW_PRIO_QUEUE_NUM			128u

#define     QMSS_HW_SEM         3 

#pragma DATA_ALIGN (hostDesc, 16)
UInt8                   hostDesc[SIZE_HOST_DESC * NUM_HOST_DESC];
/* CPDMA configuration */
Cppi_CpDmaInitCfg       cpdmaCfg;
/* QMSS configuration */
Qmss_InitCfg            qmssInitConfig;
/* Memory region configuration information */
Qmss_MemRegInfo         hostMemInfo;
/* Accumulator configuration */
Qmss_AccCmdCfg          cfg = {0};

/* QM descriptor configuration */
Qmss_DescCfg            descCfg = {0};

extern Qmss_GlobalConfigParams  qmssGblCfgParams;

UInt32 l2_global_address (UInt32 addr)
{
	UInt32 corenum;

	/* Get the core number. */
	corenum = DNUM;

	/* Compute the global address. */
	return (addr + (0x10000000 + (corenum * 0x1000000)));
}

#define QMSS_RET_RM_RANGE(err) ((err <= QMSS_RESOURCE_INIT_DENIED) && (err >= QMSS_FIRMWARE_REVISION_DIFFERENCE))

#define RM_TEST_QMSS_CHECK_PRINT_RESULT(name, expect, ret_val) do { \
    if (QMSS_RET_RM_RANGE(ret_val)) \
        RM_PRINT_RESULT(name, expect, rm_denied, ret_val); \
    else \
        RM_PRINT_RESULT(name, expect, rm_granted, ret_val); \
} while(0)

void rm_test_qmss(Rm_Handle rm_handle, rm_test_expect_e expect)
{
    Qmss_Result             result;
    //Cppi_Handle             cppiHnd;
    Qmss_StartCfg           startCfg;
    Qmss_QueueHnd           qHandle;
    Qmss_QosQueueCfg        queueCfg = {0};
    Qmss_QosClusterCfg      clusterCfg;
    uint32_t                numAllocated; 

    System_printf ("~~~~~~~~~~~~~Core %d RM QMSS TEST START~~~~~~~~~~~~~~~~\n", DNUM);

    if (DNUM == 0) {
        memset ((Void *) &qmssInitConfig, 0, sizeof (Qmss_InitCfg));

        qmssInitConfig.linkingRAM0Base = 0;
        qmssInitConfig.linkingRAM0Size = 0;
        qmssInitConfig.linkingRAM1Base = 0;
        qmssInitConfig.maxDescNum      = NUM_MONOLITHIC_DESC + NUM_HOST_DESC;

        qmssGblCfgParams.qmRmHandle = rm_handle;

        /* Initialize Queue Manager SubSystem */
        result = Qmss_init (&qmssInitConfig, &qmssGblCfgParams);
#ifdef _TCI6614_Atrenta_DSP1_H_        
        /* ARM has Linking RAM Control so expect deny always */
        RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_init", rm_denied, result);
#else  /* All other devices without an ARM */
        RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_init", expect, result);
#endif
    }
    /* Start Queue Manager SubSystem */
    startCfg.rmHandle = rm_handle;
    result = Qmss_startCfg (&startCfg);
    if (result != QMSS_SOK)
    {
        System_printf ("Core %d : Error in Qmss_StartCfg error code : %d\n", DNUM, result);
    }

    memset ((Void *) &hostDesc, 0, SIZE_HOST_DESC * NUM_HOST_DESC);
    hostMemInfo.descBase = (UInt32 *) l2_global_address ((UInt32) hostDesc);
    hostMemInfo.descSize = SIZE_HOST_DESC;
    hostMemInfo.descNum = NUM_HOST_DESC;
    hostMemInfo.manageDescFlag = Qmss_ManageDesc_MANAGE_DESCRIPTOR;
    hostMemInfo.memRegion = Qmss_MemRegion_MEMORY_REGION_NOT_SPECIFIED;
    hostMemInfo.startIndex = 0;

    result = Qmss_insertMemoryRegion (&hostMemInfo);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_insertMemoryRegion", expect, result);

    descCfg.memRegion = Qmss_MemRegion_MEMORY_REGION1;
    qHandle = Qmss_initDescriptor (&descCfg, &numAllocated);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_initDescriptor", expect, qHandle);

    qHandle = Qmss_queueOpen (Qmss_QueueType_LOW_PRIORITY_QUEUE, RM_BASE_LOW_PRIO_QUEUE_NUM, (uint8_t *)&numAllocated);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_queueOpen", expect, qHandle);

    /* Reset qHandle to a valid queue number if the queueOpen returned a resource denied error */
    if (qHandle < 0)
    {
       qHandle = 0;
    }

    result = Qmss_setQueueThreshold (qHandle, 1, 1);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_setQueueThreshold", expect, result);

    cfg.channel = TEST_ACC_CHANNEL_NUM;
    result = Qmss_programAccumulator (Qmss_PdspId_PDSP1, &cfg);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_programAccumulator", expect, result);

#if 0
    result = Qmss_disableAccumulator (Qmss_PdspId_PDSP1, TEST_ACC_CHANNEL_NUM);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_disableAccumulator", expect, result);
#endif

    result = Qmss_configureQosQueue (RM_BASE_QUEUE_NUM, &queueCfg);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_configureQosQueue", expect, result);

    result = Qmss_configureQosCluster (RM_BASE_QUEUE_NUM, &clusterCfg);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_configureQosCluster", expect, result);

    result = Qmss_resetQosQueueStats (RM_BASE_QUEUE_NUM);
    RM_TEST_QMSS_CHECK_PRINT_RESULT("Qmss_resetQosQueueStats", expect, result);

    System_printf ("~~~~~~~~~~~~~Core %d RM QMSS TEST DONE~~~~~~~~~~~~~~~~\n", DNUM);
}

    
/**********************************************************************
 *************************** OSAL Functions **************************
 **********************************************************************/
UInt32      qmssMallocCounter   = 0;
UInt32      qmssFreeCounter     = 0;
UInt32      coreKey [MAX_NUM_CORES];

/**
 *  @b Description
 *  @n  
 *      The function is used to allocate a memory block of the specified size.
 *
 *  @param[in]  num_bytes
 *      Number of bytes to be allocated.
 *
 *  @retval
 *      Allocated block address
 */
Ptr Osal_qmssMalloc (UInt32 num_bytes)
{
	Error_Block	errorBlock;
    Ptr dataPtr;

    /* Increment the allocation counter. */
    qmssMallocCounter++;	

	/* Allocate memory. */
    dataPtr = Memory_alloc(NULL, num_bytes, 0, &errorBlock);
	return (dataPtr);
}

/**
 *  @b Description
 *  @n  
 *      The function is used to free a memory block of the specified size.
 *
 *  @param[in]  ptr
 *      Pointer to the memory block to be cleaned up.
 *
 *  @param[in]  size
 *      Size of the memory block to be cleaned up.
 *
 *  @retval
 *      Not Applicable
 */
Void Osal_qmssFree (Ptr ptr, UInt32 size)
{
    /* Increment the free counter. */
    qmssFreeCounter++;	
	Memory_free(NULL, ptr, size);
}

/**
 *  @b Description
 *  @n  
 *      The function is used to enter a critical section.
 *      Function protects against 
 *      
 *      access from multiple cores 
 *      and 
 *      access from multiple threads on single core
 *
 *  @param[in]  key
 *      Key used to lock the critical section.
 *
 *  @retval
 *      Not Applicable
 */
Ptr Osal_qmssCsEnter (void)
{
    /* Get the hardware semaphore. 
     *
     * Acquire Multi core QMSS synchronization lock 
     */
    while ((CSL_semAcquireDirect (QMSS_HW_SEM)) == 0);

    /* Disable all interrupts and OS scheduler. 
     *
     * Acquire Multi threaded / process synchronization lock.
     */
    coreKey [CSL_chipReadReg (CSL_CHIP_DNUM)] = Hwi_disable();

    return NULL;
}

/**
 *  @b Description
 *  @n  
 *      The function is used to exit a critical section 
 *      protected using Osal_qmssCsEnter() API.
 *
 *  @param[in]  key
 *      Key used to unlock the critical section.
 *
 *  @retval
 *      Not Applicable
 */
Void Osal_qmssCsExit (Ptr CsHandle)
{
    /* Enable all interrupts and enables the OS scheduler back on.
     *
     * Release multi-threaded / multi-process lock on this core.
     */
    Hwi_restore(coreKey [CSL_chipReadReg (CSL_CHIP_DNUM)]);

    /* Release the hardware semaphore 
     *
     * Release multi-core lock.
     */ 
    CSL_semReleaseSemaphore (QMSS_HW_SEM);

    return;
}

/**
 * ============================================================================
 *  @n@b Osal_qmssMtCsEnter
 *
 *  @b  brief
 *  @n  This API ensures ONLY multi-threaded
 *      synchronization to the QMSS user.
 *
 *      This is a BLOCKING API.
 *
 *  @param[in] None
 *
 *  @return     
 *       Handle used to lock critical section
 * =============================================================================
 */
Ptr Osal_qmssMtCsEnter (Void)
{
    /* Disable all interrupts and OS scheduler. 
     *
     * Acquire Multi threaded / process synchronization lock.
     */
    //coreKey [CSL_chipReadReg (CSL_CHIP_DNUM)] = Hwi_disable();

    return NULL;
}

/**
 * ============================================================================
 *  @n@b Osal_qmssMtCsExit
 *
 *  @b  brief
 *  @n  This API needs to be called to exit a previously
 *      acquired critical section lock using @a Osal_cpswQmssMtCsEnter ()
 *      API. It resets the multi-threaded lock, enabling another process
 *      on the current core to grab it.
 *
 *  @param[in]  CsHandle
 *      Handle for unlocking critical section.
 *
 *  @return     None
 * =============================================================================
 */
Void Osal_qmssMtCsExit (Ptr CsHandle)
{
    /* Enable all interrupts and enables the OS scheduler back on.
     *
     * Release multi-threaded / multi-process lock on this core.
     */
    //Hwi_restore(key);

    return;
}

/**
 *  @b Description
 *  @n  
 *      The function is the QMSS OSAL Logging API which logs 
 *      the messages on the console.
 *
 *  @param[in]  fmt
 *      Formatted String.
 *
 *  @retval
 *      Not Applicable
 */
Void Osal_qmssLog ( String fmt, ... )
{
}

/**
 *  @b Description
 *  @n  
 *      The function is used to indicate that a block of memory is 
 *      about to be accessed. If the memory block is cached then this 
 *      indicates that the application would need to ensure that the 
 *      cache is updated with the data from the actual memory.
 *
 *  @param[in]  blockPtr
 *       Address of the block which is to be invalidated
 *
 *  @param[in]  size
 *       Size of the block to be invalidated

 *  @retval
 *      Not Applicable
 */
void Osal_qmssBeginMemAccess (void *blockPtr, uint32_t size)
{
    uint32_t    key;

    /* Disable Interrupts */
    key = Hwi_disable();

    /* Cleanup the prefetch buffer also. */
    CSL_XMC_invalidatePrefetchBuffer();
    
    SYS_CACHE_INV (blockPtr, size, CACHE_FENCE_WAIT);
    
    asm   (" nop      4");
    asm   (" nop      4");
    asm   (" nop      4");
    asm   (" nop      4");

    /* Reenable Interrupts. */
    Hwi_restore(key);
    
    return;
}

/**
 *  @b Description
 *  @n  
 *      The function is used to indicate that the block of memory has 
 *      finished being accessed. If the memory block is cached then the 
 *      application would need to ensure that the contents of the cache 
 *      are updated immediately to the actual memory. 
 *
 *  @param[in]  blockPtr
 *       Address of the block which is to be written back
 *
 *  @param[in]  size
 *       Size of the block to be written back

 *  @retval
 *      Not Applicable
 */
void Osal_qmssEndMemAccess (void *blockPtr, uint32_t size)
{
    uint32_t    key;

    /* Disable Interrupts */
    key = Hwi_disable();

    SYS_CACHE_WB (blockPtr, size, CACHE_FENCE_WAIT);

    asm   (" nop      4");
    asm   (" nop      4");
    asm   (" nop      4");
    asm   (" nop      4");

    /* Reenable Interrupts. */
    Hwi_restore(key);
    return;
}


