#include "STM32F0x1.h"
#include "system.h"

#if !defined  (HSE_VALUE)     
#define HSE_VALUE        ((uint32_t)8000000)           /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
#if !defined  (HSI_VALUE) 
#define HSI_VALUE        ((uint32_t)8000000)           /*!< Value of the Internal High Speed oscillator in Hz. */
#endif /* HSI_VALUE */

#define HSE_STARTUP_TIMEOUT   ((uint16_t)0x5000)       /*!< Time out for HSE start up */
//! [EnumExampleSW_EN_Def]
typedef enum {
  USEHSI = 0, USEHSE, USEPLL
} SW_EN;
//! [EnumExampleSW_EN_Def]
typedef enum {
  RCC_CFGR_PLLMUL2 = 0,
  RCC_CFGR_PLLMUL3, 
  RCC_CFGR_PLLMUL4, 
  RCC_CFGR_PLLMUL5, 
  RCC_CFGR_PLLMUL6, 
  RCC_CFGR_PLLMUL7, 
  RCC_CFGR_PLLMUL8, 
  RCC_CFGR_PLLMUL9, 
  RCC_CFGR_PLLMUL10,
  RCC_CFGR_PLLMUL11,
  RCC_CFGR_PLLMUL12,
  RCC_CFGR_PLLMUL13,
  RCC_CFGR_PLLMUL14,
  RCC_CFGR_PLLMUL15,
  RCC_CFGR_PLLMUL16,
} PLLML_EN;

/* Select the PLL clock source */
//#define PLL_SOURCE_HSI        // HSI (~8MHz) used to clock the PLL, and the PLL is used as system clock source
  #define PLL_SOURCE_HSE        // HSE (8MHz) used to clock the PLL, and the PLL is used as system clock source
//#define PLL_SOURCE_HSE_BYPASS // HSE bypassed with an external clock (8MHz, coming from ST-Link) used to clock
// the PLL, and the PLL is used as system clock source

uint32_t SystemCoreClock    = 48000000;
const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};


static void SetSysClock (void);

/**
  * @brief  Setup the microcontroller system.
  *         Initialize the Embedded Flash Interface, the PLL and update the
  *         SystemCoreClock variable.
  * @param  None
  * @retval None
  */
extern "C"
void SystemInit (void) {
  /* Set HSION bit */
  RCC.CR.R    |= (uint32_t) 0x00000001;
  /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE and MCOSEL[2:0] bits */
  RCC.CFGR.R  &= (uint32_t) 0xF8FFB80C;
  /* Reset HSEON, CSSON and PLLON bits */
  RCC.CR.R    &= (uint32_t) 0xFEF6FFFF;
  /* Reset HSEBYP bit */
  RCC.CR.R    &= (uint32_t) 0xFFFBFFFF;
  /* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */
  RCC.CFGR.R  &= (uint32_t) 0xFFC0FFFF;
  /* Reset PREDIV1[3:0] bits */
  RCC.CFGR2.R &= (uint32_t) 0xFFFFFFF0;
  /* Reset USARTSW[1:0], I2CSW, CECSW and ADCSW bits */
  RCC.CFGR3.R &= (uint32_t) 0xFFFFFEAC;
  /* Reset HSI14 bit */
  RCC.CR2.R   &= (uint32_t) 0xFFFFFFFE;
  /* Disable all interrupts */
  RCC.CIR.R = 0x00000000u;
  /* Configure the System clock frequency, AHB/APBx prescalers and Flash settings */
  SetSysClock();
}

/**
  * @brief  Update SystemCoreClock according to Clock Register Values
  *         The SystemCoreClock variable contains the core clock (HCLK), it can
  *         be used by the user application to setup the SysTick timer or configure
  *         other parameters.
  *
  * @note   Each time the core clock (HCLK) changes, this function must be called
  *         to update SystemCoreClock variable value. Otherwise, any configuration
  *         based on this variable will be incorrect.
  *
  * @note   - The system frequency computed by this function is not the real
  *           frequency in the chip. It is calculated based on the predefined
  *           constant and the selected clock source:
  *
  *           - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*)
  *
  *           - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**)
  *
  *           - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**)
  *             or HSI_VALUE(*) multiplied/divided by the PLL factors.
  *
  *         (*) HSI_VALUE is a constant defined in stm32f0xx.h file (default value
  *             8 MHz) but the real value may vary depending on the variations
  *             in voltage and temperature.
  *
  *         (**) HSE_VALUE is a constant defined in stm32f0xx.h file (default value
  *              8 MHz), user has to ensure that HSE_VALUE is same as the real
  *              frequency of the crystal used. Otherwise, this function may
  *              have wrong result.
  *
  *         - The result of this function could be not correct when using fractional
  *           value for HSE crystal.
  * @param  None
  * @retval None
  */
