/*
 * Copyright 2017-2018 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_common.h"
#include "fsl_clock.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/
/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.clock"
#endif
#define SYSPLL_MIN_INPUT_FREQ_HZ (10000000U)   /*!<  Minimum PLL input rate */
#define SYSPLL_MAX_INPUT_FREQ_HZ (25000000U)   /*!<  Maximum PLL input rate */
#define SYSPLL_MAX_OUTPUT_FREQ_HZ (100000000U) /*!< Maximum PLL output rate */
#define SYSPLL_MIN_FCCO_FREQ_HZ (156000000U)   /*!< Maximum FCCO output rate */
#define SYSPLL_MAX_FCCO_FREQ_HZ (320000000U)   /*!< Maximum FCCO output rate */
#define SYSOSC_BOUNDARY_FREQ_HZ (15000000U)    /*!< boundary frequency value */

/* External clock rate.
 * Either external clk in rate or system oscillator frequency.
 */
volatile uint32_t g_Ext_Clk_Freq = 0U;
/** watch dog oscillator rate in Hz.*/
volatile uint32_t g_Wdt_Osc_Freq = 0U;

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

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*
 * @brief   select post divider for system pll according to the target frequency.
 * @param   outFreq: Value to be output
 * @return  post divider
 */
static uint32_t findSyestemPllPsel(uint32_t outFreq);

/*
 * @brief   Get FRG input clock frequency.
 * @param fractional clock register base address.
 * @return  input clock frequency.
 */
static uint32_t CLOCK_GetFRGInputClkFreq(uint32_t *base);

/*
 * @brief Update clock source.
 * @param base clock register base address.
 * @param mask clock source update enable bit mask value.
 */
static void CLOCK_UpdateClkSrc(volatile uint32_t *base, uint32_t mask);

/*******************************************************************************
 * Code
 ******************************************************************************/
static uint32_t CLOCK_GetFRGInputClkFreq(uint32_t *base)
{
    uint32_t sel = CLK_FRG_SEL_REG_MAP(base) & SYSCON_FRG_FRGCLKSEL_SEL_MASK;

    if (sel == 0U)
    {
        return CLOCK_GetFroFreq();
    }
    else if (sel == 1U)
    {
        return CLOCK_GetMainClkFreq();
    }
    else
    {
        return CLOCK_GetSystemPLLFreq();
    }
}

static bool CLOCK_SetFRGClkFreq(uint32_t *base, uint32_t freq)
{
    assert(freq);

    uint32_t input = CLOCK_GetFRGInputClkFreq(base);
    uint32_t mul;

    if ((freq > input) || (input / freq >= 2U))
    {
        return false;
    }

    mul = ((uint64_t)(input - freq) << 8U) / ((uint64_t)freq);

    CLK_FRG_DIV_REG_MAP(base) = SYSCON_FRG_FRGDIV_DIV_MASK;
    CLK_FRG_MUL_REG_MAP(base) = SYSCON_FRG_FRGMULT_MULT(mul);

    return true;
}

static void CLOCK_UpdateClkSrc(volatile uint32_t *base, uint32_t mask)
{
    assert(base);

    *base &= ~mask;
    *base |= mask;
    while ((*base & mask) == 0U)
    {
    }
}

/*! brief Set FRG0 output frequency.
* param freq, target output frequency,freq < input and (input / freq) < 2 should be satisfy.
* retval true - successfully, false - input argument is invalid.
*
*/
bool CLOCK_SetFRG0ClkFreq(uint32_t freq)
{
    return CLOCK_SetFRGClkFreq(((uint32_t *)(&SYSCON->FRG[0U])), freq);
}

/*! brief Set FRG1 output frequency.
* param freq, target output frequency,freq < input and (input / freq) < 2 should be satisfy.
* retval true - successfully, false - input argument is invalid.
*
*/
bool CLOCK_SetFRG1ClkFreq(uint32_t freq)
{
    return CLOCK_SetFRGClkFreq(((uint32_t *)(&SYSCON->FRG[1U])), freq);
}

/*! brief  Return Frequency of FRG0 Clock.
 *  return Frequency of FRG0 Clock.
 */
uint32_t CLOCK_GetFRG0ClkFreq(void)
{
    return ((uint64_t)(CLOCK_GetFRGInputClkFreq((uint32_t *)(&SYSCON->FRG[0U])) << 8U)) /
           ((SYSCON->FRG[0U].FRGMULT & SYSCON_FRG_FRGMULT_MULT_MASK) + 256U);
}

/*! brief  Return Frequency of FRG1 Clock.
 *  return Frequency of FRG1 Clock.
 */
