/**
  **************************************************************************
  * @file     at32a423_crm.c
  * @brief    contains all the functions for the crm firmware library
  **************************************************************************
  *                       Copyright notice & Disclaimer
  *
  * The software Board Support Package (BSP) that is made available to
  * download from Artery official website is the copyrighted work of Artery.
  * Artery authorizes customers to use, copy, and distribute the BSP
  * software and its related documentation for the purpose of design and
  * development in conjunction with Artery microcontrollers. Use of the
  * software is governed by this copyright notice and the following disclaimer.
  *
  * THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
  * GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
  * TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
  * STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
  * INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
  *
  **************************************************************************
  */

#include "at32a423_conf.h"

/** @addtogroup AT32A423_periph_driver
  * @{
  */

/** @defgroup CRM
  * @brief CRM driver modules
  * @{
  */

#ifdef CRM_MODULE_ENABLED

/** @defgroup CRM_private_functions
  * @{
  */

/**
  * @brief  reset the crm register
  * @param  none
  * @retval none
  */
void crm_reset(void)
{
  /* reset the crm clock configuration to the default reset state(for debug purpose) */
  /* enable auto step mode */
  crm_auto_step_mode_enable(TRUE);

  /* set hicken bit */
  CRM->ctrl_bit.hicken = TRUE;

  /* wait hick stable */
  while(CRM->ctrl_bit.hickstbl != SET);

  /* hick used as system clock */
  CRM->cfg_bit.sclksel = CRM_SCLK_HICK;

  /* wait sclk switch status */
  while(CRM->cfg_bit.sclksts != CRM_SCLK_HICK);

  /* reset hexten, hextbyps, cfden and pllen bits */
  CRM->ctrl &= ~(0x010D0000U);

  /* reset cfg register, include sclk switch, ahbdiv, apb1div, apb2div, adcdiv, clkout bits */
  CRM->cfg = 0x40000000U;

  /* reset pllms pllns pllfr pllrcs bits */
  CRM->pllcfg = 0x00033002U;

  /* reset clkout_sel, clkoutdiv, pllclk_to_adc, hick_to_usb */
  CRM->misc1 &= 0x00005000U;
  CRM->misc1 |= 0x000F0000U;

  /* disable all interrupts enable and clear pending bits  */
  CRM->clkint = 0x009F0000U;

  /* disable auto step mode */
  crm_auto_step_mode_enable(FALSE);
}

/**
  * @brief  enable or disable crm low speed external crystal bypass
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_lext_bypass(confirm_state new_state)
{
  CRM->bpdc_bit.lextbyps = new_state;
}

/**
  * @brief  enable or disable crm high speed external crystal bypass
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_hext_bypass(confirm_state new_state)
{
  CRM->ctrl_bit.hextbyps = new_state;
}

/**
  * @brief  get crm flag status
  * @param  flag
  *         this parameter can be one of the following values:
  *         - CRM_HICK_STABLE_FLAG
  *         - CRM_HEXT_STABLE_FLAG
  *         - CRM_PLL_STABLE_FLAG
  *         - CRM_LEXT_STABLE_FLAG
  *         - CRM_LICK_STABLE_FLAG
  *         - CRM_PIN_RESET_FLAG
  *         - CRM_POR_RESET_FLAG
  *         - CRM_SW_RESET_FLAG
  *         - CRM_WDT_RESET_FLAG
  *         - CRM_WWDT_RESET_FLAG
  *         - CRM_LOWPOWER_RESET_FLAG
  *         interrupt flag:
  *         - CRM_LICK_READY_INT_FLAG
  *         - CRM_LEXT_READY_INT_FLAG
  *         - CRM_HICK_READY_INT_FLAG
  *         - CRM_HEXT_READY_INT_FLAG
  *         - CRM_PLL_READY_INT_FLAG
  *         - CRM_CLOCK_FAILURE_INT_FLAG
  * @retval flag_status (SET or RESET)
  */
flag_status crm_flag_get(uint32_t flag)
{
  flag_status status = RESET;
  if((CRM_REG(flag) & CRM_REG_BIT(flag)) != CRM_REG_BIT(flag))
  {
    status = RESET;
  }
  else
  {
    status = SET;
  }
  return status;
}

/**
  * @brief  get crm interrupt flag status
  * @param  flag
  *         this parameter can be one of the following values:
  *         - CRM_LICK_READY_INT_FLAG
  *         - CRM_LEXT_READY_INT_FLAG
  *         - CRM_HICK_READY_INT_FLAG
  *         - CRM_HEXT_READY_INT_FLAG
  *         - CRM_PLL_READY_INT_FLAG
  *         - CRM_CLOCK_FAILURE_INT_FLAG
  * @retval flag_status (SET or RESET)
  */
flag_status crm_interrupt_flag_get(uint32_t flag)
{
  flag_status status = RESET;
  switch(flag)
  {
    case CRM_LICK_READY_INT_FLAG:
      if(CRM->clkint_bit.lickstblf && CRM->clkint_bit.lickstblien)
      {
        status = SET;
      }
      break;
    case CRM_LEXT_READY_INT_FLAG:
      if(CRM->clkint_bit.lextstblf && CRM->clkint_bit.lextstblien)
      {
        status = SET;
      }
      break;
    case CRM_HICK_READY_INT_FLAG:
      if(CRM->clkint_bit.hickstblf && CRM->clkint_bit.hickstblien)
      {
        status = SET;
      }
      break;
    case CRM_HEXT_READY_INT_FLAG:
      if(CRM->clkint_bit.hextstblf && CRM->clkint_bit.hextstblien)
      {
        status = SET;
      }
      break;
    case CRM_PLL_READY_INT_FLAG:
      if(CRM->clkint_bit.pllstblf && CRM->clkint_bit.pllstblien)
      {
        status = SET;
      }
      break;
    case CRM_CLOCK_FAILURE_INT_FLAG:
      if(CRM->clkint_bit.cfdf && CRM->ctrl_bit.cfden)
      {
        status = SET;
      }
      break;
  }

  return status;
}

