Merge branch 'feature/rtc_cpu_freq_config' into 'master'

soc/rtc: Refactoring, support CPU frequencies lower than XTAL

See merge request idf/esp-idf!2856
This commit is contained in:
Ivan Grokhotkov 2018-08-22 11:32:08 +08:00
commit 90f5456dba
21 changed files with 889 additions and 509 deletions

View file

@ -29,7 +29,7 @@ void bootloader_clock_configure()
uart_tx_wait_idle(0);
/* Set CPU to 80MHz. Keep other clocks unmodified. */
rtc_cpu_freq_t cpu_freq = RTC_CPU_FREQ_80M;
int cpu_freq_mhz = 80;
/* On ESP32 rev 0, switching to 80MHz if clock was previously set to
* 240 MHz may cause the chip to lock up (see section 3.5 of the errata
@ -39,12 +39,12 @@ void bootloader_clock_configure()
uint32_t chip_ver_reg = REG_READ(EFUSE_BLK0_RDATA3_REG);
if ((chip_ver_reg & EFUSE_RD_CHIP_VER_REV1_M) == 0 &&
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ == 240) {
cpu_freq = RTC_CPU_FREQ_240M;
cpu_freq_mhz = 240;
}
rtc_clk_config_t clk_cfg = RTC_CLK_CONFIG_DEFAULT();
clk_cfg.xtal_freq = CONFIG_ESP32_XTAL_FREQ;
clk_cfg.cpu_freq = cpu_freq;
clk_cfg.cpu_freq_mhz = cpu_freq_mhz;
clk_cfg.slow_freq = rtc_clk_slow_freq_get();
clk_cfg.fast_freq = rtc_clk_fast_freq_get();
rtc_clk_init(clk_cfg);

View file

@ -77,44 +77,37 @@ void esp_clk_init(void)
select_rtc_slow_clk(RTC_SLOW_FREQ_RTC);
#endif
uint32_t freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
rtc_cpu_freq_t freq = RTC_CPU_FREQ_80M;
switch(freq_mhz) {
case 240:
freq = RTC_CPU_FREQ_240M;
break;
case 160:
freq = RTC_CPU_FREQ_160M;
break;
default:
freq_mhz = 80;
/* falls through */
case 80:
freq = RTC_CPU_FREQ_80M;
break;
}
rtc_cpu_freq_config_t old_config, new_config;
rtc_clk_cpu_freq_get_config(&old_config);
const uint32_t old_freq_mhz = old_config.freq_mhz;
const uint32_t new_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
bool res = rtc_clk_cpu_freq_mhz_to_config(new_freq_mhz, &new_config);
assert(res);
// Wait for UART TX to finish, otherwise some UART output will be lost
// when switching APB frequency
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
uint32_t freq_before = rtc_clk_cpu_freq_value(rtc_clk_cpu_freq_get()) / MHZ ;
rtc_clk_cpu_freq_set(freq);
rtc_clk_cpu_freq_set_config(&new_config);
// Re calculate the ccount to make time calculation correct.
uint32_t freq_after = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
XTHAL_SET_CCOUNT( XTHAL_GET_CCOUNT() * freq_after / freq_before );
XTHAL_SET_CCOUNT( XTHAL_GET_CCOUNT() * new_freq_mhz / old_freq_mhz );
}
int IRAM_ATTR esp_clk_cpu_freq(void)
{
return g_ticks_per_us_pro * 1000000;
return g_ticks_per_us_pro * MHZ;
}
int IRAM_ATTR esp_clk_apb_freq(void)
{
return MIN(g_ticks_per_us_pro, 80) * 1000000;
return MIN(g_ticks_per_us_pro, 80) * MHZ;
}
int IRAM_ATTR esp_clk_xtal_freq(void)
{
return rtc_clk_xtal_freq_get() * MHZ;
}
void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us)

View file