uint32_t CLOCK_GetFRG1ClkFreq(void)
{
    return ((uint64_t)(CLOCK_GetFRGInputClkFreq((uint32_t *)(&SYSCON->FRG[1U])) << 8U)) /
           ((SYSCON->FRG[1U].FRGMULT & SYSCON_FRG_FRGMULT_MULT_MASK) + 256U);
}

/*! brief  Return Frequency of Main Clock.
 *  return Frequency of Main Clock.
 */
uint32_t CLOCK_GetMainClkFreq(void)
{
    uint32_t freq = 0U;

    if ((SYSCON->MAINCLKPLLSEL & SYSCON_MAINCLKPLLSEL_SEL_MASK) == 1U)
    {
        return CLOCK_GetSystemPLLFreq();
    }

    switch (SYSCON->MAINCLKSEL)
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;

        case 1U:
            freq = CLOCK_GetExtClkFreq();
            break;

        case 2U:
            freq = CLOCK_GetWdtOscFreq();
            break;

        case 3U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;
        default:
            break;
    }

    return freq;
}

/*! brief  Return Frequency of FRO.
 *  return Frequency of FRO.
 */
uint32_t CLOCK_GetFroFreq(void)
{
    uint32_t froOscSel = SYSCON->FROOSCCTRL & 3U;
    uint32_t froOscFreq = 0U;

    if (froOscSel == 0U)
    {
        froOscFreq = 18000000U;
    }
    else if (froOscSel == 1U)
    {
        froOscFreq = 24000000U;
    }
    else
    {
        froOscFreq = 30000000U;
    }

    if (((SYSCON->FROOSCCTRL & SYSCON_FROOSCCTRL_FRO_DIRECT_MASK) >> SYSCON_FROOSCCTRL_FRO_DIRECT_SHIFT) == 0U)
    {
        /* need to check the FAIM low power boot value */
        froOscFreq /= (*((volatile uint32_t *)(CLOCK_FAIM_BASE)) & 0x2U) ? 16U : 2U;
    }

    return froOscFreq;
}

/*! brief  Return Frequency of ClockOut
 *  return Frequency of ClockOut
 */
uint32_t CLOCK_GetClockOutClkFreq(void)
{
    uint32_t divider = SYSCON->CLKOUTDIV & 0xffU, freq = 0U;

    switch (SYSCON->CLKOUTSEL)
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;

        case 1U:
            freq = CLOCK_GetMainClkFreq();
            break;

        case 2U:
            freq = CLOCK_GetSystemPLLFreq();
            break;

        case 3U:
            freq = CLOCK_GetExtClkFreq();
            break;

        case 4U:
            freq = CLOCK_GetWdtOscFreq();
            break;

        default:
            break;
    }

    return divider == 0U ? 0U : (freq / divider);
}

/*! brief  Return Frequency of UART0
 *  return Frequency of UART0
 */
uint32_t CLOCK_GetUart0ClkFreq(void)
{
    uint32_t freq = 0U;

    switch (SYSCON->FCLKSEL[0])
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;
        case 1U:
            freq = CLOCK_GetMainClkFreq();
            break;
        case 2U:
            freq = CLOCK_GetFRG0ClkFreq();
            break;
        case 3U:
            freq = CLOCK_GetFRG1ClkFreq();
            break;
        case 4U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;

        default:
            break;
    }

    return freq;
}

/*! brief  Return Frequency of UART1
 *  return Frequency of UART1
 */
uint32_t CLOCK_GetUart1ClkFreq(void)
{
    uint32_t freq = 0U;

    switch (SYSCON->FCLKSEL[1])
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;
        case 1U:
            freq = CLOCK_GetMainClkFreq();
            break;
        case 2U:
            freq = CLOCK_GetFRG0ClkFreq();
            break;
        case 3U:
            freq = CLOCK_GetFRG1ClkFreq();
            break;
        case 4U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;

        default:
            break;
    }

    return freq;
}

/*! brief  Return Frequency of UART2
 *  return Frequency of UART2
 */
uint32_t CLOCK_GetUart2ClkFreq(void)
{
    uint32_t freq = 0U;

    switch (SYSCON->FCLKSEL[2])
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;
        case 1U:
            freq = CLOCK_GetMainClkFreq();
            break;
        case 2U:
            freq = CLOCK_GetFRG0ClkFreq();
            break;
        case 3U:
            freq = CLOCK_GetFRG1ClkFreq();
            break;
        case 4U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;

        default:
            break;
    }

    return freq;
}

/*! brief  Return Frequency of UART3
 *  return Frequency of UART3
 */