/**
  * @brief  wait for hext stable
  * @param  none
  * @retval error_status (ERROR or SUCCESS)
  */
error_status crm_hext_stable_wait(void)
{
  uint32_t stable_cnt = 0;
  error_status status = ERROR;

  while((crm_flag_get(CRM_HEXT_STABLE_FLAG) != SET) && (stable_cnt < HEXT_STARTUP_TIMEOUT))
  {
    stable_cnt ++;
  }

  if(crm_flag_get(CRM_HEXT_STABLE_FLAG) != SET)
  {
    status = ERROR;
  }
  else
  {
    status = SUCCESS;
  }

  return status;
}

/**
  * @brief  set the hick trimming value
  * @param  trim_value (0x00~0x3F)
  * @retval none
  */
void crm_hick_clock_trimming_set(uint8_t trim_value)
{
  CRM->ctrl_bit.hicktrim = trim_value;
}

/**
  * @brief  set the crm calibration value
  * @param  cali_value (0x00~0xFF)
  * @retval none
  */
void crm_hick_clock_calibration_set(uint8_t cali_value)
{
  /* enable write hick calibration */
  CRM->misc1_bit.hickcal_key = 0x5A;

  /* write hick calibration value */
  CRM->ctrl_bit.hickcal = cali_value;

  /* disable write hick calibration */
  CRM->misc1_bit.hickcal_key = 0x0;
}

/**
  * @brief  enable or disable the peripheral clock
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_GPIOA_PERIPH_CLOCK        - CRM_GPIOB_PERIPH_CLOCK        - CRM_GPIOC_PERIPH_CLOCK       - CRM_GPIOD_PERIPH_CLOCK
  *         - CRM_GPIOE_PERIPH_CLOCK        - CRM_GPIOF_PERIPH_CLOCK        - CRM_CRC_PERIPH_CLOCK         - CRM_DMA1_PERIPH_CLOCK
  *         - CRM_DMA2_PERIPH_CLOCK         - CRM_OTGFS1_PERIPH_CLOCK       - CRM_XMC_PERIPH_CLOCK         - CRM_TMR2_PERIPH_CLOCK
  *         - CRM_TMR3_PERIPH_CLOCK         - CRM_TMR4_PERIPH_CLOCK         - CRM_TMR6_PERIPH_CLOCK        - CRM_TMR7_PERIPH_CLOCK
  *         - CRM_TMR12_PERIPH_CLOCK        - CRM_TMR13_PERIPH_CLOCK        - CRM_TMR14_PERIPH_CLOCK       - CRM_WWDT_PERIPH_CLOCK
  *         - CRM_SPI2_PERIPH_CLOCK         - CRM_SPI3_PERIPH_CLOCK         - CRM_USART2_PERIPH_CLOCK      - CRM_USART3_PERIPH_CLOCK
  *         - CRM_USART4_PERIPH_CLOCK       - CRM_USART5_PERIPH_CLOCK       - CRM_I2C1_PERIPH_CLOCK        - CRM_I2C2_PERIPH_CLOCK
  *         - CRM_I2C3_PERIPH_CLOCK         - CRM_CAN1_PERIPH_CLOCK         - CRM_CAN2_PERIPH_CLOCK        - CRM_PWC_PERIPH_CLOCK
  *         - CRM_DAC_PERIPH_CLOCK          - CRM_USART7_PERIPH_CLOCK       - CRM_USART8_PERIPH_CLOCK      - CRM_TMR1_PERIPH_CLOCK
  *         - CRM_USART1_PERIPH_CLOCK       - CRM_USART6_PERIPH_CLOCK       - CRM_ADC1_PERIPH_CLOCK        - CRM_SPI1_PERIPH_CLOCK
  *         - CRM_SCFG_PERIPH_CLOCK         - CRM_TMR9_PERIPH_CLOCK         - CRM_TMR10_PERIPH_CLOCK       - CRM_TMR11_PERIPH_CLOCK
  *         - CRM_ACC_PERIPH_CLOCK
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_periph_clock_enable(crm_periph_clock_type value, confirm_state new_state)
{
  /* enable periph clock */
  if(TRUE == new_state)
  {
    CRM_REG(value) |= CRM_REG_BIT(value);
  }
  /* disable periph clock */
  else
  {
    CRM_REG(value) &= ~(CRM_REG_BIT(value));
  }
}