@ -31,8 +31,10 @@ extern "C" {
* Pass a pointer to this structure as an argument to esp_pm_configure function.
*/
typedef struct {
rtc_cpu_freq_t max_cpu_freq; /*!< Maximum CPU frequency to use */
rtc_cpu_freq_t min_cpu_freq; /*!< Minimum CPU frequency to use when no frequency locks are taken */
rtc_cpu_freq_t max_cpu_freq __attribute__((deprecated)); /*!< Maximum CPU frequency to use. Deprecated, use max_freq_mhz instead. */
int max_freq_mhz; /*!< Maximum CPU frequency, in MHz */
rtc_cpu_freq_t min_cpu_freq __attribute__((deprecated)); /*!< Minimum CPU frequency to use when no frequency locks are taken. Deprecated, use min_freq_mhz instead. */
int min_freq_mhz; /*!< Minimum CPU frequency to use when no locks are taken, in MHz */
bool light_sleep_enable; /*!< Enter light sleep when no locks are taken */
} esp_pm_config_esp32_t;

View file

@ -62,6 +62,17 @@ int esp_clk_cpu_freq(void);
*/
int esp_clk_apb_freq(void);
/**
* @brief Return frequency of the main XTAL
*
* Frequency of the main XTAL can be either auto-detected or set at compile
* time (see CONFIG_ESP32_XTAL_FREQ_SEL sdkconfig option). In both cases, this
* function returns the actual value at run time.
*
* @return XTAL frequency, in Hz
*/
int esp_clk_xtal_freq(void);
/**
* @brief Read value of RTC counter, converting it to microseconds

View file

@ -407,7 +407,7 @@ static void esp_panic_dig_reset()
// make sure all the panic handler output is sent from UART FIFO
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
// switch to XTAL (otherwise we will keep running from the PLL)
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_XTAL);
rtc_clk_cpu_freq_set_xtal();
// reset the digital part
esp_cpu_unstall(PRO_CPU_NUM);
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_SYS_RST);

View file

@ -51,6 +51,11 @@
*/
#define LIGHT_SLEEP_EARLY_WAKEUP_US 100
/* Minimal divider at which REF_CLK_FREQ can be obtained */
#define REF_CLK_DIV_MIN 10
#define MHZ 1000000
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#endif
@ -80,44 +85,20 @@ static uint32_t s_ccount_mul;
*/
static volatile bool s_need_update_ccompare[portNUM_PROCESSORS];
/* When no RTOS tasks are active, these locks are released to allow going into
* a lower power mode. Used by ISR hook and idle hook.
*/
static esp_pm_lock_handle_t s_rtos_lock_handle[portNUM_PROCESSORS];
/* A flag indicating that Idle hook has run on a given CPU;
* Next interrupt on the same CPU will take s_rtos_lock_handle.
*/
static bool s_core_idle[portNUM_PROCESSORS];
/* g_ticks_us defined in ROM for PRO CPU */
extern uint32_t g_ticks_per_us_pro;
/* When no RTOS tasks are active, these locks are released to allow going into
* a lower power mode. Used by ISR hook and idle hook.
*/
static esp_pm_lock_handle_t s_rtos_lock_handle[portNUM_PROCESSORS];
/* Lookup table of CPU frequencies to be used in each mode.
/* Lookup table of CPU frequency configs to be used in each mode.
* Initialized by esp_pm_impl_init and modified by esp_pm_configure.
*/
rtc_cpu_freq_t s_cpu_freq_by_mode[PM_MODE_COUNT];
/* Lookup table of CPU ticks per microsecond for each RTC_CPU_FREQ_ value.
* Essentially the same as returned by rtc_clk_cpu_freq_value(), but without
* the function call. Not const because XTAL frequency is only known at run time.
*/
static uint32_t s_cpu_freq_to_ticks[] = {
[RTC_CPU_FREQ_XTAL] = 0, /* This is set by esp_pm_impl_init */
[RTC_CPU_FREQ_80M] = 80,
[RTC_CPU_FREQ_160M] = 160,
[RTC_CPU_FREQ_240M] = 240,
[RTC_CPU_FREQ_2M] = 2
};
/* Lookup table of names for each RTC_CPU_FREQ_ value. Used for logging only. */
static const char* s_freq_names[] __attribute__((unused)) = {
[RTC_CPU_FREQ_XTAL] = "XTAL",
[RTC_CPU_FREQ_80M] = "80",
[RTC_CPU_FREQ_160M] = "160",
[RTC_CPU_FREQ_240M] = "240",
[RTC_CPU_FREQ_2M] = "2"
};
rtc_cpu_freq_config_t s_cpu_freq_by_mode[PM_MODE_COUNT];
/* Whether automatic light sleep is enabled */
static bool s_light_sleep_en = false;
@ -167,21 +148,6 @@ pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
}
}
/* rtc_cpu_freq_t enum is not ordered by frequency, so convert to MHz,
* figure out the maximum value, then convert back to rtc_cpu_freq_t.
*/
static rtc_cpu_freq_t max_freq_of(rtc_cpu_freq_t f1, rtc_cpu_freq_t f2)
{
int f1_hz = rtc_clk_cpu_freq_value(f1);
int f2_hz = rtc_clk_cpu_freq_value(f2);
int f_max_hz = MAX(f1_hz, f2_hz);
rtc_cpu_freq_t result = RTC_CPU_FREQ_XTAL;
if (!rtc_clk_cpu_freq_from_mhz(f_max_hz/1000000, &result)) {
assert(false && "unsupported frequency");
}
return result;
}
esp_err_t esp_pm_configure(const void* vconfig)
{
#ifndef CONFIG_PM_ENABLE
@ -195,46 +161,66 @@ esp_err_t esp_pm_configure(const void* vconfig)
}
#endif
if (config->min_cpu_freq == RTC_CPU_FREQ_2M) {
/* Minimal APB frequency to achieve 1MHz REF_TICK frequency is 5 MHz */
return ESP_ERR_NOT_SUPPORTED;
int min_freq_mhz = config->min_freq_mhz;
int max_freq_mhz = config->max_freq_mhz;
if (min_freq_mhz == 0 && max_freq_mhz == 0) {
/* For compatibility, handle deprecated fields, min_cpu_freq and max_cpu_freq. */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
min_freq_mhz = rtc_clk_cpu_freq_value(config->min_cpu_freq) / MHZ;
max_freq_mhz = rtc_clk_cpu_freq_value(config->max_cpu_freq) / MHZ;
#pragma GCC diagnostic pop
}
rtc_cpu_freq_t min_freq = config->min_cpu_freq;
rtc_cpu_freq_t max_freq = config->max_cpu_freq;
int min_freq_mhz = rtc_clk_cpu_freq_value(min_freq);
int max_freq_mhz = rtc_clk_cpu_freq_value(max_freq);
if (min_freq_mhz > max_freq_mhz) {
return ESP_ERR_INVALID_ARG;
}
rtc_cpu_freq_t apb_max_freq = max_freq; /* CPU frequency in APB_MAX mode */
if (max_freq == RTC_CPU_FREQ_240M) {
rtc_cpu_freq_config_t freq_config;
if (!rtc_clk_cpu_freq_mhz_to_config(min_freq_mhz, &freq_config)) {
ESP_LOGW(TAG, "invalid min_freq_mhz value (%d)", min_freq_mhz);
return ESP_ERR_INVALID_ARG;
}
int xtal_freq_mhz = (int) rtc_clk_xtal_freq_get();
if (min_freq_mhz < xtal_freq_mhz && min_freq_mhz * MHZ / REF_CLK_FREQ < REF_CLK_DIV_MIN) {
ESP_LOGW(TAG, "min_freq_mhz should be >= %d", REF_CLK_FREQ * REF_CLK_DIV_MIN / MHZ);
return ESP_ERR_INVALID_ARG;
}
if (!rtc_clk_cpu_freq_mhz_to_config(max_freq_mhz, &freq_config)) {
ESP_LOGW(TAG, "invalid max_freq_mhz value (%d)", max_freq_mhz);
return ESP_ERR_INVALID_ARG;
}
int apb_max_freq = max_freq_mhz; /* CPU frequency in APB_MAX mode */
if (max_freq_mhz == 240) {
/* We can't switch between 240 and 80/160 without disabling PLL,
* so use 240MHz CPU frequency when 80MHz APB frequency is requested.
*/
apb_max_freq = RTC_CPU_FREQ_240M;
} else if (max_freq == RTC_CPU_FREQ_160M || max_freq == RTC_CPU_FREQ_80M) {
apb_max_freq = 240;
} else if (max_freq_mhz == 160 || max_freq_mhz == 80) {
/* Otherwise, can use 80MHz
* CPU frequency when 80MHz APB frequency is requested.
*/
apb_max_freq = RTC_CPU_FREQ_80M;
apb_max_freq = 80;
}
apb_max_freq = max_freq_of(apb_max_freq, min_freq);
apb_max_freq = MAX(apb_max_freq, min_freq_mhz);
ESP_LOGI(TAG, "Frequency switching config: "
"CPU_MAX: %s, APB_MAX: %s, APB_MIN: %s, Light sleep: %s",
s_freq_names[max_freq],
s_freq_names[apb_max_freq],
s_freq_names[min_freq],
"CPU_MAX: %d, APB_MAX: %d, APB_MIN: %d, Light sleep: %s",
max_freq_mhz,
apb_max_freq,
min_freq_mhz,
config->light_sleep_enable ? "ENABLED" : "DISABLED");
portENTER_CRITICAL(&s_switch_lock);
s_cpu_freq_by_mode[PM_MODE_CPU_MAX] = max_freq;
s_cpu_freq_by_mode[PM_MODE_APB_MAX] = apb_max_freq;
s_cpu_freq_by_mode[PM_MODE_APB_MIN] = min_freq;
s_cpu_freq_by_mode[PM_MODE_LIGHT_SLEEP] = min_freq;
rtc_clk_cpu_freq_mhz_to_config(max_freq_mhz, &s_cpu_freq_by_mode[PM_MODE_CPU_MAX]);
rtc_clk_cpu_freq_mhz_to_config(apb_max_freq, &s_cpu_freq_by_mode[PM_MODE_APB_MAX]);
rtc_clk_cpu_freq_mhz_to_config(min_freq_mhz, &s_cpu_freq_by_mode[PM_MODE_APB_MIN]);
s_cpu_freq_by_mode[PM_MODE_LIGHT_SLEEP] = s_cpu_freq_by_mode[PM_MODE_APB_MIN];
s_light_sleep_en = config->light_sleep_enable;
s_config_changed = true;
portEXIT_CRITICAL(&s_switch_lock);
@ -310,7 +296,7 @@ static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_p
}
/* Calculate new tick divisor */
_xt_tick_divisor = ticks_per_us * 1000000 / XT_TICK_PER_SEC;
_xt_tick_divisor = ticks_per_us * MHZ / XT_TICK_PER_SEC;
int core_id = xPortGetCoreID();
if (s_rtos_lock_handle[core_id] != NULL) {
@ -375,17 +361,18 @@ static void IRAM_ATTR do_switch(pm_mode_t new_mode)
s_config_changed = false;
portEXIT_CRITICAL_ISR(&s_switch_lock);
rtc_cpu_freq_t new_freq = s_cpu_freq_by_mode[new_mode];
rtc_cpu_freq_t old_freq;
rtc_cpu_freq_config_t new_config = s_cpu_freq_by_mode[new_mode];
rtc_cpu_freq_config_t old_config;
if (!config_changed) {
old_freq = s_cpu_freq_by_mode[s_mode];
old_config = s_cpu_freq_by_mode[s_mode];
} else {
old_freq = rtc_clk_cpu_freq_get();
rtc_clk_cpu_freq_get_config(&old_config);
}
if (new_freq != old_freq) {
uint32_t old_ticks_per_us = g_ticks_per_us_pro;
uint32_t new_ticks_per_us = s_cpu_freq_to_ticks[new_freq];
if (new_config.freq_mhz != old_config.freq_mhz) {
uint32_t old_ticks_per_us = old_config.freq_mhz;
uint32_t new_ticks_per_us = new_config.freq_mhz;
bool switch_down = new_ticks_per_us < old_ticks_per_us;
@ -393,7 +380,7 @@ static void IRAM_ATTR do_switch(pm_mode_t new_mode)
if (switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
rtc_clk_cpu_freq_set_fast(new_freq);
rtc_clk_cpu_freq_set_config_fast(&new_config);
if (!switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
@ -536,9 +523,9 @@ void esp_pm_impl_dump_stats(FILE* out)
/* don't display light sleep mode if it's not enabled */
continue;
}
fprintf(out, "%8s %6s %12lld %2d%%\n",
fprintf(out, "%8s %3dM %12lld %2d%%\n",
s_mode_names[i],
s_freq_names[s_cpu_freq_by_mode[i]],
s_cpu_freq_by_mode[i].freq_mhz,
time_in_mode[i],
(int) (time_in_mode[i] * 100 / now));
}
@ -547,7 +534,6 @@ void esp_pm_impl_dump_stats(FILE* out)
void esp_pm_impl_init()
{
s_cpu_freq_to_ticks[RTC_CPU_FREQ_XTAL] = rtc_clk_xtal_freq_get();
#ifdef CONFIG_PM_TRACE
esp_pm_trace_init();
#endif
@ -563,11 +549,11 @@ void esp_pm_impl_init()
/* Configure all modes to use the default CPU frequency.
* This will be modified later by a call to esp_pm_configure.
*/
rtc_cpu_freq_t default_freq;
if (!rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &default_freq)) {
rtc_cpu_freq_config_t default_config;
if (!rtc_clk_cpu_freq_mhz_to_config(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &default_config)) {
assert(false && "unsupported frequency");
}
for (size_t i = 0; i < PM_MODE_COUNT; ++i) {
s_cpu_freq_by_mode[i] = default_freq;
s_cpu_freq_by_mode[i] = default_config;
}
}

View file

@ -178,8 +178,9 @@ static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags)
}
// Save current frequency and switch to XTAL
rtc_cpu_freq_t cpu_freq = rtc_clk_cpu_freq_get();
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_XTAL);
rtc_cpu_freq_config_t cpu_freq_config;
rtc_clk_cpu_freq_get_config(&cpu_freq_config);
rtc_clk_cpu_freq_set_xtal();
// Configure pins for external wakeup
if (s_config.wakeup_triggers & RTC_EXT0_TRIG_EN) {
@ -205,7 +206,7 @@ static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags)
uint32_t result = rtc_sleep_start(s_config.wakeup_triggers, 0);
// Restore CPU frequency
rtc_clk_cpu_freq_set(cpu_freq);
rtc_clk_cpu_freq_set_config(&cpu_freq_config);
// re-enable UART output
resume_uarts();

View file

@ -334,7 +334,7 @@ void IRAM_ATTR esp_restart_noos()
DPORT_REG_WRITE(DPORT_PERIP_RST_EN_REG, 0);
// Set CPU back to XTAL source, no PLL, same as hard reset
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_XTAL);
rtc_clk_cpu_freq_set_xtal();
// Clear entry point for APP CPU
DPORT_REG_WRITE(DPORT_APPCPU_CTRL_D_REG, 0);

View file

@ -1,11 +1,12 @@
#include <esp_types.h>
#include <stdio.h>
#include <stdlib.h>
#include "esp_types.h"
#include "esp_clk.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_timer.h"
#include "soc/cpu.h"
#include "unity.h"
#include "rom/uart.h"
@ -99,49 +100,52 @@ TEST_CASE("access DPORT and APB at same time", "[esp32]")
{
dport_test_result = false;
apb_test_result = false;
printf("CPU_FREQ = %d MHz\n", rtc_clk_cpu_freq_value(rtc_clk_cpu_freq_get()) / MHZ);
printf("CPU_FREQ = %d MHz\n", esp_clk_cpu_freq());
run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000);
}
void run_tasks_with_change_freq_cpu (rtc_cpu_freq_t cpu_freq)
void run_tasks_with_change_freq_cpu(int cpu_freq_mhz)
{
dport_test_result = false;
apb_test_result = false;
rtc_cpu_freq_t cur_freq = rtc_clk_cpu_freq_get();
uint32_t freq_before_changed = rtc_clk_cpu_freq_value(cur_freq) / MHZ;
uint32_t freq_changed = freq_before_changed;
printf("CPU_FREQ = %d MHz\n", freq_before_changed);
if (cur_freq != cpu_freq) {
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
rtc_clk_cpu_freq_set(cpu_freq);
const int uart_num = CONFIG_CONSOLE_UART_NUM;
const int uart_baud = CONFIG_CONSOLE_UART_BAUDRATE;
uart_div_modify(uart_num, (rtc_clk_apb_freq_get() << 4) / uart_baud);
freq_changed = rtc_clk_cpu_freq_value(rtc_clk_cpu_freq_get()) / MHZ;
printf("CPU_FREQ switching to %d MHz\n", freq_changed);
}
run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000 / ((freq_before_changed <= freq_changed) ? 1 : (freq_before_changed / freq_changed)));
// return old freq.
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
rtc_clk_cpu_freq_set(cur_freq);
const int uart_num = CONFIG_CONSOLE_UART_NUM;
const int uart_baud = CONFIG_CONSOLE_UART_BAUDRATE;
dport_test_result = false;
apb_test_result = false;
rtc_cpu_freq_config_t old_config;
rtc_clk_cpu_freq_get_config(&old_config);
printf("CPU_FREQ = %d MHz\n", old_config.freq_mhz);
if (cpu_freq_mhz != old_config.freq_mhz) {
rtc_cpu_freq_config_t new_config;
bool res = rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &new_config);
assert(res && "invalid frequency value");
uart_tx_wait_idle(uart_num);
rtc_clk_cpu_freq_set_config(&new_config);
uart_div_modify(uart_num, (rtc_clk_apb_freq_get() << 4) / uart_baud);
/* adjust RTOS ticks */
_xt_tick_divisor = cpu_freq_mhz * 1000000 / XT_TICK_PER_SEC;
vTaskDelay(2);
printf("CPU_FREQ switched to %d MHz\n", cpu_freq_mhz);
}
run_tasks("accessDPORT", accessDPORT, "accessAPB", accessAPB, 10000);
// return old freq.
uart_tx_wait_idle(uart_num);
rtc_clk_cpu_freq_set_config(&old_config);
uart_div_modify(uart_num, (rtc_clk_apb_freq_get() << 4) / uart_baud);
_xt_tick_divisor = old_config.freq_mhz * 1000000 / XT_TICK_PER_SEC;
}
TEST_CASE("access DPORT and APB at same time (Freq CPU and APB = 80 MHz)", "[esp32] [ignore]")
{
run_tasks_with_change_freq_cpu(RTC_CPU_FREQ_80M);
run_tasks_with_change_freq_cpu(80);
}
TEST_CASE("access DPORT and APB at same time (Freq CPU and APB = 40 MHz (XTAL))", "[esp32]")
{
run_tasks_with_change_freq_cpu(RTC_CPU_FREQ_XTAL);
run_tasks_with_change_freq_cpu((int) rtc_clk_xtal_freq_get());
}
static uint32_t stall_other_cpu_counter;

