/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016 - 2019 , NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_clock.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.clock"
#endif

#define ICS_C2_BDIV_VAL ((ICS->C2 & ICS_C2_BDIV_MASK) >> ICS_C2_BDIV_SHIFT)
#define ICS_S_CLKST_VAL ((ICS->S & ICS_S_CLKST_MASK) >> ICS_S_CLKST_SHIFT)
#define ICS_S_IREFST_VAL ((ICS->S & ICS_S_IREFST_MASK) >> ICS_S_IREFST_SHIFT)
#define ICS_C1_RDIV_VAL ((ICS->C1 & ICS_C1_RDIV_MASK) >> ICS_C1_RDIV_SHIFT)
#define OSC_CR_RANGE_VAL ((OSC0->CR & OSC_CR_RANGE_MASK) >> OSC_CR_RANGE_SHIFT)
#define OSC_MODE_MASK \
    (OSC_CR_OSCOS_MASK | OSC_CR_HGO_MASK | OSC_CR_RANGE_MASK | OSC_CR_OSCEN_MASK | OSC_CR_OSCSTEN_MASK)
#define ICS_C2_LP_VAL ((ICS->C2 & ICS_C2_LP_MASK) >> ICS_C2_LP_SHIFT)
#define SIM_CLKDIV_OUTDIV1_VAL ((SIM->CLKDIV & SIM_CLKDIV_OUTDIV1_MASK) >> SIM_CLKDIV_OUTDIV1_SHIFT)
#define SIM_CLKDIV_OUTDIV2_VAL ((SIM->CLKDIV & SIM_CLKDIV_OUTDIV2_MASK) >> SIM_CLKDIV_OUTDIV2_SHIFT)
#define SIM_CLKDIV_OUTDIV3_VAL ((SIM->CLKDIV & SIM_CLKDIV_OUTDIV3_MASK) >> SIM_CLKDIV_OUTDIV3_SHIFT)

/* ICS_S_CLKST definition. */
enum
{
    kICS_ClkOutStatFll, /* FLL.            */
    kICS_ClkOutStatInt, /* Internal clock. */
    kICS_ClkOutStatExt, /* External clock. */
};

/* ICS fll clock factor. */
#define ICS_FLL_CLOCK_FACTOR (1280U)

/*******************************************************************************
 * Variables
 ******************************************************************************/

/* Slow internal reference clock frequency. */
static uint32_t s_slowIrcFreq = 37500U;

/* External XTAL0 (OSC0) clock frequency. */
volatile uint32_t g_xtal0Freq;

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*!
 * @brief Get the ICS external reference clock frequency.
 *
 * Get the current ICS external reference clock frequency in Hz.This is an internal function.
 *
 * @return ICS external reference clock frequency in Hz.
 */
static uint32_t CLOCK_GetICSExtClkFreq(void);

/*!
 * @brief Get the ICS FLL external reference clock frequency.
 *
 * Get the current ICS FLL external reference clock frequency in Hz. It is
 * the frequency after by ICS_C1[RDIV]. This is an internal function.
 *
 * @return ICS FLL external reference clock frequency in Hz.
 */
static uint32_t CLOCK_GetFllExtRefClkFreq(void);

/*!
 * @brief Get the ICS FLL reference clock frequency.
 *
 * Get the current ICS FLL reference clock frequency in Hz. It is
 * the frequency select by ICS_C1[IREFS]. This is an internal function.
 *
 * @return ICS FLL reference clock frequency in Hz.
 */
static uint32_t CLOCK_GetFllRefClkFreq(void);

/*!
 * @brief Calculate the RANGE value base on crystal frequency.
 *
 * To setup external crystal oscillator, must set the register bits RANGE
 * base on the crystal frequency. This function returns the RANGE base on the
 * input frequency. This is an internal function.
 *
 * @param freq Crystal frequency in Hz.
 * @return The RANGE value.
 */
static uint8_t CLOCK_GetOscRangeFromFreq(uint32_t freq);

/*******************************************************************************
 * Code
 ******************************************************************************/

static uint32_t CLOCK_GetICSExtClkFreq(void)
{
    /* Please call CLOCK_SetXtal0Freq base on board setting before using OSC0 clock. */
    assert(g_xtal0Freq);
    return g_xtal0Freq;
}

