/*
  ******************************************************************************
  * @file    fxx_dma.c
  * @version V1.0.0
  * @date    2020
  * @brief   DMA module driver.
  *          This file provides firmware functions to manage the following
  *          functionalities of the Direct Memory Access (DMA) peripheral:
  *           @ Initialization and de-initialization functions
  *           @ IO operation functions
  ******************************************************************************
*/
#include "fxx_std.h"

/**************** Used in cycle mode ****************/
static DMA_LLI_InitTypeDef Cycle_Channel[DMA_CHANNEL_NUM];

/**
  * @}
  */

/** @defgroup DMA_Private_Macros
  * @{
  */

/**
  * @}
  */

/** @defgroup DMA_Private_Variables
  * @{
  */

/**
  * @}
  */

/** @defgroup DMA_Private_FunctionPrototypes
  * @{
  */

/**
  * @}
  */

/** @defgroup DMA_Private_Functions
  * @{
  */

/**
  * @brief  Deinitializes the DMA Channelx registers to their default reset
  *         values.
  * @param  DMA_Channelx: where DMA_Channelx can be DMA_Channel0 to 7 for DMA to select the DMA Channel.
  * @retval None
  */
void DMA_DeInit(DMA_Channel_TypeDef* DMA_Channelx)
{
    /* Check the parameters */
    assert_param(IS_DMA_ALL_PERIPH(DMA_Channelx));

    /* Disable the selected DMA Channelx */
    DMA->CONFIG &= ~DMA_CONFIG_EN;
    
    DMA->INT_TC_CLR = 0xFFFFFFFF;
    
    DMA->INT_ERR_CLR = 0xFFFFFFFF;

    /* Reset DMA Channelx CONFIG register */
    DMA_Channelx->CONFIG  = 0;

    /* Reset DMA Channelx CTRL register */
    DMA_Channelx->CTRL = 0;
    
    System_Module_Disable(EN_DMA);
}

/**
  * @brief  Initializes the DMA Channelx according to the specified
  *         parameters in the DMA_InitStruct.
  * @param  DMA_Channelx: where DMA_Channelx can be DMA_Channel0 to 7 for DMA to select the DMA Channel.
  * @param  DMA_InitStruct: pointer to a DMA_InitTypeDef structure that
  *         contains the configuration information for the specified DMA Channel.
  * @retval None
  */