View file

@ -2,6 +2,7 @@
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/param.h>
#include "unity.h"
#include "esp_pm.h"
#include "esp_clk.h"
@ -25,19 +26,17 @@ TEST_CASE("Can dump power management lock stats", "[pm]")
static void switch_freq(int mhz)
{
rtc_cpu_freq_t max_freq;
assert(rtc_clk_cpu_freq_from_mhz(mhz, &max_freq));
int xtal_freq = rtc_clk_xtal_freq_get();
esp_pm_config_esp32_t pm_config = {
.max_cpu_freq = max_freq,
.min_cpu_freq = RTC_CPU_FREQ_XTAL,
.max_freq_mhz = mhz,
.min_freq_mhz = MIN(mhz, xtal_freq),
};
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
printf("Waiting for frequency to be set to %d (%d MHz)...\n", max_freq, mhz);
printf("Waiting for frequency to be set to %d MHz...\n", mhz);
while (esp_clk_cpu_freq() / 1000000 != mhz) {
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Frequency is %d MHz\n", esp_clk_cpu_freq());
vTaskDelay(pdMS_TO_TICKS(200));
printf("Frequency is %d MHz\n", esp_clk_cpu_freq() / 1000000);
}
printf("Frequency is set to %d MHz\n", mhz);
}
TEST_CASE("Can switch frequency using esp_pm_configure", "[pm]")
@ -52,6 +51,10 @@ TEST_CASE("Can switch frequency using esp_pm_configure", "[pm]")
switch_freq(240);
switch_freq(40);
switch_freq(80);
switch_freq(10);
switch_freq(80);
switch_freq(20);
switch_freq(40);
switch_freq(orig_freq_mhz);
}

View file