static uint32_t CLOCK_GetFllExtRefClkFreq(void)
{
    /* FllExtRef = ICSExtRef / FllExtRefDiv */
    uint8_t rDiv;
    uint8_t range;

    uint32_t freq = CLOCK_GetICSExtClkFreq();

    if (freq == 0UL)
    {
        return freq;
    }
    /* get reference clock divider */
    rDiv = ICS_C1_RDIV_VAL;

    freq >>= rDiv;
    /* OSC clock range */
    range = OSC_CR_RANGE_VAL;

    /*
       When should use divider 32, 64, 128, 256, 512, 1024.
    */
    if (((0U != range)))
    {
        switch (rDiv)
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
                freq >>= 5u;
                break;
            case 6:
            case 7:
                break;
            default:
                freq = 0u;
                break;
        }
    }

    return freq;
}

static uint32_t CLOCK_GetFllRefClkFreq(void)
{
    uint32_t freq;

    /* If use external reference clock. */
    if ((uint8_t)kICS_FllSrcExternal == ICS_S_IREFST_VAL)
    {
        freq = CLOCK_GetFllExtRefClkFreq();
    }
    /* If use internal reference clock. */
    else
    {
        freq = s_slowIrcFreq;
    }

    return freq;
}

static uint8_t CLOCK_GetOscRangeFromFreq(uint32_t freq)
{
    assert((freq <= 32768U) || (freq >= 4000000U));

    uint8_t range = 0U;

    if (freq <= 32768U)
    {
        range = 0U;
    }
    /* high freq range 4M-24M */
    else
    {
        range = 1U;
    }

    return range;
}

/*!
 * brief Get the OSC0 external reference clock frequency (OSC0ERCLK).
 *
 * return Clock frequency in Hz.
 */
uint32_t CLOCK_GetOsc0ErClkFreq(void)
{
    uint32_t freq;

    if ((OSC0->CR & OSC_CR_OSCEN_MASK) != 0U)
    {
        /* Please call CLOCK_SetXtal0Freq base on board setting before using OSC0 clock. */
        assert(g_xtal0Freq);
        freq = g_xtal0Freq;
    }
    else
    {
        freq = 0U;
    }

    return freq;
}

/*!
 * brief Get the flash clock frequency.
 *
 * return Clock frequency in Hz.
 */
uint32_t CLOCK_GetFlashClkFreq(void)
{
    uint32_t freq;

    freq = CLOCK_GetICSOutClkFreq() / (SIM_CLKDIV_OUTDIV1_VAL + 1U);
    freq /= (SIM_CLKDIV_OUTDIV2_VAL + 1U);

    return freq;
}

/*!
 * brief Get the bus clock frequency.
 *
 * return Clock frequency in Hz.
 */
uint32_t CLOCK_GetBusClkFreq(void)
{
    return CLOCK_GetFlashClkFreq();
}

/*!
 * brief Get the core clock or system clock frequency.
 *
 * return Clock frequency in Hz.
 */
uint32_t CLOCK_GetCoreSysClkFreq(void)
{
    return CLOCK_GetICSOutClkFreq() / (SIM_CLKDIV_OUTDIV1_VAL + 1U);
}

/*!
 * brief Gets the Timer(FTM/PWT) clock frequency.
 *
 * This function gets the Timer clock frequency in Hz based
 * on the current ICSOUTCLK.
 *
 * return The frequency of Timer(FTM/PWT) clock.
 */
uint32_t CLOCK_GetTimerClkFreq(void)
{
    return CLOCK_GetICSOutClkFreq() / (SIM_CLKDIV_OUTDIV3_VAL + 1U);
}

/*!
 * brief Gets the clock frequency for a specific clock name.
 *
 * This function checks the current clock configurations and then calculates
 * the clock frequency for a specific clock name defined in clock_name_t.
 * The ICS must be properly configured before using this function.
 *
 * param clockName Clock names defined in clock_name_t
 * return Clock frequency value in Hertz
 */
