/**************************************************************************//**
  * \file     Memory.c
  * \brief    Internal memory driver file
  * \details
  * \author   Zhang Xuan
  * \version  V1.0.0
  * \date     20-Jul-2018
  * \par      History:
  *           V1.0.0 Initial release
  * \par      Copyright:
  *           (c) Heilongjiang TYW Electronics co., LTD
******************************************************************************/

/* Includes ------------------------------------------------------------------*/

#include "Memory.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Keys */
#define   MEM_TCFLASH_UNLOCK_KEY            (0xCF61F1A5UL)
#define   MEM_WFLASH_UNLOCK_KEY             (0xCF6DF1A5UL)
#define   MEM_TCMRAM_UNLOCK_KEY             (0xACC55ECCUL)
#define   MEM_TCMRAM_LOCK_KEY               (0x5ECCB10CUL)
#define   MEM_SYSRAM_UNLOCK_KEY             (0x5ECC551FUL)
#define   MEM_SYSRAM_LOCK_KEY               (0x551FB10CUL)

/* Private macro -------------------------------------------------------------*/

/* Calculate TCFLASH wait states setting */
#if (CLK_CPU_FREQ % MEM_TCFLASH_MAXIMUM_FREQUENCY) != 0U
  #define MEM_TCFLASH_WAIT                  (CLK_CPU_FREQ / MEM_TCFLASH_MAXIMUM_FREQUENCY)
#else
  #define MEM_TCFLASH_WAIT                  (CLK_CPU_FREQ / MEM_TCFLASH_MAXIMUM_FREQUENCY - 1U)
#endif

/* Calculate WORKFLASH wait states setting */
#if (CLK_CPU_FREQ % MEM_WORKFLASH_MAXIMUM_FREQUENCY) != 0U
  #define MEM_WORKFLASH_WAIT                ((CLK_CPU_FREQ / MEM_WORKFLASH_MAXIMUM_FREQUENCY) / (MEM_TCFLASH_WAIT + 1U))
#else
  #define MEM_WORKFLASH_WAIT                ((CLK_CPU_FREQ / MEM_WORKFLASH_MAXIMUM_FREQUENCY) / (MEM_TCFLASH_WAIT + 1U) - 1U)
#endif

/* Check DMA block size settings */
#if (((MEM_DMA_BLOCK_SIZE) < 1UL) || ((MEM_DMA_BLOCK_SIZE) > 16UL))
    #error MEM_DMA_BLOCK_SIZE invalid (valid range: 1...16)
#endif

/* Check memory start addresses */
#if (((MEM_SYSRAM_START_ADDRESS) % (1UL << (MEM_SYSRAM_MAX_ACCESS_WIDTH))) != 0UL)
    #error MEM_SYSRAM_START_ADDRESS invalid (multiple of DMA transfer width required)
#endif

/* Check memory sizes */
#if (((MEM_TCMRAM_SIZE_BYTE) % ((1UL << (MEM_TCMRAM_MAX_ACCESS_WIDTH)) * (MEM_DMA_BLOCK_SIZE))) != 0UL)
    #error MEM_TCMRAM_SIZE_BYTE invalid (multiple of (MEM_DMA_BLOCK_SIZE * DMA transfer width) required)
#endif
#if (((MEM_SYSRAM_SIZE_BYTE) % ((1UL << (MEM_SYSRAM_MAX_ACCESS_WIDTH)) * (MEM_DMA_BLOCK_SIZE))) != 0UL)
    #error MEM_SYSRAM_SIZE_BYTE invalid (multiple of (MEM_DMA_BLOCK_SIZE * DMA transfer width) required)
#endif

/* Private variables ---------------------------------------------------------*/
extern const uint32_t ADDRESS_BOOT_USR_STACK_END;

#pragma data_alignment = 8
static const uint64_t MemClearValue[MEM_DMA_BLOCK_SIZE] = {0x0000000000000000};

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************
 ** \brief Configures internal memories
 **
 ** This function will set TCMRAM, System RAM and Backup RAM
 ** to 0 wait states as all these RAMs support maximum clock without
 ** wait states). Additionally, the appropriate ECC logic will be enabled
 ** disabled according to the settings in start.h.
 **
 ** \note Some registers written here are write-once registers. Ensure to
 **       call this function only one time during system start-up, otherwise
 **       an exception will be generated.
 **
 *****************************************************************************/
