/*******************************************************************************
 * (c) Copyright 2009 Actel Corporation.  All rights reserved.
 * 
 *  SmartFusion A2FxxxM3 CMSIS system initialization.
 *
 * SVN $Revision: 3692 $
 * SVN $Date: 2011-06-20 10:29:56 +0100 (Mon, 20 Jun 2011) $
 */
#include "a2fxxxm3.h"
#include "mss_assert.h"

/* System frequency (FCLK) coming out of reset is 25MHz. */
#define RESET_SYSCLCK_FREQ      25000000uL

/*
 * SmartFusion Microcontroller Subsystem FLCK frequency.
 * The value of SMARTFUSION_FCLK_FREQ is used to report the system's clock
 * frequency in systems which either do not use the Actel System Boot or
 * a version of the Actel System Boot older than 1.3.1. In either of these cases
 * SMARTFUSION_FCLK_FREQ should be defined in the projects settings to reflect
 * the FCLK frequency selected in the Libero MSS configurator.
 * Systems using the Actel System Boot version 1.3.1 or later do not require this
 * define since the system's frequency is retrieved from eNVM spare pages where
 * the MSS Configurator stored the frequency selected during hardware design/configuration.
 */
#ifdef SMARTFUSION_FCLK_FREQ
#define SMARTFUSION_FCLK_FREQ_DEFINED   1
#else
#define SMARTFUSION_FCLK_FREQ_DEFINED   0
#define SMARTFUSION_FCLK_FREQ    RESET_SYSCLCK_FREQ
#endif

/* Divider values for APB0, APB1 and ACE clocks. */
#define RESET_PCLK0_DIV         4uL
#define RESET_PCLK1_DIV         4uL
#define RESET_ACE_DIV           4uL
#define RESET_FPGA_CLK_DIV      4uL

/* System register clock control mask and shift for PCLK dividers. */
#define PCLK_DIV_MASK       0x00000003uL
#define PCLK0_DIV_SHIFT     2uL
#define PCLK1_DIV_SHIFT     4uL
#define ACE_DIV_SHIFT       6uL

/* System register MSS_CCC_DIV_CR mask and shift for GLB (FPGA fabric clock). */
#define OBDIV_SHIFT         8uL
#define OBDIV_MASK          0x0000001FuL
#define OBDIVHALF_SHIFT     13uL
#define OBDIVHALF_MASK      0x00000001uL

/*
 * Actel system boot version defines used to extract the system clock from eNVM
 * spare pages.
 * These defines allow detecting the presence of Actel system boot in eNVM spare
 * pages and the version of that system boot executable and associated
 * configuration data.
 */
#define SYSBOOT_KEY_ADDR        	(uint32_t *)0x6008081C
#define SYSBOOT_KEY_VALUE       	0x4C544341uL
#define SYSBOOT_VERSION_ADDR    	(uint32_t *)0x60080840
#define SYSBOOT_1_3_FCLK_ADDR   	(uint32_t *)0x6008162C
#define SYSBOOT_2_x_FCLK_ADDR   	(uint32_t *)0x60081EAC
#define SYSBOOT_A2F060_FCLK_ADDR	(uint32_t *)0x6001EF2C
/*
 * The system boot version is stored in the least significant 24 bits of a word.
 * The FCLK is stored in eNVM from version 1.3.1 of the system boot. We expect
 * that the major version number of the system boot version will change if the
 * system boot configuration data layout needs to change. 
 */
#define SYSBOOT_VERSION_MASK    0x00FFFFFFuL
#define MIN_SYSBOOT_VERSION     0x00010301uL
#define SYSBOOT_VERSION_2_X     0x00020000uL
#define MAX_SYSBOOT_VERSION     0x00030000uL

/*
 * Device IDCODE location related defines. Used to find out which device we are
 * using.
 */
#define IDCODE_LOCATION				(uint32_t *)0x60080230u
#define IDCODE_DEV_REV_MASK         0xF0000000u
#define A2F060IFX_ID                0x05A111CFu


/* Standard CMSIS global variables. */
uint32_t SystemFrequency = SMARTFUSION_FCLK_FREQ;          /*!< System Clock Frequency (Core Clock) */
uint32_t SystemCoreClock = SMARTFUSION_FCLK_FREQ;          /*!< System Clock Frequency (Core Clock) */

/* SmartFusion specific clocks. */
uint32_t g_FrequencyPCLK0 = (SMARTFUSION_FCLK_FREQ / RESET_PCLK0_DIV);      /*!< Clock frequency of APB bus 0. */  
uint32_t g_FrequencyPCLK1 = (SMARTFUSION_FCLK_FREQ / RESET_PCLK1_DIV);      /*!< Clock frequency of APB bus 1. */
uint32_t g_FrequencyACE = (SMARTFUSION_FCLK_FREQ / RESET_ACE_DIV);          /*!< Clock frequency of Analog Compute Engine. */
uint32_t g_FrequencyFPGA = (SMARTFUSION_FCLK_FREQ / RESET_FPGA_CLK_DIV);    /*!< Clock frequency of FPGA fabric */

/* Local functions */
static uint32_t GetSystemClock( void );