@ -177,13 +177,16 @@ TEST_CASE("light sleep and frequency switching", "[deepsleep]")
uart_div_modify(CONFIG_CONSOLE_UART_NUM, (uart_clk_freq << 4) / CONFIG_CONSOLE_UART_BAUDRATE);
#endif
rtc_cpu_freq_config_t config_xtal, config_default;
rtc_clk_cpu_freq_get_config(&config_default);
rtc_clk_cpu_freq_mhz_to_config((int) rtc_clk_xtal_freq_get(), &config_xtal);
esp_sleep_enable_timer_wakeup(1000);
rtc_cpu_freq_t default_freq = rtc_clk_cpu_freq_get();
for (int i = 0; i < 1000; ++i) {
if (i % 2 == 0) {
rtc_clk_cpu_freq_set_fast(RTC_CPU_FREQ_XTAL);
rtc_clk_cpu_freq_set_config_fast(&config_xtal);
} else {
rtc_clk_cpu_freq_set_fast(default_freq);
rtc_clk_cpu_freq_set_config_fast(&config_default);
}
printf("%d\n", i);
fflush(stdout);

View file

@ -4,3 +4,5 @@ SOC_NAME := esp32
COMPONENT_SRCDIRS := $(SOC_NAME) src/
COMPONENT_ADD_INCLUDEDIRS := $(SOC_NAME)/include include
-include $(COMPONENT_PATH)/$(SOC_NAME)/component.mk

View file

@ -0,0 +1 @@
esp32/rtc_clk.o: CFLAGS += -fno-jump-tables -fno-tree-switch-conversion

View file

@ -75,6 +75,26 @@ typedef enum {
RTC_CPU_FREQ_2M = 4, //!< 2 MHz
} rtc_cpu_freq_t;
/**
* @brief CPU clock source
*/
typedef enum {
RTC_CPU_FREQ_SRC_XTAL, //!< XTAL
RTC_CPU_FREQ_SRC_PLL, //!< PLL (480M or 320M)
RTC_CPU_FREQ_SRC_8M, //!< Internal 8M RTC oscillator
RTC_CPU_FREQ_SRC_APLL //!< APLL
} rtc_cpu_freq_src_t;
/**
* @brief CPU clock configuration structure
*/
typedef struct {
rtc_cpu_freq_src_t source; //!< The clock from which CPU clock is derived
uint32_t source_freq_mhz; //!< Source clock frequency
uint32_t div; //!< Divider, freq_mhz = source_freq_mhz / div
uint32_t freq_mhz; //!< CPU clock frequency
} rtc_cpu_freq_config_t;
/**
* @brief RTC SLOW_CLK frequency values
*/
@ -108,13 +128,13 @@ typedef enum {
* Initialization parameters for rtc_clk_init
*/
typedef struct {
rtc_xtal_freq_t xtal_freq : 8; //!< Main XTAL frequency
rtc_cpu_freq_t cpu_freq : 3; //!< CPU frequency to set
rtc_fast_freq_t fast_freq : 1; //!< RTC_FAST_CLK frequency to set
rtc_slow_freq_t slow_freq : 2; //!< RTC_SLOW_CLK frequency to set
uint32_t clk_8m_div : 3; //!< RTC 8M clock divider (division is by clk_8m_div+1, i.e. 0 means 8MHz frequency)
uint32_t slow_clk_dcap : 8; //!< RTC 150k clock adjustment parameter (higher value leads to lower frequency)
uint32_t clk_8m_dfreq : 8; //!< RTC 8m clock adjustment parameter (higher value leads to higher frequency)
rtc_xtal_freq_t xtal_freq : 8; //!< Main XTAL frequency
rtc_cpu_freq_t cpu_freq_mhz : 10; //!< CPU frequency to set, in MHz
rtc_fast_freq_t fast_freq : 1; //!< RTC_FAST_CLK frequency to set
rtc_slow_freq_t slow_freq : 2; //!< RTC_SLOW_CLK frequency to set
uint32_t clk_8m_div : 3; //!< RTC 8M clock divider (division is by clk_8m_div+1, i.e. 0 means 8MHz frequency)
uint32_t slow_clk_dcap : 8; //!< RTC 150k clock adjustment parameter (higher value leads to lower frequency)
uint32_t clk_8m_dfreq : 8; //!< RTC 8m clock adjustment parameter (higher value leads to higher frequency)
} rtc_clk_config_t;
/**
@ -122,7 +142,7 @@ typedef struct {
*/
#define RTC_CLK_CONFIG_DEFAULT() { \
.xtal_freq = RTC_XTAL_FREQ_AUTO, \
.cpu_freq = RTC_CPU_FREQ_80M, \
.cpu_freq_mhz = 80, \
.fast_freq = RTC_FAST_FREQ_8M, \
.slow_freq = RTC_SLOW_FREQ_RTC, \
.clk_8m_div = 0, \
@ -281,6 +301,9 @@ rtc_fast_freq_t rtc_clk_fast_freq_get();
/**
* @brief Switch CPU frequency
*
* @note This function is deprecated and will be removed.
* See rtc_clk_cpu_freq_config_set instead.
*
* If a PLL-derived frequency is requested (80, 160, 240 MHz), this function
* will enable the PLL. Otherwise, PLL will be disabled.
* Note: this function is not optimized for switching speed. It may take several
@ -288,11 +311,14 @@ rtc_fast_freq_t rtc_clk_fast_freq_get();
*
* @param cpu_freq new CPU frequency
*/
void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq);
void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq) __attribute__((deprecated));
/**
* @brief Switch CPU frequency
*
* @note This function is deprecated and will be removed.
* See rtc_clk_cpu_freq_set_config_fast instead.
*
* This is a faster version of rtc_clk_cpu_freq_set, which can handle some of
* the frequency switch paths (XTAL -> PLL, PLL -> XTAL).
* When switching from PLL to XTAL, PLL is not disabled (unlike rtc_clk_cpu_freq_set).
@ -307,11 +333,14 @@ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq);
*
* @param cpu_freq new CPU frequency
*/
void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq);
void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq) __attribute__((deprecated));
/**
* @brief Get the currently selected CPU frequency
*
* @note This function is deprecated and will be removed.
* See rtc_clk_cpu_freq_get_config instead.
*
* Although CPU can be clocked by APLL and RTC 8M sources, such support is not
* exposed through this library. As such, this function will not return
* meaningful values when these clock sources are configured (e.g. using direct
@ -320,22 +349,97 @@ void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq);
*
* @return CPU frequency (one of rtc_cpu_freq_t values)
*/
rtc_cpu_freq_t rtc_clk_cpu_freq_get();
rtc_cpu_freq_t rtc_clk_cpu_freq_get() __attribute__((deprecated));
/**
* @brief Get corresponding frequency value for rtc_cpu_freq_t enum value
*
* @note This function is deprecated and will be removed.
* See rtc_clk_cpu_freq_get/set_config instead.
*
* @param cpu_freq CPU frequency, on of rtc_cpu_freq_t values
* @return CPU frequency, in HZ
*/
uint32_t rtc_clk_cpu_freq_value(rtc_cpu_freq_t cpu_freq);
uint32_t rtc_clk_cpu_freq_value(rtc_cpu_freq_t cpu_freq) __attribute__((deprecated));
/**
* @brief Get rtc_cpu_freq_t enum value for given CPU frequency
*
* @note This function is deprecated and will be removed.
* See rtc_clk_cpu_freq_mhz_to_config instead.
*
* @param cpu_freq_mhz CPU frequency, one of 80, 160, 240, 2, and XTAL frequency
* @param[out] out_val output, rtc_cpu_freq_t value corresponding to the frequency
* @return true if the given frequency value matches one of enum values
*/
bool rtc_clk_cpu_freq_from_mhz(int cpu_freq_mhz, rtc_cpu_freq_t* out_val);
bool rtc_clk_cpu_freq_from_mhz(int cpu_freq_mhz, rtc_cpu_freq_t* out_val) __attribute__((deprecated));
/**
* @brief Get CPU frequency config corresponding to a rtc_cpu_freq_t value
* @param cpu_freq CPU frequency enumeration value
* @param[out] out_config Output, CPU frequency configuration structure
*/
void rtc_clk_cpu_freq_to_config(rtc_cpu_freq_t cpu_freq, rtc_cpu_freq_config_t* out_config);
/**
* @brief Get CPU frequency config for a given frequency
* @param freq_mhz Frequency in MHz
* @param[out] out_config Output, CPU frequency configuration structure
* @return true if frequency can be obtained, false otherwise
*/
bool rtc_clk_cpu_freq_mhz_to_config(uint32_t freq_mhz, rtc_cpu_freq_config_t* out_config);
/**
* @brief Switch CPU frequency
*
* This function sets CPU frequency according to the given configuration
* structure. It enables PLLs, if necessary.
*
* @note This function in not intended to be called by applications in FreeRTOS
* environment. This is because it does not adjust various timers based on the
* new CPU frequency.
*
* @param config CPU frequency configuration structure
*/
void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t* config);
/**
* @brief Switch CPU frequency (optimized for speed)
*
* This function is a faster equivalent of rtc_clk_cpu_freq_set_config.
* It works faster because it does not disable PLLs when switching from PLL to
* XTAL and does not enabled them when switching back. If PLL is not already
* enabled when this function is called to switch from XTAL to PLL frequency,
* or the PLL which is enabled is the wrong one, this function will fall back
* to calling rtc_clk_cpu_freq_set_config.
*
* Unlike rtc_clk_cpu_freq_set_config, this function relies on static data,
* so it is less safe to use it e.g. from a panic handler (when memory might
* be corrupted).
*
* @note This function in not intended to be called by applications in FreeRTOS
* environment. This is because it does not adjust various timers based on the
* new CPU frequency.
*
* @param config CPU frequency configuration structure
*/
void rtc_clk_cpu_freq_set_config_fast(const rtc_cpu_freq_config_t* config);
/**
* @brief Get the currently used CPU frequency configuration
* @param[out] out_config Output, CPU frequency configuration structure
*/
void rtc_clk_cpu_freq_get_config(rtc_cpu_freq_config_t* out_config);
/**
* @brief Switch CPU clock source to XTAL
*
* Short form for filling in rtc_cpu_freq_config_t structure and calling
* rtc_clk_cpu_freq_set_config when a switch to XTAL is needed.
* Assumes that XTAL frequency has been determined  don't call in startup code.
*/
void rtc_clk_cpu_freq_set_xtal();
/**
* @brief Store new APB frequency value into RTC_APB_FREQ_REG

View file

@ -16,6 +16,7 @@
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include "rom/ets_sys.h"
#include "rom/rtc.h"
#include "rom/uart.h"
@ -31,9 +32,7 @@
#include "soc_log.h"
#include "sdkconfig.h"
#include "xtensa/core-macros.h"
#define MHZ (1000000)
#include "rtc_clk_common.h"
/* Frequency of the 8M oscillator is 8.5MHz +/- 5%, at the default DCAP setting */
#define RTC_FAST_CLK_FREQ_8M 8500000
@ -41,12 +40,6 @@
#define RTC_SLOW_CLK_FREQ_8MD256 (RTC_FAST_CLK_FREQ_8M / 256)
#define RTC_SLOW_CLK_FREQ_32K 32768
static const char* TAG = "rtc_clk";
/* Various constants related to the analog internals of the chip.
* Defined here because they don't have any use outside of this file.
*/
#define BBPLL_ENDIV5_VAL_320M 0x43
#define BBPLL_BBADC_DSMP_VAL_320M 0x84
#define BBPLL_ENDIV5_VAL_480M 0xc3
@ -80,11 +73,6 @@ static const char* TAG = "rtc_clk";
#define DELAY_SLOW_CLK_SWITCH 300
#define DELAY_8M_ENABLE 50
/* Number of 8M/256 clock cycles to use for XTAL frequency estimation.
* 10 cycles will take approximately 300 microseconds.
*/
#define XTAL_FREQ_EST_CYCLES 10
/* Core voltage needs to be increased in two cases:
* 1. running at 240 MHz
* 2. running with 80MHz Flash frequency
@ -98,17 +86,17 @@ static const char* TAG = "rtc_clk";
#define DIG_DBIAS_XTAL RTC_CNTL_DBIAS_1V10
#define DIG_DBIAS_2M RTC_CNTL_DBIAS_1V00
/* PLL currently enabled, if any */
typedef enum {
RTC_PLL_NONE,
RTC_PLL_320M,
RTC_PLL_480M
} rtc_pll_t;
static rtc_pll_t s_cur_pll = RTC_PLL_NONE;
#define RTC_PLL_FREQ_320M 320
#define RTC_PLL_FREQ_480M 480
/* Current CPU frequency; saved in a variable for faster freq. switching */
static rtc_cpu_freq_t s_cur_freq = RTC_CPU_FREQ_XTAL;
static void rtc_clk_cpu_freq_to_8m();
static void rtc_clk_bbpll_disable();
static void rtc_clk_bbpll_enable();
static void rtc_clk_cpu_freq_to_pll_mhz(int cpu_freq_mhz);
static bool rtc_clk_cpu_freq_from_mhz_internal(int mhz, rtc_cpu_freq_t* out_val);
// Current PLL frequency, in MHZ (320 or 480). Zero if PLL is not enabled.
static int s_cur_pll_freq;
static void rtc_clk_32k_enable_internal(int dac, int dres, int dbias)
{
@ -275,7 +263,7 @@ rtc_fast_freq_t rtc_clk_fast_freq_get()
return REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_FAST_CLK_RTC_SEL);
}
void rtc_clk_bbpll_set(rtc_xtal_freq_t xtal_freq, rtc_cpu_freq_t cpu_freq)
void rtc_clk_bbpll_configure(rtc_xtal_freq_t xtal_freq, int pll_freq)
{
uint8_t div_ref;
uint8_t div7_0;
@ -284,7 +272,7 @@ void rtc_clk_bbpll_set(rtc_xtal_freq_t xtal_freq, rtc_cpu_freq_t cpu_freq)
uint8_t dcur;
uint8_t bw;
if (cpu_freq != RTC_CPU_FREQ_240M) {
if (pll_freq == RTC_PLL_FREQ_320M) {
/* Raise the voltage, if needed */
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_80M_160M);
/* Configure 320M PLL */
@ -376,96 +364,47 @@ void rtc_clk_bbpll_set(rtc_xtal_freq_t xtal_freq, rtc_cpu_freq_t cpu_freq)
uint32_t delay_pll_en = (rtc_clk_slow_freq_get() == RTC_SLOW_FREQ_RTC) ?
DELAY_PLL_ENABLE_WITH_150K : DELAY_PLL_ENABLE_WITH_32K;
ets_delay_us(delay_pll_en);
s_cur_pll_freq = pll_freq;
}
/**
* Switch to XTAL frequency. Does not disable the PLL.
*/
static void rtc_clk_cpu_freq_to_xtal()
void rtc_clk_cpu_freq_to_xtal(int freq, int div)
{
rtc_xtal_freq_t xtal_freq = rtc_clk_xtal_freq_get();
ets_update_cpu_frequency(xtal_freq);
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_XTAL);
REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, 0);
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_XTL);
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 0); // clear DPORT_CPUPERIOD_SEL
rtc_clk_apb_freq_update(xtal_freq * MHZ);
s_cur_freq = RTC_CPU_FREQ_XTAL;
}
/**
* Switch to one of PLL-based frequencies. Current frequency can be XTAL or PLL.
* PLL must already be enabled.
* If switching between frequencies derived from different PLLs (320M and 480M),
* fall back to rtc_clk_cpu_freq_set.
* @param cpu_freq new CPU frequency
*/
static void rtc_clk_cpu_freq_to_pll(rtc_cpu_freq_t cpu_freq)
{
int freq = 0;
if (s_cur_pll == RTC_PLL_NONE ||
(cpu_freq == RTC_CPU_FREQ_240M && s_cur_pll == RTC_PLL_320M) ||
(cpu_freq != RTC_CPU_FREQ_240M && s_cur_pll == RTC_PLL_480M)) {
/* need to switch PLLs, fall back to full implementation */
rtc_clk_cpu_freq_set(cpu_freq);
return;
}
if (cpu_freq == RTC_CPU_FREQ_80M) {
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_80M_160M);
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 0);
freq = 80;
} else if (cpu_freq == RTC_CPU_FREQ_160M) {
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_80M_160M);
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 1);
freq = 160;
} else if (cpu_freq == RTC_CPU_FREQ_240M) {
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_240M);
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 2);
freq = 240;
}
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL);
rtc_clk_apb_freq_update(80 * MHZ);
ets_update_cpu_frequency(freq);
s_cur_freq = cpu_freq;
}
void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq)
{
if (cpu_freq == s_cur_freq) {
return;
} else if (cpu_freq == RTC_CPU_FREQ_2M || s_cur_freq == RTC_CPU_FREQ_2M) {
/* fall back to full implementation if switch to/from 2M is needed */
rtc_clk_cpu_freq_set(cpu_freq);
} else if (cpu_freq == RTC_CPU_FREQ_XTAL) {
rtc_clk_cpu_freq_to_xtal();
} else if (cpu_freq > RTC_CPU_FREQ_XTAL) {
rtc_clk_cpu_freq_to_pll(cpu_freq);
rtc_clk_wait_for_slow_cycle();
/* set divider from XTAL to APB clock */
REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, div - 1);
/* adjust ref_tick */
REG_WRITE(APB_CTRL_XTAL_TICK_CONF_REG, freq * MHZ / REF_CLK_FREQ - 1);
/* switch clock source */
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_XTL);
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 0); /* clear DPORT_CPUPERIOD_SEL */
rtc_clk_apb_freq_update(freq * MHZ);
/* lower the voltage */
if (freq <= 2) {
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_2M);
} else {
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_XTAL);
}
}
void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq)
static void rtc_clk_cpu_freq_to_8m()
{
rtc_xtal_freq_t xtal_freq = rtc_clk_xtal_freq_get();
/* Switch CPU to XTAL frequency first */
ets_update_cpu_frequency(8);
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_XTAL);
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_XTL);
REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, 0);
ets_update_cpu_frequency(xtal_freq);
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_8M);
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 0); // clear DPORT_CPUPERIOD_SEL
rtc_clk_apb_freq_update(RTC_FAST_CLK_FREQ_8M);
}
/* Frequency switch is synchronized to SLOW_CLK cycle. Wait until the switch
* is complete before disabling the PLL.
*/
rtc_clk_wait_for_slow_cycle();
DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 0);
static void rtc_clk_bbpll_disable()
{
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG,
RTC_CNTL_BB_I2C_FORCE_PD | RTC_CNTL_BBPLL_FORCE_PD |
RTC_CNTL_BBPLL_I2C_FORCE_PD);
s_cur_pll = RTC_PLL_NONE;
rtc_clk_apb_freq_update(xtal_freq * MHZ);
s_cur_pll_freq = 0;
/* is APLL under force power down? */
uint32_t apll_fpd = REG_GET_FIELD(RTC_CNTL_ANA_CONF_REG, RTC_CNTL_PLLA_FORCE_PD);
@ -473,76 +412,73 @@ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq)
/* then also power down the internal I2C bus */
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_I2C_FORCE_PD);
}
/* now switch to the desired frequency */
if (cpu_freq == RTC_CPU_FREQ_XTAL) {
/* already at XTAL, nothing to do */
} else if (cpu_freq == RTC_CPU_FREQ_2M) {
/* set up divider to produce 2MHz from XTAL */
REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, (xtal_freq / 2) - 1);
ets_update_cpu_frequency(2);
rtc_clk_apb_freq_update(2 * MHZ);
/* lower the voltage */
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_2M);
}
static void rtc_clk_bbpll_enable()
{
CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG,
RTC_CNTL_BIAS_I2C_FORCE_PD | RTC_CNTL_BB_I2C_FORCE_PD |
RTC_CNTL_BBPLL_FORCE_PD | RTC_CNTL_BBPLL_I2C_FORCE_PD);
}
/**
* Switch to one of PLL-based frequencies. Current frequency can be XTAL or PLL.
* PLL must already be enabled.
* @param cpu_freq new CPU frequency
*/
static void rtc_clk_cpu_freq_to_pll_mhz(int cpu_freq_mhz)
{
int dbias = DIG_DBIAS_80M_160M;
int per_conf = 0;
if (cpu_freq_mhz == 80) {
/* nothing to do */
} else if (cpu_freq_mhz == 160) {
per_conf = 1;
} else if (cpu_freq_mhz == 240) {
dbias = DIG_DBIAS_240M;
per_conf = 2;
} else {
/* use PLL as clock source */
CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG,
RTC_CNTL_BIAS_I2C_FORCE_PD | RTC_CNTL_BB_I2C_FORCE_PD |
RTC_CNTL_BBPLL_FORCE_PD | RTC_CNTL_BBPLL_I2C_FORCE_PD);
rtc_clk_bbpll_set(xtal_freq, cpu_freq);
if (cpu_freq == RTC_CPU_FREQ_80M) {
DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 0);
ets_update_cpu_frequency(80);
s_cur_pll = RTC_PLL_320M;
} else if (cpu_freq == RTC_CPU_FREQ_160M) {
DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 1);
ets_update_cpu_frequency(160);
s_cur_pll = RTC_PLL_320M;
} else if (cpu_freq == RTC_CPU_FREQ_240M) {
DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 2);
ets_update_cpu_frequency(240);
s_cur_pll = RTC_PLL_480M;
}
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL);
rtc_clk_wait_for_slow_cycle();
rtc_clk_apb_freq_update(80 * MHZ);
assert(false && "invalid frequency");
}
s_cur_freq = cpu_freq;
DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, per_conf);
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, dbias);
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL);
rtc_clk_apb_freq_update(80 * MHZ);
ets_update_cpu_frequency(cpu_freq_mhz);
rtc_clk_wait_for_slow_cycle();
}
void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq)
{
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_to_config(cpu_freq, &config);
rtc_clk_cpu_freq_set_config(&config);
}
void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq)
{
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_to_config(cpu_freq, &config);
rtc_clk_cpu_freq_set_config_fast(&config);
}
void rtc_clk_cpu_freq_set_xtal()
{
int freq_mhz = (int) rtc_clk_xtal_freq_get();
rtc_clk_cpu_freq_to_xtal(freq_mhz, 1);
rtc_clk_wait_for_slow_cycle();
rtc_clk_bbpll_disable();
}
rtc_cpu_freq_t rtc_clk_cpu_freq_get()
{
uint32_t soc_clk_sel = REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL);
switch (soc_clk_sel) {
case RTC_CNTL_SOC_CLK_SEL_XTL: {
uint32_t pre_div = REG_GET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT);
if (pre_div == 0) {
return RTC_CPU_FREQ_XTAL;
} else if (pre_div == rtc_clk_xtal_freq_get() / 2 - 1) {
return RTC_CPU_FREQ_2M;
} else {
assert(false && "unsupported frequency");
}
break;
}
case RTC_CNTL_SOC_CLK_SEL_PLL: {
uint32_t cpuperiod_sel = DPORT_REG_GET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL);
if (cpuperiod_sel == 0) {
return RTC_CPU_FREQ_80M;
} else if (cpuperiod_sel == 1) {
return RTC_CPU_FREQ_160M;
} else if (cpuperiod_sel == 2) {
return RTC_CPU_FREQ_240M;
} else {
assert(false && "unsupported frequency");
}
break;
}
case RTC_CNTL_SOC_CLK_SEL_APLL:
case RTC_CNTL_SOC_CLK_SEL_8M:
default:
assert(false && "unsupported frequency");
}
return RTC_CNTL_SOC_CLK_SEL_XTL;
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_get_config(&config);
rtc_cpu_freq_t freq;
rtc_clk_cpu_freq_from_mhz_internal(config.freq_mhz, &freq);
return freq;
}
uint32_t rtc_clk_cpu_freq_value(rtc_cpu_freq_t cpu_freq)
@ -564,7 +500,7 @@ uint32_t rtc_clk_cpu_freq_value(rtc_cpu_freq_t cpu_freq)
}
}
bool rtc_clk_cpu_freq_from_mhz(int mhz, rtc_cpu_freq_t* out_val)
static bool rtc_clk_cpu_freq_from_mhz_internal(int mhz, rtc_cpu_freq_t* out_val)
{
if (mhz == 240) {
*out_val = RTC_CPU_FREQ_240M;
@ -582,22 +518,197 @@ bool rtc_clk_cpu_freq_from_mhz(int mhz, rtc_cpu_freq_t* out_val)
return true;
}
/* Values of RTC_XTAL_FREQ_REG and RTC_APB_FREQ_REG are stored as two copies in
* lower and upper 16-bit halves. These are the routines to work with such a
* representation.
*/
static bool clk_val_is_valid(uint32_t val) {
return (val & 0xffff) == ((val >> 16) & 0xffff) &&
val != 0 &&
val != UINT32_MAX;
bool rtc_clk_cpu_freq_from_mhz(int mhz, rtc_cpu_freq_t* out_val)
{
return rtc_clk_cpu_freq_from_mhz_internal(mhz, out_val);
}
static uint32_t reg_val_to_clk_val(uint32_t val) {
return val & UINT16_MAX;
void rtc_clk_cpu_freq_to_config(rtc_cpu_freq_t cpu_freq, rtc_cpu_freq_config_t* out_config)
{
uint32_t source_freq_mhz;
rtc_cpu_freq_src_t source;
uint32_t freq_mhz;
uint32_t divider;
switch (cpu_freq) {
case RTC_CPU_FREQ_XTAL:
case RTC_CPU_FREQ_2M:
source_freq_mhz = rtc_clk_xtal_freq_get();
source = RTC_CPU_FREQ_SRC_XTAL;
if (cpu_freq == RTC_CPU_FREQ_2M) {
freq_mhz = 2;
divider = out_config->source_freq_mhz / 2;
} else {
freq_mhz = source_freq_mhz;
divider = 1;
}
break;
case RTC_CPU_FREQ_80M:
source = RTC_CPU_FREQ_SRC_PLL;
source_freq_mhz = RTC_PLL_FREQ_320M;
divider = 4;
freq_mhz = 80;
break;
case RTC_CPU_FREQ_160M:
source = RTC_CPU_FREQ_SRC_PLL;
source_freq_mhz = RTC_PLL_FREQ_320M;
divider = 2;
freq_mhz = 160;
break;
case RTC_CPU_FREQ_240M:
source = RTC_CPU_FREQ_SRC_PLL;
source_freq_mhz = RTC_PLL_FREQ_480M;
divider = 2;
freq_mhz = 240;
break;
default:
assert(false && "invalid rtc_cpu_freq_t value");
abort();
}
*out_config = (rtc_cpu_freq_config_t) {
.source = source,
.source_freq_mhz = source_freq_mhz,
.div = divider,
.freq_mhz = freq_mhz
};
}
static uint32_t clk_val_to_reg_val(uint32_t val) {
return (val & UINT16_MAX) | ((val & UINT16_MAX) << 16);
bool rtc_clk_cpu_freq_mhz_to_config(uint32_t freq_mhz, rtc_cpu_freq_config_t* out_config)
{
uint32_t source_freq_mhz;
rtc_cpu_freq_src_t source;
uint32_t divider;
uint32_t real_freq_mhz;
uint32_t xtal_freq = (uint32_t) rtc_clk_xtal_freq_get();
if (freq_mhz <= xtal_freq) {
divider = xtal_freq / freq_mhz;
real_freq_mhz = (xtal_freq + divider / 2) / divider; /* round */
if (real_freq_mhz != freq_mhz) {
// no suitable divider
return false;
}
source_freq_mhz = xtal_freq;
source = RTC_CPU_FREQ_SRC_XTAL;
} else if (freq_mhz == 80) {
real_freq_mhz = freq_mhz;
source = RTC_CPU_FREQ_SRC_PLL;
source_freq_mhz = RTC_PLL_FREQ_320M;
divider = 4;
} else if (freq_mhz == 160) {
real_freq_mhz = freq_mhz;
source = RTC_CPU_FREQ_SRC_PLL;
source_freq_mhz = RTC_PLL_FREQ_320M;
divider = 2;
} else if (freq_mhz == 240) {
real_freq_mhz = freq_mhz;
source = RTC_CPU_FREQ_SRC_PLL;
source_freq_mhz = RTC_PLL_FREQ_480M;
divider = 2;
} else {
// unsupported frequency
return false;
}
*out_config = (rtc_cpu_freq_config_t) {
.source = source,
.div = divider,
.source_freq_mhz = source_freq_mhz,
.freq_mhz = real_freq_mhz
};
return true;
}
void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t* config)
{
rtc_xtal_freq_t xtal_freq = rtc_clk_xtal_freq_get();
uint32_t soc_clk_sel = REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL);
if (soc_clk_sel != RTC_CNTL_SOC_CLK_SEL_XTL) {
rtc_clk_cpu_freq_to_xtal(xtal_freq, 1);
rtc_clk_wait_for_slow_cycle();
}
if (soc_clk_sel == RTC_CNTL_SOC_CLK_SEL_PLL) {
rtc_clk_bbpll_disable();
}
if (config->source == RTC_CPU_FREQ_SRC_XTAL) {
if (config->div > 1) {
rtc_clk_cpu_freq_to_xtal(config->freq_mhz, config->div);
}
} else if (config->source == RTC_CPU_FREQ_SRC_PLL) {
rtc_clk_bbpll_enable();
rtc_clk_wait_for_slow_cycle();
rtc_clk_bbpll_configure(rtc_clk_xtal_freq_get(), config->source_freq_mhz);
rtc_clk_cpu_freq_to_pll_mhz(config->freq_mhz);
} else if (config->source == RTC_CPU_FREQ_SRC_8M) {
rtc_clk_cpu_freq_to_8m();
}
}
void rtc_clk_cpu_freq_get_config(rtc_cpu_freq_config_t* out_config)
{
rtc_cpu_freq_src_t source;
uint32_t source_freq_mhz;
uint32_t div;
uint32_t freq_mhz;
uint32_t soc_clk_sel = REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL);
switch (soc_clk_sel) {
case RTC_CNTL_SOC_CLK_SEL_XTL: {
source = RTC_CPU_FREQ_SRC_XTAL;
div = REG_GET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT) + 1;
source_freq_mhz = (uint32_t) rtc_clk_xtal_freq_get();
freq_mhz = source_freq_mhz / div;
}
break;
case RTC_CNTL_SOC_CLK_SEL_PLL: {
source = RTC_CPU_FREQ_SRC_PLL;
uint32_t cpuperiod_sel = DPORT_REG_GET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL);
if (cpuperiod_sel == 0) {
source_freq_mhz = RTC_PLL_FREQ_320M;
div = 4;
freq_mhz = 80;
} else if (cpuperiod_sel == 1) {
source_freq_mhz = RTC_PLL_FREQ_320M;
div = 2;
freq_mhz = 160;
} else if (cpuperiod_sel == 2) {
source_freq_mhz = RTC_PLL_FREQ_480M;
div = 2;
freq_mhz = 240;
} else {
assert(false && "unsupported frequency configuration");
}
break;
}
case RTC_CNTL_SOC_CLK_SEL_8M:
source = RTC_CPU_FREQ_SRC_8M;
source_freq_mhz = 8;
div = 1;
freq_mhz = source_freq_mhz;
break;
case RTC_CNTL_SOC_CLK_SEL_APLL:
default:
assert(false && "unsupported frequency configuration");
}
*out_config = (rtc_cpu_freq_config_t) {
.source = source,
.source_freq_mhz = source_freq_mhz,
.div = div,
.freq_mhz = freq_mhz
};
}
void rtc_clk_cpu_freq_set_config_fast(const rtc_cpu_freq_config_t* config)
{
if (config->source == RTC_CPU_FREQ_SRC_XTAL) {
rtc_clk_cpu_freq_to_xtal(config->freq_mhz, config->div);
} else if (config->source == RTC_CPU_FREQ_SRC_PLL &&
s_cur_pll_freq == config->source_freq_mhz) {
rtc_clk_cpu_freq_to_pll_mhz(config->freq_mhz);
} else {
/* fallback */
rtc_clk_cpu_freq_set_config(config);
}
}
rtc_xtal_freq_t rtc_clk_xtal_freq_get()
@ -605,7 +716,6 @@ rtc_xtal_freq_t rtc_clk_xtal_freq_get()
/* We may have already written XTAL value into RTC_XTAL_FREQ_REG */
uint32_t xtal_freq_reg = READ_PERI_REG(RTC_XTAL_FREQ_REG);
if (!clk_val_is_valid(xtal_freq_reg)) {
SOC_LOGW(TAG, "invalid RTC_XTAL_FREQ_REG value: 0x%08x", xtal_freq_reg);
return RTC_XTAL_FREQ_AUTO;
}
return reg_val_to_clk_val(xtal_freq_reg);
@ -616,42 +726,6 @@ void rtc_clk_xtal_freq_update(rtc_xtal_freq_t xtal_freq)
WRITE_PERI_REG(RTC_XTAL_FREQ_REG, clk_val_to_reg_val(xtal_freq));
}
static rtc_xtal_freq_t rtc_clk_xtal_freq_estimate()
{
/* Enable 8M/256 clock if needed */
const bool clk_8m_enabled = rtc_clk_8m_enabled();
const bool clk_8md256_enabled = rtc_clk_8md256_enabled();
if (!clk_8md256_enabled) {
rtc_clk_8m_enable(true, true);
}
uint64_t cal_val = rtc_clk_cal_ratio(RTC_CAL_8MD256, XTAL_FREQ_EST_CYCLES);
/* cal_val contains period of 8M/256 clock in XTAL clock cycles
* (shifted by RTC_CLK_CAL_FRACT bits).
* Xtal frequency will be (cal_val * 8M / 256) / 2^19
*/
uint32_t freq_mhz = (cal_val * RTC_FAST_CLK_FREQ_APPROX / MHZ / 256 ) >> RTC_CLK_CAL_FRACT;
/* Guess the XTAL type. For now, only 40 and 26MHz are supported.
*/
switch (freq_mhz) {
case 21 ... 31:
return RTC_XTAL_FREQ_26M;
case 32 ... 33:
SOC_LOGW(TAG, "Potentially bogus XTAL frequency: %d MHz, guessing 26 MHz", freq_mhz);
return RTC_XTAL_FREQ_26M;
case 34 ... 35:
SOC_LOGW(TAG, "Potentially bogus XTAL frequency: %d MHz, guessing 40 MHz", freq_mhz);
return RTC_XTAL_FREQ_40M;
case 36 ... 45:
return RTC_XTAL_FREQ_40M;
default:
SOC_LOGW(TAG, "Bogus XTAL frequency: %d MHz", freq_mhz);
return RTC_XTAL_FREQ_AUTO;
}
/* Restore 8M and 8md256 clocks to original state */
rtc_clk_8m_enable(clk_8m_enabled, clk_8md256_enabled);
}
void rtc_clk_apb_freq_update(uint32_t apb_freq)
{
WRITE_PERI_REG(RTC_APB_FREQ_REG, clk_val_to_reg_val(apb_freq >> 12));
@ -666,90 +740,6 @@ uint32_t rtc_clk_apb_freq_get()
return freq_hz - remainder;
}
void rtc_clk_init(rtc_clk_config_t cfg)
{
rtc_cpu_freq_t cpu_source_before = rtc_clk_cpu_freq_get();
/* If we get a TG WDT system reset while running at 240MHz,
* DPORT_CPUPERIOD_SEL register will be reset to 0 resulting in 120MHz
* APB and CPU frequencies after reset. This will cause issues with XTAL
* frequency estimation, so we switch to XTAL frequency first.
*
* Ideally we would only do this if RTC_CNTL_SOC_CLK_SEL == PLL and
* PLL is configured for 480M, but it takes less time to switch to 40M and
* run the following code than querying the PLL does.
*/
if (REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL) == RTC_CNTL_SOC_CLK_SEL_PLL) {
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_XTAL);
}
/* Set tuning parameters for 8M and 150k clocks.
* Note: this doesn't attempt to set the clocks to precise frequencies.
* Instead, we calibrate these clocks against XTAL frequency later, when necessary.
* - SCK_DCAP value controls tuning of 150k clock.
* The higher the value of DCAP is, the lower is the frequency.
* - CK8M_DFREQ value controls tuning of 8M clock.
* CLK_8M_DFREQ constant gives the best temperature characteristics.
*/
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_SCK_DCAP, cfg.slow_clk_dcap);
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DFREQ, cfg.clk_8m_dfreq);
/* Configure 8M clock division */
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, cfg.clk_8m_div);
/* Enable the internal bus used to configure PLLs */
SET_PERI_REG_BITS(ANA_CONFIG_REG, ANA_CONFIG_M, ANA_CONFIG_M, ANA_CONFIG_S);
CLEAR_PERI_REG_MASK(ANA_CONFIG_REG, I2C_APLL_M | I2C_BBPLL_M);
/* Estimate XTAL frequency */
rtc_xtal_freq_t xtal_freq = cfg.xtal_freq;
if (xtal_freq == RTC_XTAL_FREQ_AUTO) {
if (clk_val_is_valid(READ_PERI_REG(RTC_XTAL_FREQ_REG))) {
/* XTAL frequency has already been set, use existing value */
xtal_freq = rtc_clk_xtal_freq_get();
} else {
/* Not set yet, estimate XTAL frequency based on RTC_FAST_CLK */
xtal_freq = rtc_clk_xtal_freq_estimate();
if (xtal_freq == RTC_XTAL_FREQ_AUTO) {
SOC_LOGW(TAG, "Can't estimate XTAL frequency, assuming 26MHz");
xtal_freq = RTC_XTAL_FREQ_26M;
}
}
} else if (!clk_val_is_valid(READ_PERI_REG(RTC_XTAL_FREQ_REG))) {
/* Exact frequency was set in sdkconfig, but still warn if autodetected
* frequency is different. If autodetection failed, worst case we get a
* bit of garbage output.
*/
rtc_xtal_freq_t est_xtal_freq = rtc_clk_xtal_freq_estimate();
if (est_xtal_freq != xtal_freq) {
SOC_LOGW(TAG, "Possibly invalid CONFIG_ESP32_XTAL_FREQ setting (%dMHz). Detected %d MHz.",
xtal_freq, est_xtal_freq);
}
}
uart_tx_wait_idle(0);
rtc_clk_xtal_freq_update(xtal_freq);
rtc_clk_apb_freq_update(xtal_freq * MHZ);
/* Set CPU frequency */
rtc_clk_cpu_freq_set(cfg.cpu_freq);
/* Re-calculate the ccount to make time calculation correct. */
uint32_t freq_before = rtc_clk_cpu_freq_value(cpu_source_before) / MHZ;
uint32_t freq_after = rtc_clk_cpu_freq_value(cfg.cpu_freq) / MHZ;
XTHAL_SET_CCOUNT( XTHAL_GET_CCOUNT() * freq_after / freq_before );
/* Slow & fast clocks setup */
if (cfg.slow_freq == RTC_SLOW_FREQ_32K_XTAL) {
rtc_clk_32k_enable(true);
}
if (cfg.fast_freq == RTC_FAST_FREQ_8M) {
bool need_8md256 = cfg.slow_freq == RTC_SLOW_FREQ_8MD256;
rtc_clk_8m_enable(true, need_8md256);
}
rtc_clk_fast_freq_set(cfg.fast_freq);
rtc_clk_slow_freq_set(cfg.slow_freq);
}
/* Name used in libphy.a:phy_chip_v7.o
* TODO: update the library to use rtc_clk_xtal_freq_get
*/

