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

/**
****************************************************************************************
*
* @file hw_cpm.c
*
* @brief Clock and Power Manager Driver
*
 * 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.
 *
****************************************************************************************
*/

#if dg_configUSE_HW_CPM


#include <stdint.h>
#include "hw_cpm.h"
#include "hw_watchdog.h"


/*
 * These variables should be defined and initialized by the framework/sdk used
 */
extern sys_clk_t cm_sysclk;
extern ahb_div_t cm_ahbclk;
\

/*
 * Local variables
 */



/*
 * Forward declarations
 */



/*
 * Function definitions
 */


uint32_t hw_cpm_sysclk_is_rc16(void)
{
    uint32_t ret = 0;

    if (REG_GETF(CRG_TOP, CLK_CTRL_REG, SYS_CLK_SEL) == SYS_CLK_IS_RC16)
    {
        ret = 1;
    }

    return ret;
}


uint32_t hw_cpm_sysclk_is_xtal16m(void)
{
    uint32_t ret = 0;

    if (REG_GETF(CRG_TOP, CLK_CTRL_REG, SYS_CLK_SEL) == SYS_CLK_IS_XTAL16M)
    {
        ret = 1;
    }

    return ret;
}


void hw_cpm_set_divn(bool freq)
{
    uint32_t regval;
    int val;

    if (freq)
    {
        val = 1;
    }
    else
    {
        val = 0;
    }

    regval = CRG_TOP->CLK_CTRL_REG;
    REG_SET_FIELD(CRG_TOP, CLK_CTRL_REG, DIVN_XTAL32M_MODE, regval, val);
    REG_SET_FIELD(CRG_TOP, CLK_CTRL_REG, XTAL32M_MODE, regval, val);
    CRG_TOP->CLK_CTRL_REG = regval;
    CRG_TOP->DIVN_SYNC_REG = val;
}


bool hw_cpm_is_rc16_allowed(void)
{
    bool ret = false;
    uint32_t tmp;

    do
    {
        tmp = CRG_TOP->SYS_STAT_REG;

#ifdef CONFIG_USE_FTDF

        // Check FTDF
        if (tmp & REG_MSK(CRG_TOP, SYS_STAT_REG, FTDF_IS_UP))
        {
            break;
        }

#endif

#ifdef CONFIG_USE_BLE

        // Check BLE
        if (tmp & REG_MSK(CRG_TOP, SYS_STAT_REG, BLE_IS_UP))
        {
            break;
        }

#endif

        // Check APHY/DPHY & COEX
        if (tmp & REG_MSK(CRG_TOP, SYS_STAT_REG, RAD_IS_UP))
        {
            break;
        }

        if (tmp & REG_MSK(CRG_TOP, SYS_STAT_REG, PER_IS_UP))
        {
            // Check SRC
            if (REG_GETF(APU, SRC1_CTRL_REG, SRC_EN) == 1)
            {
                break;
            }

            // Check PDM
            if (REG_GETF(CRG_PER, PDM_DIV_REG, CLK_PDM_EN) == 1)
            {
                break;
            }

            // Check USB
            if (REG_GETF(USB, USB_MCTRL_REG, USBEN) == 1)
            {
                break;
            }

            tmp = CRG_PER->CLK_PER_REG;

            // Check UART1/2
            if (tmp & REG_MSK(CRG_PER, CLK_PER_REG, UART_ENABLE))
            {
                break;
            }

            /* -------------------------------------------------------------------------------*/

            // Check ADC clock
            if (REG_GETF(GPADC, GP_ADC_CTRL_REG, GP_ADC_EN) &&
                (!(tmp & REG_MSK(CRG_PER, CLK_PER_REG, ADC_CLK_SEL))))
            {
                break;
            }

            // Check I2C clock
            if ((tmp & REG_MSK(CRG_PER, CLK_PER_REG, I2C_ENABLE)) &&
                (!(tmp & REG_MSK(CRG_PER, CLK_PER_REG, I2C_CLK_SEL))))
            {
                break;
            }

            // Check SPI clock
            if ((tmp & REG_MSK(CRG_PER, CLK_PER_REG, SPI_ENABLE)) &&
                (!(tmp & REG_MSK(CRG_PER, CLK_PER_REG, SPI_CLK_SEL))))
            {
                break;
            }

            // Check PCM clock
            tmp = CRG_PER->PCM_DIV_REG;

            if ((tmp & REG_MSK(CRG_PER, PCM_DIV_REG, CLK_PCM_EN)) &&
                (!(tmp & REG_MSK(CRG_PER, PCM_DIV_REG, PCM_SRC_SEL))))
            {
                break;
            }

            /* KBSCN and QUAD are not seriously affected by the clock switch and, thus,
             * they cannot block it!
             */
        }

        tmp = CRG_TOP->CLK_TMR_REG;

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

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

        /* Breathe, SOC and WDOG are not seriously affected by the clock switch and, thus,
         * they cannot block it!
         */

        ret = true;
    }
    while (0);

    return ret;
}


