/*
  ******************************************************************************
  * @file    HAL_DMA.c
  * @version V1.0.0
  * @date    2020
  * @brief   DMA HAL 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 "ACM32Fxx_HAL.h"

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

/*********************************************************************************
* Function    : HAL_DMA_IRQHandler
* Description : This function handles DMA interrupt request.
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
__weak void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
    uint32_t lu32_Channel_Index;
    
    /* Get DMA Channel number */
    lu32_Channel_Index = ((uint32_t)(hdma->Instance) - (uint32_t)(DMA_Channel0)) / 0x20;

    /* Channel has been interrupted */
    if (DMA->INT_STATUS & (1 << lu32_Channel_Index))
    {
        /* Transfer complete interrupt */
        if (DMA->INT_TC_STATUS & (1 << lu32_Channel_Index))
        {
            DMA->INT_TC_CLR |= (1 << lu32_Channel_Index);

            if (NULL != hdma->DMA_ITC_Callback)
            {
                hdma->DMA_ITC_Callback();
            }
        }
        
        /* Transfer error interrupt */
        if (DMA->INT_ERR_STATUS & (1 << lu32_Channel_Index))
        {
            DMA->INT_ERR_CLR |= (1 << lu32_Channel_Index);

            if (NULL != hdma->DMA_IE_Callback)
            {
                hdma->DMA_IE_Callback();
            }
        }
    }
}

/*********************************************************************************
* Function    : HAL_DMA_Init
* Description : DMA initial with parameters. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma) 
{
#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))                 return HAL_ERROR;
    if (!IS_DMA_DATA_FLOW(hdma->Init.Data_Flow))              return HAL_ERROR;
    if (!IS_DMA_REQUEST_ID(hdma->Init.Request_ID))            return HAL_ERROR;
    if (!IS_DMA_SRC_WIDTH(hdma->Init.Source_Width))           return HAL_ERROR;
    if (!IS_DMA_DST_WIDTH(hdma->Init.Desination_Width))       return HAL_ERROR;
#endif

    /* Enable DMA Module */
    System_Module_Enable(EN_DMA);
    
    /* Enable External Interrupt */
    NVIC_ClearPendingIRQ(DMA_IRQn);
    NVIC_EnableIRQ(DMA_IRQn);

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

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

    hdma->Instance->CONFIG = hdma->Init.Data_Flow | hdma->Init.Request_ID;
    
    /* Config Channel Control */
    hdma->Instance->CTRL = DMA_CHANNEL_CTRL_ITC;
    /* Source or Desination address increase */
    hdma->Instance->CTRL |= (hdma->Init.Desination_Inc | hdma->Init.Source_Inc);
    /* Source or Desination date width */
    hdma->Instance->CTRL |= (hdma->Init.Desination_Width | hdma->Init.Source_Width);

    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_DeInit