uint32_t CLOCK_GetUart3ClkFreq(void)
{
    uint32_t freq = 0U;

    switch (SYSCON->FCLKSEL[3])
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;
        case 1U:
            freq = CLOCK_GetMainClkFreq();
            break;
        case 2U:
            freq = CLOCK_GetFRG0ClkFreq();
            break;
        case 3U:
            freq = CLOCK_GetFRG1ClkFreq();
            break;
        case 4U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;

        default:
            break;
    }

    return freq;
}

/*! brief  Return Frequency of UART4
 *  return Frequency of UART4
 */
uint32_t CLOCK_GetUart4ClkFreq(void)
{
    uint32_t freq = 0U;

    switch (SYSCON->FCLKSEL[4])
    {
        case 0U:
            freq = CLOCK_GetFroFreq();
            break;
        case 1U:
            freq = CLOCK_GetMainClkFreq();
            break;
        case 2U:
            freq = CLOCK_GetFRG0ClkFreq();
            break;
        case 3U:
            freq = CLOCK_GetFRG1ClkFreq();
            break;
        case 4U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;

        default:
            break;
    }

    return freq;
}

/*! brief	Return Frequency of selected clock
 *  return	Frequency of selected clock
 */
uint32_t CLOCK_GetFreq(clock_name_t clockName)
{
    uint32_t freq;

    switch (clockName)
    {
        case kCLOCK_CoreSysClk:
            freq = CLOCK_GetCoreSysClkFreq();
            break;
        case kCLOCK_MainClk:
            freq = CLOCK_GetMainClkFreq();
            break;
        case kCLOCK_Fro:
            freq = CLOCK_GetFroFreq();
            break;
        case kCLOCK_FroDiv:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;
        case kCLOCK_ExtClk:
            freq = CLOCK_GetExtClkFreq();
            break;
        case kCLOCK_WdtOsc:
            freq = CLOCK_GetWdtOscFreq();
            break;
        case kCLOCK_PllOut:
            freq = CLOCK_GetSystemPLLFreq();
            break;
        case kCLOCK_Frg0:
            freq = CLOCK_GetFRG0ClkFreq();
            break;
        case kCLOCK_Frg1:
            freq = CLOCK_GetFRG1ClkFreq();
            break;

        default:
            freq = 0U;
            break;
    }

    return freq;
}

/*! brief  Return System PLL input clock rate
 *  return System PLL input clock rate
 */
uint32_t CLOCK_GetSystemPLLInClockRate(void)
{
    uint32_t freq = 0U;

    switch ((SYSCON->SYSPLLCLKSEL))
    {
        /* source from fro div clock */
        case 0x03U:
            freq = CLOCK_GetFroFreq() >> 1U;
            break;
        /* source from the fro clock */
        case 0x00U:
            freq = CLOCK_GetFroFreq();
            break;
        /* source from external clock in */
        case 0x01U:
            freq = CLOCK_GetExtClkFreq();
            break;
        /* source from watchdog oscillator */
        case 0x02U:
            freq = CLOCK_GetWdtOscFreq();
            break;

        default:
            break;
    }

    return freq;
}

static uint32_t findSyestemPllPsel(uint32_t outFreq)
{
    uint32_t pSel = 0U;

    if (outFreq > (SYSPLL_MIN_FCCO_FREQ_HZ >> 1U))
    {
        pSel = 0U;
    }
    else if (outFreq > (SYSPLL_MIN_FCCO_FREQ_HZ >> 2U))
    {
        pSel = 1U;
    }
    else if (outFreq > (SYSPLL_MIN_FCCO_FREQ_HZ >> 3U))
    {
        pSel = 2U;
    }
    else
    {
        pSel = 3U;
    }

    return pSel;
}

/*! brief  System PLL initialize.
 *  param config System PLL configurations.
 */
