diff --git a/soc/arm/atmel_sam/common/soc_pmc.h b/soc/arm/atmel_sam/common/soc_pmc.h index 8f31e24dbbe..3be251a3d09 100644 --- a/soc/arm/atmel_sam/common/soc_pmc.h +++ b/soc/arm/atmel_sam/common/soc_pmc.h @@ -1,5 +1,7 @@ /* * Copyright (c) 2016 Piotr Mienkowski + * Copyright (c) 2023 Basalte bv + * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,7 +13,11 @@ #ifndef _ATMEL_SAM_SOC_PMC_H_ #define _ATMEL_SAM_SOC_PMC_H_ +#include +#include +#include #include +#include /** * @brief Enable the clock of specified peripheral module. @@ -35,4 +41,483 @@ void soc_pmc_peripheral_disable(uint32_t id); */ uint32_t soc_pmc_peripheral_is_enabled(uint32_t id); +#if !defined(CONFIG_SOC_SERIES_SAM4L) + +enum soc_pmc_fast_rc_freq { +#if defined(CKGR_MOR_MOSCRCF_4_MHz) + SOC_PMC_FAST_RC_FREQ_4MHZ = CKGR_MOR_MOSCRCF_4_MHz, +#endif +#if defined(CKGR_MOR_MOSCRCF_8_MHz) + SOC_PMC_FAST_RC_FREQ_8MHZ = CKGR_MOR_MOSCRCF_8_MHz, +#endif +#if defined(CKGR_MOR_MOSCRCF_12_MHz) + SOC_PMC_FAST_RC_FREQ_12MHZ = CKGR_MOR_MOSCRCF_12_MHz, +#endif +}; + +enum soc_pmc_mck_src { +#if defined(PMC_MCKR_CSS_SLOW_CLK) + SOC_PMC_MCK_SRC_SLOW_CLK = PMC_MCKR_CSS_SLOW_CLK, +#endif +#if defined(PMC_MCKR_CSS_MAIN_CLK) + SOC_PMC_MCK_SRC_MAIN_CLK = PMC_MCKR_CSS_MAIN_CLK, +#endif +#if defined(PMC_MCKR_CSS_PLLA_CLK) + SOC_PMC_MCK_SRC_PLLA_CLK = PMC_MCKR_CSS_PLLA_CLK, +#endif +#if defined(PMC_MCKR_CSS_PLLB_CLK) + SOC_PMC_MCK_SRC_PLLB_CLK = PMC_MCKR_CSS_PLLB_CLK, +#endif +#if defined(PMC_MCKR_CSS_UPLL_CLK) + SOC_PMC_MCK_SRC_UPLL_CLK = PMC_MCKR_CSS_UPLL_CLK, +#endif +}; + +/** + * @brief Set the prescaler of the Master clock. + * + * @param prescaler the prescaler value. + */ +static ALWAYS_INLINE void soc_pmc_mck_set_prescaler(uint32_t prescaler) +{ + uint32_t reg_val; + + switch (prescaler) { + case 1: + reg_val = PMC_MCKR_PRES_CLK_1; + break; + case 2: + reg_val = PMC_MCKR_PRES_CLK_2; + break; + case 4: + reg_val = PMC_MCKR_PRES_CLK_4; + break; + case 8: + reg_val = PMC_MCKR_PRES_CLK_8; + break; + case 16: + reg_val = PMC_MCKR_PRES_CLK_16; + break; + case 32: + reg_val = PMC_MCKR_PRES_CLK_32; + break; + case 64: + reg_val = PMC_MCKR_PRES_CLK_64; + break; + case 3: + reg_val = PMC_MCKR_PRES_CLK_3; + break; + default: + __ASSERT(false, "Invalid MCK prescaler"); + reg_val = PMC_MCKR_PRES_CLK_1; + break; + } + + PMC->PMC_MCKR = (PMC->PMC_MCKR & (~PMC_MCKR_PRES_Msk)) | reg_val; + + while (!(PMC->PMC_SR & PMC_SR_MCKRDY)) { + } +} + +#if defined(CONFIG_SOC_SERIES_SAME70) || defined(CONFIG_SOC_SERIES_SAMV71) + +/** + * @brief Set the divider of the Master clock. + * + * @param divider the divider value. + */ +static ALWAYS_INLINE void soc_pmc_mck_set_divider(uint32_t divider) +{ + uint32_t reg_val; + + switch (divider) { + case 1: + reg_val = PMC_MCKR_MDIV_EQ_PCK; + break; + case 2: + reg_val = PMC_MCKR_MDIV_PCK_DIV2; + break; + case 3: + reg_val = PMC_MCKR_MDIV_PCK_DIV3; + break; + case 4: + reg_val = PMC_MCKR_MDIV_PCK_DIV4; + break; + default: + __ASSERT(false, "Invalid MCK divider"); + reg_val = PMC_MCKR_MDIV_EQ_PCK; + break; + } + + PMC->PMC_MCKR = (PMC->PMC_MCKR & (~PMC_MCKR_MDIV_Msk)) | reg_val; + + while (!(PMC->PMC_SR & PMC_SR_MCKRDY)) { + } +} + +#endif /* CONFIG_SOC_SERIES_SAME70 || CONFIG_SOC_SERIES_SAMV71 */ + +/** + * @brief Set the source of the Master clock. + * + * @param source the source identifier. + */ +static ALWAYS_INLINE void soc_pmc_mck_set_source(enum soc_pmc_mck_src source) +{ + PMC->PMC_MCKR = (PMC->PMC_MCKR & (~PMC_MCKR_CSS_Msk)) | (uint32_t)source; + + while (!(PMC->PMC_SR & PMC_SR_MCKRDY)) { + } +} + +/** + * @brief Switch main clock source selection to internal fast RC. + * + * @param freq the internal fast RC desired frequency 4/8/12MHz. + */ +static ALWAYS_INLINE void soc_pmc_switch_mainck_to_fastrc(enum soc_pmc_fast_rc_freq freq) +{ + /* Enable Fast RC oscillator but DO NOT switch to RC now */ + PMC->CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCRCEN; + + /* Wait for the Fast RC to stabilize */ + while (!(PMC->PMC_SR & PMC_SR_MOSCRCS)) { + } + + /* Change Fast RC oscillator frequency */ + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCF_Msk) + | CKGR_MOR_KEY_PASSWD + | (uint32_t)freq; + + /* Wait for the Fast RC to stabilize */ + while (!(PMC->PMC_SR & PMC_SR_MOSCRCS)) { + } + + /* Switch to Fast RC */ + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCSEL) + | CKGR_MOR_KEY_PASSWD; +} + +/** + * @brief Enable internal fast RC oscillator. + * + * @param freq the internal fast RC desired frequency 4/8/12MHz. + */ +static ALWAYS_INLINE void soc_pmc_osc_enable_fastrc(enum soc_pmc_fast_rc_freq freq) +{ + /* Enable Fast RC oscillator but DO NOT switch to RC */ + PMC->CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCRCEN; + + /* Wait for the Fast RC to stabilize */ + while (!(PMC->PMC_SR & PMC_SR_MOSCRCS)) { + } + + /* Change Fast RC oscillator frequency */ + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCF_Msk) + | CKGR_MOR_KEY_PASSWD + | (uint32_t)freq; + + /* Wait for the Fast RC to stabilize */ + while (!(PMC->PMC_SR & PMC_SR_MOSCRCS)) { + } +} + +/** + * @brief Disable internal fast RC oscillator. + */ +static ALWAYS_INLINE void soc_pmc_osc_disable_fastrc(void) +{ + /* Disable Fast RC oscillator */ + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCEN & ~CKGR_MOR_MOSCRCF_Msk) + | CKGR_MOR_KEY_PASSWD; +} + +/** + * @brief Check if the internal fast RC is ready. + * + * @return true if internal fast RC is ready, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_osc_is_ready_fastrc(void) +{ + return (PMC->PMC_SR & PMC_SR_MOSCRCS); +} + +/** + * @brief Enable the external crystal oscillator. + * + * @param xtal_startup_time crystal start-up time, in number of slow clocks. + */ +static ALWAYS_INLINE void soc_pmc_osc_enable_main_xtal(uint32_t xtal_startup_time) +{ + uint32_t mor = PMC->CKGR_MOR; + + mor &= ~(CKGR_MOR_MOSCXTBY | CKGR_MOR_MOSCXTEN); + mor |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN | CKGR_MOR_MOSCXTST(xtal_startup_time); + + PMC->CKGR_MOR = mor; + + /* Wait the main Xtal to stabilize */ + while (!(PMC->PMC_SR & PMC_SR_MOSCXTS)) { + } +} + +/** + * @brief Bypass the external crystal oscillator. + */ +static ALWAYS_INLINE void soc_pmc_osc_bypass_main_xtal(void) +{ + uint32_t mor = PMC->CKGR_MOR; + + mor &= ~(CKGR_MOR_MOSCXTBY | CKGR_MOR_MOSCXTEN); + mor |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTBY; + + /* Enable Crystal oscillator but DO NOT switch now. Keep MOSCSEL to 0 */ + PMC->CKGR_MOR = mor; + /* The MOSCXTS in PMC_SR is automatically set */ +} + +/** + * @brief Disable the external crystal oscillator. + */ +static ALWAYS_INLINE void soc_pmc_osc_disable_main_xtal(void) +{ + uint32_t mor = PMC->CKGR_MOR; + + mor &= ~(CKGR_MOR_MOSCXTBY | CKGR_MOR_MOSCXTEN); + + PMC->CKGR_MOR = CKGR_MOR_KEY_PASSWD | mor; +} + +/** + * @brief Check if the external crystal oscillator is bypassed. + * + * @return true if external crystal oscillator is bypassed, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_osc_is_bypassed_main_xtal(void) +{ + return (PMC->CKGR_MOR & CKGR_MOR_MOSCXTBY); +} + +/** + * @brief Check if the external crystal oscillator is ready. + * + * @return true if external crystal oscillator is ready, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_osc_is_ready_main_xtal(void) +{ + return (PMC->PMC_SR & PMC_SR_MOSCXTS); +} + +/** + * @brief Switch main clock source selection to external crystal oscillator. + * + * @param bypass select bypass or xtal + * @param xtal_startup_time crystal start-up time, in number of slow clocks + */ +static ALWAYS_INLINE void soc_pmc_switch_mainck_to_xtal(bool bypass, uint32_t xtal_startup_time) +{ + soc_pmc_osc_enable_main_xtal(xtal_startup_time); + + /* Enable Main Xtal oscillator */ + if (bypass) { + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTEN) + | CKGR_MOR_KEY_PASSWD + | CKGR_MOR_MOSCXTBY + | CKGR_MOR_MOSCSEL; + } else { + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTBY) + | CKGR_MOR_KEY_PASSWD + | CKGR_MOR_MOSCXTEN + | CKGR_MOR_MOSCXTST(xtal_startup_time); + + /* Wait for the Xtal to stabilize */ + while (!(PMC->PMC_SR & PMC_SR_MOSCXTS)) { + } + + PMC->CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL; + } +} + +/** + * @brief Disable the external crystal oscillator. + * + * @param bypass select bypass or xtal + */ +static ALWAYS_INLINE void soc_pmc_osc_disable_xtal(bool bypass) +{ + /* Disable xtal oscillator */ + if (bypass) { + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTBY) + | CKGR_MOR_KEY_PASSWD; + } else { + PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTEN) + | CKGR_MOR_KEY_PASSWD; + } +} + +/** + * @brief Check if the main clock is ready. Depending on MOSCEL, main clock can be one + * of external crystal, bypass or internal RC. + * + * @return true if main clock is ready, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_osc_is_ready_mainck(void) +{ + return PMC->PMC_SR & PMC_SR_MOSCSELS; +} + +/** + * @brief Enable Wait Mode. + */ +static ALWAYS_INLINE void soc_pmc_enable_waitmode(void) +{ + PMC->PMC_FSMR |= PMC_FSMR_LPM; +} + +/** + * @brief Enable Clock Failure Detector. + */ +static ALWAYS_INLINE void soc_pmc_enable_clock_failure_detector(void) +{ + uint32_t mor = PMC->CKGR_MOR; + + PMC->CKGR_MOR = CKGR_MOR_KEY_PASSWD | CKGR_MOR_CFDEN | mor; +} + +/** + * @brief Disable Clock Failure Detector. + */ +static ALWAYS_INLINE void soc_pmc_disable_clock_failure_detector(void) +{ + uint32_t mor = PMC->CKGR_MOR & (~CKGR_MOR_CFDEN); + + PMC->CKGR_MOR = CKGR_MOR_KEY_PASSWD | mor; +} + +#if defined(PMC_MCKR_CSS_PLLA_CLK) + +/** + * @brief Disable the PLLA clock. + */ +static ALWAYS_INLINE void soc_pmc_disable_pllack(void) +{ + PMC->CKGR_PLLAR = CKGR_PLLAR_ONE | CKGR_PLLAR_MULA(0); +} + +/** + * @brief Enable the PLLA clock. + * + * @param mula PLLA multiplier + * @param pllacount PLLA lock counter, in number of slow clocks + * @param diva PLLA Divider + */ +static ALWAYS_INLINE void soc_pmc_enable_pllack(uint32_t mula, uint32_t pllacount, uint32_t diva) +{ + __ASSERT(diva > 0, "Invalid PLLA divider"); + + /* first disable the PLL to unlock the lock */ + soc_pmc_disable_pllack(); + + PMC->CKGR_PLLAR = CKGR_PLLAR_ONE + | CKGR_PLLAR_DIVA(diva) + | CKGR_PLLAR_PLLACOUNT(pllacount) + | CKGR_PLLAR_MULA(mula); + + while ((PMC->PMC_SR & PMC_SR_LOCKA) == 0) { + } +} + +/** + * @brief Check if the PLLA is locked. + * + * @return true if PLLA is locked, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_is_locked_pllack(void) +{ + return (PMC->PMC_SR & PMC_SR_LOCKA); +} + +#endif /* PMC_MCKR_CSS_PLLA_CLK */ + +#if defined(PMC_MCKR_CSS_PLLB_CLK) + +/** + * @brief Disable the PLLB clock. + */ +static ALWAYS_INLINE void soc_pmc_disable_pllbck(void) +{ + PMC->CKGR_PLLBR = CKGR_PLLBR_MULB(0); +} + +/** + * @brief Enable the PLLB clock. + * + * @param mulb PLLB multiplier + * @param pllbcount PLLB lock counter, in number of slow clocks + * @param divb PLLB Divider + */ +static ALWAYS_INLINE void soc_pmc_enable_pllbck(uint32_t mulb, uint32_t pllbcount, uint32_t divb) +{ + __ASSERT(divb > 0, "Invalid PLLB divider"); + + /* first disable the PLL to unlock the lock */ + soc_pmc_disable_pllbck(); + + PMC->CKGR_PLLBR = CKGR_PLLBR_DIVB(divb) + | CKGR_PLLBR_PLLBCOUNT(pllbcount) + | CKGR_PLLBR_MULB(mulb); + + while ((PMC->PMC_SR & PMC_SR_LOCKB) == 0) { + } +} + +/** + * @brief Check if the PLLB is locked. + * + * @return true if PLLB is locked, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_is_locked_pllbck(void) +{ + return (PMC->PMC_SR & PMC_SR_LOCKB); +} + +#endif /* PMC_MCKR_CSS_PLLB_CLK */ + +#if defined(PMC_MCKR_CSS_UPLL_CLK) + +/** + * @brief Enable the UPLL clock. + */ +static ALWAYS_INLINE void soc_pmc_enable_upllck(uint32_t upllcount) +{ + PMC->CKGR_UCKR = CKGR_UCKR_UPLLCOUNT(upllcount) + | CKGR_UCKR_UPLLEN; + + /* Wait UTMI PLL Lock Status */ + while (!(PMC->PMC_SR & PMC_SR_LOCKU)) { + } +} + +/** + * @brief Disable the UPLL clock. + */ +static ALWAYS_INLINE void soc_pmc_disable_upllck(void) +{ + PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN; +} + +/** + * @brief Check if the UPLL is locked. + * + * @return true if UPLL is locked, false otherwise + */ +static ALWAYS_INLINE bool soc_pmc_is_locked_upllck(void) +{ + return (PMC->PMC_SR & PMC_SR_LOCKU); +} + +#endif /* PMC_MCKR_CSS_UPLL_CLK */ + +#endif /* !CONFIG_SOC_SERIES_SAM4L */ + #endif /* _ATMEL_SAM_SOC_PMC_H_ */