/******************************************************************************
 * @file     clock.c
 * @brief    CLOCK firmware functions.
 * @version  V1.8.0
 * @date     31-Dec-2021
 *
 * @note
 * Copyright (C) 2021 Spintrol Limited. All rights reserved.
 *
 * @attention
 * THIS SOFTWARE JUST PROVIDES CUSTOMERS WITH CODING INFORMATION REGARDING
 * THEIR PRODUCTS, WHICH AIMS AT SAVING TIME FOR THEM. SPINTROL SHALL NOT BE
 * LIABLE FOR THE USE OF THE SOFTWARE. SPINTROL DOES NOT GUARANTEE THE
 * CORRECTNESS OF THIS SOFTWARE AND RESERVES THE RIGHT TO MODIFY THE SOFTWARE
 * WITHOUT NOTIFICATION.
 *
 ******************************************************************************/


#include "clock.h"




/******************************************************************************
 * @brief      PLL VCO frequency trimming
 *
 * @param[in]  none
 *
 * @return     none
 *
 * @note       The PLL is calibrated when factory-out, and usually do not need 
 *             to call this function!!!
 *
 ******************************************************************************/
void CLOCK_TrimPLL(void)
{
    uint32_t u32VCOTrim;

    /* Enable PLL */
    CLOCK->PLLCTL0.bit.EN       = PLLCTL0_BIT_EN_ENABLE;
    /* Enable open-loop mode for VCO trimming */
    CLOCK->PLLCTL0.bit.OPENLOOP = PLLCTL0_BIT_OPENLOOP_OPEN_LOOP;
    /* Enable PLL frequency calibration */
    CLOCK->PLLCTL0.bit.FCALEN   = PLLCTL0_BIT_FCALEN_ENABLE;

    /* VCO frequency trimming */
    for (u32VCOTrim = 0; u32VCOTrim < 8; u32VCOTrim++)
    {
        /* Set VCO trim value */
        CLOCK->PLLCTL0.bit.VCOTRIM = u32VCOTrim;

        /* Enable PLL frequency calibration time window */
        CLOCK->PLLCTL0.bit.FCALWIN = PLLCTL0_BIT_FCALWIN_ENABLE;

        /* Wait VCOFREQ signal valid */
        while (CLOCK->CLKSTS.bit.VCOFREQVLD == CLKSTS_BIT_VCOFREQVLD_INVALID);
        /* If VCO frequency is too fast, finish calibration */
        if (CLOCK->CLKSTS.bit.VCOFREQ == CLKSTS_BIT_VCOFREQ_TOO_FAST)
        {
            break;
        }

        /* Disable PLL frequency calibration time window */
        CLOCK->PLLCTL0.bit.FCALWIN = PLLCTL0_BIT_FCALWIN_DISABLE;
    }

    /* Restore to default value */
    CLOCK->PLLCTL0.bit.FCALEN   = PLLCTL0_BIT_FCALEN_DISABLE;
    CLOCK->PLLCTL0.bit.OPENLOOP = PLLCTL0_BIT_OPENLOOP_CLOSE_LOOP;
    CLOCK->PLLCTL0.bit.EN       = PLLCTL0_BIT_EN_DISABLE;
}




/******************************************************************************
 * @brief      Configure chip clock system
 *
 * @param[in]  u32HCLKFreq:  HCLK frequency in Hz
 *                           Pre-defined value in CLOCK_HCLKSelEnum can be used
 *                           for simplicity
 * @param[in]  u32PLLRef  :  Reference clock selection defined by CLOCK_RefSelEnum
 *
 * @return     none
 *
 * @note       In this function:
 *             - Select PLL as system clock source
 *             - HCLK frequency equal to the system clock frequency
 *             - PCLK, SSPCLK and I2CCLK frequency is limited to less than 50MHz
 *             - Select PLL as periheral clock source
 *             - Peripheral clock freqency (except SSP and I2C) equal to system clock
 *
 * @example    (1)  CLOCK_Init(CLOCK_FROM_XTAL6MHZ,CLOCK_HCLK_50MHZ);
 *             (2)  CLOCK_Init(CLOCK_FROM_RCO32MHZ,CLOCK_HCLK_200MHZ);
 *
 ******************************************************************************/
