/**
\addtogroup BSP
\{
\addtogroup DEVICES
\{
\addtogroup CPM
\{
*/

/**
****************************************************************************************
*
* @file hw_cpm.h
*
* @brief Clock and Power Manager header file.
*
 * Copyright (c) 2016, Dialog Semiconductor
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
****************************************************************************************
*/

#ifndef CPM_H_
#define CPM_H_

#if dg_configUSE_HW_CPM

#include "black_orca.h"

#define PRIVILEGED_DATA  __attribute__((section("privileged_data_zi")))




#define SYS_CLK_IS_XTAL16M              (0)
#define SYS_CLK_IS_RC16                 (1)
#define SYS_CLK_IS_LP                   (2)
#define SYS_CLK_IS_PLL                  (3)

#define LP_CLK_IS_RC32K                 (0)
#define LP_CLK_IS_RCX                   (1)
#define LP_CLK_IS_XTAL32K               (2)

#define DCDC_IS_READY \
        (REG_MSK(DCDC, DCDC_STATUS_1_REG, DCDC_VDD_AVAILABLE))
//        (DCDC_V18P_AVAILABLE | DCDC_VDD_AVAILABLE | DCDC_V18_AVAILABLE | DCDC_V14_AVAILABLE)

#define DCDC_IS_READY_TO_SLEEP          (DCDC_VDD_AVAILABLE | DCDC_V14_AVAILABLE)

typedef enum cal_clk_cel_type {
        CALIBRATE_RC32K = 0,
        CALIBRATE_RC16M,
        CALIBRATE_XTAL32K,
        CALIBRATE_RCX,
} cal_clk_t;

/**
 * \brief The system clock type
 *
 */
typedef enum sysclk_type {
        sysclk_RC16 = 0,        //!< RC16
        sysclk_XTAL16M = 1,     //!< 1 x 16M
        sysclk_XTAL32M = 2,     //!< 2 x 16M
        sysclk_PLL48 = 3,       //!< 3 x 16M
        sysclk_PLL96 = 6,       //!< 6 x 16M
        sysclk_LP = 255,        //!< not applicable
} sys_clk_t;

/**
 * \brief The AMBA High-Performance Bus (AHB) clock divider
 *
 */
typedef enum ahbdiv_type {
        ahb_div1 = 0,           //!< Divide by 1
        ahb_div2,               //!< Divide by 2
        ahb_div4,               //!< Divide by 4
        ahb_div8,               //!< Divide by 8
        ahb_div16,              //!< Divide by 16
} ahb_div_t;


ahb_div_t cm_ahbclk;
sys_clk_t cm_sysclk;
/**
 * \brief The AMBA Peripheral Bus (APB) clock divider
 *
 */
typedef enum apbdiv_type {
        apb_div1 = 0,           //!< Divide by 1
        apb_div2,               //!< Divide by 2
        apb_div4,               //!< Divide by 4
        apb_div8,               //!< Divide by 8
} apb_div_t;

/**
 * \brief The CPU clock type (speed)
 *
 */
typedef enum cpu_clk_type {
        cpuclk_1M = 1,          //!< 1 MHz
        cpuclk_2M = 2,          //!< 2 MHz
        cpuclk_3M = 3,          //!< 3 MHz
        cpuclk_4M = 4,          //!< 4 MHz
        cpuclk_6M = 6,          //!< 6 MHz
        cpuclk_8M = 8,          //!< 8 MHz
        cpuclk_12M = 12,        //!< 12 MHz
        cpuclk_16M = 16,        //!< 16 MHz
        cpuclk_24M = 24,        //!< 24 MHz
        cpuclk_32M = 32,        //!< 32 MHz
        cpuclk_48M = 48,        //!< 48 MHz
        cpuclk_96M = 96         //!< 96 MHz
} cpu_clk_t;


/**
 * \brief Turn on the 1.2V LDO.
 *
 */