void CLOCK_InitSystemPll(const clock_sys_pll_t *config)
{
    assert(config->targetFreq <= SYSPLL_MAX_OUTPUT_FREQ_HZ);

    uint32_t mSel = 0U, pSel = 0U, inputFreq = 0U;

    /* Power off PLL during setup changes */
    SYSCON->PDRUNCFG |= SYSCON_PDRUNCFG_SYSPLL_PD_MASK;

    /*set system pll clock source select register */
    SYSCON->SYSPLLCLKSEL |= (SYSCON->SYSPLLCLKSEL & (~SYSCON_SYSPLLCLKSEL_SEL_MASK)) | config->src;
    /* system pll clock source update */
    CLOCK_UpdateClkSrc((volatile uint32_t *)(&(SYSCON->SYSPLLCLKUEN)), SYSCON_SYSPLLCLKSEL_SEL_MASK);

    inputFreq = CLOCK_GetSystemPLLInClockRate();
    assert(inputFreq != 0U);

    /* calucate the feedback divider value and post divider value*/
    mSel = config->targetFreq / inputFreq;
    pSel = findSyestemPllPsel(config->targetFreq);

    /* configure PSEL and MSEL */
    SYSCON->SYSPLLCTRL = (SYSCON->SYSPLLCTRL & (~(SYSCON_SYSPLLCTRL_MSEL_MASK | SYSCON_SYSPLLCTRL_PSEL_MASK))) |
                         SYSCON_SYSPLLCTRL_MSEL(mSel == 0U ? 0U : (mSel - 1U)) | SYSCON_SYSPLLCTRL_PSEL(pSel);

    /* Power up PLL after setup changes */
    SYSCON->PDRUNCFG &= ~SYSCON_PDRUNCFG_SYSPLL_PD_MASK;

    /* wait pll lock */
    while ((SYSCON->SYSPLLSTAT & SYSCON_SYSPLLSTAT_LOCK_MASK) == 0U)
    {
    }
}

/*! brief  Init external CLK IN, select the CLKIN as the external clock source.
* param clkInFreq external clock in frequency.
*/
void CLOCK_InitExtClkin(uint32_t clkInFreq)
{
    /* remove the pull up and pull down resistors in the IOCON */
    IOCON->PIO[IOCON_INDEX_PIO0_1] &= ~IOCON_PIO_MODE_MASK;
    /* enable the 1 bit functions for CLKIN */
    SWM0->PINENABLE0 &= ~SWM_PINENABLE0_CLKIN_MASK;
    /* enable the external clk in */
    SYSCON->EXTCLKSEL |= SYSCON_EXTCLKSEL_SEL_MASK;
    /* record the external clock rate */
    g_Ext_Clk_Freq = clkInFreq;
}

/*! brief  XTALIN init function
 *  system oscillator is bypassed, sys_osc_clk is fed driectly from the XTALIN.
 *  param xtalInFreq XTALIN frequency value
 *  return Frequency of PLL
 */
void CLOCK_InitXtalin(uint32_t xtalInFreq)
{
    volatile uint32_t i = 0U;

    /* remove the pull up and pull down resistors in the IOCON */
    IOCON->PIO[IOCON_INDEX_PIO0_8] &= ~IOCON_PIO_MODE_MASK;
    /* enable the 1 bit functions for XTALIN and XTALOUT */
    SWM0->PINENABLE0 &= ~SWM_PINENABLE0_XTALIN_MASK;

    /* system osc configure */
    SYSCON->SYSOSCCTRL |= SYSCON_SYSOSCCTRL_BYPASS_MASK;
    /* external clock select */
    SYSCON->EXTCLKSEL &= ~SYSCON_EXTCLKSEL_SEL_MASK;
    /* enable system osc power first */
    SYSCON->PDRUNCFG &= ~SYSCON_PDRUNCFG_SYSOSC_PD_MASK;

    /* software delay 500USs */
    for (i = 0U; i < 1500U; i++)
    {
        __ASM("nop");
    }

    /* record the external clock rate */
    g_Ext_Clk_Freq = xtalInFreq;
}

/*! brief	Init SYS OSC
* param oscFreq oscillator frequency value.
*/
void CLOCK_InitSysOsc(uint32_t oscFreq)
{
    volatile uint32_t i = 0U;

    /* remove the pull up and pull down resistors in the IOCON */
    IOCON->PIO[IOCON_INDEX_PIO0_9] &= ~IOCON_PIO_MODE_MASK;
    IOCON->PIO[IOCON_INDEX_PIO0_8] &= ~IOCON_PIO_MODE_MASK;
    /* enable the 1 bit functions for XTALIN and XTALOUT */
    SWM0->PINENABLE0 &= ~(SWM_PINENABLE0_XTALIN_MASK | SWM_PINENABLE0_XTALOUT_MASK);

    /* system osc configure */
    SYSCON->SYSOSCCTRL |= (SYSCON->SYSOSCCTRL & (~(SYSCON_SYSOSCCTRL_BYPASS_MASK | SYSCON_SYSOSCCTRL_FREQRANGE_MASK))) |
                          (oscFreq > SYSOSC_BOUNDARY_FREQ_HZ ? SYSCON_SYSOSCCTRL_FREQRANGE_MASK : 0U);
    /* external clock select */
    SYSCON->EXTCLKSEL &= ~SYSCON_EXTCLKSEL_SEL_MASK;

    /* enable system osc power first */
    SYSCON->PDRUNCFG &= ~SYSCON_PDRUNCFG_SYSOSC_PD_MASK;

    /* software delay 500USs */
    for (i = 0U; i < 1500U; i++)
    {
        __ASM("nop");
    }

    /* record the external clock rate */
    g_Ext_Clk_Freq = oscFreq;
}