/**
  * @brief  enable or disable the peripheral reset
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_GPIOA_PERIPH_RESET        - CRM_GPIOB_PERIPH_RESET        - CRM_GPIOC_PERIPH_RESET       - CRM_GPIOD_PERIPH_RESET
  *         - CRM_GPIOE_PERIPH_RESET        - CRM_GPIOF_PERIPH_RESET        - CRM_CRC_PERIPH_RESET         - CRM_DMA1_PERIPH_RESET
  *         - CRM_DMA2_PERIPH_RESET         - CRM_OTGFS1_PERIPH_RESET       - CRM_XMC_PERIPH_RESET         - CRM_QSPI1_PERIPH_RESET
  *         - CRM_TMR2_PERIPH_RESET         - CRM_TMR3_PERIPH_RESET         - CRM_TMR4_PERIPH_RESET        - CRM_TMR6_PERIPH_RESET
  *         - CRM_TMR7_PERIPH_RESET         - CRM_TMR12_PERIPH_RESET        - CRM_TMR13_PERIPH_RESET       - CRM_TMR14_PERIPH_RESET
  *         - CRM_WWDT_PERIPH_RESET         - CRM_SPI2_PERIPH_RESET         - CRM_SPI3_PERIPH_RESET        - CRM_USART2_PERIPH_RESET
  *         - CRM_USART3_PERIPH_RESET       - CRM_USART4_PERIPH_RESET       - CRM_USART5_PERIPH_RESET      - CRM_I2C1_PERIPH_RESET
  *         - CRM_I2C2_PERIPH_RESET         - CRM_I2C3_PERIPH_RESET         - CRM_CAN1_PERIPH_RESET        - CRM_CAN2_PERIPH_RESET
  *         - CRM_PWC_PERIPH_RESET          - CRM_DAC_PERIPH_RESET          - CRM_USART7_PERIPH_RESET      - CRM_USART8_PERIPH_RESET
  *         - CRM_TMR1_PERIPH_RESET         - CRM_USART1_PERIPH_RESET       - CRM_USART6_PERIPH_RESET      - CRM_ADC_PERIPH_RESET
  *         - CRM_SPI1_PERIPH_RESET         - CRM_SCFG_PERIPH_RESET         - CRM_TMR9_PERIPH_RESET        - CRM_TMR10_PERIPH_RESET
  *         - CRM_TMR11_PERIPH_RESET        - CRM_ACC_PERIPH_RESET
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_periph_reset(crm_periph_reset_type value, confirm_state new_state)
{
  /* enable periph reset */
  if(new_state == TRUE)
  {
    CRM_REG(value) |= (CRM_REG_BIT(value));
  }
  /* disable periph reset */
  else
  {
    CRM_REG(value) &= ~(CRM_REG_BIT(value));
  }
}

/**
  * @brief  enable or disable the peripheral clock in lowpower mode
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_GPIOA_PERIPH_LOWPOWER     - CRM_GPIOB_PERIPH_LOWPOWER     - CRM_GPIOC_PERIPH_LOWPOWER    - CRM_GPIOD_PERIPH_LOWPOWER
  *         - CRM_GPIOE_PERIPH_LOWPOWER     - CRM_GPIOF_PERIPH_LOWPOWER     - CRM_CRC_PERIPH_LOWPOWER      - CRM_DMA1_PERIPH_LOWPOWER
  *         - CRM_DMA2_PERIPH_LOWPOWER      - CRM_OTGFS1_PERIPH_LOWPOWER    - CRM_XMC_PERIPH_LOWPOWER      - CRM_TMR2_PERIPH_LOWPOWER
  *         - CRM_TMR3_PERIPH_LOWPOWER      - CRM_TMR4_PERIPH_LOWPOWER      - CRM_TMR6_PERIPH_LOWPOWER     - CRM_TMR7_PERIPH_LOWPOWER
  *         - CRM_TMR12_PERIPH_LOWPOWER     - CRM_TMR13_PERIPH_LOWPOWER     - CRM_TMR14_PERIPH_LOWPOWER    - CRM_WWDT_PERIPH_LOWPOWER
  *         - CRM_SPI2_PERIPH_LOWPOWER      - CRM_SPI3_PERIPH_LOWPOWER      - CRM_USART2_PERIPH_LOWPOWER   - CRM_USART3_PERIPH_LOWPOWER
  *         - CRM_USART4_PERIPH_LOWPOWER    - CRM_USART5_PERIPH_LOWPOWER    - CRM_I2C1_PERIPH_LOWPOWER     - CRM_I2C2_PERIPH_LOWPOWER
  *         - CRM_I2C3_PERIPH_LOWPOWER      - CRM_CAN1_PERIPH_LOWPOWER      - CRM_CAN2_PERIPH_LOWPOWER     - CRM_PWC_PERIPH_LOWPOWER
  *         - CRM_DAC_PERIPH_LOWPOWER       - CRM_USART7_PERIPH_LOWPOWER    - CRM_USART8_PERIPH_LOWPOWER   - CRM_TMR1_PERIPH_LOWPOWER
  *         - CRM_USART1_PERIPH_LOWPOWER    - CRM_USART6_PERIPH_LOWPOWER    - CRM_ADC1_PERIPH_LOWPOWER     - CRM_SPI1_PERIPH_LOWPOWER
  *         - CRM_SCFG_PERIPH_LOWPOWER      - CRM_TMR9_PERIPH_LOWPOWER      - CRM_TMR10_PERIPH_LOWPOWER    - CRM_TMR11_PERIPH_LOWPOWER
  *         - CRM_ACC_PERIPH_LOWPOWER       - CRM_FLASH_PERIPH_LOWPOWER     - CRM_SRAM_PERIPH_LOWPOWER
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_periph_lowpower_mode_enable(crm_periph_clock_lowpower_type value, confirm_state new_state)
{
  /* enable periph clock in lowpower mode */
  if(new_state == TRUE)
  {
    CRM_REG(value) |= (CRM_REG_BIT(value));
  }
  /* disable periph clock in lowpower mode */
  else
  {
    CRM_REG(value) &= ~(CRM_REG_BIT(value));
  }
}

