From 05a0fbd49b227fad956c5eec90fac5ef397143b1 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 26 Oct 2017 18:33:13 +0800 Subject: [PATCH 1/5] soc/rtc: add a function to wait for slow clock cycle Some RTC features are synchronized to RTC_SLOW_CLK, so sometimes software needs to wait for the next slow clock cycle. This function implements waiting using Timer Group clock calibration feature. --- components/soc/esp32/include/soc/rtc.h | 9 +++++++++ components/soc/esp32/rtc_time.c | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/components/soc/esp32/include/soc/rtc.h b/components/soc/esp32/include/soc/rtc.h index f13c113b4..e2fa4791f 100644 --- a/components/soc/esp32/include/soc/rtc.h +++ b/components/soc/esp32/include/soc/rtc.h @@ -400,6 +400,15 @@ uint64_t rtc_time_slowclk_to_us(uint64_t rtc_cycles, uint32_t period); */ uint64_t rtc_time_get(); +/** + * @brief Busy loop until next RTC_SLOW_CLK cycle + * + * This function returns not earlier than the next RTC_SLOW_CLK clock cycle. + * In some cases (e.g. when RTC_SLOW_CLK cycle is very close), it may return + * one RTC_SLOW_CLK cycle later. + */ +void rtc_clk_wait_for_slow_cycle(); + /** * @brief sleep configuration for rtc_sleep_init function */ diff --git a/components/soc/esp32/rtc_time.c b/components/soc/esp32/rtc_time.c index 07a9337a3..6f354f884 100644 --- a/components/soc/esp32/rtc_time.c +++ b/components/soc/esp32/rtc_time.c @@ -135,3 +135,20 @@ uint64_t rtc_time_get() t |= ((uint64_t) READ_PERI_REG(RTC_CNTL_TIME1_REG)) << 32; return t; } + +void rtc_clk_wait_for_slow_cycle() +{ + REG_CLR_BIT(TIMG_RTCCALICFG_REG(0), TIMG_RTC_CALI_START_CYCLING | TIMG_RTC_CALI_START); + REG_CLR_BIT(TIMG_RTCCALICFG_REG(0), TIMG_RTC_CALI_RDY); + REG_SET_FIELD(TIMG_RTCCALICFG_REG(0), TIMG_RTC_CALI_CLK_SEL, RTC_CAL_RTC_MUX); + /* Request to run calibration for 0 slow clock cycles. + * RDY bit will be set on the nearest slow clock cycle. + */ + REG_SET_FIELD(TIMG_RTCCALICFG_REG(0), TIMG_RTC_CALI_MAX, 0); + REG_SET_BIT(TIMG_RTCCALICFG_REG(0), TIMG_RTC_CALI_START); + ets_delay_us(1); /* RDY needs some time to go low */ + while (!GET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0), TIMG_RTC_CALI_RDY)) { + ets_delay_us(1); + } +} + From 6d4ed4ff6c4ca6e3b3bd4af3c7aeabe33570b540 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 26 Oct 2017 18:46:27 +0800 Subject: [PATCH 2/5] soc/rtc: wait for SLOW_CLK cycle when switching CPU clock Previous implementation waited for 20us after setting RTC_CNTL_SOC_CLK_SEL_XTL register, using ets_delay_us, assuming that the CPU was running at XTAL frequency. In reality, clock switch happened on the next RTC_SLOW_CLK cycle, and CPU could be running at the previous frequency (for example, 240 MHz) until then. ets_delay_us would wait for 20 us * 40 cycles per us = 800 CPU cycles (assuming 40 MHz XTAL; even less with a 26 MHz XTAL). But if CPU was running at 240 MHz, 800 cycles would pass in just 3.3us, while SLOW_CLK cycle could happen as much as 1/150kHz = 6.7us after RTC_CNTL_SOC_CLK_SEL_XTL was set. So the software would not actually wait long enough for the clock switch to happen, and would disable the PLL while CPU was still clocked from PLL, leading to a halt. This implementation uses rtc_clk_wait_for_slow_cycle() function to wait until the clock switch, removing the need to wait for a fixed number of CPU cycles. --- components/soc/esp32/rtc_clk.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/soc/esp32/rtc_clk.c b/components/soc/esp32/rtc_clk.c index 6e2b0909d..049b47306 100644 --- a/components/soc/esp32/rtc_clk.c +++ b/components/soc/esp32/rtc_clk.c @@ -72,9 +72,6 @@ static const char* TAG = "rtc_clk"; * All values are in microseconds. * TODO: some of these are excessive, and should be reduced. */ -#define DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_150K 20 -#define DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_32K 160 -#define DELAY_CPU_FREQ_SWITCH_TO_PLL 20 #define DELAY_PLL_DBIAS_RAISE 3 #define DELAY_PLL_ENABLE_WITH_150K 80 #define DELAY_PLL_ENABLE_WITH_32K 160 @@ -397,9 +394,12 @@ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq) 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); - uint32_t delay_xtal_switch = (rtc_clk_slow_freq_get() == RTC_SLOW_FREQ_RTC) ? - DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_150K : DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_32K; - ets_delay_us(delay_xtal_switch); + + /* 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); SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BB_I2C_FORCE_PD | RTC_CNTL_BBPLL_FORCE_PD | @@ -443,7 +443,7 @@ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq) s_pll_freq = 480; } REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL); - ets_delay_us(DELAY_CPU_FREQ_SWITCH_TO_PLL); + rtc_clk_wait_for_slow_cycle(); rtc_clk_apb_freq_update(80 * MHZ); } s_cur_freq = cpu_freq; From 9317cb343482b22202d27317a4164d3c151c3fc8 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 26 Oct 2017 18:47:31 +0800 Subject: [PATCH 3/5] soc/rtc: add tests for CPU frequency switching These tests switch between PLL and XTAL frequencies for 10 seconds. --- components/soc/esp32/test/test_rtc_clk.c | 36 +++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/components/soc/esp32/test/test_rtc_clk.c b/components/soc/esp32/test/test_rtc_clk.c index 48905acd0..00fb0040f 100644 --- a/components/soc/esp32/test/test_rtc_clk.c +++ b/components/soc/esp32/test/test_rtc_clk.c @@ -1,15 +1,17 @@ #include #include "unity.h" #include "rom/ets_sys.h" +#include "rom/uart.h" #include "soc/rtc.h" #include "soc/rtc_cntl_reg.h" #include "soc/rtc_io_reg.h" #include "soc/sens_reg.h" #include "soc/io_mux_reg.h" #include "driver/rtc_io.h" - +#include "test_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) @@ -89,3 +91,35 @@ TEST_CASE("Output 8M XTAL clock to GPIO25", "[rtc_clk][ignore]") SET_PERI_REG_MASK(RTC_IO_RTC_DEBUG_SEL_REG, RTC_IO_DEBUG_12M_NO_GATING); pull_out_clk(RTC_IO_DEBUG_SEL0_8M); } + +static void test_clock_switching(void (*switch_func)(rtc_cpu_freq_t)) +{ + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); + + const int test_duration_sec = 10; + ref_clock_init(); + uint64_t t_start = ref_clock_get(); + + rtc_cpu_freq_t cur_freq = rtc_clk_cpu_freq_get(); + int count = 0; + while (ref_clock_get() - t_start < test_duration_sec * 1000000) { + switch_func(RTC_CPU_FREQ_XTAL); + switch_func(cur_freq); + ++count; + } + uint64_t t_end = ref_clock_get(); + printf("Switch count: %d. Average time to switch PLL -> XTAL -> PLL: %d us\n", count, (int) ((t_end - t_start) / count)); + ref_clock_deinit(); +} + +TEST_CASE("Test switching between PLL and XTAL", "[rtc_clk]") +{ + test_clock_switching(rtc_clk_cpu_freq_set); +} + +TEST_CASE("Test fast switching between PLL and XTAL", "[rtc_clk]") +{ + test_clock_switching(rtc_clk_cpu_freq_set_fast); +} + + From f11ad0c904018cd4fa0527a2a6600e67c12be60b Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 26 Oct 2017 18:52:00 +0800 Subject: [PATCH 4/5] soc/rtc: fix spurious warnings about XTAL frequency on startup 1. Make sure that 8MD256 clock used to estimate XTAL frequency is enabled before trying to use rtc_clk_cal_ratio. This fixes "Bogus XTAL frequency: 0 MHz" warnings after software reset. 2. Don't call rtc_clk_xtal_freq_estimate if XTAL frequency is already known. This reduces startup time after deep sleep or software reset. 3. Compare known XTAL frequency and estimated one before printing a warning. This fixes "Possibly invalid CONFIG_ESP32_XTAL_FREQ setting (40MHz). Detected 40 MHz." warnings. --- components/soc/esp32/rtc_clk.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/components/soc/esp32/rtc_clk.c b/components/soc/esp32/rtc_clk.c index 049b47306..612476cf8 100644 --- a/components/soc/esp32/rtc_clk.c +++ b/components/soc/esp32/rtc_clk.c @@ -558,6 +558,13 @@ void rtc_clk_xtal_freq_update(rtc_xtal_freq_t 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). @@ -581,6 +588,8 @@ static rtc_xtal_freq_t rtc_clk_xtal_freq_estimate() 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) @@ -634,7 +643,6 @@ void rtc_clk_init(rtc_clk_config_t cfg) CLEAR_PERI_REG_MASK(ANA_CONFIG_REG, I2C_APLL_M | I2C_BBPLL_M); /* Estimate XTAL frequency */ - rtc_xtal_freq_t est_xtal_freq = rtc_clk_xtal_freq_estimate(); 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))) { @@ -642,7 +650,7 @@ void rtc_clk_init(rtc_clk_config_t cfg) xtal_freq = rtc_clk_xtal_freq_get(); } else { /* Not set yet, estimate XTAL frequency based on RTC_FAST_CLK */ - xtal_freq = est_xtal_freq; + 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; @@ -653,8 +661,11 @@ void rtc_clk_init(rtc_clk_config_t cfg) * frequency is different. If autodetection failed, worst case we get a * bit of garbage output. */ - SOC_LOGW(TAG, "Possibly invalid CONFIG_ESP32_XTAL_FREQ setting (%dMHz). Detected %d MHz.", - xtal_freq, est_xtal_freq); + 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); From eb5752c635b1d42a322093dc3717c38b4581d664 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 26 Oct 2017 19:11:47 +0800 Subject: [PATCH 5/5] esp_restart: fix possible race while stalling other CPU, enable WDT early Previously esp_restart would stall the other CPU before enabling RTC_WDT. If the other CPU was executing an s32c1i instruction, the lock signal from CPU to the arbiter would still be held after CPU was stalled. If the CPU running esp_restart would then try to access the same locked memory pool, it would be stuck, because lock signal would never be released. With this change, esp_restart resets the other CPU before stalling it. Ideally, we would want to reset the CPU and keep it in reset, but the hardware doesn't have such feature for PRO_CPU (it is possible to hold APP_CPU in reset using DPORT register). Given that ROM code will not use s32c1i in the first few hundred cycles, doing reset and then stall seems to be safe. In addition to than, RTC_WDT initialization is moved to the beginning of the function, to prevent possible lock-up if CPU stalling still has any issue. --- components/esp32/system_api.c | 42 ++++++++++++++------------ components/soc/esp32/cpu_util.c | 6 ++++ components/soc/esp32/include/soc/cpu.h | 7 +++++ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index 37958db40..851d4a450 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -266,15 +266,10 @@ void IRAM_ATTR esp_restart(void) */ void IRAM_ATTR esp_restart_noos() { - const uint32_t core_id = xPortGetCoreID(); - const uint32_t other_core_id = core_id == 0 ? 1 : 0; - esp_cpu_stall(other_core_id); + // Disable interrupts + xt_ints_off(0xFFFFFFFF); - // other core is now stalled, can access DPORT registers directly - esp_dport_access_int_pause(); - - // We need to disable TG0/TG1 watchdogs - // First enable RTC watchdog for 1 second + // Enable RTC watchdog for 1 second REG_WRITE(RTC_CNTL_WDTWPROTECT_REG, RTC_CNTL_WDT_WKEY_VALUE); REG_WRITE(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN_M | @@ -284,6 +279,18 @@ void IRAM_ATTR esp_restart_noos() (1 << RTC_CNTL_WDT_CPU_RESET_LENGTH_S) ); REG_WRITE(RTC_CNTL_WDTCONFIG1_REG, rtc_clk_slow_freq_get_hz() * 1); + // Reset and stall the other CPU. + // CPU must be reset before stalling, in case it was running a s32c1i + // instruction. This would cause memory pool to be locked by arbiter + // to the stalled CPU, preventing current CPU from accessing this pool. + const uint32_t core_id = xPortGetCoreID(); + const uint32_t other_core_id = (core_id == 0) ? 1 : 0; + esp_cpu_reset(other_core_id); + esp_cpu_stall(other_core_id); + + // Other core is now stalled, can access DPORT registers directly + esp_dport_access_int_abort(); + // Disable TG0/TG1 watchdogs TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_config0.en = 0; @@ -292,8 +299,10 @@ void IRAM_ATTR esp_restart_noos() TIMERG1.wdt_config0.en = 0; TIMERG1.wdt_wprotect=0; - // Disable all interrupts - xt_ints_off(0xFFFFFFFF); + // Flush any data left in UART FIFOs + uart_tx_wait_idle(0); + uart_tx_wait_idle(1); + uart_tx_wait_idle(2); // Disable cache Cache_Read_Disable(0); @@ -310,11 +319,6 @@ void IRAM_ATTR esp_restart_noos() WRITE_PERI_REG(GPIO_FUNC5_IN_SEL_CFG_REG, 0x30); #endif - // Flush any data left in UART FIFOs - uart_tx_wait_idle(0); - uart_tx_wait_idle(1); - uart_tx_wait_idle(2); - // Reset wifi/bluetooth/ethernet/sdio (bb/mac) DPORT_SET_PERI_REG_MASK(DPORT_CORE_RST_EN_REG, DPORT_BB_RST | DPORT_FE_RST | DPORT_MAC_RST | @@ -337,14 +341,14 @@ void IRAM_ATTR esp_restart_noos() // Reset CPUs if (core_id == 0) { // Running on PRO CPU: APP CPU is stalled. Can reset both CPUs. - SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, - RTC_CNTL_SW_PROCPU_RST_M | RTC_CNTL_SW_APPCPU_RST_M); + esp_cpu_reset(1); + esp_cpu_reset(0); } else { // Running on APP CPU: need to reset PRO CPU and unstall it, // then reset APP CPU - SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_PROCPU_RST_M); + esp_cpu_reset(0); esp_cpu_unstall(0); - SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_APPCPU_RST_M); + esp_cpu_reset(1); } while(true) { ; diff --git a/components/soc/esp32/cpu_util.c b/components/soc/esp32/cpu_util.c index ecfcab4ba..bc052af98 100644 --- a/components/soc/esp32/cpu_util.c +++ b/components/soc/esp32/cpu_util.c @@ -44,6 +44,12 @@ void IRAM_ATTR esp_cpu_unstall(int cpu_id) } } +void IRAM_ATTR esp_cpu_reset(int cpu_id) +{ + SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, + cpu_id == 0 ? RTC_CNTL_SW_PROCPU_RST_M : RTC_CNTL_SW_APPCPU_RST_M); +} + bool IRAM_ATTR esp_cpu_in_ocd_debug_mode() { #if CONFIG_ESP32_DEBUG_OCDAWARE diff --git a/components/soc/esp32/include/soc/cpu.h b/components/soc/esp32/include/soc/cpu.h index b56fb3dc8..05ec91776 100644 --- a/components/soc/esp32/include/soc/cpu.h +++ b/components/soc/esp32/include/soc/cpu.h @@ -85,6 +85,13 @@ void esp_cpu_stall(int cpu_id); */ void esp_cpu_unstall(int cpu_id); +/** + * @brief Reset CPU using RTC controller + * @param cpu_id ID of the CPU to reset (0 = PRO, 1 = APP) + */ +void esp_cpu_reset(int cpu_id); + + /** * @brief Returns true if a JTAG debugger is attached to CPU * OCD (on chip debug) port.