void hw_cpm_set_sysclk(uint32_t mode)
{
    /* Make sure a valid sys clock is requested */
    ASSERT_WARNING(mode <= SYS_CLK_IS_PLL);

    REG_SETF(CRG_TOP, CLK_CTRL_REG, SYS_CLK_SEL, mode);

    // Wait until the switch is done!
    switch (mode)
    {
    case SYS_CLK_IS_XTAL16M:
        while (REG_GETF(CRG_TOP, CLK_CTRL_REG, RUNNING_AT_XTAL16M) == 0) {}

        break;

    case SYS_CLK_IS_RC16:
        while (REG_GETF(CRG_TOP, CLK_CTRL_REG, RUNNING_AT_RC16M) == 0) {}

        break;

    case SYS_CLK_IS_LP:
        while (REG_GETF(CRG_TOP, CLK_CTRL_REG, RUNNING_AT_32K) == 0) {}

        break;

    case SYS_CLK_IS_PLL:
        while (REG_GETF(CRG_TOP, CLK_CTRL_REG, RUNNING_AT_PLL96M) == 0) {}

        break;

    default:
        ASSERT_WARNING(0);
        break;
    }
}


void hw_cpm_short_delay(void)
{
    volatile int i;

    for (i = 0; i < 20; i++);
}


void hw_cpm_pll_sys_on(void)
{
    /* Before enabling the PLL LDO, the 1.4V voltage needs to be present; in real-life this
     * is achieved by first turning on the 1.4V ACORE LDO, then the DCDC converter to take over
     * the generation of 1.4V and finally turning off the ACORE LDO.
     */

    /* LDO PLL enable. */
    REG_SET_BIT(GPREG, PLL_SYS_CTRL1_REG, LDO_PLL_ENABLE);

    /* Configure system PLL. */
    REG_SETF(GPREG, PLL_SYS_CTRL1_REG, PLL_R_DIV, 1); /* Default/reset value. */

    /* Program N-divider and DEL_SEL. */
    REG_SET_BIT(GPREG, PLL_SYS_CTRL2_REG, PLL_SEL_MIN_CUR_INT);     // Last review date: Feb 15, 2016 - 12:25:47

    /* Now turn on PLL. */
    REG_SET_BIT(GPREG, PLL_SYS_CTRL1_REG, PLL_EN);

    while ((GPREG->PLL_SYS_STATUS_REG & REG_MSK(GPREG, PLL_SYS_STATUS_REG, LDO_PLL_OK)) == 0) {}

    /* And wait until lock. */
    while ((GPREG->PLL_SYS_STATUS_REG & REG_MSK(GPREG, PLL_SYS_STATUS_REG, PLL_LOCK_FINE)) == 0) {}
}