/**
  * @brief  enable or disable the crm clock source
  * @param  source
  *         this parameter can be one of the following values:
  *         - CRM_CLOCK_SOURCE_HICK
  *         - CRM_CLOCK_SOURCE_HEXT
  *         - CRM_CLOCK_SOURCE_PLL
  *         - CRM_CLOCK_SOURCE_LEXT
  *         - CRM_CLOCK_SOURCE_LICK
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_clock_source_enable(crm_clock_source_type source, confirm_state new_state)
{
  switch(source)
  {
    case CRM_CLOCK_SOURCE_HICK:
      CRM->ctrl_bit.hicken = new_state;
      break;
    case CRM_CLOCK_SOURCE_HEXT:
      CRM->ctrl_bit.hexten = new_state;
      break;
    case CRM_CLOCK_SOURCE_PLL:
      CRM->ctrl_bit.pllen = new_state;
      break;
    case CRM_CLOCK_SOURCE_LEXT:
      CRM->bpdc_bit.lexten = new_state;
      break;
    case CRM_CLOCK_SOURCE_LICK:
      CRM->ctrlsts_bit.licken = new_state;
      break;
    default: break;
  }
}

/**
  * @brief  clear the crm reset flags
  * @param  flag
  *         this parameter can be one of the following values:
  *         reset flag:
  *         - CRM_PIN_RESET_FLAG
  *         - CRM_POR_RESET_FLAG
  *         - CRM_SW_RESET_FLAG
  *         - CRM_WDT_RESET_FLAG
  *         - CRM_WWDT_RESET_FLAG
  *         - CRM_LOWPOWER_RESET_FLAG
  *         - CRM_ALL_RESET_FLAG
  *         interrupt flag:
  *         - CRM_LICK_READY_INT_FLAG
  *         - CRM_LEXT_READY_INT_FLAG
  *         - CRM_HICK_READY_INT_FLAG
  *         - CRM_HEXT_READY_INT_FLAG
  *         - CRM_PLL_READY_INT_FLAG
  *         - CRM_CLOCK_FAILURE_INT_FLAG
  * @retval none
  */
void crm_flag_clear(uint32_t flag)
{
  switch(flag)
  {
    case CRM_NRST_RESET_FLAG:
    case CRM_POR_RESET_FLAG:
    case CRM_SW_RESET_FLAG:
    case CRM_WDT_RESET_FLAG:
    case CRM_WWDT_RESET_FLAG:
    case CRM_LOWPOWER_RESET_FLAG:
    case CRM_ALL_RESET_FLAG:
      CRM->ctrlsts_bit.rstfc = TRUE;
      while(CRM->ctrlsts_bit.rstfc == TRUE);
      break;
    case CRM_LICK_READY_INT_FLAG:
      CRM->clkint_bit.lickstblfc = TRUE;
      break;
    case CRM_LEXT_READY_INT_FLAG:
      CRM->clkint_bit.lextstblfc = TRUE;
      break;
    case CRM_HICK_READY_INT_FLAG:
      CRM->clkint_bit.hickstblfc = TRUE;
      break;
    case CRM_HEXT_READY_INT_FLAG:
      CRM->clkint_bit.hextstblfc = TRUE;
      break;
    case CRM_PLL_READY_INT_FLAG:
      CRM->clkint_bit.pllstblfc = TRUE;
      break;
    case CRM_CLOCK_FAILURE_INT_FLAG:
      CRM->clkint_bit.cfdfc = TRUE;
      break;
    default:
      break;
  }
}

/**
  * @brief  select ertc clock
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_ERTC_CLOCK_NOCLK
  *         - CRM_ERTC_CLOCK_LEXT
  *         - CRM_ERTC_CLOCK_LICK
  *         - CRM_ERTC_CLOCK_HEXT_DIV_2
  *         - CRM_ERTC_CLOCK_HEXT_DIV_3
  *         - CRM_ERTC_CLOCK_HEXT_DIV_4
  *         - CRM_ERTC_CLOCK_HEXT_DIV_5
  *         - CRM_ERTC_CLOCK_HEXT_DIV_6
  *         - CRM_ERTC_CLOCK_HEXT_DIV_7
  *         - CRM_ERTC_CLOCK_HEXT_DIV_8
  *         - CRM_ERTC_CLOCK_HEXT_DIV_9
  *         - CRM_ERTC_CLOCK_HEXT_DIV_10
  *         - CRM_ERTC_CLOCK_HEXT_DIV_11
  *         - CRM_ERTC_CLOCK_HEXT_DIV_12
  *         - CRM_ERTC_CLOCK_HEXT_DIV_13
  *         - CRM_ERTC_CLOCK_HEXT_DIV_14
  *         - CRM_ERTC_CLOCK_HEXT_DIV_15
  *         - CRM_ERTC_CLOCK_HEXT_DIV_16
  *         - CRM_ERTC_CLOCK_HEXT_DIV_17
  *         - CRM_ERTC_CLOCK_HEXT_DIV_18
  *         - CRM_ERTC_CLOCK_HEXT_DIV_19
  *         - CRM_ERTC_CLOCK_HEXT_DIV_20
  *         - CRM_ERTC_CLOCK_HEXT_DIV_21
  *         - CRM_ERTC_CLOCK_HEXT_DIV_22
  *         - CRM_ERTC_CLOCK_HEXT_DIV_23
  *         - CRM_ERTC_CLOCK_HEXT_DIV_24
  *         - CRM_ERTC_CLOCK_HEXT_DIV_25
  *         - CRM_ERTC_CLOCK_HEXT_DIV_26
  *         - CRM_ERTC_CLOCK_HEXT_DIV_27
  *         - CRM_ERTC_CLOCK_HEXT_DIV_28
  *         - CRM_ERTC_CLOCK_HEXT_DIV_29
  *         - CRM_ERTC_CLOCK_HEXT_DIV_30
  *         - CRM_ERTC_CLOCK_HEXT_DIV_31
  * @retval none
  */