/*! brief  Init watch dog OSC
* Any setting of the FREQSEL bits will yield a Fclkana value within 40% of the
* listed frequency value. The watchdog oscillator is the clock source with the lowest power
* consumption. If accurate timing is required, use the FRO or system oscillator.
* The frequency of the watchdog oscillator is undefined after reset. The watchdog
* oscillator frequency must be programmed by writing to the WDTOSCCTRL register before
* using the watchdog oscillator.
* Watchdog osc output frequency = wdtOscFreq / wdtOscDiv, should in range 9.3KHZ to 2.3MHZ.
* param wdtOscFreq watch dog analog part output frequency, reference _wdt_analog_output_freq.
* param wdtOscDiv watch dog analog part output frequency divider, shoule be a value >= 2U and multiple of 2
*/
void CLOCK_InitWdtOsc(clock_wdt_analog_freq_t wdtOscFreq, uint32_t wdtOscDiv)
{
    assert(wdtOscDiv >= 2U);

    uint32_t wdtOscCtrl = SYSCON->WDTOSCCTRL;

    wdtOscCtrl &= ~(SYSCON_WDTOSCCTRL_DIVSEL_MASK | SYSCON_WDTOSCCTRL_FREQSEL_MASK);

    wdtOscCtrl |=
        SYSCON_WDTOSCCTRL_DIVSEL((wdtOscDiv >> 1U) - 1U) | SYSCON_WDTOSCCTRL_FREQSEL(CLK_WDT_OSC_GET_REG(wdtOscFreq));

    SYSCON->WDTOSCCTRL = wdtOscCtrl;

    /* power up watchdog oscillator */
    SYSCON->PDRUNCFG &= ~SYSCON_PDRUNCFG_WDTOSC_PD_MASK;
    /* update watch dog oscillator value */
    g_Wdt_Osc_Freq = CLK_WDT_OSC_GET_FREQ(wdtOscFreq) / wdtOscDiv;
}

/*! brief  Set main clock reference source.
* param src, reference clock_main_clk_src_t to set the main clock source.
*/
void CLOCK_SetMainClkSrc(clock_main_clk_src_t src)
{
    uint32_t mainMux = CLK_MAIN_CLK_MUX_GET_MUX(src), mainPreMux = CLK_MAIN_CLK_MUX_GET_PRE_MUX(src);

    if (((SYSCON->MAINCLKSEL & SYSCON_MAINCLKSEL_SEL_MASK) != mainPreMux) && (mainMux == 0U))
    {
        SYSCON->MAINCLKSEL = (SYSCON->MAINCLKSEL & (~SYSCON_MAINCLKSEL_SEL_MASK)) | SYSCON_MAINCLKSEL_SEL(mainPreMux);
        CLOCK_UpdateClkSrc((volatile uint32_t *)(&(SYSCON->MAINCLKUEN)), SYSCON_MAINCLKUEN_ENA_MASK);
    }

    if ((SYSCON->MAINCLKPLLSEL & SYSCON_MAINCLKPLLSEL_SEL_MASK) != mainMux)
    {
        SYSCON->MAINCLKPLLSEL =
            (SYSCON->MAINCLKPLLSEL & (~SYSCON_MAINCLKPLLSEL_SEL_MASK)) | SYSCON_MAINCLKPLLSEL_SEL(mainMux);
        CLOCK_UpdateClkSrc((volatile uint32_t *)(&(SYSCON->MAINCLKPLLUEN)), SYSCON_MAINCLKPLLUEN_ENA_MASK);
    }
}

/*! brief Set FRO clock source
* param src, please reference _clock_fro_src definition.
*
*/
void CLOCK_SetFroOutClkSrc(clock_fro_src_t src)
{
    if (src != (SYSCON->FROOSCCTRL & SYSCON_FROOSCCTRL_FRO_DIRECT_MASK))
    {
        SYSCON->FROOSCCTRL = (SYSCON->FROOSCCTRL & (~SYSCON_FROOSCCTRL_FRO_DIRECT_MASK)) | (uint32_t)src;
        /* Update clock source */
        CLOCK_UpdateClkSrc((volatile uint32_t *)(&(SYSCON->FRODIRECTCLKUEN)), SYSCON_FRODIRECTCLKUEN_ENA_MASK);
    }
}