uint32_t CLOCK_GetFreq(clock_name_t clockName)
{
    uint32_t freq;

    switch (clockName)
    {
        case kCLOCK_CoreSysClk:
        case kCLOCK_PlatClk:
            freq = CLOCK_GetCoreSysClkFreq();
            break;

        case kCLOCK_BusClk:
        case kCLOCK_FlashClk:
            freq = CLOCK_GetFlashClkFreq();
            break;

        case kCLOCK_Osc0ErClk:
            freq = CLOCK_GetOsc0ErClkFreq();
            break;

        case kCLOCK_ICSInternalRefClk:
            freq = CLOCK_GetInternalRefClkFreq();
            break;
        case kCLOCK_ICSFixedFreqClk:
            freq = CLOCK_GetICSFixedFreqClkFreq();
            break;
        case kCLOCK_ICSFllClk:
            freq = CLOCK_GetFllFreq();
            break;
        case kCLOCK_ICSOutClk:
            freq = CLOCK_GetICSOutClkFreq();
            break;

        case kCLOCK_TimerClk:
            freq = CLOCK_GetTimerClkFreq();
            break;

        case kCLOCK_LpoClk:
            freq = LPO_CLK_FREQ;
            break;
        default:
            freq = 0U;
            break;
    }

    return freq;
}

/*!
 * brief Set the clock configure in SIM module.
 *
 * This function sets system layer clock settings in SIM module.
 *
 * param config Pointer to the configure structure.
 */
void CLOCK_SetSimConfig(sim_clock_config_t const *config)
{
    /* config divider */
    CLOCK_SetOutDiv(config->outDiv1, config->outDiv2, config->outDiv3);
    /* config bus clock prescaler optional */
    SIM->SOPT0 |= SIM_SOPT0_BUSREF(config->busClkPrescaler);
}

/*!
 * brief Gets the ICS output clock (ICSOUTCLK) frequency.
 *
 * This function gets the ICS output clock frequency in Hz based on the current ICS
 * register value.
 *
 * return The frequency of ICSOUTCLK.
 */
uint32_t CLOCK_GetICSOutClkFreq(void)
{
    uint32_t icsoutclk;
    uint8_t clkst = ICS_S_CLKST_VAL;

    switch (clkst)
    {
        case kICS_ClkOutStatFll:
            icsoutclk = CLOCK_GetFllFreq();
            break;
        case kICS_ClkOutStatInt:
            icsoutclk = s_slowIrcFreq;
            break;
        case kICS_ClkOutStatExt:
            icsoutclk = CLOCK_GetICSExtClkFreq();
            break;
        default:
            icsoutclk = 0U;
            break;
    }

    return (icsoutclk / (1UL << ICS_C2_BDIV_VAL));
}

/*!
 * brief Gets the ICS FLL clock (ICSFLLCLK) frequency.
 *
 * This function gets the ICS FLL clock frequency in Hz based on the current ICS
 * register value. The FLL is enabled in FEI/FBI/FEE/FBE mode and
 * disabled in low power state in other modes.
 *
 * return The frequency of ICSFLLCLK.
 */
uint32_t CLOCK_GetFllFreq(void)
{
    uint32_t freq;

    /* If FLL is not enabled currently, then return 0U. */
    if ((ICS->C2 & ICS_C2_LP_MASK) != 0U)
    {
        freq = 0U;
    }
    else
    {
        /* Get FLL reference clock frequency. */
        freq = CLOCK_GetFllRefClkFreq() * ICS_FLL_CLOCK_FACTOR;
    }

    return freq;
}

/*!
 * brief Gets the ICS internal reference clock (ICSIRCLK) frequency.
 *
 * This function gets the ICS internal reference clock frequency in Hz based
 * on the current ICS register value.
 *
 * return The frequency of ICSIRCLK.
 */
uint32_t CLOCK_GetInternalRefClkFreq(void)
{
    uint32_t freq;

    /* If ICSIRCLK is gated. */
    if ((ICS->C1 & ICS_C1_IRCLKEN_MASK) == 0U)
    {
        freq = 0U;
    }
    else
    {
        freq = s_slowIrcFreq;
    }

    return freq;
}

/*!
 * brief Gets the ICS fixed frequency clock (ICSFFCLK) frequency.
 *
 * This function gets the ICS fixed frequency clock frequency in Hz based
 * on the current ICS register value.
 *
 * return The frequency of ICSFFCLK.
 */