void DMA_Init(DMA_Channel_TypeDef* DMA_Channelx, DMA_InitTypeDef* DMA_InitStruct)
{
    uint32_t lu32_Channel_Index;
    
    /* Check DMA Parameter */
    assert_param(IS_DMA_DATA_FLOW(DMA_InitStruct->Data_Flow));
    assert_param(IS_DMA_REQUEST_ID(DMA_InitStruct->Request_ID)); 
    assert_param(IS_DMA_SRC_WIDTH(DMA_InitStruct->Source_Width));
    assert_param(IS_DMA_DST_WIDTH(DMA_InitStruct->Desination_Width));
    assert_param(IS_DMA_MODE(DMA_InitStruct->DMA_Mode));
    
    System_Module_Enable(EN_DMA);

    /* Default Little-Endian、Enable DMA */
    DMA->CONFIG = DMA_CONFIG_EN;

    /* Clear Channel Config */
    DMA_Channelx->CONFIG = 0x00000000; 
    
    if (DMA_InitStruct->Data_Flow == DMA_DATA_FLOW_M2P) 
    {
        DMA_InitStruct->Request_ID <<= DMA_CHANNEL_CONFIG_DEST_PERIPH_POS;
    }
    else if (DMA_InitStruct->Data_Flow == DMA_DATA_FLOW_P2M) 
    {
        DMA_InitStruct->Request_ID <<= DMA_CHANNEL_CONFIG_SRC_PERIPH_POS;
    }

    DMA_Channelx->CONFIG = DMA_InitStruct->Data_Flow | DMA_InitStruct->Request_ID;
    
    /* Config Channel Control */
    DMA_Channelx->CTRL = DMA_CHANNEL_CTRL_ITC;
    /* Source or Desination address increase */
    DMA_Channelx->CTRL |= (DMA_InitStruct->Desination_Inc | DMA_InitStruct->Source_Inc);
    /* Source or Desination date width */
    DMA_Channelx->CTRL |= (DMA_InitStruct->Desination_Width | DMA_InitStruct->Source_Width);
    
    /* Set source address and desination address */
    DMA_Channelx->SRC_ADDR  = DMA_InitStruct->SrcAddr;
    DMA_Channelx->DEST_ADDR = DMA_InitStruct->DstAddr;

    if(DMA_InitStruct->DMA_Mode == DMA_CIRCULAR)
    {
        /* Get DMA Channel number */
        lu32_Channel_Index = ((uint32_t)(DMA_Channelx) - (uint32_t)(DMA_Channel0)) / 0x20;
        
        /* Set Next Link */
        DMA_Channelx->LLI = (uint32_t)&Cycle_Channel[lu32_Channel_Index];
        
        /* The list point to oneself */
        Cycle_Channel[lu32_Channel_Index].SrcAddr = DMA_InitStruct->SrcAddr;
        Cycle_Channel[lu32_Channel_Index].DstAddr = DMA_InitStruct->DstAddr;
        Cycle_Channel[lu32_Channel_Index].Next    = &Cycle_Channel[lu32_Channel_Index];
        Cycle_Channel[lu32_Channel_Index].Control = (DMA_Channelx->CTRL & (~0xFFF)) | DMA_InitStruct->DMA_BufferSize;    
    }
    
    /* Set Transfer Size */
    DMA_Channelx->CTRL = (DMA_Channelx->CTRL & (~0xFFF)) | DMA_InitStruct->DMA_BufferSize;
}

/**
  * @brief  Fills each DMA_InitStruct member with its default value.
  * @param  DMA_InitStruct : pointer to a DMA_InitTypeDef structure which will
  *         be initialized.
  * @retval None
  */
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct)
{
/*-------------- Reset DMA init structure parameters values ------------------*/
    /* Initialize the DMA source address member */
    DMA_InitStruct->SrcAddr = 0;
    /* Initialize the DMA desination address member */
    DMA_InitStruct->DstAddr = 0;
    /* Initialize the Data_Flow member */
    DMA_InitStruct->Data_Flow = DMA_DATA_FLOW_M2M;
    /* Initialize the Request_ID member */
    DMA_InitStruct->Request_ID = REG_M2M;
    /* Initialize the DMA_BufferSize member */
    DMA_InitStruct->DMA_BufferSize = 0;
    /* Initialize the Source_Inc member */
    DMA_InitStruct->Source_Inc = DMA_SOURCE_ADDR_INCREASE_DISABLE;
    /* Initialize the Desination_Inc member */
    DMA_InitStruct->Desination_Inc = DMA_DST_ADDR_INCREASE_DISABLE;
    /* Initialize the Source_Width member */
    DMA_InitStruct->Source_Width = DMA_SRC_WIDTH_BYTE;
    /* Initialize the Desination_Width member */
    DMA_InitStruct->Desination_Width = DMA_DST_WIDTH_BYTE;
    /* Initialize the DMA_Mode member */
    DMA_InitStruct->DMA_Mode = DMA_NORMAL;
}

/**
  * @brief  Enables or disables the specified DMA Channelx.
  * @param  DMA_Channelx: where DMA_Channelx can be DMA_Channel0 to 7 for DMA to select the DMA Channel.
  * @param  NewState: new state of the DMA Channelx. 
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void DMA_Cmd(DMA_Channel_TypeDef* DMA_Channelx, FunctionalState NewState)
{
    /* Check the parameters */
    assert_param(IS_DMA_ALL_PERIPH(DMA_Channelx));
    assert_param(IS_FUNCTIONAL_STATE(NewState));

    if (NewState != DISABLE)
    {
        /* Enable the selected DMA Channelx */
        DMA_Channelx->CONFIG |= DMA_CHANNEL_CONFIG_EN;
    }
    else
    {
        /* Disable the selected DMA Channelx */
        DMA_Channelx->CONFIG &= (~DMA_CHANNEL_CONFIG_EN);
    }
}