extern "C"
void SystemCoreClockUpdate (void) {
  uint32_t prediv1factor, pllmull;

//! [EnumExampleSW_EN_Use]
  switch (RCC.CFGR.B.SWS) {
    case USEHSI:  /* HSI used as system clock */
      SystemCoreClock = HSI_VALUE;
      break;
    case USEHSE:  /* HSE used as system clock */
      SystemCoreClock = HSE_VALUE;
      break;
    case USEPLL:  /* PLL used as system clock */
      /* Get PLL clock source and multiplication factor */
      pllmull = RCC.CFGR.B.PLLMUL + 2u;
      // ...
//! [EnumExampleSW_EN_Use]
      if (RCC.CFGR.B.PLLSRC == RESET) {
        /* HSI oscillator clock divided by 2 selected as PLL clock entry */
        SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
      } else {
        prediv1factor = RCC.CFGR2.B.PREDIV + 1;
        /* HSE oscillator clock selected as PREDIV1 clock entry */
        SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull;
      }
      break;
    default: /* HSI used as system clock */
      SystemCoreClock = HSI_VALUE;
      break;
  }
  /* Compute HCLK clock frequency */
  /* Get HCLK prescaler */
  pllmull = AHBPrescTable[RCC.CFGR.B.HPRE];
  /* HCLK clock frequency */
  SystemCoreClock >>= pllmull;
}

/**
  * @brief  Configures the System clock frequency, AHB/APBx prescalers and Flash
  *         settings.
  * @note   This function should be called only once the RCC clock configuration
  *         is reset to the default reset state (done in SystemInit() function).
  * @param  None
  * @retval None
  */

static void SetSysClock (void) {
  /* SYSCLK, HCLK, PCLK configuration */
#if defined (PLL_SOURCE_HSI)
  /* At this stage the HSI is already enabled */
  /* Enable Prefetch Buffer and set Flash Latency */
  Flash.ACR.setbit([] (auto & r) -> auto { // C++14
    r.B.PRFTBE  = SET;
    r.B.LATENCY = SET;
    return r.R;
  });
  RCC.CFGR.modify([] (auto & r) -> auto {
    r.B.HPRE     = 0;
    r.B.PPRE     = 0;
    r.B.PLLSRC   = RESET;
    r.B.PLLXTPRE = RESET;
    r.B.PLLMUL   = RCC_CFGR_PLLMUL12;
    return r.R;
  });
  /* Enable PLL */
  RCC.CR.B.PLLON = SET;
  /* Wait till PLL is ready */
  while ((RCC.CR.B.PLLRDY) == RESET);
  /* Select PLL as system clock source */
  RCC.CFGR.B.SW = USEPLL;
  /* Wait till PLL is used as system clock source */
  while (RCC.CFGR.B.SWS != USEPLL);
#else
#if defined (PLL_SOURCE_HSE)
  /* Enable HSE */
  RCC.CR.B.HSEON = SET;
#elif defined (PLL_SOURCE_HSE_BYPASS)
  /* HSE oscillator bypassed with external clock */
  RCC.CR.B.HSEON  = SET;
  RCC.CR.B.HSEBYP = SET;
#endif /* PLL_SOURCE_HSE */
  __IO uint32_t StartUpCounter = 0;
  __IO uint32_t HSEStatus;
  /* Wait till HSE is ready and if Time out is reached exit */
  do {
    HSEStatus = RCC.CR.B.HSERDY;
    StartUpCounter++;
  } while ((HSEStatus == RESET) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  HSEStatus = RCC.CR.B.HSERDY;

  if (HSEStatus == SET) {
    /* Enable Prefetch Buffer and set Flash Latency */
    Flash.ACR.setbit([] (auto & r) -> uint32_t {
      r.B.PRFTBE  = SET;
      r.B.LATENCY = SET;
      return r.R;
    });
    RCC.CFGR.modify([] (auto & r) -> uint32_t {
      r.B.HPRE     = 0;
      r.B.PPRE     = 0;
      r.B.PLLSRC   = SET;
      r.B.PLLXTPRE = RESET;
      r.B.PLLMUL   = RCC_CFGR_PLLMUL12;
      return r.R;
    });
    /* Enable PLL */
    RCC.CR.B.PLLON = SET;
    /* Wait till PLL is ready */
    while ((RCC.CR.B.PLLRDY) == RESET);
    /* Select PLL as system clock source */
    RCC.CFGR.B.SW = USEPLL;
    /* Wait till PLL is used as system clock source */
    while (RCC.CFGR.B.SWS != USEPLL);
  } else {
    /* If HSE fails to start-up, the application will have wrong clock
         configuration. User can add here some code to deal with this error */
  }
#endif /* PLL_SOURCE_HSI */
}