ErrorStatus CLOCK_Init(CLOCK_RefSelEnum u32PLLRef, uint32_t u32HCLKFreq)
{
    ErrorStatus Status = SUCCESS;

    uint32_t u32PCLKDiv;


    if (u32PLLRef == 0UL)
    {
        /* Select RCO as reference */
        Status = CLOCK_ConfigurePLL(0, 32000000, u32HCLKFreq);
    }
    else
    {
        /* Set the pin channel to XO */
        GPIO_SetPinChannel(GPIO_16, GPIO16_XIN);
        GPIO_SetPinChannel(GPIO_17, GPIO17_XIO);
        
        /* Select XO as reference */
        Status = CLOCK_ConfigurePLL(1, u32PLLRef, u32HCLKFreq);
    }


    if (Status == SUCCESS)
    {
        u32PCLKDiv = u32HCLKFreq / 50000000;
        if ((u32PCLKDiv * 50000000) != u32HCLKFreq)
        {
            u32PCLKDiv += 1;
        }

        /* Update PCLK, SSPCLK, I2CCLK dividing ratio */
        CLOCK->PCLKCTL.bit.DIV    = (u32PCLKDiv - 1);
        CLOCK->SSPCLKCTL.bit.DIV  = (u32PCLKDiv - 1);
        CLOCK->I2CCLKCTL.bit.DIV  = (u32PCLKDiv - 1);

        /* Set HCLK dividing ratio to 1 */
        CLOCK->HCLKCTL.bit.DIV    = 0;    /* HCLK Frequency = System clock frequency */


        /* Select PLL clock as system clock */
        CLOCK->SYSCLK0CTL.bit.SRC = SYSCLK0CTL_BIT_SRC_PLL;
        CLOCK->SYSCLK1CTL.bit.SRC = SYSCLK1CTL_BIT_SRC_PLL;

        /* Update system information */
        SystemCoreClock = u32HCLKFreq;
        SysInfo.u32HCLK = u32HCLKFreq;
        SysInfo.u32PCLK = u32HCLKFreq / u32PCLKDiv;


        /* Configure peripheral clock dividing ratio */
        CLOCK->ADCCLKCTL.bit.DIV  = 0;
        CLOCK->PWMCLKCTL.bit.DIV  = 0;
        CLOCK->ECAPCLKCTL.bit.DIV = 0;
        CLOCK->TMR0CLKCTL.bit.DIV = 0;
        CLOCK->TMR1CLKCTL.bit.DIV = 0;
        CLOCK->TMR2CLKCTL.bit.DIV = 0;
        CLOCK->UARTCLKCTL.bit.DIV = 0;


        /* Configure Watchdog clock source - use default setting */
        CLOCK->WDT0CLKCTL.bit.SRC = WDT0CLKCTL_BIT_SRC_RCO0;
        CLOCK->WDT1CLKCTL.bit.SRC = WDT1CLKCTL_BIT_SRC_RCO1;

        /* Configure Watchdog clock dividing ratio - use default setting */
        CLOCK->WDT0CLKCTL.bit.DIV = 0xF;
        CLOCK->WDT1CLKCTL.bit.DIV = 0x0;
    }

    return Status;
}




/******************************************************************************
 * @brief      Configure chip clock system with RCO0 as reference
 *
 * @param[in]  u32HCLKFreq:  HCLK frequency in Hz
 *                           Pre-defined value in CLOCK_HCLKSelEnum can be used
 *                           for simplicity
 *
 * @return     none
 *
 * @note       In this function:
 *             - Select PLL as system clock source
 *             - HCLK frequency equal to the system clock frequency
 *             - PCLK, SSPCLK and I2CCLK frequency is limited to less than 50MHz
 *             - Select PLL as periheral clock source
 *             - Peripheral clock freqency (except SSP and I2C) equal to system clock
 *
 * @example    (1)  CLOCK_InitWithRCO(CLOCK_HCLK_50MHZ);
 *             (2)  CLOCK_InitWithRCO(CLOCK_HCLK_200MHZ);
 *
 ******************************************************************************/
