/******************************************************************************
*@file  : std_mpu.c
*@brief : MPU  module driver
******************************************************************************/


#include "acm32g103_spl_conf.h" 

//#ifdef SPL_MPU_MODULE_ENABLED

static void MPU_ConfigRegion(MPU_Type* MPUx, MPU_Region_InitTypeDef *MPU_RegionInit);
static void MPU_ConfigMemoryAttributes(MPU_Type* MPUx, MPU_Attributes_InitTypeDef *MPU_AttributesInit);


/******************************************************************************
*@brief : Enable the MPU.
*@param : MPU_Control: Specifies the control mode of the MPU during hard fault,
*           NMI, FAULTMASK and privileged access to the default memory
*           This parameter can be one of the following values:
*           @arg MPU_HFNMI_PRIVDEF_NONE
*           @arg MPU_HARDFAULT_NMI
*           @arg MPU_PRIVILEGED_DEFAULT
*           @arg MPU_HFNMI_PRIVDEF
*@ret   : None
******************************************************************************/
void SPL_MPU_Enable(uint32_t MPU_Control)
{
    /* Enable the MPU */
    MPU->CTRL = MPU_Control | MPU_CTRL_ENABLE_Msk;
  
    /* Enable fault exceptions */
    SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
  
    /* Ensure MPU setting take effects */
    __DSB();
    __ISB();
}

/******************************************************************************
*@brief : Disables the MPU. 
*@ret   : None
******************************************************************************/
void SPL_MPU_Disable(void)
{
    /* Make sure outstanding transfers are done */
    __DMB();

    /* Disable fault exceptions */
    SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk;
  
    /* Disable the MPU and clear the control register*/
    MPU->CTRL = 0U;
}


/******************************************************************************
*@brief : Initializes and configures the Region and the memory to be protected.
*@param : MPU_RegionInit: Pointer to a MPU_Region_InitTypeDef structure that contains 
*           the initialization and configuration information.
*@ret   : None
******************************************************************************/
void SPL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_RegionInit)
{
    MPU_ConfigRegion(MPU, MPU_RegionInit);
}

/******************************************************************************
*@brief : Initialize and configure the memory attributes.
*@param : MPU_AttributesInit Pointer to a MPU_Attributes_InitTypeDef structure that contains
*           the initialization and configuration information.
*@ret   : None
******************************************************************************/
void SPL_MPU_ConfigMemoryAttributes(MPU_Attributes_InitTypeDef *MPU_AttributesInit)
{
    MPU_ConfigMemoryAttributes(MPU, MPU_AttributesInit);
}

static void MPU_ConfigRegion(MPU_Type* MPUx, MPU_Region_InitTypeDef *MPU_RegionInit)
{
    /* Check the parameters */
    assert_param(IS_MPU_REGION_NUMBER(MPU_RegionInit->Number));
    assert_param(IS_MPU_REGION_ENABLE(MPU_RegionInit->Enable));

    /* Follow ARM recommendation with Data Memory Barrier prior to MPU configuration */
    __DMB();

    /* Set the Region number */
    MPUx->RNR = MPU_RegionInit->Number;

    if (MPU_RegionInit->Enable != MPU_REGION_DISABLE)
    {
        /* Check the parameters */
        assert_param(IS_MPU_INSTRUCTION_ACCESS(MPU_RegionInit->DisableExec));
        assert_param(IS_MPU_REGION_PERMISSION_ATTRIBUTE(MPU_RegionInit->AccessPermission));
        assert_param(IS_MPU_ACCESS_SHAREABLE(MPU_RegionInit->IsShareable));

        MPUx->RBAR = (((uint32_t)MPU_RegionInit->BaseAddress & 0xFFFFFFE0U)  |
                  ((uint32_t)MPU_RegionInit->IsShareable      << MPU_RBAR_SH_Pos)  |
                  ((uint32_t)MPU_RegionInit->AccessPermission << MPU_RBAR_AP_Pos)  |
                  ((uint32_t)MPU_RegionInit->DisableExec      << MPU_RBAR_XN_Pos));

        MPUx->RLAR = (((uint32_t)MPU_RegionInit->LimitAddress & 0xFFFFFFE0U) |
                  ((uint32_t)MPU_RegionInit->AttributesIndex << MPU_RLAR_AttrIndx_Pos) |
                  ((uint32_t)MPU_RegionInit->Enable          << MPU_RLAR_EN_Pos));
    }
    else
    {
        MPUx->RBAR = 0U;
        MPUx->RLAR = 0U;
    }
}