uint32_t CLOCK_GetICSFixedFreqClkFreq(void)
{
    uint32_t freq = CLOCK_GetFllRefClkFreq();
    uint32_t ret;
    uint32_t ICSOUTCLK;

    ICSOUTCLK = CLOCK_GetICSOutClkFreq();
    /* ICSFFCLK must be no more than ICSOUTCLK/4. */
    if ((freq != 0UL) && (freq <= (ICSOUTCLK / 4U)))
    {
        ret = freq;
    }
    else
    {
        ret = 0U;
    }

    return ret;
}

/*!
 * brief Initializes the OSC0.
 *
 * This function initializes the OSC0 according to the board configuration.
 *
 * param  config Pointer to the OSC0 configuration structure.
 */
void CLOCK_InitOsc0(osc_config_t const *config)
{
    uint8_t range = CLOCK_GetOscRangeFromFreq(config->freq);

    OSC0->CR = ((OSC0->CR & (uint8_t)(~OSC_MODE_MASK)) | (uint8_t)(OSC_CR_RANGE(range)) | ((uint8_t)config->workMode) |
                ((uint8_t)config->enableMode));

    if (((uint8_t)kOSC_ModeExt != config->workMode) && ((OSC0->CR & OSC_CR_OSCEN_MASK) != 0U))
    {
        /* Wait for stable. */
        while (0U == (OSC0->CR & OSC_CR_OSCINIT_MASK))
        {
        }
    }
}

/*!
 * brief Deinitializes the OSC0.
 *
 * This function deinitializes the OSC0.
 */
void CLOCK_DeinitOsc0(void)
{
    OSC0->CR = 0U;
}

/*!
 * brief Gets the current ICS mode.
 *
 * This function checks the ICS registers and determines the current ICS mode.
 *
 * return Current ICS mode or error code; See ref ics_mode_t.
 */