ErrorStatus CLOCK_InitWithRCO(uint32_t u32HCLKFreq)
{
    ErrorStatus Status = SUCCESS;
    uint32_t u32PCLKDiv;
    uint32_t u32TimeOut;

    if (u32HCLKFreq == CLOCK_HCLK_32MHZ)
    {
        /* Enable RCO0 */
        CLOCK->RCO0CTL.all |= RCO0CTL_ALL_EN_ENABLE;

        /* Wait RCO0 ready */
        u32TimeOut = 10000;
        while (CLOCK->CLKSTS.bit.RCO0RDY == CLKSTS_BIT_RCO0RDY_NOT_READY)
        {
            if ((u32TimeOut--) == 0)
            {
                Status = ERROR;
                break;
            }
        }
    }
    else
    {
        /* Configure PLL clock */
        Status = CLOCK_ConfigurePLLWithRCO(u32HCLKFreq);
    }


    if (Status == SUCCESS)
    {
        u32PCLKDiv = u32HCLKFreq / 50000000;
        if ((u32PCLKDiv * 50000000) != u32HCLKFreq)
        {
            u32PCLKDiv += 1;
        }


        /* Update PCLK, SSPCLK, I2CCLK dividing ratio */
        CLOCK->PCLKCTL.bit.DIV    = (u32PCLKDiv - 1);
        CLOCK->SSPCLKCTL.bit.DIV  = (u32PCLKDiv - 1);
        CLOCK->I2CCLKCTL.bit.DIV  = (u32PCLKDiv - 1);

        /* Set HCLK dividing ratio to 1 */
        CLOCK->HCLKCTL.bit.DIV    = 0;    /* HCLK Frequency = System clock frequency */


        if (u32HCLKFreq == CLOCK_HCLK_32MHZ)
        {
            /* Select RCO0 clock as system clock */
            CLOCK->SYSCLK0CTL.bit.SRC = SYSCLK0CTL_BIT_SRC_RCO0;
            CLOCK->SYSCLK1CTL.bit.SRC = SYSCLK1CTL_BIT_SRC_RCO0;
        }
        else
        {
            /* Select PLL clock as system clock */
            CLOCK->SYSCLK0CTL.bit.SRC = SYSCLK0CTL_BIT_SRC_PLL;
            CLOCK->SYSCLK1CTL.bit.SRC = SYSCLK1CTL_BIT_SRC_PLL;
        }

        /* Update system information */
        SystemCoreClock = u32HCLKFreq;
        SysInfo.u32HCLK = u32HCLKFreq;
        SysInfo.u32PCLK = u32HCLKFreq / u32PCLKDiv;


        /* Configure peripheral clock dividing ratio */
        CLOCK->ADCCLKCTL.bit.DIV  = 0;
        CLOCK->PWMCLKCTL.bit.DIV  = 0;
        CLOCK->ECAPCLKCTL.bit.DIV = 0;
        CLOCK->TMR0CLKCTL.bit.DIV = 0;
        CLOCK->TMR1CLKCTL.bit.DIV = 0;
        CLOCK->TMR2CLKCTL.bit.DIV = 0;
        CLOCK->UARTCLKCTL.bit.DIV = 0;


        /* Configure Watchdog clock source - use default setting */
        CLOCK->WDT0CLKCTL.bit.SRC = WDT0CLKCTL_BIT_SRC_RCO0;
        CLOCK->WDT1CLKCTL.bit.SRC = WDT1CLKCTL_BIT_SRC_RCO1;

        /* Configure Watchdog clock dividing ratio - use default setting */
        CLOCK->WDT0CLKCTL.bit.DIV = 0xF;
        CLOCK->WDT1CLKCTL.bit.DIV = 0x0;
    }

    return Status;
}