void crm_ertc_clock_select(crm_ertc_clock_type value)
{
  CRM->cfg_bit.ertcdiv = ((value & 0x1F0) >> 4);
  CRM->bpdc_bit.ertcsel = (value & 0xF);
}

/**
  * @brief  enable or disable ertc
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_ertc_clock_enable(confirm_state new_state)
{
  CRM->bpdc_bit.ertcen = new_state;
}

/**
  * @brief  set crm ahb division
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_AHB_DIV_1
  *         - CRM_AHB_DIV_2
  *         - CRM_AHB_DIV_4
  *         - CRM_AHB_DIV_8
  *         - CRM_AHB_DIV_16
  *         - CRM_AHB_DIV_64
  *         - CRM_AHB_DIV_128
  *         - CRM_AHB_DIV_256
  *         - CRM_AHB_DIV_512
  * @retval none
  */
void crm_ahb_div_set(crm_ahb_div_type value)
{
  /* enable auto step mode */
  crm_auto_step_mode_enable(TRUE);

  CRM->cfg_bit.ahbdiv = value;

  /* disable auto step mode */
  crm_auto_step_mode_enable(FALSE);
}

/**
  * @brief  set crm apb1 division
  * @note   the maximum frequency of APB1 clock is 120 MHz
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_APB1_DIV_1
  *         - CRM_APB1_DIV_2
  *         - CRM_APB1_DIV_4
  *         - CRM_APB1_DIV_8
  *         - CRM_APB1_DIV_16
  * @retval none
  */
void crm_apb1_div_set(crm_apb1_div_type value)
{
  CRM->cfg_bit.apb1div = value;
}

/**
  * @brief  set crm apb2 division
  * @note   the maximum frequency of APB2 clock is 150 MHz
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_APB2_DIV_1
  *         - CRM_APB2_DIV_2
  *         - CRM_APB2_DIV_4
  *         - CRM_APB2_DIV_8
  *         - CRM_APB2_DIV_16
  * @retval none
  */
void crm_apb2_div_set(crm_apb2_div_type value)
{
  CRM->cfg_bit.apb2div = value;
}

/**
  * @brief  set crm hext to sysclk division
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_HEXT_SCLK_DIV_1
  *         - CRM_HEXT_SCLK_DIV_2
  *         - CRM_HEXT_SCLK_DIV_4
  *         - CRM_HEXT_SCLK_DIV_8
  *         - CRM_HEXT_SCLK_DIV_16
  *         - CRM_HEXT_SCLK_DIV_32
  * @retval none
  */
void crm_hext_sclk_div_set(crm_hext_sclk_div_type value)
{
  CRM->misc2_bit.hext_to_sclk_div = value;
}

/**
  * @brief  set crm hick to sysclk division
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_HICK_SCLK_DIV_1
  *         - CRM_HICK_SCLK_DIV_2
  *         - CRM_HICK_SCLK_DIV_4
  *         - CRM_HICK_SCLK_DIV_8
  *         - CRM_HICK_SCLK_DIV_16
  * @retval none
  */
void crm_hick_sclk_div_set(crm_hick_sclk_div_type value)
{
  CRM->misc2_bit.hick_to_sclk_div = value;
}

/**
  * @brief  set usb division
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_USB_DIV_3
  *         - CRM_USB_DIV_2
  *         - CRM_USB_DIV_5
  *         - CRM_USB_DIV_4
  *         - CRM_USB_DIV_7
  *         - CRM_USB_DIV_6
  *         - CRM_USB_DIV_9
  *         - CRM_USB_DIV_8
  * @retval none
  */
void crm_usb_clock_div_set(crm_usb_div_type value)
{
    CRM->misc2_bit.usbdiv = value;
}

/**
  * @brief  enable or disable clock failure detection
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_clock_failure_detection_enable(confirm_state new_state)
{
  CRM->ctrl_bit.cfden = new_state;
}

/**
  * @brief  battery powered domain software reset
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_battery_powered_domain_reset(confirm_state new_state)
{
  CRM->bpdc_bit.bpdrst = new_state;
}

/**
  * @brief  auto step clock switch enable
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_auto_step_mode_enable(confirm_state new_state)
{
  if(new_state == TRUE)
    CRM->misc2_bit.auto_step_en = CRM_AUTO_STEP_MODE_ENABLE;
  else
    CRM->misc2_bit.auto_step_en = CRM_AUTO_STEP_MODE_DISABLE;
}

/** 
  * @brief  usart multi clock source select
  * @param  usart_index
  *         this parameter can be one of the following values:
  *         - CRM_USART1
  *         - CRM_USART2
  *         - CRM_USART3
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_USART_CLOCK_SOURCE_PCLK
  *         - CRM_USART_CLOCK_SOURCE_SCLK
  *         - CRM_USART_CLOCK_SOURCE_HICK
  *         - CRM_USART_CLOCK_SOURCE_LEXT
  * @retval none
  */
void crm_usart_clock_select(crm_usart_type usart_index, crm_usart_clock_source_type value)
{
  switch(usart_index)
  {
    case CRM_USART1:
      CRM->piclks_bit.usart1sel = value;
      break;
    case CRM_USART2:
      CRM->piclks_bit.usart2sel = value;
      break;
    case CRM_USART3:
      CRM->piclks_bit.usart3sel = value;
      break;
  }
}

/** 
  * @brief  usart multi clock source select
  * @param  usart_index
  *         this parameter can be one of the following values:
  *         - CRM_USART1
  *         - CRM_USART2
  *         - CRM_USART3
  * @retval crm_usart_clock_source_type : the clock source of usart index.
  */