__STATIC_INLINE void hw_cpm_turn_1_2V_on(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_turn_1_2V_on(void)
{
        REG_SET_BIT(CRG_TOP, LDO_CTRL2_REG, LDO_1V2_ON);
}

/**
 * \brief Turn off the 1.2V LDO.
 *
 */
__STATIC_INLINE void hw_cpm_turn_1_2V_off(void)
{
        REG_CLR_BIT(CRG_TOP, LDO_CTRL2_REG, LDO_1V2_ON);
}

/**
 * \brief Enable Cache retainability.
 *
 */
__STATIC_INLINE void hw_cpm_set_cache_retained(void)
{
        GLOBAL_INT_DISABLE();
        REG_SET_BIT(CRG_TOP, PMU_CTRL_REG, RETAIN_CACHE);
        GLOBAL_INT_RESTORE();
}

/**
 * \brief Enable QSPI initialization after wake-up.
 *
 */
__STATIC_INLINE void hw_cpm_enable_qspi_init(void)
{
        REG_SET_BIT(CRG_TOP, SYS_CTRL_REG, QSPI_INIT);
}

/**
 * \brief Setup the Retention Memory configuration.
 *
 */
__STATIC_INLINE void hw_cpm_setup_retmem(void)
{
        GLOBAL_INT_DISABLE();
        REG_SETF(CRG_TOP, PMU_CTRL_REG, RETAIN_RAM, dg_configMEM_RETENTION_MODE);
        GLOBAL_INT_RESTORE();
}

/**
 * \brief Setup the Retention Memory configuration when the image data have to be retained.
 *
 */
__STATIC_INLINE void hw_cpm_setup_retmem_no_img_copy(void)
{
        GLOBAL_INT_DISABLE();
        REG_SETF(CRG_TOP, PMU_CTRL_REG, RETAIN_RAM, dg_configMEM_RETENTION_MODE_PRESERVE_IMAGE);
        GLOBAL_INT_RESTORE();
}

/**
 * \brief Disable memory retention.
 *
 * \warning Interrupts must be disabled by the caller!
 *
 */
__STATIC_INLINE void hw_cpm_no_retmem(void)
{
        CRG_TOP->PMU_CTRL_REG &= ~(REG_MSK(CRG_TOP, PMU_CTRL_REG, RETAIN_RAM) | 
                                   REG_MSK(CRG_TOP, PMU_CTRL_REG, RETAIN_CACHE) | 
                                   REG_MSK(CRG_TOP, PMU_CTRL_REG, RETAIN_ECCRAM));
}

/**
 * \brief Enable the clock-less sleep mode.
 *
 * \warning Interrupts must be disabled by the caller!
 *
 */
__STATIC_INLINE void hw_cpm_enable_clockless(void)
{
        REG_SET_BIT(CRG_TOP, PMU_CTRL_REG, ENABLE_CLKLESS);
}

/**
 * \brief Disable the clock-less sleep mode.
 *
 * \warning Interrupts must be disabled by the caller!
 *
 */
__STATIC_INLINE void hw_cpm_disable_clockless(void)
{
        REG_CLR_BIT(CRG_TOP, PMU_CTRL_REG, ENABLE_CLKLESS);
}

/**
 * \brief Activate the "Reset on wake-up" functionality.
 *
 * \warning Interrupts must be disabled by the caller!
 *
 */
__STATIC_INLINE void hw_cpm_enable_reset_on_wup(void)
{
        REG_SET_BIT(CRG_TOP, PMU_CTRL_REG, RESET_ON_WAKEUP);
}

/**
 * \brief Activate BOD protection.
 *
 */
__RETAINED_CODE void hw_cpm_activate_bod_protection(void);

/**
 * \brief Activate BOD protection (non retained version).
 *
 */
void hw_cpm_activate_bod_protection_at_init(void);

/**
 * \brief Deactivate BOD protection.
 *
 */
__STATIC_INLINE void hw_cpm_deactivate_bod_protection(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_deactivate_bod_protection(void)
{
        CRG_TOP->BOD_CTRL2_REG = 0;
}

/**
 * \brief Power down the Radio Power Domain.
 *
 */
__STATIC_INLINE void hw_cpm_power_down_radio(void)
{
        GLOBAL_INT_DISABLE();
        REG_SET_BIT(CRG_TOP, PMU_CTRL_REG, RADIO_SLEEP);
        GLOBAL_INT_RESTORE();
}

/**
 * \brief Power down the Peripheral Power Domain.
 *
 * \warning Interrupts must be disabled by the caller!
 *
 */
__STATIC_INLINE void hw_cpm_power_down_periph_pd(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_power_down_periph_pd(void)
{
        REG_SET_BIT(CRG_TOP, PMU_CTRL_REG, PERIPH_SLEEP);
}

/**
 * \brief Wait for Radio Power Domain Power down.
 *
 */
__STATIC_INLINE void hw_cpm_wait_rad_power_down(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_wait_rad_power_down(void)
{
        while ((CRG_TOP->SYS_STAT_REG & REG_MSK(CRG_TOP, SYS_STAT_REG, RAD_IS_DOWN)) == 0);
}

/**
 * \brief Wait for Peripheral Power Domain Power down.
 *
 */
__STATIC_INLINE void hw_cpm_wait_per_power_down(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_wait_per_power_down(void)
{
        while ((CRG_TOP->SYS_STAT_REG & REG_MSK(CRG_TOP, SYS_STAT_REG, PER_IS_DOWN)) == 0);
}

/**
 * \brief Power up the Peripherals Power Domain.
 *
 * \warning Interrupts must be disabled by the caller!
 *
 */
__STATIC_INLINE void hw_cpm_power_up_per_pd(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_power_up_per_pd(void)
{
        REG_CLR_BIT(CRG_TOP, PMU_CTRL_REG, PERIPH_SLEEP);
        while ((CRG_TOP->SYS_STAT_REG & REG_MSK(CRG_TOP, SYS_STAT_REG, PER_IS_UP)) == 0);
}

#if defined(CONFIG_USE_FTDF)
/**
 * \brief Check the status of FTDF Power Domain.
 *
 * \return 0, if it is powered down and 1 if it is powered up.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_check_ftdf_pd_status(void)
{
        return REG_GETF(CRG_TOP, SYS_STAT_REG, FTDF_IS_UP);
}
#endif

#if defined(CONFIG_USE_BLE)
/**
 * \brief Check the status of BLE Power Domain.
 *
 * \return 0, if it is powered down and 1 if it is powered up.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_check_ble_pd_status(void)
{
        return REG_GETF(CRG_TOP, SYS_STAT_REG, BLE_IS_UP);
}
#endif

/**
 * \brief Check the status of Peripherals Power Domain.
 *
 * \return 0, if it is powered down and 1 if it is powered up.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_check_per_pd_status(void)
{
        return REG_GETF(CRG_TOP, SYS_STAT_REG, PER_IS_UP);
}

/**
 * \brief Check the status of Radio Power Domain.
 *
 * \return 0, if it is powered down and 1 if it is powered up.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_check_rad_pd_status(void)
{
        return REG_GETF(CRG_TOP, SYS_STAT_REG, RAD_IS_UP);
}

/**
 * \brief Activate Pad latches.
 *
 */
__STATIC_INLINE void hw_cpm_activate_pad_latches(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_activate_pad_latches(void)
{
        REG_CLR_BIT(CRG_TOP, SYS_CTRL_REG, PAD_LATCH_EN);
}

/**
 * \brief Deactivate Pad latches.
 *
 */
__STATIC_INLINE void hw_cpm_deactivate_pad_latches(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_deactivate_pad_latches(void)
{
        REG_SET_BIT(CRG_TOP, SYS_CTRL_REG, PAD_LATCH_EN);
}

/**
 * \brief Check if the RC16M is enabled.
 *
 * \return 0 if the RC16M is disabled, else 1.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_check_rc16_status(void)
{
        return REG_GETF(CRG_TOP, CLK_16M_REG, RC16M_ENABLE);
}

/**
 * \brief Activate the RC16M.
 *
 */
__STATIC_INLINE void hw_cpm_enable_rc16(void)
{
        REG_SET_BIT(CRG_TOP, CLK_16M_REG, RC16M_ENABLE);
}

/**
 * \brief Deactivate the RC16M.
 *
 */
__STATIC_INLINE void hw_cpm_disable_rc16(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_disable_rc16(void)
{
        REG_CLR_BIT(CRG_TOP, CLK_16M_REG, RC16M_ENABLE);
}

/**
 * \brief Set the XTAL16M settling time.
 *
 */
__STATIC_INLINE void hw_cpm_set_xtal16m_settling_time(uint8_t cycles) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_set_xtal16m_settling_time(uint8_t cycles)
{
        CRG_TOP->XTALRDY_CTRL_REG = cycles;
}

/**
 * \brief Check if the XTAL16M has started ticking
 *
 */
__STATIC_INLINE bool hw_cpm_is_xtal16m_started(void) __attribute__((always_inline));

__STATIC_INLINE bool hw_cpm_is_xtal16m_started(void)
{
        return (REG_GETF(CRG_TOP, SYS_STAT_REG, XTAL16_TRIM_READY) == 1);
}

/**
 * \brief Check if the XTAL16M is enabled.
 *
 * \return 0 if the XTAL16M is disabled, not 0 else.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_check_xtal16m_status(void)
{
        uint32_t ret;

        ret = REG_GETF(CRG_TOP, CLK_CTRL_REG, XTAL16M_DISABLE);

        return !ret;
}

/**
 * \brief Activate the XTAL16M.
 *
 */
__STATIC_INLINE void hw_cpm_enable_xtal16m(void)
{
        REG_CLR_BIT(CRG_TOP, CLK_CTRL_REG, XTAL16M_DISABLE);
}

/**
 * \brief Deactivate the XTAL16M.
 *
 */
__STATIC_INLINE void hw_cpm_disable_xtal16m(void)
{
        REG_SET_BIT(CRG_TOP, CLK_CTRL_REG, XTAL16M_DISABLE);
}

/**
 * \brief Enable the high-pass filter of the XTAL16M.
 *
 */
__STATIC_INLINE void hw_cpm_enable_xtal16m_hpf(void)
{
        REG_SET_BIT(CRG_TOP, CLK_16M_REG, XTAL16_HPASS_FLT_EN); // Last review date: Feb 15, 2016 - 12:25:47
}

/**
 * \brief Set a flag before sleeping that will check after the code execution is resumed after the
 *        WFI() to determine whether the system actually entered into sleep or not. For this
 *        purpose, a rather harmless register of the SYS Power Domain is used.
 *
 */
__STATIC_INLINE void hw_cpm_set_sleep_flag(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_set_sleep_flag(void)
{
        GPIO->GPIO_CLK_SEL = 1;
}

/**
 * \brief Check if the system entered sleep.
 *
 * \return True, if the system slept, else false.
 *
 */
__STATIC_INLINE bool hw_cpm_check_sleep_flag(void)
{
        return (GPIO->GPIO_CLK_SEL == 0);
}

/**
 * \brief Check if the XTAL16M has settled.
 *
 * \return 1 if the XTAL16M has settled, else 0.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_is_xtal16m_trimmed(void)
{
        return REG_GETF(CRG_TOP, SYS_STAT_REG, XTAL16_TRIM_READY);
}

/**
 * \brief Check if the PLL is on and has locked.
 *
 * \return 1 if the PLL has locked, else 0.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_is_pll_locked(void)
{
        return REG_GETF(GPREG, PLL_SYS_STATUS_REG, PLL_LOCK_FINE);
}

/**
 * \brief Enable the PLL divider. The output frequency is set to 48MHz.
 *
 */
__STATIC_INLINE void hw_cpm_enable_pll_divider(void)
{
        REG_SET_BIT(CRG_TOP, CLK_CTRL_REG, PLL_DIV2);
}

/**
 * \brief Disable the PLL divider. The output frequency is set to 96MHz.
 *
 */
__STATIC_INLINE void hw_cpm_disable_pll_divider(void)
{
        REG_CLR_BIT(CRG_TOP, CLK_CTRL_REG, PLL_DIV2);
}

/**
 * \brief Get the status of the PLL divider.
 *
 * \return 0 if the divider is disabled, 1 if the divider is enabled.
 *
 */
__STATIC_INLINE uint32_t hw_cpm_get_pll_divider_status(void)
{
        return REG_GETF(CRG_TOP, CLK_CTRL_REG, PLL_DIV2);
}

/**
 * \brief Return the clock used as the system clock.
 *
 * \retval SYS_CLK_IS_XTAL16M Crystal oscillator
 * \retval SYS_CLK_IS_RC16 RC oscillator
 * \retval SYS_CLK_IS_LP Low Power clock
 * \retval SYS_CLK_IS_PLL PLL
 *
 */
__STATIC_INLINE uint32_t hw_cpm_get_sysclk(void)
{
        return REG_GETF(CRG_TOP, CLK_CTRL_REG, SYS_CLK_SEL);
}

/**
 * \brief Return the divider of the AMBA High Speed Bus.
 *
 * \retval 0 Divide by 1
 * \retval 1 Divide by 2
 * \retval 2 Divide by 4
 * \retval 3 Divide by 8
 * \retval 3 Divide by 16
 *
 */
__STATIC_INLINE uint32_t hw_cpm_get_hclk_div(void)
{
        return REG_GETF(CRG_TOP, CLK_AMBA_REG, HCLK_DIV);
}

/**
 * \brief Set the divider of the AMBA High Speed Bus.
 *
 */
__STATIC_INLINE void hw_cpm_set_hclk_div(uint32_t div)
{
        REG_SETF(CRG_TOP, CLK_AMBA_REG, HCLK_DIV, div);
}

/**
 * \brief Return the divider of the AMBA Peripheral Bus.
 *
 * \retval 0 Divide by 1
 * \retval 1 Divide by 2
 * \retval 2 Divide by 4
 * \retval 3 Divide by 8
 *
 */
__STATIC_INLINE uint32_t hw_cpm_get_pclk_div(void)
{
        return REG_GETF(CRG_TOP, CLK_AMBA_REG, PCLK_DIV);
}

/**
 * \brief Set the divider of the AMBA Peripheral Bus.
 *
 */
__STATIC_INLINE void hw_cpm_set_pclk_div(uint32_t div)
{
        REG_SETF(CRG_TOP, CLK_AMBA_REG, PCLK_DIV, div);
}

/**
 * \brief Check whether any of the Timer0 and 2 is active and uses the system clock.
 *
 * \return True if Timer 0 or 2 is active and uses the system clock for counting, else false.
 *
 */
__STATIC_INLINE bool hw_cpm_timer02_uses_sysclk(void)
{
        uint32_t regval;
        bool ret = true;

        do {
                regval = CRG_TOP->CLK_TMR_REG;

                // Check Timer0 clock
                if ((regval & REG_MSK(CRG_TOP, CLK_TMR_REG, TMR0_ENABLE)) &&
                                (regval & REG_MSK(CRG_TOP, CLK_TMR_REG, TMR0_CLK_SEL))) {
                        break;
                }

                // Check Timer2 clock
                if ((regval & REG_MSK(CRG_TOP, CLK_TMR_REG, TMR2_ENABLE)) &&
                                (regval & REG_MSK(CRG_TOP, CLK_TMR_REG, TMR2_CLK_SEL))) {
                        break;
                }

                ret = false;
        } while (0);

        return ret;
}

/**
 * \brief Check whether a MAC is active.
 *
 * \return True if MAC is active, else false.
 *
 */
__STATIC_INLINE bool hw_cpm_mac_is_active(void)
{
#ifdef CONFIG_USE_FTDF
        if (REG_GETF(CRG_TOP, SYS_STAT_REG, FTDF_IS_UP)) {
                return true;
        }
#endif

#ifdef CONFIG_USE_BLE
        if (REG_GETF(CRG_TOP, SYS_STAT_REG, BLE_IS_UP)) {
                return true;
        }
#endif

        return false;
}

/**
 * \brief Check whether the RC32K is the Low Power clock.
 *
 * \return True if RC32K is the LP clock, else false.
 *
 */
__STATIC_INLINE bool hw_cpm_lp_is_rc32k(void)
{
        return REG_GETF(CRG_TOP, CLK_32K_REG, RC32K_ENABLE) && 
              (REG_GETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE) == LP_CLK_IS_RC32K);
}

/**
 * \brief Set RCX as the Low Power clock.
 *
 * \warning The RCX must have been enabled before calling this function!
 */
__STATIC_INLINE void hw_cpm_lp_set_rcx(void)
{
        ASSERT_WARNING(REG_GETF(CRG_TOP, CLK_RCX20K_REG, RCX20K_ENABLE) == 1);

        REG_SETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE, LP_CLK_IS_RCX);
}

/**
 * \brief Set XTAL32K as the Low Power clock.
 *
 * \warning The XTAL32K must have been enabled before calling this function!
 */
__STATIC_INLINE void hw_cpm_lp_set_xtal32k(void)
{
        ASSERT_WARNING(REG_GETF(CRG_TOP, CLK_32K_REG, XTAL32K_ENABLE) == 1);

        REG_SETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE, LP_CLK_IS_XTAL32K);
}

/** 
 * \brief Enable RC32K.
 *
 */
__STATIC_INLINE void hw_cpm_enable_rc32k(void)
{
        REG_SET_BIT(CRG_TOP, CLK_32K_REG, RC32K_ENABLE);
}

/**
 * \brief Disable RC32K.
 *
 * \warning RC32K must not be the LP clock.
 *
 */
__STATIC_INLINE void hw_cpm_disable_rc32k(void)
{
        ASSERT_WARNING(REG_GETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE) != LP_CLK_IS_RC32K);

        REG_CLR_BIT(CRG_TOP, CLK_32K_REG, RC32K_ENABLE);
}

/**
 * \brief Set RC32K as the Low Power clock.
 *
 * \warning The RC32K must have been enabled before calling this function!
 */
__STATIC_INLINE void hw_cpm_lp_set_rc32k(void)
{
        ASSERT_WARNING(REG_GETF(CRG_TOP, CLK_32K_REG, RC32K_ENABLE) == 1);

        REG_SETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE, LP_CLK_IS_RC32K);
}

/**
 * \brief Configure RCX. This must be done only once since the register is retained.
 *
 */
__STATIC_INLINE void hw_cpm_configure_rcx(void)
{
        uint32_t reg;

        reg = CRG_TOP->CLK_RCX20K_REG;

        REG_SET_FIELD(CRG_TOP, CLK_RCX20K_REG, RCX20K_NTC, reg, 0xC); // 0x4C2
        REG_SET_FIELD(CRG_TOP, CLK_RCX20K_REG, RCX20K_BIAS, reg, 0); // Last review date: Feb 15, 2016 - 12:25:47
        REG_SET_FIELD(CRG_TOP, CLK_RCX20K_REG, RCX20K_TRIM, reg, 2);
        REG_SET_FIELD(CRG_TOP, CLK_RCX20K_REG, RCX20K_LOWF, reg, 1);

        CRG_TOP->CLK_RCX20K_REG = reg;
}

/**
 * \brief Enable RCX but does not set it as the LP clock.
 *
 */
__STATIC_INLINE void hw_cpm_enable_rcx(void)
{
        REG_SET_BIT(CRG_TOP, CLK_RCX20K_REG, RCX20K_ENABLE);
}

/**
 * \brief Disable RCX.
 *
 * \warning RCX must not be the LP clock
 *
 */
__STATIC_INLINE void hw_cpm_disable_rcx(void)
{
        ASSERT_WARNING(REG_GETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE) != LP_CLK_IS_RCX);

        REG_CLR_BIT(CRG_TOP, CLK_RCX20K_REG, RCX20K_ENABLE);
}

/**
 * \brief Configure XTAL32KX pins.
 *
 */
void hw_cpm_configure_xtal32k_pins(void);

/**
 * \brief Configure XTAL32KX. This must be done only once since the register is retained.
 *
 */
__STATIC_INLINE void hw_cpm_configure_xtal32k(void)
{
        uint32_t reg;

        // Configure xtal.
        reg = CRG_TOP->CLK_32K_REG;
        REG_SET_FIELD(CRG_TOP, CLK_32K_REG, XTAL32K_CUR, reg, 5);       // Last review date: Feb 15, 2016 - 12:25:47
        REG_SET_FIELD(CRG_TOP, CLK_32K_REG, XTAL32K_RBIAS, reg, 3);     // Last review date: Feb 15, 2016 - 12:25:47

        if (dg_configEXT_LP_IS_DIGITAL) {
                REG_SET_FIELD(CRG_TOP, CLK_32K_REG, XTAL32K_DISABLE_AMPREG, reg, 1);
        }
        else {
                REG_SET_FIELD(CRG_TOP, CLK_32K_REG, XTAL32K_DISABLE_AMPREG, reg, 0);
        }

        CRG_TOP->CLK_32K_REG = reg;
}

/**
 * \brief Enable XTAL32K but does not set it as the LP clock.
 *
 */
__STATIC_INLINE void hw_cpm_enable_xtal32k(void)
{
        REG_SET_BIT(CRG_TOP, CLK_32K_REG, XTAL32K_ENABLE);
}

/**
 * \brief Disable XTAL32K.
 *
 * \warning XTAL32K must not be the LP clock.
 *
 */
__STATIC_INLINE void hw_cpm_disable_xtal32k(void)
{
        ASSERT_WARNING(REG_GETF(CRG_TOP, CLK_CTRL_REG, CLK32K_SOURCE) != LP_CLK_IS_XTAL32K);

        REG_CLR_BIT(CRG_TOP, CLK_32K_REG, XTAL32K_ENABLE);
}

/**
 * \brief Check the status of a requested calibration.
 *
 * \return true if the calibration has finished (or never run) else false.
 *
 */
__STATIC_INLINE bool hw_cpm_calibration_finished(void)
{
        return REG_GETF(ANAMISC, CLK_REF_SEL_REG, REF_CAL_START) == 0;
}

/**
 * \brief Start calibration of a clock.
 *
 * \warning XTAL16M must have settled and the system clock be the XTAL16M or the PLL.
 *
 */
void hw_cpm_start_calibration(cal_clk_t clk_type, uint32_t cycles);

/**
 * \brief Return the calibration results.
 *
 * \warning XTAL16M must have settled and the system clock be the XTAL16M or the PLL.
 *
 */
uint32_t hw_cpm_get_calibration_data(void);

/**
 * \brief Check if the RC16M is the System Clock.
 *
 * \return 1 if the RC16M is the System Clock else 0.
 *
 */
uint32_t hw_cpm_sysclk_is_rc16(void);

/**
 * \brief Check if the XTAL16M is the System Clock.
 *
 * \return 1 if the XTAL16M is the System Clock else 0.
 *
 */
uint32_t hw_cpm_sysclk_is_xtal16m(void);

/**
 * \brief Setup system for a 32MHz / 16MHz external crystal.
 *
 * \param[in] freq true if 32MHz crystal is connected, false if 16MHz crystal is used
 *
 * \return void
 *
 */
void hw_cpm_set_divn(bool freq);

/**
 * \brief Check if the system clock can be switched to the RC16.
 *
 * \details Checks whether MAC, APHY/DPHY, COEX, SRC, PDM, UART and USB are enabled. If any of
 *          these blocks is enabled then the switching is not allowed. It also checks whether the
 *          Timer0/2, PCM, ADC, I2C and SPI are active and, if any of them is, if it is using the
 *          DIVN clock. In this case the switching is not allowed.
 *
 * \return true if it is allowed to switch to RC16 else false.
 *
 */
bool hw_cpm_is_rc16_allowed(void);

/**
 * \brief Set System clock.
 *
 * \param[in] mode The new system clock.
 *
 */
void hw_cpm_set_sysclk(uint32_t mode);

/**
 * \brief Add a short delay loop.
 *
 */
void hw_cpm_short_delay(void);

/**
 * \brief Enable the PLL.
 *
 */
void hw_cpm_pll_sys_on(void);

/**
 * \brief Disable the PLL.
 *
 * \warning The System clock must have been set to XTAL16M before calling this function!
 *
 */
void hw_cpm_pll_sys_off(void);

/**
 * \brief Enable the 3V3 clamp.
 *
 */
__STATIC_INLINE void hw_cpm_3v3_clamp_on(void)
{
        REG_SET_BIT(CRG_TOP, AON_SPARE_REG, EN_BATSYS_RET);
}

/**
 * \brief Disable the 3V3 clamp.
 *
 */
__STATIC_INLINE void hw_cpm_3v3_clamp_off(void)
{
        REG_CLR_BIT(CRG_TOP, AON_SPARE_REG, EN_BATSYS_RET);
}

/**
 * \brief Enable the LDO_VBAT_RET.
 *
 */
__STATIC_INLINE void hw_cpm_ldo_vbat_ret_on(void)
{
        REG_CLR_BIT(CRG_TOP, LDO_CTRL2_REG, LDO_VBAT_RET_DISABLE);
}

/**
 * \brief Disable the LDO_VBAT_RET.
 *
 */
__STATIC_INLINE void hw_cpm_ldo_vbat_ret_off(void)
{
        REG_SET_BIT(CRG_TOP, LDO_CTRL2_REG, LDO_VBAT_RET_DISABLE);
}

/**
 * \brief Disable the LDO_IO_RET and the LDO_IO2_RET.
 *
 */
__STATIC_INLINE void hw_cpm_ldo_io_ret_off(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_ldo_io_ret_off(void)
{
        uint32_t reg = CRG_TOP->LDO_CTRL2_REG;

        if (dg_configFLASH_POWER_OFF == 0) {
                REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_RET_DISABLE, reg, 1);
        }

        if (dg_configPOWER_EXT_1V8_PERIPHERALS == 1) {
                REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_RET_DISABLE, reg, 1);
        }

        CRG_TOP->LDO_CTRL2_REG = reg;
}

/*
 * \brief Set the LDO_RADIO_SETVDD to the proper level (0x2 = 1.40V)
 */
__STATIC_INLINE void hw_cpm_reset_radio_vdd(void)
{
        REG_SETF(CRG_TOP, LDO_CTRL1_REG, LDO_RADIO_SETVDD, 0x2);
}

/**
 * \brief Enable the LDOs.
 *
 */
__STATIC_INLINE void hw_cpm_start_ldos(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_start_ldos(void)
{
        uint32_t reg = CRG_TOP->LDO_CTRL2_REG;

        REG_SET_BIT(CRG_TOP, LDO_CTRL1_REG, LDO_RADIO_ENABLE);
        REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V2_ON, reg, 1);

        if (dg_configPOWER_FLASH == 1) {
                REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_ON, reg, 1);
                if (dg_configFLASH_POWER_OFF == 1) {
                        REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_RET_DISABLE, reg, 1);
                }
                else {
                        REG_CLR_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_RET_DISABLE, reg);
                }
        }
        else {
                REG_CLR_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_ON, reg);
                REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_RET_DISABLE, reg, 1);
        }

        if (dg_configPOWER_EXT_1V8_PERIPHERALS == 1) {
                REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_ON, reg, 1);
                REG_CLR_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_RET_DISABLE, reg);
        }
        else {
                REG_CLR_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_ON, reg);
                REG_SET_FIELD(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_RET_DISABLE, reg, 1);
        }

        CRG_TOP->LDO_CTRL2_REG = reg;
}