void hw_cpm_pll_sys_off(void)
{
    // The PLL is not the system clk.
    while ((CRG_TOP->CLK_CTRL_REG & REG_MSK(CRG_TOP, CLK_CTRL_REG, RUNNING_AT_PLL96M)) != 0);

    GPREG->PLL_SYS_CTRL1_REG = 0x0000;                     // Switch off the PLL.
}


void hw_cpm_start_calibration(cal_clk_t clk_type, uint32_t cycles)
{
    ASSERT_WARNING(REG_GETF(ANAMISC, CLK_REF_SEL_REG, REF_CAL_START) == 0); // Must be disabled

    ANAMISC->CLK_REF_CNT_REG = cycles;                      // # of cal clock cycles
    REG_SETF(ANAMISC, CLK_REF_SEL_REG, REF_CLK_SEL, clk_type);
    REG_SET_BIT(ANAMISC, CLK_REF_SEL_REG, REF_CAL_START);
}


uint32_t hw_cpm_get_calibration_data(void)
{
    uint32_t high;
    uint32_t low;
    uint32_t value;

    while (REG_GETF(ANAMISC, CLK_REF_SEL_REG, REF_CAL_START) == 1);         // Wait until it's finished

    high = ANAMISC->CLK_REF_VAL_H_REG;
    low = ANAMISC->CLK_REF_VAL_L_REG;
    value = (high << 16) + low;

    return value;
}

void hw_cpm_dcdc_config(void)
{
    uint32_t reg;

    /*
     * Preferred settings section
     * Last review date: Feb 15, 2016 - 12:25:47
     */
    REG_CLR_BIT(DCDC, DCDC_CTRL_0_REG, DCDC_FW_ENABLE);

    reg = DCDC->DCDC_IRQ_MASK_REG;
    REG_SET_FIELD(DCDC, DCDC_IRQ_MASK_REG, DCDC_V18P_TIMEOUT_IRQ_MASK, reg, 1);
    REG_SET_FIELD(DCDC, DCDC_IRQ_MASK_REG, DCDC_VDD_TIMEOUT_IRQ_MASK, reg, 1);
    REG_SET_FIELD(DCDC, DCDC_IRQ_MASK_REG, DCDC_V18_TIMEOUT_IRQ_MASK, reg, 1);
    REG_SET_FIELD(DCDC, DCDC_IRQ_MASK_REG, DCDC_V14_TIMEOUT_IRQ_MASK, reg, 1);
    DCDC->DCDC_IRQ_MASK_REG = reg;

    REG_SET_BIT(DCDC, DCDC_TRIM_REG, DCDC_P_COMP_MAN_TRIM);

    DCDC->DCDC_V14_0_REG &= ~(REG_MSK(DCDC, DCDC_V14_0_REG, DCDC_V14_CUR_LIM_MIN) |
                              REG_MSK(DCDC, DCDC_V14_0_REG, DCDC_V14_FAST_RAMPING));

    DCDC->DCDC_V18_0_REG &= ~(REG_MSK(DCDC, DCDC_V18_0_REG, DCDC_V18_CUR_LIM_MIN) |
                              REG_MSK(DCDC, DCDC_V18_0_REG, DCDC_V18_FAST_RAMPING));

    DCDC->DCDC_V18P_0_REG &= ~(REG_MSK(DCDC, DCDC_V18P_0_REG, DCDC_V18P_CUR_LIM_MIN) |
                               REG_MSK(DCDC, DCDC_V18P_0_REG, DCDC_V18P_FAST_RAMPING));

    DCDC->DCDC_VDD_0_REG &= ~(REG_MSK(DCDC, DCDC_VDD_0_REG, DCDC_VDD_CUR_LIM_MIN) |
                              REG_MSK(DCDC, DCDC_VDD_0_REG, DCDC_VDD_FAST_RAMPING));

    REG_SETF(DCDC, DCDC_VDD_1_REG, DCDC_VDD_CUR_LIM_MAX_LV, 0xD);
    // End of preferred settings

    DCDC->DCDC_VDD_1_REG |= ((1 << REG_POS(DCDC, DCDC_VDD_1_REG, DCDC_VDD_ENABLE_HV)) |
                             (1 << REG_POS(DCDC, DCDC_VDD_1_REG, DCDC_VDD_ENABLE_LV)));

    REG_SETF(DCDC, DCDC_V14_0_REG, DCDC_V14_VOLTAGE, 0x7);

    if (dg_configPOWER_FLASH == 1)
    {
        REG_SETF(DCDC, DCDC_V18_0_REG, DCDC_V18_VOLTAGE, 0x16);
    }

    if (dg_configPOWER_EXT_1V8_PERIPHERALS == 1)
    {
        REG_SETF(DCDC, DCDC_V18P_0_REG, DCDC_V18P_VOLTAGE, 0x16);
    }

    if (dg_configPOWER_FLASH == 1)
    {
        DCDC->DCDC_V18_1_REG |= (1 << REG_POS(DCDC, DCDC_V18_1_REG, DCDC_V18_ENABLE_HV));
        DCDC->DCDC_V18_1_REG &= ~REG_MSK(DCDC, DCDC_V18_1_REG, DCDC_V18_ENABLE_LV);
    }
    else
    {
        DCDC->DCDC_V18_1_REG &= ~(REG_MSK(DCDC, DCDC_V18_1_REG, DCDC_V18_ENABLE_HV) |
                                  REG_MSK(DCDC, DCDC_V18_1_REG, DCDC_V18_ENABLE_LV));
    }

    if (dg_configPOWER_EXT_1V8_PERIPHERALS == 1)
    {
        DCDC->DCDC_V18P_1_REG |= (1 << REG_POS(DCDC, DCDC_V18P_1_REG, DCDC_V18P_ENABLE_HV));
        DCDC->DCDC_V18P_1_REG &= ~REG_MSK(DCDC, DCDC_V18P_1_REG, DCDC_V18P_ENABLE_LV);
    }
    else
    {
        DCDC->DCDC_V18P_1_REG &= ~(REG_MSK(DCDC, DCDC_V18P_1_REG, DCDC_V18P_ENABLE_HV) |
                                   REG_MSK(DCDC, DCDC_V18P_1_REG, DCDC_V18P_ENABLE_LV));
    }
}