static void MPU_ConfigMemoryAttributes(MPU_Type* MPUx, MPU_Attributes_InitTypeDef *MPU_AttributesInit)
{
    __IO uint32_t *mair;
    uint32_t      attr_values;
    uint32_t      attr_number;

    /* Check the parameters */
    assert_param(IS_MPU_ATTRIBUTES_NUMBER(MPU_AttributesInit->Number));
    /* No need to check Attributes value as all 0x0..0xFF possible */

    /* Follow ARM recommendation with Data Memory Barrier prior to MPUx configuration */
    __DMB();

    if(MPU_AttributesInit->Number < MPU_ATTRIBUTES_NUMBER4)
    {
        /* Program MPU_MAIR0 */
        mair = &(MPUx->MAIR0);
        attr_number = MPU_AttributesInit->Number;
    }
    else
    {
        /* Program MPU_MAIR1 */
        mair = &(MPUx->MAIR1);
        attr_number = (uint32_t)MPU_AttributesInit->Number - 4U;
    }

    attr_values = *(mair);
    attr_values &=  ~(0xFFU << (attr_number * 8U));
    *(mair) = attr_values | ((uint32_t)MPU_AttributesInit->Attributes << (attr_number * 8U));
}


// default mpu attrs
static const uint8_t MPU_attrs[8] = 
{
    MPU_ATTR_NO_CACHE,
    MPU_ATTR_WRITE_THROUGH,
    MPU_ATTR_WRITE_BACK,
    MPU_ATTR_DEVICE, 
    0,
    0,
    0,
    0
};

/******************************************************************************
*@brief : MPU config.        
*@param : MPU_configInit: mpu config init parameter
*@return: void
******************************************************************************/
void SPL_MPU_Config(MPU_ConfigInitTypeDef *MPU_configInit)
{
    uint32_t i,j,count,attrIndex;
    uint32_t ctrl;
    MPU_RegionConfigTypeDef *pRegion;
    
    SPL_MPU_Disable();
    
    __DMB();
    
    //Config Memory Attributes
    MPU->MAIR0 = (uint32_t)(MPU_attrs[0] | (MPU_attrs[1]<<8) | (MPU_attrs[2]<<16) | (MPU_attrs[3]<<24));    
    MPU->MAIR1 = (uint32_t)(MPU_attrs[4] | (MPU_attrs[5]<<8) | (MPU_attrs[6]<<16) | (MPU_attrs[7]<<24));

    count = MPU_configInit->RegionCount;    
    if(count>8)
    {
        count = 8;
    }
    
    for(i=0; i<count; i++)
    {
        pRegion = MPU_configInit->RegionConfigs + i;  
        
        //get index by attribute
        attrIndex = 0; //default attr index
        for(j=0;j<8;j++)
        {
            if(MPU_attrs[j] == pRegion->Attr)
            {
                attrIndex = j;
                break; 
            }
        }        
 
        //config region
        MPU->RNR = i; 
        
        MPU->RBAR = ((pRegion->BaseAddr & MPU_RBAR_BASE_Msk) | 
                     ((pRegion->Access<<MPU_RBAR_AP_Pos) & MPU_RBAR_SH_Msk) |
                     ((pRegion->Execute<<MPU_RBAR_XN_Pos) & MPU_RBAR_XN_Msk));

        MPU->RLAR = ((pRegion->LimitAddr & MPU_RLAR_LIMIT_Msk) | 
                     ((attrIndex<<MPU_RLAR_AttrIndx_Pos) & MPU_RLAR_AttrIndx_Msk) |
                     (MPU_RLAR_EN_Msk));             
    }
    
    for(; i<8; i++)
    {
        MPU->RNR = i;
        MPU->RBAR = 0;
        MPU->RLAR = 0;   
    }
    
    
    ctrl = (((MPU_configInit->PrivDef<<MPU_CTRL_PRIVDEFENA_Pos)&MPU_CTRL_PRIVDEFENA_Msk) | 
            ((MPU_configInit->HfNmi<<MPU_CTRL_HFNMIENA_Pos)&MPU_CTRL_HFNMIENA_Msk) |
            (MPU_CTRL_ENABLE_Msk));             
    
    SPL_MPU_Enable(ctrl); //enable mpu
    __DMB();
}


//#endif /* STD_CORTEX_MODULE_ENABLED */