void Mem_Init(void)
{
  /*** TCFLASH ***/
  
  /* Set wait states */
  TCFCFG0_FCPROTKEY  = MEM_TCFLASH_UNLOCK_KEY;
  TCFCFG0_FCFGR_FAWC = MEM_TCFLASH_WAIT;
  
  #if MEM_TCFLASH_ECC_ENABLE
  /* Enable ECC logic (write-once register) */
  TCFCFG0_FCPROTKEY  = MEM_TCFLASH_UNLOCK_KEY;
  TCFCFG0_FECCCTRL_ECCOFF = 0U;
  #else
  /* Disable ECC logic (write-once register) */
  TCFCFG0_FCPROTKEY  = MEM_TCFLASH_UNLOCK_KEY;
  TCFCFG0_FECCCTRL_ECCOFF = 1U;
  #endif

  /*** WorkFlash ***/
  
  /* Set wait states */
  WFCFG_CPR          = MEM_WFLASH_UNLOCK_KEY;
  WFCFG_CR_FAWC      = MEM_WORKFLASH_WAIT;
  
  /*** TCMRAM ***/
  
  /* Unlock configuration registers */
  TRCFG0_TCMUNLOCK = MEM_TCMRAM_UNLOCK_KEY;
  while (TRCFG0_TCMCFG0_LOCKSTATUS == 1U){}
  
  /* Set 0 wait states */
  TRCFG0_TCMCFG0_DWAIT = 0U;
  
  /* Lock configuration registers */
  TRCFG0_TCMUNLOCK = MEM_TCMRAM_LOCK_KEY;
  while (TRCFG0_TCMCFG0_LOCKSTATUS == 0U){}

  /*** System RAM ***/
  
  /* Avoid changing of settings if code is executed from System RAM */
  #if   MEM_SYSRAM_CLEAR_SIZE_BYTE
  /* Unlock configuration registers */
  SRCFG_KEY = MEM_SYSRAM_UNLOCK_KEY;
  while (SRCFG_CFG0_LOCK_STATUS == 1U){}
  
  /* Set 0 wait states */
  SRCFG_CFG0_RDWAIT   = 0U;
  SRCFG_CFG0_WRWAIT   = 0U;
  
  #if MEM_SYSRAM_ECC_ENABLE
    /* Enable ECC logic (write-once register) */
    SRCFG_ECCE_ECCEN = 1U;
  #else
    /* Disable ECC logic (write-once register) */
    SRCFG_ECCE_ECCEN = 0U;
  #endif

  /* Lock configuration registers */
  SRCFG_KEY = MEM_SYSRAM_LOCK_KEY;
  while (SRCFG_CFG0_LOCK_STATUS == 0U){}
  #endif
}

/******************************************************************************
 ** \brief Starts DMA operation(s) to clear all RAM areas that have ECC logic enabled
 **
 ** One ore more DMA channels are configured to clear all RAM areas to '0'
 ** to force initial calculation of ECC bits.
 **
 ** \note No interrupts are used
 **
 ** \post One or more DMA channels may be operating. Use ClearEccRamWaitCompletion()
 **       to wait for completion of all DMA operations.
 *****************************************************************************/