void hw_cpm_dcdc_on(void)
{
    DCDC->DCDC_V14_1_REG |= ((1 << REG_POS(DCDC, DCDC_V14_1_REG, DCDC_V14_ENABLE_HV)) |
                             (1 << REG_POS(DCDC, DCDC_V14_1_REG, DCDC_V14_ENABLE_LV)));

    REG_SETF(DCDC, DCDC_VDD_0_REG, DCDC_VDD_VOLTAGE, 0x10);      // 1.2 V

    REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_MODE, 1);

    // Trim the LDOs down to lowest possible voltage so DCDC can take over
    CRG_TOP->LDO_CTRL1_REG &= ~REG_MSK(CRG_TOP, LDO_CTRL1_REG, LDO_RADIO_SETVDD);
    REG_SETF(CRG_TOP, LDO_CTRL1_REG, LDO_CORE_SETVDD, 0x2);

    // Turn off LDOs
    CRG_TOP->LDO_CTRL1_REG &= ~REG_MSK(CRG_TOP, LDO_CTRL1_REG, LDO_RADIO_ENABLE);
    CRG_TOP->LDO_CTRL2_REG &= ~(REG_MSK(CRG_TOP, LDO_CTRL2_REG, LDO_1V2_ON) |
                                REG_MSK(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_FLASH_ON) |
                                REG_MSK(CRG_TOP, LDO_CTRL2_REG, LDO_1V8_PA_ON));

    // Trim the LDOs back to normal levels
    hw_cpm_reset_radio_vdd();
    CRG_TOP->LDO_CTRL1_REG &= ~REG_MSK(CRG_TOP, LDO_CTRL1_REG, LDO_CORE_SETVDD);
}