View file

@ -0,0 +1,38 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#define MHZ (1000000)
void rtc_clk_cpu_freq_to_xtal(int freq, int div);
/* Values of RTC_XTAL_FREQ_REG and RTC_APB_FREQ_REG are stored as two copies in
* lower and upper 16-bit halves. These are the routines to work with such a
* representation.
*/
static inline bool clk_val_is_valid(uint32_t val) {
return (val & 0xffff) == ((val >> 16) & 0xffff) &&
val != 0 &&
val != UINT32_MAX;
}
static inline uint32_t reg_val_to_clk_val(uint32_t val) {
return val & UINT16_MAX;
}
static inline uint32_t clk_val_to_reg_val(uint32_t val) {
return (val & UINT16_MAX) | ((val & UINT16_MAX) << 16);
}

View file

@ -0,0 +1,176 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include "rom/ets_sys.h"
#include "rom/rtc.h"
#include "rom/uart.h"
#include "rom/gpio.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/sens_reg.h"
#include "soc/dport_reg.h"
#include "soc/efuse_reg.h"
#include "soc/apb_ctrl_reg.h"
#include "i2c_rtc_clk.h"
#include "soc_log.h"
#include "sdkconfig.h"
#include "xtensa/core-macros.h"
#include "rtc_clk_common.h"
/* Number of 8M/256 clock cycles to use for XTAL frequency estimation.
* 10 cycles will take approximately 300 microseconds.
*/
#define XTAL_FREQ_EST_CYCLES 10
static rtc_xtal_freq_t rtc_clk_xtal_freq_estimate();
static const char* TAG = "rtc_clk_init";
void rtc_clk_init(rtc_clk_config_t cfg)
{
rtc_cpu_freq_config_t old_config, new_config;
/* If we get a TG WDT system reset while running at 240MHz,
* DPORT_CPUPERIOD_SEL register will be reset to 0 resulting in 120MHz
* APB and CPU frequencies after reset. This will cause issues with XTAL
* frequency estimation, so we switch to XTAL frequency first.
*
* Ideally we would only do this if RTC_CNTL_SOC_CLK_SEL == PLL and
* PLL is configured for 480M, but it takes less time to switch to 40M and
* run the following code than querying the PLL does.
*/
if (REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL) == RTC_CNTL_SOC_CLK_SEL_PLL) {
/* We don't know actual XTAL frequency yet, assume 40MHz.
* REF_TICK divider will be corrected below, once XTAL frequency is
* determined.
*/
rtc_clk_cpu_freq_to_xtal(40, 1);
}
/* Set tuning parameters for 8M and 150k clocks.
* Note: this doesn't attempt to set the clocks to precise frequencies.
* Instead, we calibrate these clocks against XTAL frequency later, when necessary.
* - SCK_DCAP value controls tuning of 150k clock.
* The higher the value of DCAP is, the lower is the frequency.
* - CK8M_DFREQ value controls tuning of 8M clock.
* CLK_8M_DFREQ constant gives the best temperature characteristics.
*/
REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_SCK_DCAP, cfg.slow_clk_dcap);
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DFREQ, cfg.clk_8m_dfreq);
/* Configure 8M clock division */
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, cfg.clk_8m_div);
/* Enable the internal bus used to configure PLLs */
SET_PERI_REG_BITS(ANA_CONFIG_REG, ANA_CONFIG_M, ANA_CONFIG_M, ANA_CONFIG_S);
CLEAR_PERI_REG_MASK(ANA_CONFIG_REG, I2C_APLL_M | I2C_BBPLL_M);
/* Estimate XTAL frequency */
rtc_xtal_freq_t xtal_freq = cfg.xtal_freq;
if (xtal_freq == RTC_XTAL_FREQ_AUTO) {
if (clk_val_is_valid(READ_PERI_REG(RTC_XTAL_FREQ_REG))) {
/* XTAL frequency has already been set, use existing value */
xtal_freq = rtc_clk_xtal_freq_get();
} else {
/* Not set yet, estimate XTAL frequency based on RTC_FAST_CLK */
xtal_freq = rtc_clk_xtal_freq_estimate();
if (xtal_freq == RTC_XTAL_FREQ_AUTO) {
SOC_LOGW(TAG, "Can't estimate XTAL frequency, assuming 26MHz");
xtal_freq = RTC_XTAL_FREQ_26M;
}
}
} else if (!clk_val_is_valid(READ_PERI_REG(RTC_XTAL_FREQ_REG))) {
/* Exact frequency was set in sdkconfig, but still warn if autodetected
* frequency is different. If autodetection failed, worst case we get a
* bit of garbage output.
*/
rtc_xtal_freq_t est_xtal_freq = rtc_clk_xtal_freq_estimate();
if (est_xtal_freq != xtal_freq) {
SOC_LOGW(TAG, "Possibly invalid CONFIG_ESP32_XTAL_FREQ setting (%dMHz). Detected %d MHz.",
xtal_freq, est_xtal_freq);
}
}
uart_tx_wait_idle(0);
rtc_clk_xtal_freq_update(xtal_freq);
rtc_clk_apb_freq_update(xtal_freq * MHZ);
/* Set CPU frequency */
rtc_clk_cpu_freq_get_config(&old_config);
uint32_t freq_before = old_config.freq_mhz;
bool res = rtc_clk_cpu_freq_mhz_to_config(cfg.cpu_freq_mhz, &new_config);
assert(res && "invalid CPU frequency value");
/* Configure REF_TICK */
REG_WRITE(APB_CTRL_XTAL_TICK_CONF_REG, xtal_freq - 1);
REG_WRITE(APB_CTRL_PLL_TICK_CONF_REG, APB_CLK_FREQ / MHZ - 1); /* Under PLL, APB frequency is always 80MHz */
/* Re-calculate the ccount to make time calculation correct. */
XTHAL_SET_CCOUNT( XTHAL_GET_CCOUNT() * cfg.cpu_freq_mhz / freq_before );
/* Slow & fast clocks setup */
if (cfg.slow_freq == RTC_SLOW_FREQ_32K_XTAL) {
rtc_clk_32k_enable(true);
}
if (cfg.fast_freq == RTC_FAST_FREQ_8M) {
bool need_8md256 = cfg.slow_freq == RTC_SLOW_FREQ_8MD256;
rtc_clk_8m_enable(true, need_8md256);
}
rtc_clk_fast_freq_set(cfg.fast_freq);
rtc_clk_slow_freq_set(cfg.slow_freq);
}
static rtc_xtal_freq_t rtc_clk_xtal_freq_estimate()
{
/* Enable 8M/256 clock if needed */
const bool clk_8m_enabled = rtc_clk_8m_enabled();
const bool clk_8md256_enabled = rtc_clk_8md256_enabled();
if (!clk_8md256_enabled) {
rtc_clk_8m_enable(true, true);
}
uint64_t cal_val = rtc_clk_cal_ratio(RTC_CAL_8MD256, XTAL_FREQ_EST_CYCLES);
/* cal_val contains period of 8M/256 clock in XTAL clock cycles
* (shifted by RTC_CLK_CAL_FRACT bits).
* Xtal frequency will be (cal_val * 8M / 256) / 2^19
*/
uint32_t freq_mhz = (cal_val * RTC_FAST_CLK_FREQ_APPROX / MHZ / 256 ) >> RTC_CLK_CAL_FRACT;
/* Guess the XTAL type. For now, only 40 and 26MHz are supported.
*/
switch (freq_mhz) {
case 21 ... 31:
return RTC_XTAL_FREQ_26M;
case 32 ... 33:
SOC_LOGW(TAG, "Potentially bogus XTAL frequency: %d MHz, guessing 26 MHz", freq_mhz);
return RTC_XTAL_FREQ_26M;
case 34 ... 35:
SOC_LOGW(TAG, "Potentially bogus XTAL frequency: %d MHz, guessing 40 MHz", freq_mhz);
return RTC_XTAL_FREQ_40M;
case 36 ... 45:
return RTC_XTAL_FREQ_40M;
default:
SOC_LOGW(TAG, "Bogus XTAL frequency: %d MHz", freq_mhz);
return RTC_XTAL_FREQ_AUTO;
}
/* Restore 8M and 8md256 clocks to original state */
rtc_clk_8m_enable(clk_8m_enabled, clk_8md256_enabled);
}