void Mem_Clear(void)
{
  un_dma0_an_t    DmaAn;
  un_dma0_bn_t    DmaBn;
  un_dma0_cn_t    DmaCn;
  un_dma0_dn_1_t  DmaDnB1;
  un_dma0_dn_3_t  DmaDnB3;
  
  DmaAn.u32Register  = 0x00000000UL;
  DmaBn.u32Register  = 0x00000000UL;
  DmaCn.u32Register  = 0x00000000UL;
  DmaDnB1.u8Register = 0x00U;
  DmaDnB3.u8Register = 0x00U;
  
  /* Generate common DMA settings */
  DmaAn.stcField.u2BL = 3U;                        /* Beat limit = INCR16 */
  DmaAn.stcField.u4BC = MEM_DMA_BLOCK_SIZE - 1U;   /* Block count */
  DmaAn.stcField.u4TO = 0xfU;                      /* Timeout (reserved in this product, keep default) */
  
  DmaBn.stcField.u2MS = 1U;                        /* Burst transfer mode */
  DmaBn.stcField.u4SP = 2U | 1U;                   /* Source protection (keep default value) = priv. data access, not cacheable/bufferable */
  DmaBn.stcField.u4DP = 2U | 1U;                   /* Destination protection (keep default value) = priv. data access, not cacheable/bufferable */
  DmaBn.stcField.u7PN = 0x7fU;                     /* Priority (keep default value) = 127 */
  
  DmaCn.stcField.u1CD = 1U;                        /* Clear DMA0_DMACB0_DQ flag */
  
  DmaDnB1.stcField.u1FD  = 0U;                     /* Increment destination address */
  
  DmaDnB3.stcField.u1FS  = 0U;                     /* increment source address (within one block) */
  DmaDnB3.stcField.u1FBS = 1U;                     /* source address for each block is fixed (SA) */

  /* Globally enable DMA operations */
  DMA0_R_PR = 0U;                                  /* Priority type (keep default value) = fixed */
  DMA0_R_DE = 1U;                                  /* Enable all DMA channels */

  #if MEM_TCMRAM_CLEAR_SIZE_BYTE
  /* Set memory specific transfer count and width */
  DmaAn.stcField.u16TC = ((MEM_TCMRAM_CLEAR_SIZE_BYTE - ADDRESS_BOOT_USR_STACK_END) / (MEM_DMA_BLOCK_SIZE * (1UL << MEM_TCMRAM_MAX_ACCESS_WIDTH))) - 1UL;
  DmaBn.stcField.u2TW  = MEM_TCMRAM_MAX_ACCESS_WIDTH;
  
  /* Set (fixed) source and destination addresses */
  /* - source:      BootROM */
  /* - destination: TCMRAM (via AXI bus slave interface, no DMA access via TCM port) */
  DMA0_SA0    = (uint32_t)MemClearValue + 0x04000000UL;
  DMA0_DA0    = MEM_TCMRAM_AXI_START_ADDRESS + ADDRESS_BOOT_USR_STACK_END;
  
  /* Configure DMA channel 0 */
  DMA0_A0     = DmaAn.u32Register;
  DMA0_B0     = DmaBn.u32Register;
  DMA0_C0     = DmaCn.u32Register;
  DMA0_D0_1   = DmaDnB1.u8Register;
  DMA0_D0_3   = DmaDnB3.u8Register;
  
  /* Enable DMA channel 0 */
  DMA0_A0_EB  = 1U;
  
  /* Start DMA channel 0 (Software trigger) */
  DMA0_A0_ST  = 1U;
  #endif
  
  #if MEM_SYSRAM_CLEAR_SIZE_BYTE
  /* Set memory specific transfer count and width */
  DmaAn.stcField.u16TC = (MEM_SYSRAM_CLEAR_SIZE_BYTE / (MEM_DMA_BLOCK_SIZE * (1U << MEM_SYSRAM_MAX_ACCESS_WIDTH))) - 1U;
  DmaBn.stcField.u2TW  = MEM_SYSRAM_MAX_ACCESS_WIDTH;
  
  /* Set (fixed) source and destination addresses */
  /* - source:      BootROM */
  /* - destination: System RAM */
  DMA0_SA1    = (uint32_t)MemClearValue + 0x04000000UL;
  
  #if MEM_CLEAR_BURAM
    DMA0_DA1  = MEM_SYSRAM_START_ADDRESS;
  #else
    DMA0_DA1  = MEM_SYSRAM_START_ADDRESS + MEM_BURAM_SIZE_BYTE;
  #endif
  
  /* Configure DMA channel 1 */
  DMA0_A1     = DmaAn.u32Register;
  DMA0_B1     = DmaBn.u32Register;
  DMA0_C1     = DmaCn.u32Register;
  DMA0_D1_1   = DmaDnB1.u8Register;
  DMA0_D1_3   = DmaDnB3.u8Register;
  
  /* Enable DMA channel 1 */
  DMA0_A1_EB  = 1U;
  
  /* Start DMA channel 1 (Software trigger) */
  DMA0_A1_ST  = 1U;
  #endif
}


/******************************************************************************
 ** \brief Waits until all DMA operations for clearing the ECC RAM areas are completed.
 **
 ** \pre ClearEccRamStart() must be called otherwise this function will never return.
 **
 ** \post All DMA operations are completed, DMA channels 0...3 are disabled,
 **       DMA registers for channel 0...3 may be different from reset state
 *****************************************************************************/
void MEM_Wait_Clear_Complete(void)
{
  #if MEM_TCMRAM_CLEAR_SIZE_BYTE
  /* Check DMA channel 0 completion status */
  while (DMA0_B0_DQ == 0U){}
  
  /* Disable channel 0 */
  DMA0_A0_EB = 0U;
  
  /* Clear DQ flag */
  DMA0_C0_CD = 1U;
  #endif

  #if MEM_SYSRAM_CLEAR_SIZE_BYTE
  /* Check DMA channel 1 completion status */
  while (DMA0_B1_DQ == 0U){}
  
  /* Disable channel 1 */
  DMA0_A1_EB = 0U;
  
  /* Clear DQ flag */
  DMA0_C1_CD = 1U;
  #endif
  
  /* Globally disable DMA operations */
  DMA0_R = 0U;
}