void hw_cpm_set_preferred_values(void)
{
    uint32_t reg;

    reg = CRG_TOP->CLK_16M_REG;

    REG_SET_FIELD(CRG_TOP, CLK_16M_REG, XTAL16_HPASS_FLT_EN, reg, 1); // Last review date: Feb 15, 2016 - 12:25:47
    REG_SET_FIELD(CRG_TOP, CLK_16M_REG, XTAL16_AMP_TRIM, reg, 5);
    REG_SET_FIELD(CRG_TOP, CLK_16M_REG, XTAL16_CUR_SET, reg, 5);

    CRG_TOP->CLK_16M_REG = reg;

    REG_SETF(CRG_TOP, BANDGAP_REG, LDO_SLEEP_TRIM, 0x8);

    if (BLACK_ORCA_TARGET_IC >= BLACK_ORCA_IC_VERSION(A, C))
    {
        REG_SETF(CRG_TOP, BANDGAP_REG, BYPASS_COLD_BOOT_DISABLE, 1); // Last review date: Feb 15, 2016 - 12:25:47
    }
}

void hw_cpm_configure_xtal32k_pins(void)
{
    GPIO->P20_MODE_REG = 0x26;
    GPIO->P21_MODE_REG = 0x26;
}

void hw_cpm_trigger_sw_cursor(void)
{
    volatile int i;

    if (dg_configUSE_SW_CURSOR == 1)
    {
        if (dg_configBLACK_ORCA_MB_REV == BLACK_ORCA_MB_REV_B)
        {
            SW_CURSOR_SET = 1 << SW_CURSOR_PIN;
        }
        else
        {
            SW_CURSOR_RESET = 1 << SW_CURSOR_PIN;
        }

        SW_CURSOR_GPIO = 0x300;

        for (i = 0; i < 100; i++)
        {
        }

        if (dg_configBLACK_ORCA_MB_REV == BLACK_ORCA_MB_REV_B)
        {
            SW_CURSOR_RESET = 1 << SW_CURSOR_PIN;
        }

        hw_cpm_setup_sw_cursor();
    }
}

void hw_cpm_reset_system(void)
{
    __asm volatile(" cpsid i ");

    hw_watchdog_unregister_int();
    hw_watchdog_set_pos_val(1);
    hw_watchdog_unfreeze();

    while (1);
}

void hw_cpm_reboot_system(void)
{
    __asm volatile(" cpsid i ");

    hw_watchdog_gen_RST();
    hw_watchdog_set_pos_val(1);
    hw_watchdog_unfreeze();

    while (1);
}

void hw_cpm_assert_trigger_gpio(void)
{
    if (EXCEPTION_DEBUG == 1)
    {
        if (dg_configUSE_LP_CLK != LP_CLK_RCX)
        {
            hw_cpm_configure_xtal32k_pins();
        }

        hw_cpm_power_up_per_pd();
        hw_cpm_deactivate_pad_latches();

        DBG_SET_HIGH(EXCEPTION_DEBUG, EXCEPTIONDBG);
    }
}