ics_mode_t CLOCK_GetMode(void)
{
    ics_mode_t mode = kICS_ModeError;
    uint8_t clkst   = ICS_S_CLKST_VAL;
    uint8_t irefst  = ICS_S_IREFST_VAL;
    uint8_t lp      = ICS_C2_LP_VAL;

    /*------------------------------------------------------------------
                           Mode and Registers
    ____________________________________________________________________

      Mode   |   CLKST    |   IREFST   |      LP
    ____________________________________________________________________

      FEI    |  00(FLL)   |   1(INT)   |      X
    ____________________________________________________________________

      FEE    |  00(FLL)   |   0(EXT)   |      X
    ____________________________________________________________________

      FBE    |  10(EXT)   |   0(EXT)   |   0(NORMAL)
    ____________________________________________________________________

      FBI    |  01(INT)   |   1(INT)   |   0(NORMAL)
    ____________________________________________________________________

      FBILP   |  01(INT)   |   1(INT)   |   1(LOW POWER)
    ____________________________________________________________________

      FBELP   |  10(EXT)   |   0(EXT)   |   1(LOW POWER)
    ____________________________________________________________________

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

    switch (clkst)
    {
        case kICS_ClkOutStatFll:
            if ((uint8_t)kICS_FllSrcExternal == irefst)
            {
                mode = kICS_ModeFEE;
            }
            else
            {
                mode = kICS_ModeFEI;
            }
            break;
        case kICS_ClkOutStatInt:
            if (lp != 0U)
            {
                mode = kICS_ModeBILP;
            }
            else
            {
                mode = kICS_ModeFBI;
            }
            break;
        case kICS_ClkOutStatExt:
            if (lp != 0U)
            {
                mode = kICS_ModeBELP;
            }
            else
            {
                mode = kICS_ModeFBE;
            }
            break;
        default:
            mode = kICS_ModeError;
            break;
    }

    return mode;
}

/*!
 * brief Sets the ICS to FEI mode.
 *
 * This function sets the ICS to FEI mode. If setting to FEI mode fails
 * from the current mode, this function returns an error.
 *
 * param       bDiv bus clock divider
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_SetFeiMode(uint8_t bDiv)
{
#if (defined(ICS_CONFIG_CHECK_PARAM) && ICS_CONFIG_CHECK_PARAM)
    ics_mode_t mode = CLOCK_GetMode();
    if (!((kICS_ModeFEI == mode) || (kICS_ModeFBI == mode) || (kICS_ModeFBE == mode) || (kICS_ModeFEE == mode)))
    {
        return kStatus_ICS_ModeUnreachable;
    }
#endif

    /*Note: When mode switching from FEE, FBE, to FEI, it is suggested to wait IREFST switch
     * completion, then change ICS_C1[CLKS].
     */

    /* Set IREFS. */
    ICS->C1 = (uint8_t)((ICS->C1 & ~(ICS_C1_IREFS_MASK)) | ICS_C1_IREFS(kICS_FllSrcInternal)); /* IREFS = 1 */

    /* Set CLKS */
    ICS->C1 = (uint8_t)((ICS->C1 & (~ICS_C1_CLKS_MASK)) | ICS_C1_CLKS(kICS_ClkOutSrcFll)); /* CLKS = 0 */
    /* set bus clock divider */
    ICS->C2 = (uint8_t)((ICS->C2 & (~ICS_C2_BDIV_MASK)) | ICS_C2_BDIV(bDiv));

    /* Wait and check status. */
    while ((uint8_t)kICS_FllSrcInternal != ICS_S_IREFST_VAL)
    {
    }

    /* Check ICS_S[CLKST] */
    while ((uint8_t)kICS_ClkOutStatFll != ICS_S_CLKST_VAL)
    {
    }

    /* wait for FLL to lock */
    while (0U == (ICS->S & ICS_S_LOCK_MASK))
    {
    }

    /* clear Loss of lock sticky bit */
    ICS->S |= ICS_S_LOLS_MASK;

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to FEE mode.
 *
 * This function sets the ICS to FEE mode. If setting to FEE mode fails
 * from the current mode, this function returns an error.
 *
 * param   bDiv bus clock divider
 * param   rdiv  FLL reference clock divider setting, RDIV.
 *
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_SetFeeMode(uint8_t bDiv, uint8_t rDiv)
{
#if (defined(ICS_CONFIG_CHECK_PARAM) && ICS_CONFIG_CHECK_PARAM)
    ics_mode_t mode = CLOCK_GetMode();
    if (!((kICS_ModeFEE == mode) || (kICS_ModeFBI == mode) || (kICS_ModeFBE == mode) || (kICS_ModeFEI == mode)))
    {
        return kStatus_ICS_ModeUnreachable;
    }
#endif

    /* Set CLKS, rDiv and IREFS. */
    ICS->C1 = (uint8_t)((ICS->C1 & ~(ICS_C1_CLKS_MASK | ICS_C1_RDIV_MASK | ICS_C1_IREFS_MASK)) |
                        (ICS_C1_CLKS(kICS_ClkOutSrcFll)         /* CLKS = 0 */
                         | ICS_C1_RDIV(rDiv)                    /* FRDIV */
                         | ICS_C1_IREFS(kICS_FllSrcExternal))); /* IREFS = 0 */
    /* set bus clock divider */
    ICS->C2 = (uint8_t)((ICS->C2 & (~ICS_C2_BDIV_MASK)) | ICS_C2_BDIV(bDiv));

    /* If use external crystal as clock source, wait for it stable. */
    {
        if ((OSC0->CR & OSC_CR_OSCOS_MASK) != 0U)
        {
            while (0U == (OSC0->CR & OSC_CR_OSCINIT_MASK))
            {
            }
        }
    }

    /* Wait and check status. */
    while ((uint8_t)kICS_FllSrcExternal != ICS_S_IREFST_VAL)
    {
    }

    /* Check ICS_S[CLKST] */
    while ((uint8_t)kICS_ClkOutStatFll != ICS_S_CLKST_VAL)
    {
    }

    /* wait for FLL to lock */
    while (0U == (ICS->S & ICS_S_LOCK_MASK))
    {
    }

    /* clear Loss of lock sticky bit */
    ICS->S |= ICS_S_LOLS_MASK;

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to FBI mode.
 *
 * This function sets the ICS to FBI mode. If setting to FBI mode fails
 * from the current mode, this function returns an error.
 *
 * param bDiv bus clock divider

 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_SetFbiMode(uint8_t bDiv)
{
#if (defined(ICS_CONFIG_CHECK_PARAM) && ICS_CONFIG_CHECK_PARAM)
    ics_mode_t mode = CLOCK_GetMode();

    if (!((kICS_ModeFEE == mode) || (kICS_ModeFBI == mode) || (kICS_ModeFBE == mode) || (kICS_ModeFEI == mode) ||
          (kICS_ModeBILP == mode)))

    {
        return kStatus_ICS_ModeUnreachable;
    }
#endif

    /* set bus clock divider and disable low power */
    ICS->C2 = (uint8_t)((ICS->C2 & (~(ICS_C2_BDIV_MASK | ICS_C2_LP_MASK))) | ICS_C2_BDIV(bDiv));
    /* Set CLKS and IREFS. */
    ICS->C1 = (uint8_t)((ICS->C1 & ~(ICS_C1_CLKS_MASK | ICS_C1_IREFS_MASK)) |
                        (ICS_C1_CLKS(kICS_ClkOutSrcInternal)    /* CLKS = 1 */
                         | ICS_C1_IREFS(kICS_FllSrcInternal))); /* IREFS = 1 */

    /* Wait and check status. */
    while ((uint8_t)kICS_FllSrcInternal != ICS_S_IREFST_VAL)
    {
    }

    while ((uint8_t)kICS_ClkOutStatInt != ICS_S_CLKST_VAL)
    {
    }

    /* clear Loss of lock sticky bit */
    ICS->S |= ICS_S_LOLS_MASK;

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to FBE mode.
 *
 * This function sets the ICS to FBE mode. If setting to FBE mode fails
 * from the current mode, this function returns an error.
 *
 * param   bDiv bus clock divider
 * param   rdiv  FLL reference clock divider setting, RDIV.
 *
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_SetFbeMode(uint8_t bDiv, uint8_t rDiv)
{
#if (defined(ICS_CONFIG_CHECK_PARAM) && ICS_CONFIG_CHECK_PARAM)
    ics_mode_t mode = CLOCK_GetMode();
    if (!((kICS_ModeFEE == mode) || (kICS_ModeFBI == mode) || (kICS_ModeFBE == mode) || (kICS_ModeFEI == mode) ||
          (kICS_ModeBELP == mode)))
    {
        return kStatus_ICS_ModeUnreachable;
    }
#endif

    /* set bus clock divider and disable low power */
    ICS->C2 = (uint8_t)((ICS->C2 & (~(ICS_C2_BDIV_MASK | ICS_C2_LP_MASK))) | ICS_C2_BDIV(bDiv));

    /* Set CLKS and IREFS. */
    ICS->C1 = (uint8_t)((ICS->C1 & ~(ICS_C1_CLKS_MASK | ICS_C1_RDIV_MASK | ICS_C1_IREFS_MASK)) |
                        (ICS_C1_CLKS(kICS_ClkOutSrcExternal)    /* CLKS = 2 */
                         | ICS_C1_RDIV(rDiv)                    /* FRDIV = frDiv */
                         | ICS_C1_IREFS(kICS_FllSrcExternal))); /* IREFS = 0 */

    /* If use external crystal as clock source, wait for it stable. */
    {
        if ((OSC0->CR & OSC_CR_OSCOS_MASK) != 0U)
        {
            while (0U == (OSC0->CR & OSC_CR_OSCINIT_MASK))
            {
            }
        }
    }

    /* Wait for Reference clock Status bit to clear */
    while ((uint8_t)kICS_FllSrcExternal != ICS_S_IREFST_VAL)
    {
    }

    /* Wait for clock status bits to show clock source is ext ref clk */
    while ((uint8_t)kICS_ClkOutStatExt != ICS_S_CLKST_VAL)
    {
    }

    /* clear Loss of lock sticky bit */
    ICS->S |= ICS_S_LOLS_MASK;

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to BILP mode.
 *
 * This function sets the ICS to BILP mode. If setting to BILP mode fails
 * from the current mode, this function returns an error.
 *
 * param   bDiv bus clock divider
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_SetBilpMode(uint8_t bDiv)
{
#if (defined(ICS_CONFIG_CHECK_PARAM) && ICS_CONFIG_CHECK_PARAM)
    if (ICS_S_CLKST_VAL != kICS_ClkOutStatInt)
    {
        return kStatus_ICS_ModeUnreachable;
    }
#endif /* ICS_CONFIG_CHECK_PARAM */

    /* set bus clock divider and enable low power */
    ICS->C2 = (uint8_t)((ICS->C2 & (~ICS_C2_BDIV_MASK)) | ICS_C2_BDIV(bDiv) | ICS_C2_LP_MASK);

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to BELP mode.
 *
 * This function sets the ICS to BELP mode. If setting to BELP mode fails
 * from the current mode, this function returns an error.
 *
 * param   bDiv bus clock divider
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_SetBelpMode(uint8_t bDiv)
{
#if (defined(ICS_CONFIG_CHECK_PARAM) && ICS_CONFIG_CHECK_PARAM)
    if (ICS_S_CLKST_VAL != kICS_ClkOutStatExt)
    {
        return kStatus_ICS_ModeUnreachable;
    }
#endif

    /* set bus clock divider and enable low power */
    ICS->C2 = (uint8_t)((ICS->C2 & (~ICS_C2_BDIV_MASK)) | ICS_C2_BDIV(bDiv) | ICS_C2_LP_MASK);

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to FEI mode during system boot up.
 *
 * This function sets the ICS to FEI mode from the reset mode. It can also be used to
 * set up ICS during system boot up.
 *
 * param  bDiv bus clock divider.
 *
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_BootToFeiMode(uint8_t bDiv)
{
    return CLOCK_SetFeiMode(bDiv);
}

/*!
 * brief Sets the ICS to FEE mode during system bootup.
 *
 * This function sets ICS to FEE mode from the reset mode. It can also be used to
 * set up the ICS during system boot up.
 *
 * param   bDiv bus clock divider.
 * param   rdiv  FLL reference clock divider setting, RDIV.
 *
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_BootToFeeMode(uint8_t bDiv, uint8_t rDiv)
{
    return CLOCK_SetFeeMode(bDiv, rDiv);
}

/*!
 * brief Sets the ICS to BILP mode during system boot up.
 *
 * This function sets the ICS to BILP mode from the reset mode. It can also be used to
 * set up the ICS during system boot up.
 *
 * param   bDiv bus clock divider.
 * retval kStatus_ICS_SourceUsed Could not change ICSIRCLK setting.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_BootToBilpMode(uint8_t bDiv)
{
    /* If reset mode is not BILP, first enter FBI mode. */
    ICS->C1 = (uint8_t)((ICS->C1 & ~ICS_C1_CLKS_MASK) | ICS_C1_CLKS(kICS_ClkOutSrcInternal));
    while (ICS_S_CLKST_VAL != (uint8_t)kICS_ClkOutStatInt)
    {
    }

    /* set bus clock divider and enable low power */
    ICS->C2 = (uint8_t)((ICS->C2 & (~ICS_C2_BDIV_MASK)) | ICS_C2_BDIV(bDiv) | ICS_C2_LP_MASK);

    return kStatus_Success;
}

/*!
 * brief Sets the ICS to BELP mode during system boot up.
 *
 * This function sets the ICS to BELP mode from the reset mode. It can also be used to
 * set up the ICS during system boot up.
 *
 * param   bDiv bus clock divider.
 *
 * retval kStatus_ICS_ModeUnreachable Could not switch to the target mode.
 * retval kStatus_Success Switched to the target mode successfully.
 */
status_t CLOCK_BootToBelpMode(uint8_t bDiv)
{
    /* Set to FBE mode. */
    ICS->C1 = (uint8_t)((ICS->C1 & ~(ICS_C1_CLKS_MASK | ICS_C1_IREFS_MASK)) |
                        (ICS_C1_CLKS(kICS_ClkOutSrcExternal)    /* CLKS = 2 */
                         | ICS_C1_IREFS(kICS_FllSrcExternal))); /* IREFS = 0 */
    /* If use external crystal as clock source, wait for it stable. */
    {
        if ((OSC0->CR & OSC_CR_OSCOS_MASK) != 0U)
        {
            while (0U == (OSC0->CR & OSC_CR_OSCINIT_MASK))
            {
            }
        }
    }

    /* Wait for ICS_S[CLKST] and ICS_S[IREFST]. */
    while ((ICS->S & (ICS_S_IREFST_MASK | ICS_S_CLKST_MASK)) !=
           (ICS_S_IREFST(kICS_FllSrcExternal) | ICS_S_CLKST(kICS_ClkOutStatExt)))
    {
    }

    /* set bus clock divider and enable low power */
    ICS->C2 = (uint8_t)((ICS->C2 & (~ICS_C2_BDIV_MASK)) | ICS_C2_BDIV(bDiv) | ICS_C2_LP_MASK);

    return kStatus_Success;
}

/*
   The transaction matrix. It defines the path for mode switch, the row is for
   current mode and the column is target mode.
   For example, switch from FEI to BELP:
   1. Current mode FEI, next mode is ICSModeMatrix[FEI][BELP] = FBE, so swith to FBE.
   2. Current mode FBE, next mode is ICSModeMatrix[FBE][BELP] = BELP, so swith to BELP.
   Thus the ICS mode has changed from FEI to BELP.
 */
static const ics_mode_t ICSModeMatrix[6][6] = {
    {kICS_ModeFEI, kICS_ModeFBI, kICS_ModeFBI, kICS_ModeFEE, kICS_ModeFBE, kICS_ModeFBE},  /* FEI */
    {kICS_ModeFEI, kICS_ModeFBI, kICS_ModeBILP, kICS_ModeFEE, kICS_ModeFBE, kICS_ModeFBE}, /* FBI */
    {kICS_ModeFBI, kICS_ModeFBI, kICS_ModeBILP, kICS_ModeFBI, kICS_ModeFBI, kICS_ModeFBI}, /* BILP */
    {kICS_ModeFEI, kICS_ModeFBI, kICS_ModeFBI, kICS_ModeFEE, kICS_ModeFBE, kICS_ModeFBE},  /* FEE */
    {kICS_ModeFEI, kICS_ModeFBI, kICS_ModeFBI, kICS_ModeFEE, kICS_ModeFBE, kICS_ModeBELP}, /* FBE */
    {kICS_ModeFBE, kICS_ModeFBE, kICS_ModeFBE, kICS_ModeFBE, kICS_ModeFBE, kICS_ModeBELP}, /* BELP */
    /*      FEI           FBI           BILP          FEE           FBE           BELP      */
};

/*!
 * brief Sets the ICS to a target mode.
 *
 * This function sets ICS to a target mode defined by the configuration
 * structure. If switching to the target mode fails, this function
 * chooses the correct path.
 *
 * param  config Pointer to the target ICS mode configuration structure.
 * return Return kStatus_Success if switched successfully; Otherwise, it returns an error code #_ICS_status.
 *
 * note If the external clock is used in the target mode, ensure that it is
 * enabled. For example, if the OSC0 is used, set up OSC0 correctly before calling this
 * function.
 */
status_t CLOCK_SetIcsConfig(const ics_config_t *config)
{
    ics_mode_t next_mode;
    status_t status = kStatus_Success;

    /* Configure ICSIRCLK. */
    CLOCK_SetInternalRefClkConfig(config->irClkEnableMode);

    next_mode = CLOCK_GetMode();

    do
    {
        next_mode = ICSModeMatrix[next_mode][config->icsMode];

        switch (next_mode)
        {
            case kICS_ModeFEI:
                status = CLOCK_SetFeiMode(config->bDiv);
                break;
            case kICS_ModeFEE:
                status = CLOCK_SetFeeMode(config->bDiv, config->rDiv);
                break;
            case kICS_ModeFBI:
                status = CLOCK_SetFbiMode(config->bDiv);
                break;
            case kICS_ModeFBE:
                status = CLOCK_SetFbeMode(config->bDiv, config->rDiv);
                break;
            case kICS_ModeBILP:
                status = CLOCK_SetBilpMode(config->bDiv);
                break;
            case kICS_ModeBELP:
                status = CLOCK_SetBelpMode(config->bDiv);
                break;
            default:
                status = kStatus_Success;
                break;
        }
        if (kStatus_Success != status)
        {
            return status;
        }
    } while (next_mode != config->icsMode);

    return kStatus_Success;
}