/**
  * @brief  Enables or disables the specified DMA Channelx interrupts.
  * @param  DMA_Channelx: where DMA_Channelx can be DMA_Channel0 to 7 for DMA to select the DMA Channel.
  * @param  DMA_IT: specifies the DMA interrupts sources to be enabled
  *   or disabled. 
  *   This parameter can be any combination of the following values:
  *     @arg DMA_CHANNEL_CONFIG_ITC:  Transfer complete interrupt mask
  *     @arg DMA_CHANNEL_CONFIG_IE:  Transfer error interrupt mask
  * @param  NewState: new state of the specified DMA interrupts.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void DMA_ITConfig(DMA_Channel_TypeDef* DMA_Channelx, uint32_t DMA_IT, FunctionalState NewState)
{
    /* Check the parameters */
    assert_param(IS_DMA_ALL_PERIPH(DMA_Channelx));
    assert_param(IS_DMA_CONFIG_IT(DMA_IT));
    assert_param(IS_FUNCTIONAL_STATE(NewState));
    if (NewState != DISABLE)
    {
        /* Enable the selected DMA interrupts */
        DMA_Channelx->CONFIG |= DMA_IT;
    }
    else
    {
        /* Disable the selected DMA interrupts */
        DMA_Channelx->CONFIG &= ~DMA_IT;
    }
}

/**
  * @brief  Sets the number of data units in the current DMA Channelx transfer.
  * @param  DMA_Channelx: where DMA_Channelx can be DMA_Channel0 to 7 for DMA to select the DMA Channel.
  * @param  DataNumber: The number of data units in the current DMA Channelx
  *         transfer.   
  * @note   This function can only be used when the DMA_Channelx is disabled.                 
  * @retval None.
  */
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMA_Channelx, uint16_t DataNumber)
{
    /* Check the parameters */
    assert_param(IS_DMA_ALL_PERIPH(DMA_Channelx));
  
/*--------------------------- DMA Channelx Transfer Size Configuration ---------------*/
    /* Set Transfer Size */
    DMA_Channelx->CTRL = (DMA_Channelx->CTRL & (~0xFFF)) | DataNumber;
}

/**
  * @brief  Returns the number of remaining data units in the current
  *         DMA Channelx transfer.
  * @param  DMA_Channelx: where DMA_Channelx can be DMA_Channel0 to 7 for DMA to select the DMA Channel.
  * @retval The number of remaining data units in the current DMA Channelx
  *         transfer.
  */
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMA_Channelx)
{
    /* Check the parameters */
    assert_param(IS_DMA_ALL_PERIPH(DMA_Channelx));
    /* Return the number of remaining data units for DMA Channelx */
    return ((uint16_t)(DMA_Channelx->CTRL & 0xFFF));
}

/**
  * @brief  Checks whether the specified DMA Channelx flag is set or not.
  * @param  DMA_FLAG: specifies the flag to check.
  *   This parameter can be one of the following values:
  *     @arg DMA_IT_FLAG_TC0~7: DMA Channel0~7 Transfer complete interrupt or flag.
  *     @arg DMA_IT_FLAG_TE0~7: DMA Channel0~7 Transfer error interrupt or flag.
  * @retval The new state of DMA_FLAG (SET or RESET).
  */
FlagStatus DMA_GetFlagStatus(uint32_t DMA_FLAG)
{
    FlagStatus bitstatus = RESET;
    uint32_t tmpreg = 0;

    /* Check the parameters */
    assert_param(IS_DMA_IT_FLAG(DMA_FLAG));

    if(DMA_FLAG < DMA_IT_FLAG_TE0)
    {
        /* Get DMA RAW_INT_TC_STATUS register value */
        tmpreg = DMA->RAW_INT_TC_STATUS ;
    }
    else
    {
        /* Get DMA RAW_INT_ERR_STATUS register value */
        tmpreg = DMA->RAW_INT_ERR_STATUS ;
        tmpreg <<= 16;
    }

    /* Check the status of the specified DMA flag */
    if ((tmpreg & DMA_FLAG) != (uint32_t)RESET)
    {
        /* DMA_FLAG is set */
        bitstatus = SET;
    }
    else
    {
        /* DMA_FLAG is reset */
        bitstatus = RESET;
    }

    /* Return the DMA_FLAG status */
    return  bitstatus;
}