#define HW_CPM_ACTIVATE_BOD_PROTECTION                                                             \
do {                                                                                               \
        uint16_t val = 0;                                                                          \
                                                                                                   \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL_REG, BOD_VDD_LVL, val, 1);        /* VDD Level (700mV) */  \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL_REG, BOD_V33_TRIM, val, 2);       /* V33 trim */           \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL_REG, BOD_1V8_PA_TRIM, val, 2);    /* 1V8P trim */          \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL_REG, BOD_1V8_FLASH_TRIM, val, 2); /* 1V8 trim */           \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL_REG, BOD_VDD_TRIM, val, 2);       /* VDD trim */           \
        CRG_TOP->BOD_CTRL_REG = val;                                                               \
                                                                                                   \
        val = 0;                                                                                   \
        /* 1V8 Flash enable */                                                                     \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL2_REG, BOD_VBAT_EN, val, 1);                                \
        /* 1V8 Flash enable */                                                                     \
        if ((dg_configPOWER_FLASH == 1) && (dg_configFLASH_POWER_OFF == 0)) {                      \
                REG_SET_FIELD(CRG_TOP, BOD_CTRL2_REG, BOD_1V8_FLASH_EN, val, 1);                   \
        }                                                                                          \
        /* 1V8P enable */                                                                          \
        if (dg_configPOWER_EXT_1V8_PERIPHERALS == 1) {                                             \
                REG_SET_FIELD(CRG_TOP, BOD_CTRL2_REG, BOD_1V8_PA_EN, val, 1);                      \
        }                                                                                          \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL2_REG, BOD_VDD_EN, val, 1);        /* VDD enable */         \
        REG_SET_FIELD(CRG_TOP, BOD_CTRL2_REG, BOD_RESET_EN, val, 1);      /* Reset enable */       \
        CRG_TOP->BOD_CTRL2_REG = val;                                                              \
} while (0);

void hw_cpm_activate_bod_protection(void)
{
    HW_CPM_ACTIVATE_BOD_PROTECTION
}

void hw_cpm_activate_bod_protection_at_init(void)
{
    HW_CPM_ACTIVATE_BOD_PROTECTION
}