crm_usart_clock_source_type crm_usart_clock_get(crm_usart_type usart_index)
{
  crm_usart_clock_source_type value = CRM_USART_CLOCK_SOURCE_PCLK;

  switch(usart_index)
  {
    case CRM_USART1:
      value = (crm_usart_clock_source_type)CRM->piclks_bit.usart1sel;
      break;
    case CRM_USART2:
      value = (crm_usart_clock_source_type)CRM->piclks_bit.usart2sel;
      break;
    case CRM_USART3:
      value = (crm_usart_clock_source_type)CRM->piclks_bit.usart3sel;
      break;
  }

  return value;
}

/** 
  * @brief  i2c multi clock source select
  * @param  i2c_index
  *         this parameter can be one of the following values:
  *         - CRM_I2C1
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_I2C_CLOCK_SOURCE_PCLK
  *         - CRM_I2C_CLOCK_SOURCE_SCLK
  *         - CRM_I2C_CLOCK_SOURCE_HICK
  * @retval none
  */
void crm_i2c_clock_select(crm_i2c_type i2c_index, crm_i2c_clock_source_type value)
{
  switch(i2c_index)
  {
    case CRM_I2C1:
      CRM->piclks_bit.i2c1sel = value;
      break;
  }
}

/** 
  * @brief  i2c multi clock source select
  * @param  i2c_index
  *         this parameter can be one of the following values:
  *         - CRM_I2C1
  * @retval crm_i2c_clock_source_type : the clock source of i2c index.
  */
crm_i2c_clock_source_type crm_i2c_clock_get(crm_i2c_type i2c_index)
{
  crm_i2c_clock_source_type value = CRM_I2C_CLOCK_SOURCE_PCLK;

  switch(i2c_index)
  {
    case CRM_I2C1:
      value = (crm_i2c_clock_source_type)CRM->piclks_bit.i2c1sel;
      break;
  }

  return value;
}

/** 
  * @brief  adc multi clock source select
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_ADC_CLOCK_SOURCE_HCLK  
  *         - CRM_ADC_CLOCK_SOURCE_PLLCLK
  * @retval none
  */
void crm_adc_clock_select(crm_adc_clock_source_type value)
{
  CRM->misc1_bit.pllclk_to_adc = value;
}

/**
  * @brief  config hick divider select
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_HICK48_DIV6
  *         - CRM_HICK48_NODIV
  * @retval none
  */
void crm_hick_divider_select(crm_hick_div_6_type value)
{
  __IO uint8_t temp_div = CRM->misc2_bit.hick_to_sclk_div;
  __IO uint8_t temp_sclk = CRM->misc1_bit.hick_to_sclk;

  crm_hick_sclk_div_set(CRM_HICK_SCLK_DIV_16);
  /* delay */
  {
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
  }

  CRM->misc1_bit.hick_to_sclk = TRUE;
  CRM->misc1_bit.hickdiv = value;
  CRM->misc1_bit.hick_to_sclk = temp_sclk;
  crm_hick_sclk_div_set((crm_hick_sclk_div_type)temp_div);
}

/** 
  * @brief  hick as system clock frequency select
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_HICK_SCLK_8MHZ
  *         - CRM_HICK_SCLK_48MHZ
  * @retval none
  */
void crm_hick_sclk_frequency_select(crm_hick_sclk_frequency_type value)
{
  __IO uint8_t temp_div = CRM->misc2_bit.hick_to_sclk_div;

  crm_hick_sclk_div_set(CRM_HICK_SCLK_DIV_16);
  /* delay */
  {
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
  }

  CRM->misc1_bit.hick_to_sclk = TRUE;
  CRM->misc1_bit.hickdiv = CRM_HICK48_NODIV;
  CRM->misc1_bit.hick_to_sclk = value;
  crm_hick_sclk_div_set((crm_hick_sclk_div_type)temp_div);
}

/** 
  * @brief  usb 48 mhz clock source select
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_USB_CLOCK_SOURCE_PLL
  *         - CRM_USB_CLOCK_SOURCE_HICK
  * @retval none
  */
void crm_usb_clock_source_select(crm_usb_clock_source_type value)
{
  if(value == CRM_USB_CLOCK_SOURCE_HICK)
  {
    crm_hick_sclk_frequency_select(CRM_HICK_SCLK_48MHZ);
  }
  CRM->misc1_bit.hick_to_usb = value;
}

/**
  * @brief  config crm pll
  *                        pll_rcs_freq * pll_ns
  *         pll clock = --------------------------------
  *                           pll_ms * pll_fr_n
  *         attemtion:
  *                  31 <= pll_ns <= 500
  *                  1  <= pll_ms <= 15
  *
  *                       pll_rcs_freq
  *         2mhz <=  ---------------------- <= 16mhz
  *                          pll_ms
  *
  *                       pll_rcs_freq * pll_ns
  *         500mhz <=  -------------------------------- <= 1000mhz
  *                               pll_ms
  * @param  clock_source
  *         this parameter can be one of the following values:
  *         - CRM_PLL_SOURCE_HICK
  *         - CRM_PLL_SOURCE_HEXT
  * @param  pll_ns (31~500)
  * @param  pll_ms (1~15)
  * @param  pll_fr
  *         this parameter can be one of the following values:
  *         - CRM_PLL_FR_1
  *         - CRM_PLL_FR_2
  *         - CRM_PLL_FR_4
  *         - CRM_PLL_FR_8
  *         - CRM_PLL_FR_16
  *         - CRM_PLL_FR_32
  * @retval none
  */