/******************************************************************************
 * @brief      Configure PLL clock
 *
 * @param[in]  u8RefClkFromXO:  Reference clock selection
 *                               - \ref 1 -> clock source(reference) is from external crystal clock
 *                               - \ref 0 -> clock source(reference) is from internal RCO(32MHz)
 * @param[in]  u32RefClkHz   :  Reference clock frequency (Hz)
 * @param[in]  u32PLLClk     :  PLL output clock frequency (Hz)
 *
 * @return     SUCCESS or ERROR
 *
 ******************************************************************************/
ErrorStatus CLOCK_ConfigurePLL(uint8_t u8RefClkFromXO, uint32_t u32RefClkHz, uint32_t u32PLLClk)
{
    uint32_t u32TimeOut;
    uint32_t u32NOUT, u32NIN, u32NFB;
    uint32_t u32VCOTrim;
    uint64_t u64NFB;


    /* Check PLL frequency */
    if (u32PLLClk > PLL_MAX_FREQ)
    {
        return ERROR;
    }
    
    /* Enable XO */
    if (u8RefClkFromXO == 1)
    {
        CLOCK->XOCTL.all =  XOCTL_ALL_PRECNT_(0xFFU)                 /* Set pre-counted target value            */
                          | XOCTL_ALL_FASTEN_ENABLE                  /* Enable noise injection for fast startup */
                          | XOCTL_ALL_EXTRFB_DISABLE;                /* Use internal feedback resistor          */

        /* Enable XO */
        CLOCK->XOCTL.bit.EN = XOCTL_BIT_EN_ENABLE;

        /* Wait XO ready */
        u32TimeOut = 100000;
        while (CLOCK->CLKSTS.bit.XORDY == CLKSTS_BIT_XORDY_NOT_READY)
        {
            if ((u32TimeOut--) == 0)
            {
                return ERROR;
            }
        }
    }
    else
    {
        /* Enable RCO0 */
        CLOCK->RCO0CTL.all |= RCO0CTL_ALL_EN_ENABLE;

        /* Wait RCO0 ready */
        u32TimeOut = 10000;
        while (CLOCK->CLKSTS.bit.RCO0RDY == CLKSTS_BIT_RCO0RDY_NOT_READY)
        {
            if ((u32TimeOut--) == 0)
            {
                return ERROR;
            }
        }
    }


    /* NIN number */
    u32NIN = u32RefClkHz / 8000000;
    if ((u32RefClkHz % 8000000) != 0)
    {
        u32NIN++;
    }


    /* NOUT number (VCO range is [400 : 600] MHz) */
    if (u32PLLClk > 150000000U)
    {
        u32NOUT = 3U;      /* (150 : 200] MHz */
    }
    else if (u32PLLClk > 100000000U)
    {
        u32NOUT = 4U;      /* (100 : 150] MHz    */
    }
    else if (u32PLLClk > 75000000U)
    {
        u32NOUT = 6U;      /* (75 : 100] MHz  */
    }
    else if (u32PLLClk > 50000000U)
    {
        u32NOUT = 8U;      /* (50 : 75] MHz  */
    }
    else if (u32PLLClk > 37500000U)
    {
        u32NOUT = 12U;     /* (37.5 : 50] MHz  */
    }
    else if (u32PLLClk >= 25000000U)
    {
        u32NOUT = 16U;     /* [25 : 37.5] MHz */
    }
    else
    {
        return ERROR;
    }


    /* NFB Number */
    u64NFB = (uint64_t)u32PLLClk * u32NOUT * u32NIN * 65536;
    u32NFB = (uint32_t)(u64NFB / (uint64_t)u32RefClkHz);


    /* Set NIN, NOUT and NFB */
    CLOCK->PLLCTL1.bit.NIN  = u32NIN;
    CLOCK->PLLCTL1.bit.NOUT = (u32NOUT - 1);
    CLOCK->PLLCTL1.bit.NFB  = u32NFB;

    /* Set ICP */
    u32VCOTrim = CLOCK->PLLCTL0.bit.VCOTRIM;
    CLOCK->PLLCTL0.bit.ICP = (96 * u32NFB) / (14 - u32VCOTrim) / 65536 / 100;

    /* Select PLL reference clock */
    CLOCK->PLLCTL0.bit.RCLKSELXO = u8RefClkFromXO;

    /* Enable PLL and output */
    CLOCK->PLLCTL0.all |= (PLLCTL0_ALL_EN_ENABLE | PLLCTL0_ALL_OE_ENABLE);


    /* Wait PLL clock ready */
    u32TimeOut = 100000;
    while (CLOCK->CLKSTS.bit.PLLRDY == CLKSTS_BIT_PLLRDY_NOT_READY)
    {
        if ((u32TimeOut--) == 0)
        {
            return ERROR;
        }
    }

    return SUCCESS;
}