void hw_cpm_delay_usec(uint32_t usec)
{
    sys_clk_t sclk;
    ahb_div_t hclk;
    uint8_t freq;

    sclk = cm_sysclk;
    hclk = cm_ahbclk;
    freq = 16 >> hclk;

    /* Requested delay time must be > 0 usec */
    ASSERT_WARNING(usec != 0);

    /*
     *      ldr r3, [pc, #148]      2 cycles
     *      push {r4, lr}           2 cycles
     *      ldrb r1, [r3, #17]      2 cycles
     *      ldrb r2, [r3, #8]       2 cycles
     *      movs r3, #16            1 cycle
     *      asrs r3, r2             1 cycle
     *      uxtb r3, r2             1 cycle
     *      ----------
     *      cmp r0, #0              4 cycles overhead in total
     *      bne.n 0x8003106 <hw_cpm_delay_usec+30>
     *      cpsid i
     *      bkpt 0x0002
     *
     *      Total: 11 cycles
     */

    __asm volatile("       cmp %[sclk], #1                 \n"        // 1 cycle      : 1 (sysclk_RC16, sysclk_XTAL16M)
                   "       ble start                       \n"     // 1 or 3 cycles: 2/4
                   "       cmp %[sclk], #3                 \n"     // 1 cycle      : 3 (sysclk_PLL48)
                   "       bgt s96M                        \n"     // 1 or 3 cycles: 4/6
                   "       blt s32M                        \n"     // 1 or 3 cycles: 5/7
                   "s48M:  add r4, %[freq], %[freq]        \n"     // 1 cycle      : 6
                   "       add %[freq], r4, %[freq]        \n"     // 1 cycle      : 7
                   "       b start                         \n"     // 3 cycles     : 10
                   "s96M:  add r4, %[freq], %[freq]        \n"     // 1 cycle      : 7
                   "       add %[freq], r4, %[freq]        \n"     // 1 cycle      : 8
                   "       lsl %[freq], %[freq], #1        \n"     // 1 cycle      : 9
                   "       b start                         \n"     // 3 cycles     : 12
                   "s32M:  lsl %[freq], %[freq], #1        \n"     // 1 cycle      : 8
                   /* -----------------------------------------------------*/
                   /* Overhead up to this point:
                    *      sysclk_RC16     : 15 cycles (error: 15/16  - 15   usec)
                    *      sysclk_XTAL16M  : 15 cycles (error: 15/16  - 15   usec)
                    *      sysclk_XTAL32M  : 19 cycles (error: 19/32  - 19/2 usec)
                    *      sysclk_PLL48    : 21 cycles (error: 21/48  - 21/3 usec)
                    *      sysclk_PLL96    : 23 cycles (error: 23/96 -  23/6 usec)
                    */
                   "start: cmp %[freq], #16                \n"     // 1 cycle      : 1
                   "       bgt high                        \n"     // 1 or 3 cycles: 2/4
                   "       blt low                         \n"     // 1 or 3 cycles: 3/5
                   "       mov %[sclk], #4                 \n"     // 1 cycle      : 4
                   "       b calc                          \n"     // 3 cycles     : 7
                   "high:  cmp %[freq], #24                \n"     // 1 cycle      : 5
                   "       bne c32M                        \n"     // 1 or 3 cycles: 6/8
                   "       mov %[sclk], #6                 \n"     // 1 cycle      : 7
                   "       b calc                          \n"     // 3 cycles     : 10
                   "c32M:  cmp %[freq], #32                \n"     // 1 cycle      : 9
                   "       bne c48M                        \n"     // 1 or 3 cycles: 10/12
                   "       mov %[sclk], #8                 \n"     // 1 cycle      : 11
                   "       b calc                          \n"     // 3 cycles     : 12
                   "c48M:  cmp %[freq], #48                \n"     // 1 cycle      : 13
                   "       bne c96M                        \n"     // 1 or 3 cycles: 14/16
                   "       mov %[sclk], #12                \n"     // 1 cycle      : 15
                   "       b calc                          \n"     // 3 cycles     : 18
                   "c96M:  mov %[sclk], #24                \n"     // 1 cycle      : 17
                   "       b calc                          \n"     // 3 cycles     : 20
                   "low:   cmp %[freq], #1                 \n"     // 1 cycle      : 6
                   "       bgt c2M                         \n"     // 1 or 3 cycles: 7/9
                   "       lsr %[usec], %[usec], #2        \n"     // 1 cycle      : 8
                   "       b loop                          \n"     // 3 cycles     : 11
                   "c2M:   cmp %[freq], #2                 \n"     // 1 cycle      : 10
                   "       bgt c3M                         \n"     // 1 or 3 cycles: 11/13
                   "       lsr %[usec], %[usec], #1        \n"     // 1 cycle      : 12
                   "       b loop                          \n"     // 3 cycles     : 15
                   "c3M:   cmp %[freq], #3                 \n"     // 1 cycle      : 14
                   "       bgt c4M                         \n"     // 1 or 3 cycles: 15/17
                   "       lsr %[usec], %[usec], #1        \n"     // 1 cycle      : 16
                   "       b loop1                         \n"     // 3 cycles     : 19
                   "c4M:   cmp %[freq], #4                 \n"     // 1 cycle      : 18
                   "       bgt c6M                         \n"     // 1 or 3 cycles: 19/21
                   "       b loop                          \n"     // 3 cycles     : 22
                   "c6M:   cmp %[freq],#6                  \n"     // 1 cycle      : 22
                   "       bgt c8M                         \n"     // 1 or 3 cycles: 23/25
                   "       b loop1                         \n"     // 3 cycles     : 26
                   "c8M:   cmp %[freq], #8                 \n"     // 1 cycle      : 26
                   "       bgt c12M                        \n"     // 1 or 3 cycles: 27/29
                   "       mov %[sclk], #2                 \n"     // 1 cycle      : 28
                   "       b calc                          \n"     // 3 cycles     : 31
                   "c12M:  mov %[sclk], #2                 \n"     // 1 cycle      : 30
                   "       mul %[usec], %[sclk], %[usec]   \n"     // 1 cycle      : 31
                   /* Error:
                    *       1MHz: 11 cycles, 11usec
                    *       2MHz: 15 cycles,  7.5usec
                    *       3MHz: 19 cycles,  6.33usec
                    *       4MHz: 22 cycles,  5.5usec
                    *       6MHz: 26 cycles,  4.33usec
                    *       8MHz: 31 cycles,  3.875usec
                    *      12MHz: 31 cycles,  2.584usec
                    *      16MHz:  8 cycles,  0.5usec
                    *      24MHz: 11 cycles,  0.459usec
                    *      32MHz: 13 cycles,  0.406usec
                    *      48MHz: 19 cycles,  0.396usec
                    *      96MHz: 21 cycles,  0.219usec
                    *
                    * 1 loop of 4 cycles is --- 1 usec is # loops
                    *       1MHz: 4usec     ---  divide (usec) by 4 (up to 5usec error)
                    *       2MHz: 2usec     ---  divide (usec) by 2 (up to 2usec error)
                    *       4MHz: 1usec     ---  1 loop  ( 0*4 + 1*2, 0.5000usec error)
                    *       8MHz: 500nsec   ---  2 loops ( 1*4 + 1*2, 0.2500usec error)
                    *      16MHz: 250nsec   ---  4 loops ( 3*4 + 1*2, 0.5000usec error)
                    *      24MHz: 167nsec   ---  6 loops ( 5*4 + 1*2, 0.0830usec error)
                    *      32MHz: 125nsec   ---  8 loops ( 7*4 + 1*2, 0.0625usec error)
                    *      48MHz:  84nsec   --- 12 loops (11*4 + 1*2, 0.0420usec error)
                    *      96MHz:  42nsec   --- 24 loops (23*4 + 1*2, 0.0210usec error)
                    *
                    * 1 loop of 6 cycles is --- 1 usec is # loops
                    *       3MHz: 2usec     ---  divide (usec) by 2 (up to 3usec error)
                    *       6MHz: 1usec     ---  1 loop  ( 0*6 + 1*7, 0.1670usec error)
                    *      12MHz: 0.5usec   ---  2 loops ( 1*6 + 1*7, 0.0830usec error)
                    *
                    * Cumulative error is:
                    *       1MHz: 11 + 5         = 16usec
                    *       2MHz: 7.5 + 2        = 9.5usec
                    *       3MHz: 6.33 + 3       = 9.33usec
                    *       4MHz: 5.5 + 0.5      = 6usec
                    *       6MHz: 4.33 + 0.167   = 4.5usec
                    *       8MHz: 3.875 + 0.25   = 4.125usec
                    *      12MHz: 2.584 + 0.083  = 2.67usec
                    *      16MHz: 0.5 + 0.5      = 1usec
                    *      24MHz: 0.459 + 0.083  = 0.541usec
                    *      32MHz: 0.406 + 0.0625 = 0.469usec
                    *      48MHz: 0.396 + 0.042  = 0.438usec
                    *      96MHz: 0.219 + 0.021  = 0.429usec
                    */
                   "loop1: sub  %[usec], %[usec], #1       \n"     // 1 cycle
                   "       nop                             \n"     // 1 cycle
                   "       nop                             \n"     // 1 cycle
                   "       bne loop1                       \n"     // 3 cycles except for the last one which is 1
                   "       b exit                          \n"     // 3 cycles
                   "calc:  mul %[usec], %[sclk], %[usec]   \n"     // 1 cycle
                   "loop:  sub  %[usec], %[usec], #1       \n"     // 1 cycle
                   "       bne loop                        \n"     // 3 cycles except for the last one which is 1
                   "exit:                                  \n"
                   :
                   :                                                         /* output */
                   [usec] "r"(usec), [freq] "r"(freq), [sclk] "r"(sclk) :    /* inputs (%0, %1, %2) */
                   "r4");                                                    /* registers that are destroyed */
}

#endif /* dg_configUSE_HW_CPM */
/**
\}
\}
\}
*/