View file

@ -95,7 +95,7 @@ TEST_CASE("Output 8M XTAL clock to GPIO25", "[rtc_clk][ignore]")
pull_out_clk(RTC_IO_DEBUG_SEL0_8M);
}
static void test_clock_switching(void (*switch_func)(rtc_cpu_freq_t))
static void test_clock_switching(void (*switch_func)(const rtc_cpu_freq_config_t* config))
{
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
@ -103,11 +103,16 @@ static void test_clock_switching(void (*switch_func)(rtc_cpu_freq_t))
ref_clock_init();
uint64_t t_start = ref_clock_get();
rtc_cpu_freq_t cur_freq = rtc_clk_cpu_freq_get();
rtc_cpu_freq_config_t cur_config;
rtc_clk_cpu_freq_get_config(&cur_config);
rtc_cpu_freq_config_t xtal_config;
rtc_clk_cpu_freq_mhz_to_config((uint32_t) rtc_clk_xtal_freq_get(), &xtal_config);
int count = 0;
while (ref_clock_get() - t_start < test_duration_sec * 1000000) {
switch_func(RTC_CPU_FREQ_XTAL);
switch_func(cur_freq);
switch_func(&xtal_config);
switch_func(&cur_config);
++count;
}
uint64_t t_end = ref_clock_get();
@ -126,12 +131,12 @@ TEST_CASE("Calculate 8M clock frequency", "[rtc_clk]")
TEST_CASE("Test switching between PLL and XTAL", "[rtc_clk]")
{
test_clock_switching(rtc_clk_cpu_freq_set);
test_clock_switching(rtc_clk_cpu_freq_set_config);
}
TEST_CASE("Test fast switching between PLL and XTAL", "[rtc_clk]")
{
test_clock_switching(rtc_clk_cpu_freq_set_fast);
test_clock_switching(rtc_clk_cpu_freq_set_config_fast);
}
#define COUNT_TEST 3

View file

@ -20,16 +20,25 @@ Power management can be enabled at compile time, using :envvar:`CONFIG_PM_ENABLE
Enabling power management features comes at the cost of increased interrupt latency. Extra latency depends on a number of factors, among which are CPU frequency, single/dual core mode, whether frequency switch needs to be performed or not. Minimal extra latency is 0.2us (when CPU frequency is 240MHz, and frequency scaling is not enabled), maximum extra latency is 40us (when frequency scaling is enabled, and a switch from 40MHz to 80MHz is performed on interrupt entry).
Dynamic frequency scaling (DFS) and automatic light sleep can be enabled in the application by calling :cpp:func:`esp_pm_configure` function. Its argument is a structure defining frequency scaling settings (for ESP32, minimum and maximum CPU frequencies). Alternatively, :envvar:`CONFIG_PM_DFS_INIT_AUTO` option can be enabled in menuconfig. If enabled, maximal CPU frequency is determined by :envvar:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ` setting, and minimal CPU frequency is set to the XTAL frequency.
Dynamic frequency scaling (DFS) and automatic light sleep can be enabled in the application by calling :cpp:func:`esp_pm_configure` function. Its argument is a structure defining frequency scaling settings, cpp:class:`esp_pm_config_esp32_t`. In this structure, 3 fields need to be initialized:
* ``max_freq_mhz`` - Maximal CPU frequency, in MHZ (i.e. frequency used when ``ESP_PM_CPU_FREQ_MAX`` lock is taken). This will usually be set to :envvar:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ`.
* ``min_freq_mhz`` — Minimal CPU frequency, in MHz (i.e. frequency used when only ``ESP_PM_APB_FREQ_MAX`` locks are taken). This can be set to XTAL frequency, or XTAL frequency divided by integer. Note that 10MHz is the lowest frequency at which the default REF_TICK clock of 1MHz can be generated.
* ``light_sleep_enable`` — Whether system should automatically enter light sleep when no locks are taken (``true``/``false``).
.. note::
Automatic light sleep is based on FreeRTOS Tickless Idle functionality. :cpp:func:`esp_pm_configure` will return an `ESP_ERR_NOT_SUPPORTED` error if :envvar:`CONFIG_FREERTOS_USE_TICKLESS_IDLE` option is not enabled in menuconfig, but automatic light sleep is requested.
Automatic light sleep is based on FreeRTOS Tickless Idle functionality. :cpp:func:`esp_pm_configure` will return an `ESP_ERR_NOT_SUPPORTED` error if :envvar:`CONFIG_FREERTOS_USE_TICKLESS_IDLE` option is not enabled in menuconfig, but automatic light sleep is requested.
.. note::
In light sleep, peripherals are clock gated, and interrupts (from GPIOs and internal peripherals) will not be generated. Wakeup source described in :doc:`Sleep Modes <sleep_modes>` documentation can be used to wake from light sleep state. For example, EXT0 and EXT1 wakeup source can be used to wake up from a GPIO.
Alternatively, :envvar:`CONFIG_PM_DFS_INIT_AUTO` option can be enabled in menuconfig. If enabled, maximal CPU frequency is determined by :envvar:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ` setting, and minimal CPU frequency is set to the XTAL frequency.
Power Management Locks
----------------------
@ -58,7 +67,7 @@ When dynamic frequency scaling is enabled, CPU frequency will be switched as fol
1. When ``ESP_PM_CPU_FREQ_MAX`` or ``ESP_PM_APB_FREQ_MAX`` locks are acquired, CPU frequency will be 240 MHz, and APB frequency will be 80 MHz.
2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL).
2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure`.
- If maximal CPU frequency is 160 MHz:
@ -66,13 +75,13 @@ When dynamic frequency scaling is enabled, CPU frequency will be switched as fol
2. When ``ESP_PM_CPU_FREQ_MAX`` is not acquired, but ``ESP_PM_APB_FREQ_MAX`` is, CPU and APB frequencies are set to 80 MHz.
3. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL).
3. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure`.
- If maximal CPU frequency is 80 MHz:
1. When ``ESP_PM_CPU_FREQ_MAX`` or ``ESP_PM_APB_FREQ_MAX`` locks are acquired, CPU and APB frequencies will be 80 MHz.
2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL).
2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure`.
- When none of the locks are aquired, and light sleep is enabled in a call to :cpp:func:`esp_pm_configure`, the system will go into light sleep mode. The duration of light sleep will be determined by:

View file

@ -1,43 +1,96 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config WIFI_LISTEN_INTERVAL
int "WiFi listen interval"
default 3
help
Interval for station to listen to beacon from AP. The unit of listen interval is one beacon interval.
For example, if beacon interval is 100 ms and listen interval is 3, the interval for station to listen
to beacon is 300 ms.
int "WiFi listen interval"
default 3
help
Interval for station to listen to beacon from AP. The unit of listen interval is one beacon interval.
For example, if beacon interval is 100 ms and listen interval is 3, the interval for station to listen
to beacon is 300 ms.
choice POWER_SAVE_MODE
prompt "power save mode"
default POWER_SAVE_MIN_MODEM
help
Power save mode for the esp32 to use. Modem sleep mode includes minimum and maximum power save modes.
In minimum power save mode, station wakes up every DTIM to receive beacon. Broadcast data will not be
lost because it is transmitted after DTIM. However, it can not save much more power if DTIM is short
for DTIM is determined by AP.
In maximum power save mode, station wakes up every listen interval to receive beacon. Broadcast data
may be lost because station may be in sleep state at DTIM time. If listen interval is longer, more power
is saved but broadcast data is more easy to lose.
prompt "power save mode"
default POWER_SAVE_MIN_MODEM
help
Power save mode for the esp32 to use. Modem sleep mode includes minimum and maximum power save modes.
In minimum power save mode, station wakes up every DTIM to receive beacon. Broadcast data will not be
lost because it is transmitted after DTIM. However, it can not save much more power if DTIM is short
for DTIM is determined by AP.
In maximum power save mode, station wakes up every listen interval to receive beacon. Broadcast data
may be lost because station may be in sleep state at DTIM time. If listen interval is longer, more power
is saved but broadcast data is more easy to lose.
config POWER_SAVE_NONE
bool "none"
bool "none"
config POWER_SAVE_MIN_MODEM
bool "minimum modem"
bool "minimum modem"
config POWER_SAVE_MAX_MODEM
bool "maximum modem"
bool "maximum modem"
endchoice
choice EXAMPLE_MAX_CPU_FREQ
prompt "Maximum CPU frequency"
default EXAMPLE_MAX_CPU_FREQ_80
help
Maximum CPU frequency to use for dynamic frequency scaling.
config EXAMPLE_MAX_CPU_FREQ_80
bool "80 MHz"
config EXAMPLE_MAX_CPU_FREQ_160
bool "160 MHz"
config EXAMPLE_MAX_CPU_FREQ_240
bool "240 MHz"
endchoice
config EXAMPLE_MAX_CPU_FREQ_MHZ
int
default 80 if EXAMPLE_MAX_CPU_FREQ_80
default 160 if EXAMPLE_MAX_CPU_FREQ_160
default 240 if EXAMPLE_MAX_CPU_FREQ_240
choice EXAMPLE_MIN_CPU_FREQ
prompt "Minimum CPU frequency"
default EXAMPLE_MIN_CPU_FREQ_10M
help
Minimum CPU frequency to use for dynamic frequency scaling.
Should be set to XTAL frequency or XTAL frequency divided by integer.
config EXAMPLE_MIN_CPU_FREQ_40M
bool "40 MHz (use with 40MHz XTAL)"
depends on ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_20M
bool "20 MHz (use with 40MHz XTAL)"
depends on ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_10M
bool "10 MHz (use with 40MHz XTAL)"
depends on ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_26M
bool "26 MHz (use with 26MHz XTAL)"
depends on ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_13M
bool "13 MHz (use with 26MHz XTAL)"
depends on ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO
endchoice
config EXAMPLE_MIN_CPU_FREQ_MHZ
int
default 40 if EXAMPLE_MIN_CPU_FREQ_40M
default 20 if EXAMPLE_MIN_CPU_FREQ_20M
default 10 if EXAMPLE_MIN_CPU_FREQ_10M
default 26 if EXAMPLE_MIN_CPU_FREQ_26M
default 13 if EXAMPLE_MIN_CPU_FREQ_13M
endmenu

View file

@ -96,13 +96,12 @@ void app_main()
ESP_ERROR_CHECK( ret );
#if CONFIG_PM_ENABLE
// Configure dynamic frequency scaling: maximum frequency is set in sdkconfig,
// minimum frequency is XTAL.
rtc_cpu_freq_t max_freq;
rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &max_freq);
// Configure dynamic frequency scaling:
// maximum and minimum frequencies are set in sdkconfig,
// automatic light sleep is enabled if tickless idle support is enabled.
esp_pm_config_esp32_t pm_config = {
.max_cpu_freq = max_freq,
.min_cpu_freq = RTC_CPU_FREQ_XTAL,
.max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
.min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
.light_sleep_enable = true
#endif