/*
 *
 *	system_stm32f10x.c - This provides system clock functions and an exported
 *                      variable for System init and clock configuration.
 *
 * Copyright (C) 2013 Christos Choutouridis <houtouridis.ch@gmail.com>
 * All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Christos Choutouridis. The intellectual
 * and technical concepts contained herein are proprietary to
 * Christos Choutouridis and are protected by copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Christos Choutouridis.
 *
 * Author:     Christos Choutouridis <houtouridis.ch@gmail.com>
 * Date:       11/2011
 * Version:    0.1
 */

#include <stm32f10x_clock.h>


/**
  * CLK_t Sys_Sysclk_Update (void)
  *
  * @brief  Update SystemCoreClock variable 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.
  *
  * @param  None
  * @retval CLK_t SystemCoreClock
  */
CLK_t Sys_Sysclk_Update (void)
{
   uint8_t           MUL = (uint8_t )((RCC->CFGR & RCC_CFGR_PLLMULL) >> 18);
   Sys_CLK_src_t     cs = Sys_GetSysclk_src ();
	#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
   uint32_t          PD;
   #endif

   switch (cs)
   {
      default:
      case HSI:
         return SystemCoreClock = (CLK_t)HSI_VALUE;
      case HSE:
         return SystemCoreClock = (CLK_t)HSE_VALUE;
      case PLLCLK:
         #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
         PD = RCC->CFGR2;
         if (RCC->CFGR & RCC_CFGR_PLLSRC) //HSE
            return SystemCoreClock = (CLK_t)((MUL+2) * (HSE_VALUE/(PD+1)));
         else
            return SystemCoreClock = (CLK_t)((MUL+2) * (HSI_VALUE >> 1));
         #else
         if (RCC->CFGR & RCC_CFGR_PLLXTPRE)  // HSE oscillator clock divided by 2 or not
            return SystemCoreClock = (CLK_t)((MUL+2) * (HSI_VALUE >> 1));
         else
            return SystemCoreClock = (CLK_t)((MUL+2) * HSI_VALUE);
         #endif
   }
}





/**
  * Sys_CLK_src_t Sys_SetSysclk_src (void)
  *
  * @brief  This function returns the SYSCLK source.
  * @param  none.
  * @retval Sys_CLK_src_t : HSI, HSE, PLLCLK
  */
Sys_CLK_src_t Sys_GetSysclk_src (void)
{
   return (Sys_CLK_src_t)((RCC->CFGR & RCC_CFGR_SWS) >> 2);
}



/**
  * Sys_Exit_t Sys_SetSysclk_src (Sys_CLK_src_t cs)
  *
  * @brief  This function set the SYSCLK source.
  *         Call this function after the desired clock setup.
  *         This function does not have error handler.
  * @param  Sys_CLK_src_t cs: HSI, HSE, PLLCLK
  * @retval none
  */
Sys_Exit_t Sys_SetSysclk_src (Sys_CLK_src_t cs)
{
   uint32_t REG = RCC->CFGR & ~RCC_CFGR_SW;

   if (cs>PLLCLK)
      return System_ERROR;
   else
   {
      RCC->CFGR  = REG + cs;
      Sys_Sysclk_Update ();
      return System_OK;
   }
}




/**
  * Sys_Exit_t
  *   Sys_HSI_Cmd (Sys_FunctionalState_t state)
  *
  * @brief  This function enables and disables the internal clock oscillator.
  *         If the HSI could not start or stops then it return error.
  *         This depends on SYSTEM_STARTUP_TIMEOUT value.
  * @param  Sys_FunctionalState_t state: ON or OFF
  * @retval Sys_Exit_t
  */
Sys_Exit_t  Sys_HSI_Cmd (Sys_FunctionalState_t state)
{
   uint32_t StartUpCounter = 0;

   if (state)
   {
      // Try to enable and wait untill Ready or Time out
      RCC->CR |= RCC_CR_HSION;
      while ( !(RCC->CR & RCC_CR_HSIRDY) && (++StartUpCounter < SYSTEM_STARTUP_TIMEOUT) )
         ;
      if (StartUpCounter < SYSTEM_STARTUP_TIMEOUT)
         return System_OK;
      else
         return System_ERROR;
   }
   else
   {
      // Try to disable and return the result
      RCC->CR &= ~RCC_CR_HSION;
      return (Sys_Exit_t) (RCC->CR & RCC_CR_HSION);
   }
}



/**
  * Sys_Exit_t
  *   Sys_HSE_Cmd (Sys_FunctionalState_t state)
  *
  * @brief  This function enables and disables the external clock oscillator.
  *         If the HSE could not start or stops then it return error.
  *         This depends on SYSTEM_STARTUP_TIMEOUT value.
  * @param  Sys_FunctionalState_t state: ON or OFF
  * @retval Sys_Exit_t
  */
Sys_Exit_t  Sys_HSE_Cmd (Sys_FunctionalState_t state)
{
   uint32_t StartUpCounter = 0;

   if (state)
   {
      // Try to enable and wait untill Ready or Time out
      RCC->CR |= RCC_CR_HSEON;
      while ( !(RCC->CR & RCC_CR_HSERDY) && (++StartUpCounter < SYSTEM_STARTUP_TIMEOUT) )
         ;
      if (StartUpCounter < SYSTEM_STARTUP_TIMEOUT)
         return System_OK;
      else
         return System_ERROR;
   }
   else
   {
      // Try to disable and return the result
      RCC->CR &= ~RCC_CR_HSEON;
      return (Sys_Exit_t) (RCC->CR & RCC_CR_HSEON);
   }
}



