From eb0c34e5c9515ed637ddfbdbea068d8878ebbd7c Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Oct 2017 15:24:51 +0800 Subject: [PATCH] esp_timer: add support for frequency scaling --- components/esp32/Kconfig | 13 +++++++ components/esp32/esp_timer_esp32.c | 58 ++++++++++++++++++++++++++++++ components/esp32/esp_timer_impl.h | 9 +++++ 3 files changed, 80 insertions(+) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 06ab28d69..1b219cf2e 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -970,6 +970,19 @@ config PM_DFS_INIT_AUTO If disabled, DFS will not be active until the application configures it using esp_pm_configure function. +config PM_USE_RTC_TIMER_REF + bool "Use RTC timer to prevent time drift (EXPERIMENTAL)" + depends on PM_ENABLE && (ESP32_TIME_SYSCALL_USE_RTC || ESP32_TIME_SYSCALL_USE_RTC_FRC1) + default n + help + When APB clock frequency changes, high-resolution timer (esp_timer) + scale and base value need to be adjusted. Each adjustment may cause + small error, and over time such small errors may cause time drift. + If this option is enabled, RTC timer will be used as a reference to + compensate for the drift. + It is recommended that this option is only used if 32k XTAL is selected + as RTC clock source. + config PM_PROFILING bool "Enable profiling counters for PM locks" depends on PM_ENABLE diff --git a/components/esp32/esp_timer_esp32.c b/components/esp32/esp_timer_esp32.c index 365a11dbc..e2d271186 100644 --- a/components/esp32/esp_timer_esp32.c +++ b/components/esp32/esp_timer_esp32.c @@ -19,6 +19,7 @@ #include "esp_attr.h" #include "esp_intr_alloc.h" #include "esp_log.h" +#include "esp_clk.h" #include "esp_timer_impl.h" #include "soc/frc_timer_reg.h" #include "soc/rtc.h" @@ -112,6 +113,14 @@ static uint32_t s_timer_us_per_overflow; // will not increment s_time_base_us if this flag is set. static bool s_mask_overflow; +#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF +// If DFS is enabled, upon the first frequency change this value is set to the +// difference between esp_timer value and RTC timer value. On every subsequent +// frequency change, s_time_base_us is adjusted to maintain the same difference +// between esp_timer and RTC timer. (All mentioned values are in microseconds.) +static uint64_t s_rtc_time_diff = 0; +#endif + // Spinlock used to protect access to static variables above and to the hardware // registers. portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED; @@ -208,6 +217,55 @@ static void IRAM_ATTR timer_alarm_isr(void *arg) (*s_alarm_handler)(arg); } +void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us) +{ + portENTER_CRITICAL(&s_time_update_lock); + /* Bail out if the timer is not initialized yet */ + if (s_timer_interrupt_handle == NULL) { + portEXIT_CRITICAL(&s_time_update_lock); + return; + } + + uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV; + uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1)); + uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1)); + uint64_t ticks_to_alarm = alarm - count; + uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us; + uint32_t new_alarm_val; + if (alarm > count && new_ticks <= FRC_TIMER_LOAD_VALUE(1)) { + new_alarm_val = new_ticks; + } else { + new_alarm_val = ALARM_OVERFLOW_VAL; + if (alarm != ALARM_OVERFLOW_VAL) { + s_mask_overflow = true; + } + } + REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val); + REG_WRITE(FRC_TIMER_LOAD_REG(1), 0); + + s_time_base_us += count / s_timer_ticks_per_us; + +#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF + // Due to the extra time required to read RTC time, don't attempt this + // adjustment when switching to a higher frequency (which usually + // happens in an interrupt). + if (new_ticks_per_us < s_timer_ticks_per_us) { + uint64_t rtc_time = esp_clk_rtc_time(); + uint64_t new_rtc_time_diff = s_time_base_us - rtc_time; + if (s_rtc_time_diff != 0) { + uint64_t correction = new_rtc_time_diff - s_rtc_time_diff; + s_time_base_us -= correction; + } else { + s_rtc_time_diff = new_rtc_time_diff; + } + } +#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF + + s_timer_ticks_per_us = new_ticks_per_us; + s_timer_us_per_overflow = FRC_TIMER_LOAD_VALUE(1) / new_ticks_per_us; + + portEXIT_CRITICAL(&s_time_update_lock); +} esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler) { diff --git a/components/esp32/esp_timer_impl.h b/components/esp32/esp_timer_impl.h index f5d73f5a3..7d49e3eeb 100644 --- a/components/esp32/esp_timer_impl.h +++ b/components/esp32/esp_timer_impl.h @@ -51,6 +51,15 @@ void esp_timer_impl_deinit(); */ void esp_timer_impl_set_alarm(uint64_t timestamp); +/** + * @brief Notify esp_timer implementation that APB frequency has changed + * + * Called by the frequency switching code. + * + * @param apb_ticks_per_us new number of APB clock ticks per microsecond + */ +void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us); + /** * @brief Get time, in microseconds, since esp_timer_impl_init was called * @return timestamp in microseconds