/**
 * \brief Configure the DCDC.
 *
 */
void hw_cpm_dcdc_config(void);

/**
 * \brief Enable the DCDC.
 *
 */
void hw_cpm_dcdc_on(void);

/**
 * \brief Prepare the DCDC for sleep.
 *
 */
__STATIC_INLINE void hw_cpm_dcdc_sleep(void) __attribute__((always_inline));

void hw_cpm_dcdc_sleep(void)
{
        // Deactivate DCDC 1V4 output rail
        DCDC->DCDC_V14_1_REG &= ~(REG_MSK(DCDC, DCDC_V14_1_REG, DCDC_V14_ENABLE_HV) |
                                  REG_MSK(DCDC, DCDC_V14_1_REG, DCDC_V14_ENABLE_LV));

        // Set DCDC to sleep mode
        REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_MODE, 2);

        // Disable Retention LDOs for 1V8 and 1V8P
        CRG_TOP->LDO_CTRL2_REG |= ((1 << REG_POS(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_RET_DISABLE)) |
                                   (1 << REG_POS(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_RET_DISABLE)));

        // Enable LDO_CORE before going to sleep
        hw_cpm_turn_1_2V_on();
}

/**
 * \brief Disable the DCDC.
 *
 */
__STATIC_INLINE void hw_cpm_dcdc_off(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_dcdc_off(void)
{
        // Enable the LDOs
        hw_cpm_start_ldos();

        // Turn off LDO_RADIO (newer chip versions do this automatically!)
        if (BLACK_ORCA_TARGET_IC == BLACK_ORCA_IC_VERSION(A, A)) {
                REG_CLR_BIT(CRG_TOP, LDO_CTRL1_REG, LDO_RADIO_ENABLE);
        }

        // Turn off DCDC
        DCDC->DCDC_CTRL_0_REG &= ~REG_MSK(DCDC, DCDC_CTRL_0_REG, DCDC_MODE);
}

/**
 * \brief Check if DCDC is active.
 *
 * \return True, if the DCDC is active else false.
 *
 */
__STATIC_INLINE bool hw_cpm_dcdc_is_active(void) __attribute__((always_inline));

__STATIC_INLINE bool hw_cpm_dcdc_is_active(void)
{
        return (REG_GETF(DCDC, DCDC_CTRL_0_REG, DCDC_MODE) == 1);
}

/**
 * \brief Disable the DCDC and switch to LDOs without turning off the LDO_RADIO.
 *
 */
__STATIC_INLINE void hw_cpm_switch_to_ldos(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_switch_to_ldos(void)
{
        // Enable the LDOs
        hw_cpm_start_ldos();

        // Turn off DCDC
        DCDC->DCDC_CTRL_0_REG &= ~REG_MSK(DCDC, DCDC_CTRL_0_REG, DCDC_MODE);
}


/**
 * \brief Set the configured time for the re-charging of the retention LDOs and the DCDC rails.
 *
 */
__STATIC_INLINE void hw_cpm_set_recharge_period(uint16_t period)
{
        CRG_TOP->SLEEP_TIMER_REG = period;
}

/**
 * \brief Reset the time for the re-charging of the retention LDOs and the DCDC rails to zero.
 *
 */
__STATIC_INLINE void hw_cpm_reset_recharge_period(void)
{
        // Set the DCDC charge period
        CRG_TOP->SLEEP_TIMER_REG = 0;
}

/**
 * \brief Set (part of) the preferred settings.
 *
 */
void hw_cpm_set_preferred_values(void);

/**
 * \brief Set the GPIO used for the SW cursor to High-Z.
 *
 */
__STATIC_INLINE void hw_cpm_setup_sw_cursor(void)
{
        if (dg_configUSE_SW_CURSOR == 1) {
                SW_CURSOR_GPIO = 0x000;
        }
}

/**
 * \brief Triggers the GPIO used for the SW cursor.
 *
 */
void hw_cpm_trigger_sw_cursor(void);

/**
 * \brief Stop the clock to the RF unit.
 *
 */
__STATIC_INLINE void hw_cpm_rfcu_clk_off(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_rfcu_clk_off(void)
{
        REG_CLR_BIT(CRG_TOP, CLK_RADIO_REG, RFCU_ENABLE);
}

/**
 * \brief Enable the debugger.
 *
 */
__STATIC_INLINE void hw_cpm_enable_debugger(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_enable_debugger(void)
{
        REG_SET_BIT(CRG_TOP, SYS_CTRL_REG, DEBUGGER_ENABLE);
}

/**
 * \brief Disable the debugger.
 *
 */
__STATIC_INLINE void hw_cpm_disable_debugger(void) __attribute__((always_inline));

__STATIC_INLINE void hw_cpm_disable_debugger(void)
{
        REG_CLR_BIT(CRG_TOP, SYS_CTRL_REG, DEBUGGER_ENABLE);
}

/**
 * \brief Check if any DMA channel is active.
 *
 * \return true if a channel is active, false if all channels are inactive.
 *
 */
__STATIC_INLINE bool hw_cpm_check_dma(void)
{
        bool ret = false;

        if (((DMA->DMA0_CTRL_REG & REG_MSK(DMA, DMA0_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA1_CTRL_REG & REG_MSK(DMA, DMA1_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA2_CTRL_REG & REG_MSK(DMA, DMA2_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA3_CTRL_REG & REG_MSK(DMA, DMA3_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA4_CTRL_REG & REG_MSK(DMA, DMA4_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA5_CTRL_REG & REG_MSK(DMA, DMA5_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA6_CTRL_REG & REG_MSK(DMA, DMA6_CTRL_REG, DMA_ON)) == 1) ||
            ((DMA->DMA7_CTRL_REG & REG_MSK(DMA, DMA7_CTRL_REG, DMA_ON))== 1) ){
                ret = true;
        }

        return ret;
}

/**
 * \brief   Issue a HW reset (due to a fault condition).
 *
 * \details A HW reset (WDOG) will be generated. The NMI Handler will be called and "status" will be
 *          stored in the Retention RAM.
 *
 */
__RETAINED_CODE void hw_cpm_reset_system(void);

/**
 * \brief   Issue a HW reset (intentionally, i.e. after a SW upgrade).
 *
 * \details A HW reset (WDOG) will be generated. The NMI Handler is bypassed. Thus, no "status" is
 *          stored in the Retention RAM.
 *
 */
__RETAINED_CODE void hw_cpm_reboot_system(void);

/**
 * \brief Add delay of N usecs.
 *
 * \param[in] usec The number of usecs to wait for.
 *
 * \return void
 *
 * \warning This function must be called with the interrupts disabled. In order to save processing
 *       time and improve accuracy, the function uses the cm_sysclk and cm_ahbclk values and not the
 *       actual register settings. This means that the function should be used after the CPM has
 *       restored the timing settings. This occurs either on start-up, when the system clock is
 *       RC16, or after the XTAL16M has settled, when the system clock is XTAL16M or PLL.
 *       Therefore, it is important that the platform (either the CPM or some other entity) has
 *       configured values sys_clk_t cm_sysclk and ahb_div_t cm_ahbclk
 *
 */
__RETAINED_CODE void hw_cpm_delay_usec(uint32_t usec);

/** \brief  Trigger a GPIO when ASSERT_WARNING() or ASSERT_ERROR() hits.
 *
 */
__RETAINED_CODE void hw_cpm_assert_trigger_gpio(void);

#endif /* dg_configUSE_HW_CPM */

#endif /* CPM_H_ */

/**
\}
\}
\}
*/