* Description : DMA De-initial with parameters. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_DeInit(DMA_HandleTypeDef *hdma) 
{
#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))                 return HAL_ERROR;
    if (!IS_DMA_DATA_FLOW(hdma->Init.Data_Flow))              return HAL_ERROR;
    if (!IS_DMA_REQUEST_ID(hdma->Init.Request_ID))            return HAL_ERROR;
    if (!IS_DMA_SRC_WIDTH(hdma->Init.Source_Width))           return HAL_ERROR;
    if (!IS_DMA_DST_WIDTH(hdma->Init.Desination_Width))       return HAL_ERROR;
#endif

    /* Reset DMA Module */
    System_Module_Reset(RST_DMA);
    
    /* Disable DMA Module */
    System_Module_Disable(EN_DMA);
    
    /* Disable Interrupt */
    NVIC_ClearPendingIRQ(DMA_IRQn);
    NVIC_DisableIRQ(DMA_IRQn);
    
    hdma->DMA_IE_Callback = NULL;
    hdma->DMA_ITC_Callback = NULL;

    memset(&hdma->Init, 0, sizeof(hdma->Init));
    
    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_NormalMode_Start
* Description : DMA transfer start. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Input       : fu32_SrcAddr: source address
* Input       : fu32_DstAddr: desination address
* Input       : fu32_Size:    transfer size (This parameter can be a 12-bit Size)
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_NormalMode_Start(DMA_HandleTypeDef *hdma, uint32_t fu32_SrcAddr, uint32_t fu32_DstAddr, uint32_t fu32_Size)
{
#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))    return HAL_ERROR;
#endif
    
    if (fu32_Size > 0xFFF) 
    {
        return HAL_ERROR;
    }

    /* Set source address and desination address */
    hdma->Instance->SRC_ADDR  = fu32_SrcAddr;
    hdma->Instance->DEST_ADDR = fu32_DstAddr;

    /* Set Transfer Size */
    hdma->Instance->CTRL = (hdma->Instance->CTRL & (~0xFFF)) | fu32_Size;

    /* DMA Channel Enable */
    hdma->Instance->CONFIG |= DMA_CHANNEL_CONFIG_EN;

    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_NormalMode_Start_IT
* Description : DMA transfer start with interrupt. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Input       : fu32_SrcAddr: source address
* Input       : fu32_DstAddr: desination address
* Input       : fu32_Size:    transfer size (This parameter can be a 12-bit Size)
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_NormalMode_Start_IT(DMA_HandleTypeDef *hdma, uint32_t fu32_SrcAddr, uint32_t fu32_DstAddr, uint32_t fu32_Size)
{
#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))    return HAL_ERROR;
#endif

    /* Set source address and desination address */
    hdma->Instance->SRC_ADDR  = fu32_SrcAddr;
    hdma->Instance->DEST_ADDR = fu32_DstAddr;

    /* Set Transfer Size and enable LLI interrupt */
    hdma->Instance->CTRL = (hdma->Instance->CTRL & (~0xFFF)) | fu32_Size;

    /* DMA Channel Enable and enable transfer error interrupt and transfer complete interrupt*/
    hdma->Instance->CONFIG |= DMA_CHANNEL_CONFIG_ITC | DMA_CHANNEL_CONFIG_IE | DMA_CHANNEL_CONFIG_EN;

    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_CycleMode_Start
* Description : DMA Cycle transfer start. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Input       : fu32_SrcAddr: source address
* Input       : fu32_DstAddr: desination address
* Input       : fu32_Size:    transfer size (This parameter can be a 12-bit Size)
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_CycleMode_Start(DMA_HandleTypeDef *hdma, uint32_t fu32_SrcAddr, uint32_t fu32_DstAddr, uint32_t fu32_Size)
{
    uint32_t lu32_Channel_Index;

#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))    return HAL_ERROR;
#endif

    /* Get DMA Channel number */
    lu32_Channel_Index = ((uint32_t)(hdma->Instance) - (uint32_t)(DMA_Channel0)) / 0x20;
    
    /* Set source address and desination address */
    hdma->Instance->SRC_ADDR  = fu32_SrcAddr;
    hdma->Instance->DEST_ADDR = fu32_DstAddr;

    /* Set Next Link */
    hdma->Instance->LLI = (uint32_t)&Cycle_Channel[lu32_Channel_Index];

    /* Set Transfer Size */
    hdma->Instance->CTRL = (hdma->Instance->CTRL & (~0xFFF)) | fu32_Size;

    /* The list point to oneself */
    Cycle_Channel[lu32_Channel_Index].SrcAddr = fu32_SrcAddr;
    Cycle_Channel[lu32_Channel_Index].DstAddr = fu32_DstAddr;
    Cycle_Channel[lu32_Channel_Index].Next    = &Cycle_Channel[lu32_Channel_Index];
    Cycle_Channel[lu32_Channel_Index].Control = (hdma->Instance->CTRL & (~0xFFF)) | fu32_Size;

    /* DMA Channel Enable */
    hdma->Instance->CONFIG |= DMA_CHANNEL_CONFIG_EN;

    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_CycleMode_Start_IT
* Description : DMA Cycle transfer start with interrupt. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Input       : fu32_SrcAddr: source address
* Input       : fu32_DstAddr: desination address
* Input       : fu32_Size:    transfer size (This parameter can be a 12-bit Size)
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_CycleMode_Start_IT(DMA_HandleTypeDef *hdma, uint32_t fu32_SrcAddr, uint32_t fu32_DstAddr, uint32_t fu32_Size)
{
    uint32_t lu32_Channel_Index;

#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))    return HAL_ERROR;
#endif

    /* Get DMA Channel number */
    lu32_Channel_Index = ((uint32_t)(hdma->Instance) - (uint32_t)(DMA_Channel0)) / 0x20;
    
    /* Set source address and desination address */
    hdma->Instance->SRC_ADDR  = fu32_SrcAddr;
    hdma->Instance->DEST_ADDR = fu32_DstAddr;

    /* Set Next Link */
    hdma->Instance->LLI = (uint32_t)&Cycle_Channel[lu32_Channel_Index];

    /* Set Transfer Size */
    hdma->Instance->CTRL = (hdma->Instance->CTRL & (~0xFFF)) | fu32_Size;

    /* The list point to oneself */
    Cycle_Channel[lu32_Channel_Index].SrcAddr = fu32_SrcAddr;
    Cycle_Channel[lu32_Channel_Index].DstAddr = fu32_DstAddr;
    Cycle_Channel[lu32_Channel_Index].Next    = &Cycle_Channel[lu32_Channel_Index];
    Cycle_Channel[lu32_Channel_Index].Control = (hdma->Instance->CTRL & (~0xFFF)) | fu32_Size;

    /* DMA Channel Enable and enable transfer error interrupt and transfer complete interrupt*/
    hdma->Instance->CONFIG |= DMA_CHANNEL_CONFIG_ITC | DMA_CHANNEL_CONFIG_IE | DMA_CHANNEL_CONFIG_EN;

    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_Start
* Description : DMA transfer start. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Input       : fu32_SrcAddr: source address
* Input       : fu32_DstAddr: desination address
* Input       : fu32_Size:    transfer size (This parameter can be a 12-bit Size)
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t fu32_SrcAddr, uint32_t fu32_DstAddr, uint32_t fu32_Size)
{
    /* Check DMA Parameter */
    if (!IS_DMA_MODE(hdma->Init.Mode))    return HAL_ERROR;
        
    if (hdma->Init.Mode == DMA_NORMAL)
    {
        return HAL_DMA_NormalMode_Start(hdma, fu32_SrcAddr, fu32_DstAddr, fu32_Size);
    }
    else
    {
        return HAL_DMA_CycleMode_Start(hdma, fu32_SrcAddr, fu32_DstAddr, fu32_Size);
    }
}

/*********************************************************************************
* Function    : HAL_DMA_Start_IT
* Description : DMA transfer start with interrupt. 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Input       : fu32_SrcAddr: source address
* Input       : fu32_DstAddr: desination address
* Input       : fu32_Size:    transfer size (This parameter can be a 12-bit Size)
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t fu32_SrcAddr, uint32_t fu32_DstAddr, uint32_t fu32_Size)
{
    /* Check DMA Parameter */
    if (!IS_DMA_MODE(hdma->Init.Mode))    return HAL_ERROR;

    if (hdma->Init.Mode == DMA_NORMAL)
    {
        return HAL_DMA_NormalMode_Start_IT(hdma, fu32_SrcAddr, fu32_DstAddr, fu32_Size);
    }
    else
    {
        return HAL_DMA_CycleMode_Start_IT(hdma, fu32_SrcAddr, fu32_DstAddr, fu32_Size);
    }
}

/*********************************************************************************
* Function    : HAL_DMA_Abort
* Description : Abort the DMA Transfer 
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Output      : 
* Author      : Chris_Kyle                         Data : 2020
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
{
    uint32_t lu32_Channel_Index;

#if (USE_FULL_ASSERT == 1)
    /* Check DMA Parameter */
    if (!IS_DMA_ALL_INSTANCE(hdma->Instance))    return HAL_ERROR;
#endif

    /* Get DMA Channel number */
    lu32_Channel_Index = ((uint32_t)(hdma->Instance) - (uint32_t)(DMA_Channel0)) / 0x20;

    /* DMA Channel Disable */
    hdma->Instance->CONFIG &= ~(1 << 0);

    /* Clear TC ERR Falg */
    DMA->INT_TC_CLR  |= (1 << lu32_Channel_Index);
    DMA->INT_ERR_CLR |= (1 << lu32_Channel_Index);

    return HAL_OK;
}

/*********************************************************************************
* Function    : HAL_DMA_GetState
* Description : Returns the DMA state..
* Input       : hdma : pointer to a DMA_HandleTypeDef structure that contains
*                      the configuration information for DMA module
* Output      : 
* Author      :                          Data : 2021
**********************************************************************************/
HAL_StatusTypeDef HAL_DMA_GetState(DMA_HandleTypeDef *hdma)
{
    uint32_t lu32_Channel_Index;
    HAL_StatusTypeDef States = HAL_ERROR;
    
    /* Get DMA Channel number */
    lu32_Channel_Index = ((uint32_t)(hdma->Instance) - (uint32_t)(DMA_Channel0)) / 0x20;

    /* Transfer complete interrupt */
    if (DMA->RAW_INT_TC_STATUS & (1 << lu32_Channel_Index))
    {
        DMA->INT_TC_CLR |= (1 << lu32_Channel_Index);

        States = HAL_OK;
    }
    
    /* Transfer error interrupt */
    if (DMA->RAW_INT_ERR_STATUS & (1 << lu32_Channel_Index))
    {
        DMA->INT_ERR_CLR |= (1 << lu32_Channel_Index);

        States = HAL_ERROR;
    }
    
    return States;
}