/******************************************************************************
 * @brief      Configure PLL clock with reference from RCO0
 *
 * @param[in]  u32PLLClk:  PLL clock output frequency (Hz)
 *
 * @return     SUCCESS or ERROR
 *
 ******************************************************************************/
ErrorStatus CLOCK_ConfigurePLLWithRCO(uint32_t u32PLLClk)
{
    uint32_t u32TimeOut;
    uint32_t u32NOUT, u32NIN, u32NFB;
    uint32_t u32VCOTrim;


    u32TimeOut = 10000;

    /* Enable RCO0 */
    CLOCK->RCO0CTL.all |= RCO0CTL_ALL_EN_ENABLE;
    while (CLOCK->CLKSTS.bit.RCO0RDY == CLKSTS_BIT_RCO0RDY_NOT_READY)
    {
        if ((u32TimeOut--) == 0)
        {
            return ERROR;
        }
    }


    /* NIN number */
    u32NIN = 4;


    /* NOUT number (VCO range is [400 : 600] MHz) */
    if (u32PLLClk > 150000000U)
    {
        u32NOUT = 3U;      /* (150 : 200] MHz */
    }
    else if (u32PLLClk > 100000000U)
    {
        u32NOUT = 4U;      /* (100 : 150] MHz    */
    }
    else if (u32PLLClk > 75000000U)
    {
        u32NOUT = 6U;      /* (75 : 100] MHz  */
    }
    else if (u32PLLClk > 50000000U)
    {
        u32NOUT = 8U;      /* (50 : 75] MHz  */
    }
    else if (u32PLLClk > 37500000U)
    {
        u32NOUT = 12U;     /* (37.5 : 50] MHz  */
    }
    else if (u32PLLClk >= 25000000U)
    {
        u32NOUT = 16U;     /* [25 : 37.5] MHz */
    }
    else
    {
        return ERROR;
    }


    /* NFB Number */
    u32NFB = u32PLLClk * u32NOUT / 25 * 128 / 625;


    /* Set NIN, NOUT and NFB */
    CLOCK->PLLCTL1.bit.NIN  = u32NIN;
    CLOCK->PLLCTL1.bit.NOUT = (u32NOUT - 1);
    CLOCK->PLLCTL1.bit.NFB  = u32NFB;

    /* Set ICP */
    u32VCOTrim = CLOCK->PLLCTL0.bit.VCOTRIM;
    CLOCK->PLLCTL0.bit.ICP = (96 * u32NFB) / (14 - u32VCOTrim) / 65536 / 100;

    /* Select RCO0 as reference clock */
    CLOCK->PLLCTL0.bit.RCLKSELXO = PLLCTL0_BIT_RCLKSELXO_RCO0;

    /* Enable PLL and output */
    CLOCK->PLLCTL0.all |= (PLLCTL0_ALL_EN_ENABLE | PLLCTL0_ALL_OE_ENABLE);


    /* Wait PLL clock ready */
    u32TimeOut = 100000;
    while (CLOCK->CLKSTS.bit.PLLRDY == CLKSTS_BIT_PLLRDY_NOT_READY)
    {
        if ((u32TimeOut--) == 0)
        {
            return ERROR;
        }
    }

    return SUCCESS;
}




