esp-idf/components/esp_hw_support/clk_ctrl_os.c

230 lines
7.1 KiB
C
Raw Normal View History

/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <freertos/FreeRTOS.h>
#include "clk_ctrl_os.h"
2022-12-01 07:48:27 -05:00
#include "soc/rtc.h"
#include "esp_private/esp_clk_tree_common.h"
2021-12-02 07:24:19 -05:00
#include "esp_check.h"
#if SOC_CLK_MPLL_SUPPORTED
#include "rtc_clk.h"
#include "esp_private/esp_ldo_psram.h"
#endif
#if SOC_CLK_APLL_SUPPORTED || SOC_CLK_MPLL_SUPPORTED
static const char *TAG = "clk_ctrl_os";
#endif
static portMUX_TYPE periph_spinlock = portMUX_INITIALIZER_UNLOCKED;
static uint8_t s_periph_ref_counts = 0;
2022-12-01 07:48:27 -05:00
static uint32_t s_rc_fast_freq = 0; // Frequency of the RC_FAST clock in Hz
2021-12-02 07:24:19 -05:00
#if SOC_CLK_APLL_SUPPORTED
// Current APLL frequency, in HZ. Zero if APLL is not enabled.
static uint32_t s_cur_apll_freq = 0;
static int s_apll_ref_cnt = 0;
#endif
#if SOC_CLK_MPLL_SUPPORTED
static uint32_t s_cur_mpll_freq = 0;
static int s_mpll_ref_cnt = 0;
static esp_ldo_unit_handle_t s_ldo_unit_hndl = NULL;
#endif
bool periph_rtc_dig_clk8m_enable(void)
{
portENTER_CRITICAL(&periph_spinlock);
if (s_periph_ref_counts == 0) {
rtc_dig_clk8m_enable();
2022-12-01 07:48:27 -05:00
#if SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
s_rc_fast_freq = esp_clk_tree_rc_fast_get_freq_hz(ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT);
2022-12-01 07:48:27 -05:00
if (s_rc_fast_freq == 0) {
rtc_dig_clk8m_disable();
portEXIT_CRITICAL(&periph_spinlock);
return false;
}
2022-12-01 07:48:27 -05:00
#endif //SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
}
s_periph_ref_counts++;
portEXIT_CRITICAL(&periph_spinlock);
return true;
}
uint32_t periph_rtc_dig_clk8m_get_freq(void)
{
2022-12-01 07:48:27 -05:00
#if !SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
/* Workaround: CLK8M calibration cannot be performed, we can only return its theoretic value */
return SOC_CLK_RC_FAST_FREQ_APPROX;
#else
2022-12-01 07:48:27 -05:00
return s_rc_fast_freq;
#endif
}
void periph_rtc_dig_clk8m_disable(void)
{
portENTER_CRITICAL(&periph_spinlock);
assert(s_periph_ref_counts > 0);
s_periph_ref_counts--;
if (s_periph_ref_counts == 0) {
2022-12-01 07:48:27 -05:00
s_rc_fast_freq = 0;
rtc_dig_clk8m_disable();
}
portEXIT_CRITICAL(&periph_spinlock);
}
2021-12-02 07:24:19 -05:00
#if SOC_CLK_APLL_SUPPORTED
void periph_rtc_apll_acquire(void)
{
portENTER_CRITICAL(&periph_spinlock);
s_apll_ref_cnt++;
if (s_apll_ref_cnt == 1) {
// For the first time enable APLL, need to set power up
rtc_clk_apll_enable(true);
}
portEXIT_CRITICAL(&periph_spinlock);
}
void periph_rtc_apll_release(void)
{
portENTER_CRITICAL(&periph_spinlock);
assert(s_apll_ref_cnt > 0);
s_apll_ref_cnt--;
if (s_apll_ref_cnt == 0) {
// If there is no peripheral using APLL, shut down the power
s_cur_apll_freq = 0;
rtc_clk_apll_enable(false);
}
portEXIT_CRITICAL(&periph_spinlock);
}
esp_err_t periph_rtc_apll_freq_set(uint32_t expt_freq, uint32_t *real_freq)
{
uint32_t o_div = 0;
uint32_t sdm0 = 0;
uint32_t sdm1 = 0;
uint32_t sdm2 = 0;
// Guarantee 'periph_rtc_apll_acquire' has been called before set apll freq
assert(s_apll_ref_cnt > 0);
uint32_t apll_freq = rtc_clk_apll_coeff_calc(expt_freq, &o_div, &sdm0, &sdm1, &sdm2);
ESP_RETURN_ON_FALSE(apll_freq, ESP_ERR_INVALID_ARG, TAG, "APLL coefficients calculate failed");
bool need_config = true;
portENTER_CRITICAL(&periph_spinlock);
/* If APLL is not in use or only one peripheral in use, its frequency can be changed as will
* But when more than one peripheral refers APLL, its frequency is not allowed to change once it is set */
if (s_cur_apll_freq == 0 || s_apll_ref_cnt < 2) {
s_cur_apll_freq = apll_freq;
} else {
apll_freq = s_cur_apll_freq;
need_config = false;
}
portEXIT_CRITICAL(&periph_spinlock);
*real_freq = apll_freq;
if (need_config) {
ESP_LOGD(TAG, "APLL will working at %"PRIu32" Hz with coefficients [sdm0] %"PRIu32" [sdm1] %"PRIu32" [sdm2] %"PRIu32" [o_div] %"PRIu32"",
2021-12-02 07:24:19 -05:00
apll_freq, sdm0, sdm1, sdm2, o_div);
/* Set coefficients for APLL, notice that it doesn't mean APLL will start */
rtc_clk_apll_coeff_set(o_div, sdm0, sdm1, sdm2);
} else {
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
2021-08-18 07:45:51 -04:00
#endif // SOC_CLK_APLL_SUPPORTED
#if SOC_CLK_MPLL_SUPPORTED
void periph_rtc_mpll_early_acquire(void)
{
portENTER_CRITICAL(&periph_spinlock);
s_mpll_ref_cnt++;
if (s_mpll_ref_cnt == 1) {
#if SOC_PSRAM_VDD_POWER_MPLL
// configure MPPL power (MPLL power pin is the same as for the PSRAM)
s_ldo_unit_hndl = esp_ldo_vdd_psram_early_init();
#endif
// For the first time enable MPLL, need to set power up
rtc_clk_mpll_enable();
}
portEXIT_CRITICAL(&periph_spinlock);
}
esp_err_t periph_rtc_mpll_acquire(void)
{
esp_err_t ret = ESP_OK;
portENTER_CRITICAL(&periph_spinlock);
s_mpll_ref_cnt++;
if (s_mpll_ref_cnt == 1) {
#if SOC_PSRAM_VDD_POWER_MPLL
// configure MPPL power (MPLL power pin is the same as for the PSRAM)
ret = esp_ldo_vdd_psram_init(&s_ldo_unit_hndl);
// external power supply in use is a valid condition
if (ret == ESP_ERR_INVALID_STATE) {
ret = ESP_OK;
} else if (ret != ESP_OK ) {
portEXIT_CRITICAL(&periph_spinlock);
ESP_LOGE(TAG, "failed to initialize PSRAM internal LDO");
goto err;
}
#endif
// For the first time enable MPLL, need to set power up
rtc_clk_mpll_enable();
}
portEXIT_CRITICAL(&periph_spinlock);
#if SOC_PSRAM_VDD_POWER_MPLL
err:
#endif
return ret;
}
void periph_rtc_mpll_release(void)
{
portENTER_CRITICAL(&periph_spinlock);
assert(s_mpll_ref_cnt > 0);
s_mpll_ref_cnt--;
if (s_mpll_ref_cnt == 0) {
// If there is no peripheral using MPLL, shut down the power
s_cur_mpll_freq = 0;
rtc_clk_mpll_disable();
#if SOC_PSRAM_VDD_POWER_MPLL
if (s_ldo_unit_hndl) {
esp_ldo_vdd_psram_deinit(s_ldo_unit_hndl);
}
#endif
}
portEXIT_CRITICAL(&periph_spinlock);
}
esp_err_t periph_rtc_mpll_freq_set(uint32_t expt_freq, uint32_t *real_freq)
{
esp_err_t ret = ESP_OK;
// Guarantee 'periph_rtc_apll_acquire' has been called before set apll freq
assert(s_mpll_ref_cnt > 0);
portENTER_CRITICAL(&periph_spinlock);
if (s_cur_mpll_freq == expt_freq) {
goto end;
}
/* If MPLL is not in use or only one peripheral in use, its frequency can be changed as will
* But when more than one peripheral refers MPLL, its frequency is not allowed to change once it is set */
if (s_cur_mpll_freq == 0 || s_mpll_ref_cnt < 2) {
uint32_t xtal_freq = 0;
ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_XTAL, ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT, &xtal_freq));
rtc_clk_mpll_configure(xtal_freq / MHZ, expt_freq / MHZ);
ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_MPLL, ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT, &s_cur_mpll_freq));
} else {
ret = ESP_ERR_INVALID_STATE;
}
end:
if (real_freq != NULL) {
*real_freq = s_cur_mpll_freq;
}
portEXIT_CRITICAL(&periph_spinlock);
return ret;
}
#endif // SOC_CLK_MPLL_SUPPORTED