void crm_pll_config(crm_pll_clock_source_type clock_source, uint16_t pll_ns, \
                    uint16_t pll_ms, crm_pll_fr_type pll_fr)
{
  /* config pll clock source */
  if(clock_source == CRM_PLL_SOURCE_HICK)
  {
    CRM->misc1_bit.hickdiv = CRM_HICK48_NODIV;
  }
  CRM->pllcfg_bit.pllrcs = clock_source;

  /* config pll multiplication factor */
  CRM->pllcfg_bit.pllns = pll_ns;
  CRM->pllcfg_bit.pllms = pll_ms;
  CRM->pllcfg_bit.pllfr = pll_fr;
}

/**
  * @brief  select system clock source
  * @param  value
  *         this parameter can be one of the following values:
  *         - CRM_SCLK_HICK
  *         - CRM_SCLK_HEXT
  *         - CRM_SCLK_PLL
  * @retval none
  */
void crm_sysclk_switch(crm_sclk_type value)
{
  /* enable auto step mode */
  crm_auto_step_mode_enable(TRUE);

  CRM->cfg_bit.sclksel = value;

  /* disable auto step mode */
  crm_auto_step_mode_enable(FALSE);
}

/**
  * @brief  indicate which clock source is used as system clock
  * @param  none
  * @retval crm_sclk
  *         this return can be one of the following values:
  *         - CRM_SCLK_HICK
  *         - CRM_SCLK_HEXT
  *         - CRM_SCLK_PLL
  */
crm_sclk_type crm_sysclk_switch_status_get(void)
{
  return (crm_sclk_type)CRM->cfg_bit.sclksts;
}

/**
  * @brief  get crm clocks freqency
  * @param  clocks_struct
  *         - pointer to the crm_clocks_freq_type structure
  * @retval none
  */
