diff --git a/components/esp_hal_clock/esp32s31/include/hal/clk_gate_ll.h b/components/esp_hal_clock/esp32s31/include/hal/clk_gate_ll.h index 42ac40569b7..1352205bab1 100644 --- a/components/esp_hal_clock/esp32s31/include/hal/clk_gate_ll.h +++ b/components/esp_hal_clock/esp32s31/include/hal/clk_gate_ll.h @@ -14,6 +14,7 @@ #include "soc/hp_sys_clkrst_struct.h" #include "soc/lp_clkrst_struct.h" #include "soc/lp_clkrst_reg.h" +#include "soc/hp_sys_clkrst_struct.h" #ifdef __cplusplus extern "C" { @@ -34,6 +35,96 @@ FORCE_INLINE_ATTR void _clk_gate_ll_rtc_fast_to_lp_periph_en(bool enable) _clk_gate_ll_rtc_fast_to_lp_periph_en(__VA_ARGS__); \ } while(0) +/** + * Enable or disable the clock gate for ref_20m (derived from BBPLL). + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_ref_20m_clk_en(bool enable) +{ + HP_SYS_CLKRST.ref_20m_ctrl0.reg_ref_20m_clk_en = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_ref_20m_clk_en(...) do { \ + (void)__DECLARE_RCC_ATOMIC_ENV; \ + _clk_gate_ll_ref_20m_clk_en(__VA_ARGS__); \ + } while(0) + +/** + * Enable or disable the clock gate for ref_25m (derived from MPLL). + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_ref_25m_clk_en(bool enable) +{ + HP_SYS_CLKRST.ref_25m_ctrl0.reg_ref_25m_clk_en = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_ref_25m_clk_en(...) do { \ + (void)__DECLARE_RCC_ATOMIC_ENV; \ + _clk_gate_ll_ref_25m_clk_en(__VA_ARGS__); \ + } while(0) + +/** + * Enable or disable the clock gate for ref_50m (derived from MPLL). + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_ref_50m_clk_en(bool enable) +{ + HP_SYS_CLKRST.ref_50m_ctrl0.reg_ref_50m_clk_en = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_ref_50m_clk_en(...) do { \ + (void)__DECLARE_RCC_ATOMIC_ENV; \ + _clk_gate_ll_ref_50m_clk_en(__VA_ARGS__); \ + } while(0) + +/** + * Enable or disable the clock gate for ref_80m (derived from BBPLL). + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_ref_80m_clk_en(bool enable) +{ + HP_SYS_CLKRST.ref_80m_ctrl0.reg_ref_80m_clk_en = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_ref_80m_clk_en(...) do { \ + (void)__DECLARE_RCC_ATOMIC_ENV; \ + _clk_gate_ll_ref_80m_clk_en(__VA_ARGS__); \ + } while(0) + +/** + * Enable or disable the clock gate for ref_160m (derived from BBPLL). + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_ref_160m_clk_en(bool enable) +{ + HP_SYS_CLKRST.ref_160m_ctrl0.reg_ref_160m_clk_en = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_ref_160m_clk_en(...) do { \ + (void)__DECLARE_RCC_ATOMIC_ENV; \ + _clk_gate_ll_ref_160m_clk_en(__VA_ARGS__); \ + } while(0) + +/** + * Enable or disable the clock gate for ref_240m (derived from BBPLL). + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_ref_240m_clk_en(bool enable) +{ + HP_SYS_CLKRST.ref_240m_ctrl0.reg_ref_240m_clk_en = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_ref_240m_clk_en(...) do { \ + (void)__DECLARE_RCC_ATOMIC_ENV; \ + _clk_gate_ll_ref_240m_clk_en(__VA_ARGS__); \ + } while(0) + /** * @brief Set the default clock gate configuration */ diff --git a/components/esp_hal_clock/esp32s31/include/hal/clk_tree_ll.h b/components/esp_hal_clock/esp32s31/include/hal/clk_tree_ll.h index 4abe91dba49..d40c5fb1d6b 100644 --- a/components/esp_hal_clock/esp32s31/include/hal/clk_tree_ll.h +++ b/components/esp_hal_clock/esp32s31/include/hal/clk_tree_ll.h @@ -701,13 +701,16 @@ static inline __attribute__((always_inline)) uint32_t clk_ll_apb_get_divider(voi return HAL_FORCE_READ_U32_REG_FIELD(HP_SYS_CLKRST.apb_freq_ctrl0, reg_apb_clk_div_num) + 1; } -// TODO: IDF-14730 -// static inline __attribute__((always_inline)) void clk_ll_ref_500m_set_src(soc_ref_500m_clk_src_t in_sel) -// { -// // 0: cpll (320MHz) -// // 1: mpll (500MHz) -// HP_SYS_CLKRST.ref_500m_ctrl0.reg_ref_500m_sel = in_sel; -// } +/** + * @brief Select REF_500M_CLK source + * + * @param in_sel 0 selects CPLL (320 MHz), 1 selects MPLL (500 MHz). + */ +static inline __attribute__((always_inline)) void clk_ll_ref_500m_set_src(uint8_t in_sel) +{ + HAL_ASSERT(in_sel == 0 || in_sel == 1); + HP_SYS_CLKRST.ref_500m_ctrl0.reg_ref_500m_sel = in_sel; +} /** * @brief Set PLL_F50M_CLK divider. freq of PLL_F50M_CLK = freq of MPLL_CLK / divider diff --git a/components/esp_hw_support/include/esp_private/esp_clk_tree_common.h b/components/esp_hw_support/include/esp_private/esp_clk_tree_common.h index 82ac6ec93e8..c7b2a724341 100644 --- a/components/esp_hw_support/include/esp_private/esp_clk_tree_common.h +++ b/components/esp_hw_support/include/esp_private/esp_clk_tree_common.h @@ -15,6 +15,11 @@ extern "C" { #endif +typedef struct { + soc_module_clk_t clk_upstream; + uint32_t divider; +} esp_clk_tree_src_config_t; + #if SOC_CLK_RC_FAST_D256_SUPPORTED /** * @brief Get frequency of RC_FAST_D256_CLK @@ -155,6 +160,45 @@ void esp_clk_tree_apll_release(void); esp_err_t esp_clk_tree_apll_freq_set(uint32_t expt_freq_hz, uint32_t *real_freq_hz); #endif // SOC_CLK_APLL_SUPPORTED +/** + * @brief Select the upstream PLL that feeds a derived clock + * + * Programs the upstream-mux of `clk_src` (e.g. `SOC_MOD_CLK_PLL_F50M`) to + * source from `upstream` (e.g. `SOC_MOD_CLK_MPLL`, `SOC_MOD_CLK_CPLL`). The + * clock must already be acquired via `esp_clk_tree_enable_src(clk_src, true)`. + * + * Use `esp_clk_tree_src_set_freq_hz()` afterwards to choose the divider for + * the desired output frequency. If `select_upstream` is not called before + * `set_freq_hz`, the engine auto-picks the first upstream in the descriptor + * whose current frequency divides cleanly to the requested target. + * + * On targets where the derived clock has no upstream mux (e.g. ESP32-P4 + * `PLL_F50M` is fixed to MPLL), this function still validates that the + * requested upstream matches the fixed source and updates internal state, + * but does not touch any hardware register. + * + * Has effect only on "derived" clocks (those with a descriptor registered + * via `esp_clk_tree_get_derived_clk_desc()`). For other clocks returns + * `ESP_ERR_NOT_SUPPORTED`. + * + * Share-lock semantics: when more than one peripheral has acquired + * `clk_src`, only a request that matches the upstream already programmed by + * the first peer succeeds. Any other request returns + * `ESP_ERR_INVALID_STATE`. + * + * @param[in] clk_src Derived module clock id (e.g. `SOC_MOD_CLK_PLL_F50M`) + * @param[in] upstream Upstream PLL to route from (e.g. `SOC_MOD_CLK_MPLL`) + * @return + * - ESP_OK Upstream selected + * - ESP_ERR_INVALID_ARG `upstream` is not in this clock's allowed list + * - ESP_ERR_INVALID_STATE Clock is shared and another peripheral has + * already selected a different upstream + * - ESP_ERR_NOT_SUPPORTED `clk_src` has no derived-clock descriptor on + * this target + */ +esp_err_t esp_clk_tree_src_select_upstream(soc_module_clk_t clk_src, + soc_module_clk_t upstream); + #if SOC_CLK_MPLL_SUPPORTED /** * @brief Enable MPLL power if it has not enabled diff --git a/components/esp_hw_support/include/esp_private/esp_clk_tree_derived.h b/components/esp_hw_support/include/esp_private/esp_clk_tree_derived.h new file mode 100644 index 00000000000..b95db4df21f --- /dev/null +++ b/components/esp_hw_support/include/esp_private/esp_clk_tree_derived.h @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "soc/clk_tree_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Mutable runtime state for a derived PLL clock + * + * Owned and statically allocated by the per-target file that registers the + * derived clock; the engine in `esp_clk_tree_common.c` only reads/writes + * through `esp_clk_tree_derived_clk_desc_t::state`. + */ +typedef struct { + int ref_cnt; /*!< Number of acquirers; 0 means gated off. */ + soc_module_clk_t cur_upstream; /*!< Currently programmed upstream clk (`SOC_MOD_CLK_INVALID` until first config). */ + uint32_t cur_divider; /*!< Currently programmed divider (0 until first config). */ +} esp_clk_tree_derived_clk_state_t; + +/** + * @brief Single candidate upstream PLL for a derived clock + * + * Lists one allowed upstream and the mux selector value the engine must pass + * to `set_src` to route from that upstream. For derived clocks with no + * upstream mux (e.g. ESP32-P4 PLL_F50M is fixed to MPLL), `mux_sel` is + * unused and may be 0. + */ +typedef struct { + soc_module_clk_t upstream; /*!< Allowed upstream module clock (CPLL, MPLL, ...) */ + uint8_t mux_sel; /*!< Mux selector value passed to `set_src` to route from `upstream` */ +} esp_clk_tree_derived_upstream_t; + +/** + * @brief Descriptor for a derived PLL clock (e.g. PLL_F50M, PLL_F25M, PLL_F20M) + * + * Each target that wants a derived clock to participate in the shared + * acquire/release/freq_set engine in `esp_clk_tree_common.c` provides a + * descriptor and overrides the weak `esp_clk_tree_get_derived_clk_desc()`. + * + * Upstream selection is pure data: the engine iterates `upstreams[]` in + * order, querying each candidate's currently programmed frequency, and + * picks the first one that divides cleanly to the requested target + * frequency. Iteration order encodes per-target preference. + * + * @warning `set_src` / `set_divider` / `set_gate` are invoked from a critical section + * and must be short and must not call functions that take other locks or block. + * It means you must use `RCC_ATOMIC` lock free form of HAL functions (e.g.`_clk_gate_ll_*`)! + */ +typedef struct { + soc_module_clk_t clk_id; /*!< Module clock id this descriptor belongs to */ + void (*set_src)(uint8_t mux_sel); /*!< Optional: program the upstream PLL mux. NULL when the clock has no mux (e.g. ESP32-P4 PLL_F50M is fixed to MPLL). */ + void (*set_divider)(uint32_t divider); /*!< Required: program the divider register (divider value, not the raw "div_num - 1" form). */ + void (*set_gate)(bool enable); /*!< Required: enable/disable the clock gate. */ + const esp_clk_tree_derived_upstream_t *upstreams; /*!< Required: candidate upstream PLLs in preference order (used by both auto-pick and explicit-upstream paths). */ + size_t upstream_count; /*!< Required: number of entries in `upstreams[]`. */ + esp_clk_tree_derived_clk_state_t *state; /*!< Required: pointer to a statically-allocated mutable state slot owned by the target. Engine reads/writes ref_cnt / cur_upstream / cur_divider through this pointer. */ +} esp_clk_tree_derived_clk_desc_t; + +/** + * @brief Look up the derived-clock descriptor for a given module clock id + * + * Default implementation in `esp_clk_tree_common.c` returns NULL for every + * clock. Each target that supports derived-clock locking overrides this + * function in its `port//esp_clk_tree.c`. + * + * @param clk_src Module clock id + * @return Pointer to a static descriptor, or NULL if the clock is not a + * lockable derived clock on this target. + */ +const esp_clk_tree_derived_clk_desc_t *esp_clk_tree_get_derived_clk_desc(soc_module_clk_t clk_src); + +/** + * @brief Acquire a derived clock (refcount-based) + * + * Increments the descriptor's reference counter and enables the gate on the + * first acquire. Public callers should use `esp_clk_tree_enable_src(clk, true)` + * which dispatches here for any clock with a registered descriptor. + */ +esp_err_t esp_clk_tree_derived_clk_acquire(soc_module_clk_t clk_src); + +/** + * @brief Release a derived clock previously acquired + * + * Decrements the descriptor's reference counter and disables the gate on the + * last release. Public callers should use `esp_clk_tree_enable_src(clk, false)`. + */ +esp_err_t esp_clk_tree_derived_clk_release(soc_module_clk_t clk_src); + +/** + * @brief Configure the divider of a derived clock for a target frequency + * + * Uses the upstream previously chosen via `esp_clk_tree_src_select_upstream()` + * (`state->cur_upstream`) when set; otherwise iterates the descriptor's + * `upstreams[]` and auto-picks the first whose currently programmed + * frequency divides cleanly to `expt_freq_hz`. Programs both the upstream + * mux (if not already programmed) and the divider atomically. Public + * callers should use `esp_clk_tree_src_set_freq_hz()` which dispatches here. + * + * Share-lock semantics: when ref_cnt > 1 the request must match the + * currently applied (upstream, divider) pair, otherwise returns + * `ESP_ERR_INVALID_STATE` and reports the active frequency via + * `real_freq_hz`. + */ +esp_err_t esp_clk_tree_derived_clk_freq_set(soc_module_clk_t clk_src, + uint32_t expt_freq_hz, + uint32_t *real_freq_hz); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/port/esp32p4/esp_clk_tree.c b/components/esp_hw_support/port/esp32p4/esp_clk_tree.c index 8ccee3dc962..8a4016cbd0d 100644 --- a/components/esp_hw_support/port/esp32p4/esp_clk_tree.c +++ b/components/esp_hw_support/port/esp32p4/esp_clk_tree.c @@ -19,10 +19,43 @@ #include "hal/clk_tree_hal.h" #include "hal/clk_tree_ll.h" #include "esp_private/esp_clk_tree_common.h" +#include "esp_private/esp_clk_tree_derived.h" #include "esp_private/periph_ctrl.h" ESP_LOG_ATTR_TAG(TAG, "esp_clk_tree"); +// ESP32-P4 PLL_F50M is fixed-source (MPLL only, no upstream mux); set_src is +// NULL and the mux_sel value is ignored by the engine. +static const esp_clk_tree_derived_upstream_t s_pll_f50m_upstreams[] = { + { SOC_MOD_CLK_MPLL, 0 }, +}; + +static esp_clk_tree_derived_clk_state_t s_pll_f50m_state = { + .ref_cnt = 0, + .cur_upstream = SOC_MOD_CLK_INVALID, + .cur_divider = 0, +}; + +static const esp_clk_tree_derived_clk_desc_t s_pll_f50m_desc = { + .clk_id = SOC_MOD_CLK_PLL_F50M, + .set_src = NULL, + .set_divider = clk_ll_pll_f50m_set_divider, + .set_gate = _clk_gate_ll_ref_50m_clk_en, // using RCC_ATOMIC lock free function version to avoid nesting critical sections + .upstreams = s_pll_f50m_upstreams, + .upstream_count = sizeof(s_pll_f50m_upstreams) / sizeof(s_pll_f50m_upstreams[0]), + .state = &s_pll_f50m_state, +}; + +const esp_clk_tree_derived_clk_desc_t *esp_clk_tree_get_derived_clk_desc(soc_module_clk_t clk_src) +{ + switch (clk_src) { + case SOC_MOD_CLK_PLL_F50M: + return &s_pll_f50m_desc; + default: + return NULL; + } +} + esp_err_t esp_clk_tree_src_get_freq_hz(soc_module_clk_t clk_src, esp_clk_tree_src_freq_precision_t precision, uint32_t *freq_value) { @@ -109,6 +142,12 @@ esp_err_t esp_clk_tree_src_set_freq_hz(soc_module_clk_t clk_src, uint32_t expt_f ESP_RETURN_ON_FALSE(clk_src > 0 && clk_src < SOC_MOD_CLK_INVALID, ESP_ERR_INVALID_ARG, TAG, "unknown clk src"); ESP_RETURN_ON_FALSE(expt_freq_value > 0, ESP_ERR_INVALID_ARG, TAG, "invalid frequency"); + // Derived PLL clocks (PLL_F50M, ...) are configured by the shared engine, + // which picks an upstream + divider yielding `expt_freq_value`. + if (esp_clk_tree_get_derived_clk_desc(clk_src) != NULL) { + return esp_clk_tree_derived_clk_freq_set(clk_src, expt_freq_value, ret_freq_value); + } + uint32_t real_freq_value = 0; esp_err_t ret = ESP_OK; switch (clk_src) { @@ -178,6 +217,14 @@ esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable) return ESP_OK; } + // Derived PLL clocks (PLL_F50M, ...) that participate in the shared + // refcount/lock engine route through that engine instead of the + // global s_pll_src_cg_ref_cnt array below. + if (esp_clk_tree_get_derived_clk_desc(clk_src) != NULL) { + return enable ? esp_clk_tree_derived_clk_acquire(clk_src) + : esp_clk_tree_derived_clk_release(clk_src); + } + // these clock sources have their own reference counting switch (clk_src) { case SOC_MOD_CLK_APLL: @@ -215,7 +262,6 @@ esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable) case SOC_MOD_CLK_RC_FAST: enable ? rtc_dig_clk8m_enable() : rtc_dig_clk8m_disable(); break; case SOC_MOD_CLK_PLL_F20M: ENABLE_CLK_GATE(clk_gate_ll_ref_20m_clk_en, enable); break; case SOC_MOD_CLK_PLL_F25M: ENABLE_CLK_GATE(clk_gate_ll_ref_25m_clk_en, enable); break; - case SOC_MOD_CLK_PLL_F50M: ENABLE_CLK_GATE(clk_gate_ll_ref_50m_clk_en, enable); break; case SOC_MOD_CLK_PLL_F80M: ENABLE_CLK_GATE(clk_gate_ll_ref_80m_clk_en, enable); break; case SOC_MOD_CLK_PLL_F120M: ENABLE_CLK_GATE(clk_gate_ll_ref_120m_clk_en, enable); break; case SOC_MOD_CLK_PLL_F160M: ENABLE_CLK_GATE(clk_gate_ll_ref_160m_clk_en, enable); break; diff --git a/components/esp_hw_support/port/esp32s31/esp_clk_tree.c b/components/esp_hw_support/port/esp32s31/esp_clk_tree.c index 110bed8f8fc..d58f35cf219 100644 --- a/components/esp_hw_support/port/esp32s31/esp_clk_tree.c +++ b/components/esp_hw_support/port/esp32s31/esp_clk_tree.c @@ -6,19 +6,57 @@ #include #include +#include "sdkconfig.h" #include "esp_clk_tree.h" #include "esp_err.h" #include "esp_check.h" +#include "esp_log.h" +#include "soc/clk_tree_defs.h" #include "soc/rtc.h" +#include "soc/reset_reasons.h" +#include "soc/soc_caps.h" +#include "hal/clk_gate_ll.h" #include "hal/clk_tree_hal.h" #include "hal/clk_tree_ll.h" #include "esp_private/esp_clk_tree_common.h" +#include "esp_private/esp_clk_tree_derived.h" #include "esp_private/periph_ctrl.h" ESP_LOG_ATTR_TAG(TAG, "esp_clk_tree"); -static _Atomic int16_t s_pll_src_cg_ref_cnt[SOC_MOD_CLK_INVALID] = { 0 }; -static bool s_clk_tree_initialized = false; +// Allowed upstreams for PLL_F50M, in preference order (used by both auto-pick +// and explicit-upstream paths). Mux selectors match the HP_SYS_CLKRST +// `reg_ref_500m_sel` field: 0 = CPLL, 1 = MPLL. +static const esp_clk_tree_derived_upstream_t s_pll_f50m_upstreams[] = { + { SOC_MOD_CLK_CPLL, 0 }, + { SOC_MOD_CLK_MPLL, 1 }, +}; + +static esp_clk_tree_derived_clk_state_t s_pll_f50m_state = { + .ref_cnt = 0, + .cur_upstream = SOC_MOD_CLK_INVALID, + .cur_divider = 0, +}; + +static const esp_clk_tree_derived_clk_desc_t s_pll_f50m_desc = { + .clk_id = SOC_MOD_CLK_PLL_F50M, + .set_src = clk_ll_ref_500m_set_src, + .set_divider = clk_ll_pll_f50m_set_divider, + .set_gate = _clk_gate_ll_ref_50m_clk_en, // using RCC_ATOMIC lock free function version to avoid nesting critical sections + .upstreams = s_pll_f50m_upstreams, + .upstream_count = sizeof(s_pll_f50m_upstreams) / sizeof(s_pll_f50m_upstreams[0]), + .state = &s_pll_f50m_state, +}; + +const esp_clk_tree_derived_clk_desc_t *esp_clk_tree_get_derived_clk_desc(soc_module_clk_t clk_src) +{ + switch (clk_src) { + case SOC_MOD_CLK_PLL_F50M: + return &s_pll_f50m_desc; + default: + return NULL; + } +} esp_err_t esp_clk_tree_src_get_freq_hz(soc_module_clk_t clk_src, esp_clk_tree_src_freq_precision_t precision, uint32_t *freq_value) @@ -35,6 +73,9 @@ esp_err_t esp_clk_tree_src_get_freq_hz(soc_module_clk_t clk_src, esp_clk_tree_sr case SOC_MOD_CLK_XTAL: clk_src_freq = SOC_XTAL_FREQ_40M * MHZ; break; + case SOC_MOD_CLK_SYS: + clk_src_freq = clk_hal_sys_get_freq_hz(); + break; case SOC_MOD_CLK_PLL_F20M: clk_src_freq = CLK_LL_PLL_480M_FREQ_MHZ / clk_ll_pll_f20m_get_divider() * MHZ; break; @@ -86,7 +127,6 @@ esp_err_t esp_clk_tree_src_get_freq_hz(soc_module_clk_t clk_src, esp_clk_tree_sr ESP_RETURN_ON_FALSE(clk_src_freq, ESP_FAIL, TAG, "freq shouldn't be 0, calibration failed"); *freq_value = clk_src_freq; - return ESP_OK; } @@ -95,12 +135,21 @@ esp_err_t esp_clk_tree_src_set_freq_hz(soc_module_clk_t clk_src, uint32_t expt_f ESP_RETURN_ON_FALSE(clk_src > 0 && clk_src < SOC_MOD_CLK_INVALID, ESP_ERR_INVALID_ARG, TAG, "unknown clk src"); ESP_RETURN_ON_FALSE(expt_freq_value > 0, ESP_ERR_INVALID_ARG, TAG, "invalid frequency"); + // Derived PLL clocks (PLL_F50M, ...) are configured by the shared engine, + // which picks an upstream + divider yielding `expt_freq_value`. + if (esp_clk_tree_get_derived_clk_desc(clk_src) != NULL) { + return esp_clk_tree_derived_clk_freq_set(clk_src, expt_freq_value, ret_freq_value); + } + uint32_t real_freq_value = 0; esp_err_t ret = ESP_OK; switch (clk_src) { case SOC_MOD_CLK_APLL: ret = esp_clk_tree_apll_freq_set(expt_freq_value, &real_freq_value); break; + case SOC_MOD_CLK_MPLL: + ret = esp_clk_tree_mpll_freq_set(expt_freq_value, &real_freq_value); + break; default: return ESP_ERR_NOT_SUPPORTED; } @@ -110,21 +159,39 @@ esp_err_t esp_clk_tree_src_set_freq_hz(soc_module_clk_t clk_src, uint32_t expt_f return ret; } +static _Atomic int16_t s_pll_src_cg_ref_cnt[SOC_MOD_CLK_INVALID] = { 0 }; +static bool s_clk_tree_initialized = false; static int16_t s_cpll_ref_cnt = 0; void esp_clk_tree_initialize(void) { + soc_reset_reason_t rst_reason = esp_rom_get_reset_reason(0); + if ((rst_reason == RESET_REASON_CPU_SW) || (rst_reason == RESET_REASON_CPU_MWDT) \ + || (rst_reason == RESET_REASON_CPU_RWDT) || (rst_reason == RESET_REASON_CPU_JTAG) \ + || (rst_reason == RESET_REASON_CPU_LOCKUP)) { + s_clk_tree_initialized = true; + return; + } + // Power soc_cpu_clk_src_t cpu_clk_src_btld = clk_ll_cpu_get_src(); if (cpu_clk_src_btld == SOC_CPU_CLK_SRC_CPLL) { s_cpll_ref_cnt++; } else if (cpu_clk_src_btld == SOC_CPU_CLK_SRC_PLL_F240M) { // TODO: IDF-15502 - // pll_f240m clock gating ref count ++ + s_pll_src_cg_ref_cnt[SOC_MOD_CLK_PLL_F240M] = 1; } - // Gating // flash clock source is set to BBPLL in bootloader + s_pll_src_cg_ref_cnt[SOC_MOD_CLK_BBPLL] = 1; + + // Gating: disable all PLL-derived reference clocks; they will be re-enabled on demand via esp_clk_tree_enable_src + _clk_gate_ll_ref_20m_clk_en(false); + _clk_gate_ll_ref_25m_clk_en(false); + _clk_gate_ll_ref_50m_clk_en(false); + _clk_gate_ll_ref_80m_clk_en(false); + _clk_gate_ll_ref_160m_clk_en(false); + if (s_pll_src_cg_ref_cnt[SOC_MOD_CLK_PLL_F240M] == 0) _clk_gate_ll_ref_240m_clk_en(false); s_clk_tree_initialized = true; } @@ -156,6 +223,11 @@ bool esp_clk_tree_enable_power(soc_root_clk_circuit_t clk_circuit, bool enable) return toggled; } +#define ENABLE_CLK_GATE(clk_src_en_func, enable) \ + PERIPH_RCC_ATOMIC() { \ + clk_src_en_func(enable); \ + } + esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable) { if (clk_src < 1 || clk_src >= SOC_MOD_CLK_INVALID) { @@ -167,28 +239,37 @@ esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable) return ESP_OK; } - // These clock sources have their own reference counting - switch (clk_src) { - case SOC_MOD_CLK_APLL: - if (enable) { - esp_clk_tree_apll_acquire(); - } else { - esp_clk_tree_apll_release(); - } - return ESP_OK; - case SOC_MOD_CLK_MPLL: - if (enable) { - return esp_clk_tree_mpll_acquire(); - } else { - esp_clk_tree_mpll_release(); - } - return ESP_OK; - default: - break; + int16_t prev_ref_cnt = 0; + + // Derived PLL clocks (PLL_F50M, ...) that participate in the shared + // refcount/lock engine route through that engine instead of the + // global s_pll_src_cg_ref_cnt array below. + if (esp_clk_tree_get_derived_clk_desc(clk_src) != NULL) { + return enable ? esp_clk_tree_derived_clk_acquire(clk_src) + : esp_clk_tree_derived_clk_release(clk_src); } - // Other clock sources use the global reference counting - int16_t prev_ref_cnt = 0; + // these clock sources have their own reference counting + switch (clk_src) { + case SOC_MOD_CLK_APLL: + if (enable) { + esp_clk_tree_apll_acquire(); + } else { + esp_clk_tree_apll_release(); + } + return ESP_OK; + case SOC_MOD_CLK_MPLL: + if (enable) { + return esp_clk_tree_mpll_acquire(); + } else { + esp_clk_tree_mpll_release(); + } + return ESP_OK; + default: + break; + } + + // other clock sources use the global reference counting if (enable) { prev_ref_cnt = atomic_fetch_add(&s_pll_src_cg_ref_cnt[clk_src], 1); } else { @@ -199,8 +280,17 @@ esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable) return ESP_OK; } } - - // TODO: IDF-15502 + if ((prev_ref_cnt == 0 && enable) || (prev_ref_cnt == 1 && !enable)) { + switch (clk_src) { + case SOC_MOD_CLK_RC_FAST: enable ? rtc_dig_clk8m_enable() : rtc_dig_clk8m_disable(); break; + case SOC_MOD_CLK_PLL_F20M: ENABLE_CLK_GATE(clk_gate_ll_ref_20m_clk_en, enable); break; + case SOC_MOD_CLK_PLL_F25M: ENABLE_CLK_GATE(clk_gate_ll_ref_25m_clk_en, enable); break; + case SOC_MOD_CLK_PLL_F80M: ENABLE_CLK_GATE(clk_gate_ll_ref_80m_clk_en, enable); break; + case SOC_MOD_CLK_PLL_F160M: ENABLE_CLK_GATE(clk_gate_ll_ref_160m_clk_en, enable); break; + case SOC_MOD_CLK_PLL_F240M: ENABLE_CLK_GATE(clk_gate_ll_ref_240m_clk_en, enable); break; + default: break; + } + } return ESP_OK; } diff --git a/components/esp_hw_support/port/esp_clk_tree_common.c b/components/esp_hw_support/port/esp_clk_tree_common.c index a19f0bd8a1b..6663316c56e 100644 --- a/components/esp_hw_support/port/esp_clk_tree_common.c +++ b/components/esp_hw_support/port/esp_clk_tree_common.c @@ -8,7 +8,9 @@ #include #include #include "esp_private/esp_clk_tree_common.h" +#include "esp_private/esp_clk_tree_derived.h" #include "esp_private/critical_section.h" +#include "esp_private/periph_ctrl.h" #include "hal/clk_tree_hal.h" #include "hal/clk_tree_ll.h" #include "soc/rtc.h" @@ -390,3 +392,240 @@ end: return ret; } #endif /* SOC_CLK_MPLL_SUPPORTED */ + +/* --------------------------------------------------------------------------- + * Generic derived-PLL clock engine. Target-agnostic: per-clock descriptor and + * state come from `esp_clk_tree_get_derived_clk_desc()` (overridden per target + * in port//esp_clk_tree.c). The engine never references any specific + * clock id or chip cap; adding F25M / F20M / F80M / ... is purely a target-side + * change. + * + * Public API entry points: + * - `esp_clk_tree_enable_src(clk, true/false)` -> acquire / release + * - `esp_clk_tree_src_select_upstream(clk, src)` -> mux selection + * - `esp_clk_tree_src_set_freq_hz(clk, hz, &real)` -> divider selection + * The engine functions below are the internal targets the per-target + * dispatchers route into. + * + * Semantics: + * - acquire: ref_cnt++; on first acquire enable the gate. + * - release: ref_cnt--; on last release disable the gate. + * - select_upstream: program the mux to source from `upstream`. + * - freq_set: pick a divider for `state->cur_upstream` (if `select_upstream` + * was called) or auto-pick an upstream that divides cleanly + * (if no upstream was selected); program the divider (and the + * mux on first call) atomically. + * Share-lock (applies to select_upstream and freq_set when ref_cnt > 1): + * request must match the currently applied (upstream / divider) value; + * otherwise return ESP_ERR_INVALID_STATE and report the active frequency. + * ------------------------------------------------------------------------- */ +static portMUX_TYPE __attribute__((unused)) s_derived_clk_spinlock = portMUX_INITIALIZER_UNLOCKED; + +__attribute__((weak)) const esp_clk_tree_derived_clk_desc_t * +esp_clk_tree_get_derived_clk_desc(soc_module_clk_t clk_src) +{ + (void)clk_src; + return NULL; +} + +esp_err_t esp_clk_tree_derived_clk_acquire(soc_module_clk_t clk_src) +{ + const esp_clk_tree_derived_clk_desc_t *desc = esp_clk_tree_get_derived_clk_desc(clk_src); + if (desc == NULL || desc->state == NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + esp_clk_tree_derived_clk_state_t *state = desc->state; + + esp_os_enter_critical(&s_derived_clk_spinlock); + state->ref_cnt++; + if (state->ref_cnt == 1) { + desc->set_gate(true); + } + esp_os_exit_critical(&s_derived_clk_spinlock); + return ESP_OK; +} + +esp_err_t esp_clk_tree_derived_clk_release(soc_module_clk_t clk_src) +{ + const esp_clk_tree_derived_clk_desc_t *desc = esp_clk_tree_get_derived_clk_desc(clk_src); + if (desc == NULL || desc->state == NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + esp_clk_tree_derived_clk_state_t *state = desc->state; + + bool released_too_many = false; + esp_os_enter_critical(&s_derived_clk_spinlock); + if (state->ref_cnt <= 0) { + state->ref_cnt = 0; + released_too_many = true; + } else { + state->ref_cnt--; + if (state->ref_cnt == 0) { + desc->set_gate(false); + state->cur_upstream = SOC_MOD_CLK_INVALID; + state->cur_divider = 0; + } + } + esp_os_exit_critical(&s_derived_clk_spinlock); + if (released_too_many) { + ESP_HW_LOGW(TAG, "derived clk %d released without matching acquire", (int)clk_src); + } + return ESP_OK; +} + +/** + * Iterate the descriptor's upstream candidates and pick the first one + * (in preference order) whose currently programmed frequency is an integer + * multiple of `expt_freq_hz`. When `preferred != SOC_MOD_CLK_INVALID`, only + * that single candidate is considered. + */ +static esp_err_t s_derived_pick_config(const esp_clk_tree_derived_clk_desc_t *desc, + soc_module_clk_t preferred, + uint32_t expt_freq_hz, + soc_module_clk_t *out_upstream, + uint32_t *out_divider, + uint8_t *out_mux_sel) +{ + bool any_match = false; + for (size_t i = 0; i < desc->upstream_count; i++) { + const esp_clk_tree_derived_upstream_t *cand = &desc->upstreams[i]; + if (preferred != SOC_MOD_CLK_INVALID && cand->upstream != preferred) { + continue; + } + any_match = true; + uint32_t up_hz = 0; + if (esp_clk_tree_src_get_freq_hz(cand->upstream, + ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, + &up_hz) != ESP_OK || up_hz == 0) { + continue; + } + if (up_hz % expt_freq_hz == 0) { + *out_upstream = cand->upstream; + *out_divider = up_hz / expt_freq_hz; + *out_mux_sel = cand->mux_sel; + return ESP_OK; + } + } + // Distinguish "not in allowed list" from "in list but doesn't divide cleanly" + // so callers get a more useful error. + return (preferred != SOC_MOD_CLK_INVALID && !any_match) ? ESP_ERR_INVALID_ARG + : ESP_ERR_NOT_FOUND; +} + +esp_err_t esp_clk_tree_derived_clk_freq_set(soc_module_clk_t clk_src, + uint32_t expt_freq_hz, + uint32_t *real_freq_hz) +{ + const esp_clk_tree_derived_clk_desc_t *desc = esp_clk_tree_get_derived_clk_desc(clk_src); + if (desc == NULL || desc->state == NULL || desc->upstreams == NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + esp_clk_tree_derived_clk_state_t *state = desc->state; + ESP_RETURN_ON_FALSE(expt_freq_hz > 0, ESP_ERR_INVALID_ARG, TAG, "freq must be > 0"); + + // If `esp_clk_tree_src_select_upstream()` was called, honor that choice; + // otherwise auto-pick the first upstream in the descriptor that divides + // cleanly to the requested frequency. + soc_module_clk_t preferred = state->cur_upstream; + soc_module_clk_t upstream = SOC_MOD_CLK_INVALID; + uint32_t divider = 0; + uint8_t mux_sel = 0; + ESP_RETURN_ON_ERROR(s_derived_pick_config(desc, preferred, expt_freq_hz, + &upstream, ÷r, &mux_sel), + TAG, + "no upstream divides cleanly to %" PRIu32 " Hz for derived clk %d", + expt_freq_hz, (int)clk_src); + + esp_err_t ret = ESP_OK; + soc_module_clk_t reported_upstream = SOC_MOD_CLK_INVALID; + uint32_t reported_divider = 0; + + esp_os_enter_critical(&s_derived_clk_spinlock); + // Caller must have acquired the clock first. + assert(state->ref_cnt > 0); + + bool same_config = (state->cur_upstream == upstream) && + (state->cur_divider == divider); + // Allow the first peer to commit a divider even when ref_cnt > 1 + // (e.g. another peer ran enable_src already but hasn't called freq_set + // yet). Mirrors MPLL's `cur == 0 || ref_cnt < 2` check. + bool first_commit = (state->cur_divider == 0); + if (state->ref_cnt < 2 || same_config || first_commit) { + // First-time configuration: also program the mux. When the caller + // already invoked `select_upstream`, `state->cur_upstream` already + // matches `upstream` so the mux is left untouched. + if (state->cur_upstream != upstream && desc->set_src != NULL) { + desc->set_src(mux_sel); + } + desc->set_divider(divider); + state->cur_upstream = upstream; + state->cur_divider = divider; + } else { + ret = ESP_ERR_INVALID_STATE; + } + esp_os_exit_critical(&s_derived_clk_spinlock); + reported_upstream = state->cur_upstream; + reported_divider = state->cur_divider; + + if (real_freq_hz != NULL) { + uint32_t up_hz = 0; + if (reported_upstream != SOC_MOD_CLK_INVALID && reported_divider != 0 && + esp_clk_tree_src_get_freq_hz(reported_upstream, + ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, + &up_hz) == ESP_OK) { + *real_freq_hz = up_hz / reported_divider; + } else { + *real_freq_hz = 0; + } + } + return ret; +} + +esp_err_t esp_clk_tree_src_select_upstream(soc_module_clk_t clk_src, + soc_module_clk_t upstream) +{ + const esp_clk_tree_derived_clk_desc_t *desc = esp_clk_tree_get_derived_clk_desc(clk_src); + if (desc == NULL || desc->state == NULL || desc->upstreams == NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + ESP_RETURN_ON_FALSE(upstream > 0 && upstream < SOC_MOD_CLK_INVALID, + ESP_ERR_INVALID_ARG, TAG, "invalid upstream clk %d", (int)upstream); + + // Validate `upstream` is in the descriptor's allowed list and recover its + // mux selector value. + uint8_t mux_sel = 0; + bool found = false; + for (size_t i = 0; i < desc->upstream_count; i++) { + if (desc->upstreams[i].upstream == upstream) { + mux_sel = desc->upstreams[i].mux_sel; + found = true; + break; + } + } + ESP_RETURN_ON_FALSE(found, ESP_ERR_INVALID_ARG, TAG, + "upstream %d not allowed for derived clk %d", + (int)upstream, (int)clk_src); + + esp_clk_tree_derived_clk_state_t *state = desc->state; + esp_err_t ret = ESP_OK; + esp_os_enter_critical(&s_derived_clk_spinlock); + // Caller must have acquired the clock first. + assert(state->ref_cnt > 0); + + // Allow the first peer to commit an upstream even when ref_cnt > 1. + bool first_commit = (state->cur_upstream == SOC_MOD_CLK_INVALID); + if (state->ref_cnt > 1 && !first_commit && state->cur_upstream != upstream) { + // Another peer is already using this clock with a different upstream. + ret = ESP_ERR_INVALID_STATE; + } else if (state->cur_upstream != upstream) { + if (desc->set_src != NULL) { + desc->set_src(mux_sel); + } + state->cur_upstream = upstream; + // Divider for the previous upstream is no longer meaningful; the next + // `set_freq_hz` call will program a fresh divider for `upstream`. + state->cur_divider = 0; + } + esp_os_exit_critical(&s_derived_clk_spinlock); + return ret; +} diff --git a/components/soc/esp32s31/include/soc/clk_tree_defs.h b/components/soc/esp32s31/include/soc/clk_tree_defs.h index 55e54163c72..6168f2d30f5 100644 --- a/components/soc/esp32s31/include/soc/clk_tree_defs.h +++ b/components/soc/esp32s31/include/soc/clk_tree_defs.h @@ -139,9 +139,10 @@ typedef enum { SOC_MOD_CLK_RTC_FAST, /*!< RTC_FAST_CLK can be sourced from XTAL, or RC_FAST by configuring soc_rtc_fast_clk_src_t */ SOC_MOD_CLK_RTC_SLOW, /*!< RTC_SLOW_CLK can be sourced from RC_SLOW, XTAL32K, or RC32K by configuring soc_rtc_slow_clk_src_t */ // For digital domain: peripherals + SOC_MOD_CLK_SYS, /*!< SYS_CLK is the system clock, derived from SOC_CLK clock source */ SOC_MOD_CLK_PLL_F20M, /*!< PLL_F20M_CLK is derived from BBPLL (clock gating + default divider 24), its default frequency is 20MHz */ SOC_MOD_CLK_PLL_F25M, /*!< PLL_F25M_CLK is derived from MPLL (clock gating + configurable divider), it will have a frequency of 25MHz */ - SOC_MOD_CLK_PLL_F50M, /*!< PLL_F50M_CLK is derived from MPLL (clock gating + configurable divider 10), it will have a frequency of 50MHz */ + SOC_MOD_CLK_PLL_F50M, /*!< PLL_F50M_CLK is derived from C/MPLL (clock gating + configurable divider 10), it will have a frequency of 50MHz */ SOC_MOD_CLK_PLL_F80M, /*!< PLL_F80M_CLK is derived from BBPLL (clock gating + default divider 6), its default frequency is 80MHz */ SOC_MOD_CLK_PLL_F160M, /*!< PLL_F160M_CLK is derived from BBPLL (clock gating + default divider 3), its default frequency is 160MHz */ SOC_MOD_CLK_PLL_F240M, /*!< PLL_F240M_CLK is derived from BBPLL (clock gating + default divider 2), its default frequency is 240MHz */ @@ -758,6 +759,41 @@ typedef enum { TEMPERATURE_SENSOR_CLK_SRC_DEFAULT = SOC_MOD_CLK_LP_PERI, /*!< Select LP_PERI as the default choice */ } soc_periph_temperature_sensor_clk_src_t; +//////////////////////////////////////////////////EMAC/////////////////////////////////////////////////////////////// +/** + * @brief Array initializer for all supported clock sources of EMAC clock output + */ +#define SOC_EMAC_OUT_CLK {SOC_MOD_CLK_MPLL, SOC_MOD_CLK_CPLL, SOC_MOD_CLK_APLL} + +typedef enum { + EMAC_CLK_OUT_SRC_MPLL = SOC_MOD_CLK_MPLL, + EMAC_CLK_OUT_SRC_CPLL = SOC_MOD_CLK_CPLL, + EMAC_CLK_OUT_SRC_APLL = SOC_MOD_CLK_APLL, + EMAC_CLK_OUT_SRC_DEFAULT = SOC_MOD_CLK_MPLL, +} soc_periph_emac_clk_out_src_t; + +/** + * @brief Array initializer for all supported clock sources of EMAC REF_PHY clock output + */ +#define SOC_EMAC_REF_PHY_CLK {SOC_MOD_CLK_MPLL, SOC_MOD_CLK_CPLL} + +typedef enum { + EMAC_REF_PHY_CLK_SRC_MPLL = SOC_MOD_CLK_MPLL, + EMAC_REF_PHY_CLK_SRC_CPLL = SOC_MOD_CLK_CPLL, + EMAC_REF_PHY_CLK_SRC_DEFAULT = SOC_MOD_CLK_MPLL, +} soc_periph_emac_ref_phy_clk_src_t; + +/** + * @brief Array initializer for all supported clock sources of EMAC PTP + */ +#define SOC_EMAC_PTP_CLK {SOC_MOD_CLK_XTAL, SOC_MOD_CLK_PLL_F80M} + +typedef enum { + EMAC_PTP_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, + EMAC_PTP_CLK_SRC_PLL_F80M = SOC_MOD_CLK_PLL_F80M, + EMAC_PTP_CLK_SRC_DEFAULT = SOC_MOD_CLK_XTAL, +} soc_periph_emac_ptp_clk_src_t; + #ifdef __cplusplus } #endif