/**
  * @brief  Clears the DMA Channelx's pending flags.
  * @param  DMA_FLAG: specifies the flag to clear.
  *   This parameter can be any combination (for the same DMA) of the following values:
  *     @arg DMA_IT_FLAG_TC0~7: DMA Channel0~7 Transfer complete interrupt or flag.
  *     @arg DMA_IT_FLAG_TE0~7: DMA Channel0~7 Transfer error interrupt or flag.
  * @retval None
  */
void DMA_ClearFlag(uint32_t DMA_FLAG)
{
    /* Check the parameters */
    assert_param(IS_DMA_IT_FLAG(DMA_FLAG));

    if(DMA_FLAG < DMA_IT_FLAG_TE0)
    {
        /* Get DMA INT_TC_CLR register value */
        DMA->INT_TC_CLR = DMA_FLAG ;
    }
    else
    {
        /* Get DMA INT_ERR_CLR register value */
        DMA->INT_ERR_CLR = (DMA_FLAG >> 16);
    }
}

/**
  * @brief  Checks whether the specified DMA Channelx interrupt has occurred or not.
  * @param  DMA_IT: specifies the DMA interrupt source to check. 
  *   This parameter can be one of the following values:
  *     @arg DMA_IT_FLAG_TC0~7: DMA Channel0~7 Transfer complete interrupt or flag.
  *     @arg DMA_IT_FLAG_TE0~7: DMA Channel0~7 Transfer error interrupt or flag.
  * @retval The new state of DMA_IT (SET or RESET).
  */
ITStatus DMA_GetITStatus(uint32_t DMA_IT)
{
    FlagStatus bitstatus = RESET;
    uint32_t tmpreg = 0;

    /* Check the parameters */
    assert_param(IS_DMA_IT_FLAG(DMA_IT));

    if(DMA_IT < DMA_IT_FLAG_TE0)
    {
        /* Get DMA INT_TC_STATUS register value */
        tmpreg = DMA->INT_TC_STATUS ;
    }
    else
    {
        /* Get DMA INT_ERR_STATUS register value */
        tmpreg = DMA->INT_ERR_STATUS ;
        tmpreg <<= 16;
    }

    /* Check the status of the specified DMA flag */
    if ((tmpreg & DMA_IT) != (uint32_t)RESET)
    {
        /* DMA_FLAG is set */
        bitstatus = SET;
    }
    else
    {
        /* DMA_FLAG is reset */
        bitstatus = RESET;
    }

    /* Return the DMA_FLAG status */
    return  bitstatus;
}

/**
  * @brief  Clears the DMA Channelx's interrupt pending bits.
  * @param  DMA_IT: specifies the DMA interrupt pending bit to clear.
  *   This parameter can be any combination (for the same DMA) of the following values:
  *     @arg DMA_IT_FLAG_TC0~7: DMA Channel0~7 Transfer complete interrupt or flag.
  *     @arg DMA_IT_FLAG_TE0~7: DMA Channel0~7 Transfer error interrupt or flag.
  * @retval None
  */
void DMA_ClearITPendingBit(uint32_t DMA_IT)
{
    /* Check the parameters */
    assert_param(IS_DMA_IT_FLAG(DMA_IT));

    if(DMA_IT < DMA_IT_FLAG_TE0)
    {
        /* Get DMA INT_TC_CLR register value */
        DMA->INT_TC_CLR = DMA_IT ;
    }
    else
    {
        /* Get DMA INT_ERR_CLR register value */
        DMA->INT_ERR_CLR = (DMA_IT >> 16);
    }
}

/**
  * @}
  */