/***************************************************************************//**
 * See system_a2fxxxm3f.h for details.
 */
void SystemInit(void)
{
    /*
     * Do not make use of global variables or make any asumptions regarding
     * memory content if modifying this function. The memory content has not been
     * initialised by the time this function is called by the start-up code.
     */
}

/***************************************************************************//**
 *
 */
void SystemCoreClockUpdate (void)
{
    uint32_t PclkDiv0;
    uint32_t PclkDiv1;
    uint32_t AceDiv;
    uint32_t FabDiv;

    const uint32_t pclk_div_lut[4] = { 1uL, 2uL, 4uL, 1uL };

    /* Read PCLK dividers from system registers. Multiply the value read from
     * system register by two to get actual divider value. */
    PclkDiv0 = pclk_div_lut[((SYSREG->MSS_CLK_CR >> PCLK0_DIV_SHIFT) & PCLK_DIV_MASK)];
    PclkDiv1 = pclk_div_lut[((SYSREG->MSS_CLK_CR >> PCLK1_DIV_SHIFT) & PCLK_DIV_MASK)];
    AceDiv = pclk_div_lut[((SYSREG->MSS_CLK_CR >> ACE_DIV_SHIFT) & PCLK_DIV_MASK)];
    {
        /* Compute the FPGA fabric frequency divider. */
        uint32_t obdiv;
        uint32_t obdivhalf;
        
        obdiv = (SYSREG->MSS_CCC_DIV_CR >> OBDIV_SHIFT) & OBDIV_MASK;
        obdivhalf = (SYSREG->MSS_CCC_DIV_CR >> OBDIVHALF_SHIFT) & OBDIVHALF_MASK;
        FabDiv = obdiv + 1uL;
        if ( obdivhalf != 0uL )
        {
            FabDiv = FabDiv * 2uL;
        }
    }
    
    /* Retrieve FCLK from eNVM spare pages if Actel system boot programmed as part of the system. */
    
    /* Read system clock from eNVM spare pages. */
    SystemCoreClock = GetSystemClock();
    g_FrequencyPCLK0 = SystemCoreClock / PclkDiv0;
    g_FrequencyPCLK1 = SystemCoreClock / PclkDiv1;
    g_FrequencyACE = SystemCoreClock / AceDiv;
    g_FrequencyFPGA = SystemCoreClock / FabDiv;
    
    /* Keep SystemFrequency as well as SystemCoreClock for legacy reasons. */
    SystemFrequency = SystemCoreClock;
}

/***************************************************************************//**
 * Retrieve the system clock frequency from eNVM spare page if available.
 * Returns the frequency defined through SMARTFUSION_FCLK_FREQ if FCLK cannot be
 * retrieved from eNVM spare pages.
 * The FCLK frequency value selected in the MSS Configurator software tool is
 * stored in eNVM spare pages as part of the Actel system boot configuration data.
 */
uint32_t GetSystemClock( void )
{
    uint32_t fclk = 0uL;
    
    uint32_t * p_sysboot_key = SYSBOOT_KEY_ADDR;
    uint32_t * p_idcode = IDCODE_LOCATION;
    uint32_t idcode;
	
    idcode = *p_idcode & ~IDCODE_DEV_REV_MASK;
	
    if ( A2F060IFX_ID == idcode )
    {
        uint32_t *p_fclk = SYSBOOT_A2F060_FCLK_ADDR;
        fclk = *p_fclk;
    }
    else if ( SYSBOOT_KEY_VALUE == *p_sysboot_key )
    {
        /* Actel system boot programmed, check if it has the FCLK value stored. */
        uint32_t *p_sysboot_version = SYSBOOT_VERSION_ADDR;
        uint32_t sysboot_version = *p_sysboot_version;
        
        sysboot_version &= SYSBOOT_VERSION_MASK;
        
        if ( sysboot_version >= MIN_SYSBOOT_VERSION )
        {
            /* Handle change of eNVM location of FCLK between 1.3.x and 2.x.x versions of the system boot. */
            if ( sysboot_version < SYSBOOT_VERSION_2_X )
            {
                /* Read FCLK value from MSS configurator generated configuration
                 * data stored in eNVM spare pages as part of system boot version 1.3.x
                 * configuration tables. */
                uint32_t *p_fclk = SYSBOOT_1_3_FCLK_ADDR;
                fclk = *p_fclk;
            }
            else if ( sysboot_version < MAX_SYSBOOT_VERSION )
            {
                /* Read FCLK value from MSS configurator generated configuration
                 * data stored in eNVM spare pages as part of system boot version 2.x.x
                 * configuration tables. */
                uint32_t *p_fclk = SYSBOOT_2_x_FCLK_ADDR;
                fclk = *p_fclk;
            }
            else
            {
                fclk = 0uL;
            }
        }
    }
    
    if ( 0uL == fclk )
    {
        /* 
         * Could not retrieve FCLK from system boot configuration data. Fall back
         * to using SMARTFUSION_FCLK_FREQ which must then be defined as part of
         * project settings.
         */
        ASSERT( SMARTFUSION_FCLK_FREQ_DEFINED );
        fclk = SMARTFUSION_FCLK_FREQ;
    }
    
    return fclk;
}