/******************************************************************************
 * @brief      Enable the clock of the selected peripheral module
 *
 * @param[in]  eModule:  The peripheral module defined by CLOCK_ModuleEnum
 *
 * @return     none
 *
 * @example    CLOCK_EnableModule(ADC_MODULE);
 *
 ******************************************************************************/
void CLOCK_EnableModule(CLOCK_ModuleEnum eModule)
{
    if (eModule == SSP_MODULE)
    {
        CLOCK->SSPCLKCTL.bit.EN = 1;
    }
    else
    {
        CLOCK_MODULECTL(eModule)->bit.EN = 1;
    }
}




/******************************************************************************
 * @brief      Disable the clock of the selected peripheral module
 *
 * @param[in]  eModule:  The peripheral module defined by CLOCK_ModuleEnum
 *
 * @return     none
 *
 * @example    CLOCK_DisableModule(ADC_MODULE);
 *
 ******************************************************************************/
void CLOCK_DisableModule(CLOCK_ModuleEnum eModule)
{
    if (eModule == SSP_MODULE)
    {
        CLOCK->SSPCLKCTL.bit.EN = 0;
    }
    else
    {
        CLOCK_MODULECTL(eModule)->bit.EN = 0;
    }
}




/******************************************************************************
 * @brief      Set the clock dividing ratio of the selected peripheral module
 *
 * @param[in]  eModule:  The peripheral module defined by CLOCK_ModuleEnum
 * @param[in]  u32Div :  Clock dividingw ratio value
 *
 * @return     none
 *
 * @example    CLOCK_SetModuleDiv(PWM_MODULE, 2);
 *             CLOCK_SetModuleDiv(ADC_MODULE, 4);
 *
 ******************************************************************************/
void CLOCK_SetModuleDiv(CLOCK_ModuleEnum eModule, uint32_t u32Div)
{
    if (eModule == SSP_MODULE)
    {
        CLOCK->SSPCLKCTL.bit.DIV = (u32Div - 1);
    }
    else
    {
        CLOCK_MODULECTL(eModule)->bit.DIV = (u32Div - 1);
    }
}




/******************************************************************************
 * @brief      Get the clock dividing ratio of the selected peripheral module
 *
 * @param[in]  eModule:  The peripheral module defined by CLOCK_ModuleEnum
 *
 * @return     Clock dividing ratio value range from 1~64
 *
 ******************************************************************************/
uint32_t CLOCK_GetModuleDiv(CLOCK_ModuleEnum eModule)
{
    uint32_t u32Div;

    if (eModule == SSP_MODULE)
    {
        u32Div = CLOCK->SSPCLKCTL.bit.DIV;
    }
    else
    {
        u32Div = CLOCK_MODULECTL(eModule)->bit.DIV;
    }

    return (u32Div + 1);
}




/******************************************************************************
 * @brief      Get the clock frequency (Hz) of the selected peripheral module
 *
 * @param[in]  eModule:  The peripheral module defined by CLOCK_ModuleEnum
 *
 * @return     Clock frequency in Hz
 *
 * @note       This function can only excute correctly after CLOCK_Init()
 *
 ******************************************************************************/
uint32_t CLOCK_GetModuleClock(CLOCK_ModuleEnum eModule)
{
    uint32_t u32Div, u32Clk;

    u32Div = CLOCK_GetModuleDiv(eModule);

    if (eModule == WDT0_MODULE)
    {
        u32Clk = __RCO0_CLOCK / u32Div;
    }
    else if (eModule == WDT1_MODULE)
    {
        u32Clk = __RCO1_CLOCK / u32Div;
    }
    else
    {
        u32Clk = SystemCoreClock / u32Div;
    }

    return u32Clk;
}


/******************* (C) COPYRIGHT 2021 SPINTROL LIMITED ***** END OF FILE ****/