void crm_clocks_freq_get(crm_clocks_freq_type *clocks_struct)
{
  uint32_t pll_ns = 0, pll_ms = 0, pll_fr = 0, pll_clock_source = 0, pllrcsfreq = 0;
  uint32_t temp = 0, div_value = 0, psc = 0;
  crm_sclk_type sclk_source;

  static const uint8_t sclk_ahb_div_table[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
  static const uint8_t ahb_apb1_div_table[8] = {0, 0, 0, 0, 1, 2, 3, 4};
  static const uint8_t ahb_apb2_div_table[8] = {0, 0, 0, 0, 1, 2, 3, 4};
  static const uint8_t pll_fr_table[6] = {1, 2, 4, 8, 16, 32};

  /* get sclk source */
  sclk_source = crm_sysclk_switch_status_get();

  switch(sclk_source)
  {
    case CRM_SCLK_HICK:
      if(((CRM->misc1_bit.hick_to_sclk) != RESET) && ((CRM->misc1_bit.hickdiv) != RESET))
        clocks_struct->sclk_freq = HICK_VALUE * 6;
      else
        clocks_struct->sclk_freq = HICK_VALUE;

      psc = CRM->misc2_bit.hick_to_sclk_div;
      clocks_struct->sclk_freq = clocks_struct->sclk_freq >> psc;
      break;
    case CRM_SCLK_HEXT:
      clocks_struct->sclk_freq = HEXT_VALUE;
      psc = CRM->misc2_bit.hext_to_sclk_div;
      clocks_struct->sclk_freq = clocks_struct->sclk_freq >> psc;
      break;
    case CRM_SCLK_PLL:
      /* get pll clock source */
      pll_clock_source = CRM->pllcfg_bit.pllrcs;

      /* get multiplication factor */
      pll_ns = CRM->pllcfg_bit.pllns;
      pll_ms = CRM->pllcfg_bit.pllms;
      pll_fr = pll_fr_table[CRM->pllcfg_bit.pllfr];

      if (pll_clock_source == CRM_PLL_SOURCE_HICK)
      {
        /* hick selected as pll clock entry */
        pllrcsfreq = HICK_VALUE;
      }
      else
      {
        /* hext selected as pll clock entry */
        pllrcsfreq = HEXT_VALUE;
      }

      clocks_struct->sclk_freq = (uint32_t)(((uint64_t)pllrcsfreq * pll_ns) / (pll_ms * pll_fr) / 2);
      break;
    default:
      clocks_struct->sclk_freq = HICK_VALUE;
      break;
  }

  /* compute sclk, ahbclk, abp1clk and apb2clk frequencies */
  /* get ahb division */
  temp = CRM->cfg_bit.ahbdiv;
  div_value = sclk_ahb_div_table[temp];
  /* ahbclk frequency */
  clocks_struct->ahb_freq = clocks_struct->sclk_freq >> div_value;

  /* get apb1 division */
  temp = CRM->cfg_bit.apb1div;
  div_value = ahb_apb1_div_table[temp];
  /* apb1clk frequency */
  clocks_struct->apb1_freq = clocks_struct->ahb_freq >> div_value;

  /* get apb2 division */
  temp = CRM->cfg_bit.apb2div;
  div_value = ahb_apb2_div_table[temp];
  /* apb2clk frequency */
  clocks_struct->apb2_freq = clocks_struct->ahb_freq >> div_value;
}

/**
  * @brief  set crm clkout
  * @param  clkout
  *         this parameter can be one of the following values:
  *         - CRM_CLKOUT_SCLK
  *         - CRM_CLKOUT_HEXT
  *         - CRM_CLKOUT_PLL
  *         - CRM_CLKOUT_USB
  *         - CRM_CLKOUT_ADC
  *         - CRM_CLKOUT_HICK
  *         - CRM_CLKOUT_LICK
  *         - CRM_CLKOUT_LEXT
  * @retval none
  */
void crm_clock_out_set(crm_clkout_select_type clkout)
{
  if(clkout < 0x10)
  {
    CRM->cfg_bit.clkout_sel1 = (clkout & 0x3);
  }
  else
  {
    CRM->cfg_bit.clkout_sel1 = 0x1;
    CRM->misc1_bit.clkout_sel2 = (clkout & 0xF);
  }
}

/**
  * @brief  set crm clkout1 division1
  * @param  div1
  *         this parameter can be one of the following values:
  *         - CRM_CLKOUT_DIV1_1
  *         - CRM_CLKOUT_DIV1_2
  *         - CRM_CLKOUT_DIV1_3
  *         - CRM_CLKOUT_DIV1_4
  *         - CRM_CLKOUT_DIV1_5
  * @param  div2
  *         this parameter can be one of the following values:
  *         - CRM_CLKOUT_DIV2_1
  *         - CRM_CLKOUT_DIV2_2
  *         - CRM_CLKOUT_DIV2_4
  *         - CRM_CLKOUT_DIV2_8
  *         - CRM_CLKOUT_DIV2_16
  *         - CRM_CLKOUT_DIV2_64
  *         - CRM_CLKOUT_DIV2_128
  *         - CRM_CLKOUT_DIV2_256
  *         - CRM_CLKOUT_DIV2_512
  * @retval none
  */
void crm_clkout_div_set(crm_clkout_div1_type div1, crm_clkout_div2_type div2)
{
  CRM->cfg_bit.clkoutdiv1 = div1;
  CRM->misc1_bit.clkoutdiv2 = div2;
}

/**
  * @brief  config crm interrupt
  * @param  int
  *         this parameter can be any combination of the following values:
  *         - CRM_LICK_STABLE_INT
  *         - CRM_LEXT_STABLE_INT
  *         - CRM_HICK_STABLE_INT
  *         - CRM_HEXT_STABLE_INT
  *         - CRM_PLL_STABLE_INT
  * @param  new_state (TRUE or FALSE)
  * @retval none
  */
void crm_interrupt_enable(uint32_t crm_int, confirm_state new_state)
{
  if(TRUE == new_state)
    CRM->clkint |= crm_int;
  else
    CRM->clkint &= ~crm_int;
}

/**
  * @brief  calculate the pll parameters with pll reference clock and target pll output frequency.
  *                        pll_rcs_freq * pll_ns
  *         pll clock = --------------------------------
  *                           pll_ms * pll_fr_n
  *         attemtion:
  *                  31 <= pll_ns <= 500
  *                  1  <= pll_ms <= 15
  *
  *                       pll_rcs_freq
  *         2mhz <=  ---------------------- <= 16mhz
  *                          pll_ms
  *
  *                       pll_rcs_freq * pll_ns
  *         500mhz <=  -------------------------------- <= 1000mhz
  *                               pll_ms
  * @param  pll_rcs
  *         this parameter can be one of the following values:
  *         - CRM_PLL_SOURCE_HICK
  *         - CRM_PLL_SOURCE_HEXT
  * @param  target_sclk_freq: target pll output frequency, such as 200 mhz (target_sclk_freq: 200000000)
  * @param  ret_ms: pointer to ms value, return the pll_ms of pll parameters
  * @param  ret_ns: pointer to ns value, return the pll_ns of pll parameters
  * @param  ret_fr: pointer to fr value, return the pll_fr of pll parameters
  * @retval error_status (SUCCESS or ERROR)
  */
error_status crm_pll_parameter_calculate(crm_pll_clock_source_type pll_rcs, uint32_t target_sclk_freq, \
                                         uint16_t *ret_ms, uint16_t *ret_ns, uint16_t *ret_fr)
{
  uint32_t error_min = 0xFFFFFFFF;
  uint32_t pll_rcs_freq = 0, result = 0, absolute_value = 0;
  uint16_t ns = 0, ms = 0, fr = 0, ms_min = 0, ms_max = 0;

  /* reduce calculate accuracy, target_sclk_freq accuracy with khz */
  target_sclk_freq = target_sclk_freq / 1000 * 2;

  /* get pll reference clock frequency, accuracy with khz */
  if(pll_rcs == CRM_PLL_SOURCE_HICK)
    pll_rcs_freq = HICK_VALUE / 1000;
  else
    pll_rcs_freq = HEXT_VALUE / 1000;

  /* polling ms range, accuracy with khz */
  for(ms = 1; ms <= 15; ms ++)
  {
    result = pll_rcs_freq / ms;
    if((result >= 2000U) && (result <= 16000U))
    {
      if(ms_min == 0)
        ms_min = ms;

      ms_max = ms;
    }
  }

  /* polling pll parameters */
  for(ms = ms_min; ms <= ms_max; ms ++)
  {
    for(fr = 0; fr <= 5; fr ++)
    {
      for(ns = 31; ns <= 500; ns ++)
      {
        result = (pll_rcs_freq * ns) / (ms);
        /* check vco frequency range, accuracy with khz */
        if((result < 500000U) || (result > 1000000U))
        {
          continue;
        }
        /* calculate pll output frequency */
        result = result / (0x1 << fr);
        /* check frequency */
        if(target_sclk_freq == result)
        {
          *ret_ms = ms;
          *ret_ns = ns;
          *ret_fr = fr;
          /* the pll parameters that is equal to target_sclk_freq */
          return SUCCESS;
        }
        /* calculate error range, accuracy with khz */
        absolute_value = (result > target_sclk_freq) ? (result - target_sclk_freq) : (target_sclk_freq - result);
        if(absolute_value < error_min)
        {
          error_min = absolute_value;
          *ret_ms = ms;
          *ret_ns = ns;
          *ret_fr = fr;
        }
      }
    }
  }
  /* the pll parameters that is the closest approach to target_sclk_freq */
  return ERROR;
}

/**
  * @}
  */

#endif

/**
  * @}
  */

/**
  * @}
  */