/**
  * Sys_Exit_t
  *   Sys_ExtClock_Cmd (Sys_FunctionalState_t state)
  *
  * @brief  This function enables and disables the external clock source.
  * @param  Sys_FunctionalState_t state: ON or OFF
  * @retval Sys_Exit_t
  */
Sys_Exit_t  Sys_ExtClock_Cmd (Sys_FunctionalState_t state)
{
   Sys_CLK_src_t s = Sys_GetSysclk_src();
   if (s == HSE)
      return System_ERROR;
   else
   {
      if (state)  RCC->CR |= RCC_CR_HSEBYP;
      else        RCC->CR &= ~RCC_CR_HSEBYP;
      return System_OK;
   }
}




/**
  * Sys_Exit_t
  *   Sys_PLL_Cmd (Sys_FunctionalState_t state)
  *
  * @brief  This function enables and disables the PLL.
  *         If the PLL could not lock or stops then it return error.
  *         This depends on SYSTEM_STARTUP_TIMEOUT value.
  * @param  Sys_FunctionalState_t state: ON or OFF
  * @retval Sys_Exit_t
  */
Sys_Exit_t  Sys_PLL_Cmd (Sys_FunctionalState_t state)
{
   uint32_t StartUpCounter = 0;
   if (state)
   {
      // Try to enable and wait untill Ready or Time out
      RCC->CR |= RCC_CR_PLLON;
      while ( !(RCC->CR & RCC_CR_PLLRDY) && (++StartUpCounter < SYSTEM_STARTUP_TIMEOUT) )
         ;
      if (StartUpCounter < SYSTEM_STARTUP_TIMEOUT)
         return System_OK;
      else
         return System_ERROR;
   }
   else
   {
      // Try to disable and return the result
      RCC->CR &= ~RCC_CR_PLLON;
      return (Sys_Exit_t) (RCC->CR & RCC_CR_PLLON);
   }

}




/**
  * Sys_Exit_t
  *   Sys_SetPLLClock (CLK_t clk, Sys_PLL_src_t ps)
  *
  * @brief  Setup the internal PLL.
  *         This function configures the PLL to provide the desired CLK
  * @param  CLK_t clk         : The desired clk
            Sys_PLL_src_t ps  : The PLL source
  * @retval Sys_Exit_t
  */
Sys_Exit_t  Sys_SetPLLClock (CLK_t clk, Sys_PLL_src_t ps)
{
   uint8_t     pll_mul, pd=0, done=0, max_pd;
   uint32_t    REG = RCC->CFGR & ~RCC_CFGR_PLLMULL;   //Clear PLLMUL
   #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
   uint32_t    PD = RCC->CFGR2 & ~RCC_CFGR2_PREDIV1;  //Clear PREDIV
   #endif

   if (RCC->CR & RCC_CR_PLLON)   // See page 81 of Reference manual
      return System_ERROR;

   if ((clk > SYSTEM_MAX_CLK) || (clk < SYSTEM_MIN_CLK))
      return System_ERROR;

   if (ps == HSI_2)  // HSI_2 = HSI/2
   {
      RCC->CFGR &= ~RCC_CFGR_PLLSRC;
      // Find the PLL MUL value.
      for (pll_mul=1 ; pll_mul<=16 ; ++pll_mul)
         if( ((HSI_VALUE / 2) * pll_mul) > clk)
         {
            done = 1;
            --pll_mul;  //load the smaller nearest value.
            clk = (HSI_VALUE / 2) * pll_mul;    //update clk
            break;
         }
   }
   else  // HSE from PREDIV
   {
      #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
      max_pd = 16;
      #else
      max_pd = 2;
      #endif
      RCC->CFGR |= RCC_CFGR_PLLSRC;
      // Find the PLL MUL and PREDIV value pair.
      for (pd = 1 ; !done && pd<=max_pd ; ++pd )
         for (pll_mul=1 ; pll_mul <= 16; ++pll_mul)
            if (((HSE_VALUE / pd) * pll_mul) == clk )
            {
               done = 1;
               break;
            }
      --pd; // Regain the correct prediv value.
   }

   //Load the register values
   if (done)
   {
      if (pll_mul >= 2)
         RCC->CFGR = REG + ((pll_mul - 2) << 18);
      if (ps == PREDIV)
      {
         #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
         RCC->CFGR2 = PD + pd - 1;
         #else
         if (pd)  RCC->CFGR |= RCC_CFGR_PLLXTPRE_HSE_Div2;
         else     RCC->CFGR &= ~RCC_CFGR_PLLXTPRE_HSE_Div2;
         #endif
      }

      //Flash Prefetch controller and latency
      //Sys_Sysclk_Update ();
      FLASH->ACR |= FLASH_ACR_PRFTBE;
      FLASH->ACR &= ~FLASH_ACR_LATENCY;

      if (clk <= 24000000)
         FLASH->ACR |= FLASH_ACR_LATENCY_0;
      else if (clk <= 48000000)
         FLASH->ACR |= FLASH_ACR_LATENCY_1;
      else
         FLASH->ACR |= FLASH_ACR_LATENCY_2;

      return System_OK;
   }
   else
      return System_ERROR;

}


