Merge branch 'refactor/esp_pm' into 'master'

esp32, esp32s2: create esp_pm component

See merge request espressif/esp-idf!9722
This commit is contained in:
Angus Gratton 2020-09-25 15:01:52 +08:00
commit 59b763bb9a
45 changed files with 269 additions and 1552 deletions

View File

@ -563,7 +563,7 @@ endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "${include_dirs}"
PRIV_INCLUDE_DIRS "${priv_include_dirs}"
REQUIRES nvs_flash soc esp_timer
REQUIRES nvs_flash soc esp_timer esp_pm
PRIV_REQUIRES esp_ipc)
if(CONFIG_BT_ENABLED)

View File

@ -52,8 +52,8 @@ endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ${includes}
PRIV_INCLUDE_DIRS "include/driver"
PRIV_REQUIRES efuse esp_timer esp_ipc
REQUIRES esp_ringbuf freertos soc hal) # cannot totally hide soc headers, since there are a lot arguments in the driver are chip-dependent
PRIV_REQUIRES efuse esp_pm esp_timer esp_ipc
REQUIRES esp_ringbuf freertos soc hal) #cannot totally hide soc headers, since there are a lot arguments in the driver are chip-dependent
# uses C11 atomic feature
set_source_files_properties(spi_master.c PROPERTIES COMPILE_FLAGS -std=gnu11)

View File

@ -20,8 +20,6 @@ else()
"esp_himem.c"
"hw_random.c"
"intr_alloc.c"
"pm_esp32.c"
"pm_trace.c"
"spiram.c"
"spiram_psram.c"
"system_api_esp32.c")

View File

@ -523,8 +523,10 @@ menu "ESP32-specific"
config ESP32_TIME_SYSCALL_USE_RTC_FRC1
bool "RTC and high-resolution timer"
select ESP_TIMER_RTC_USE
config ESP32_TIME_SYSCALL_USE_RTC
bool "RTC"
select ESP_TIMER_RTC_USE
config ESP32_TIME_SYSCALL_USE_FRC1
bool "High-resolution timer"
config ESP32_TIME_SYSCALL_USE_NONE
@ -759,72 +761,4 @@ menu "ESP32-specific"
RTC fast memory is accessible to PRO cpu only and hence this is allowed for single core
configuration only for ESP32.
endmenu # ESP32-Specific
menu "Power Management"
# TODO: this component simply shouldn't be included
# in the build at the CMake level, but this is currently
# not working so we just hide all items here
visible if IDF_TARGET_ESP32
config PM_ENABLE
bool "Support for power management"
default n
help
If enabled, application is compiled with support for power management.
This option has run-time overhead (increased interrupt latency,
longer time to enter idle state), and it also reduces accuracy of
RTOS ticks and timers used for timekeeping.
Enable this option if application uses power management APIs.
config PM_DFS_INIT_AUTO
bool "Enable dynamic frequency scaling (DFS) at startup"
depends on PM_ENABLE
default n
help
If enabled, startup code configures dynamic frequency scaling.
Max CPU frequency is set to CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ setting,
min frequency is set to XTAL frequency.
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 && ESP_TIMER_IMPL_FRC2 && (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
default n
help
If enabled, esp_pm_* functions will keep track of the amount of time
each of the power management locks has been held, and esp_pm_dump_locks
function will print this information.
This feature can be used to analyze which locks are preventing the chip
from going into a lower power state, and see what time the chip spends
in each power saving mode. This feature does incur some run-time
overhead, so should typically be disabled in production builds.
config PM_TRACE
bool "Enable debug tracing of PM using GPIOs"
depends on PM_ENABLE
default n
help
If enabled, some GPIOs will be used to signal events such as RTOS ticks,
frequency switching, entry/exit from idle state. Refer to pm_trace.c
file for the list of GPIOs.
This feature is intended to be used when analyzing/debugging behavior
of power management implementation, and should be kept disabled in
applications.
endmenu # "Power Management"
endmenu # ESP32-Specific

View File

@ -1,613 +0,0 @@
// Copyright 2016-2017 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 <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_pm.h"
#include "esp_log.h"
#include "esp32/clk.h"
#include "esp_private/crosscore_int.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_timer.h"
#include "xtensa/core-macros.h"
#include "esp_private/pm_impl.h"
#include "esp_private/pm_trace.h"
#include "esp_private/esp_timer_private.h"
#include "esp32/pm.h"
#include "esp_sleep.h"
/* CCOMPARE update timeout, in CPU cycles. Any value above ~600 cycles will work
* for the purpose of detecting a deadlock.
*/
#define CCOMPARE_UPDATE_TIMEOUT 1000000
/* When changing CCOMPARE, don't allow changes if the difference is less
* than this. This is to prevent setting CCOMPARE below CCOUNT.
*/
#define CCOMPARE_MIN_CYCLES_IN_FUTURE 1000
/* When light sleep is used, wake this number of microseconds earlier than
* the next tick.
*/
#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
static portMUX_TYPE s_switch_lock = portMUX_INITIALIZER_UNLOCKED;
/* The following state variables are protected using s_switch_lock: */
/* Current sleep mode; When switching, contains old mode until switch is complete */
static pm_mode_t s_mode = PM_MODE_CPU_MAX;
/* True when switch is in progress */
static volatile bool s_is_switching;
/* When switch is in progress, this is the mode we are switching into */
static pm_mode_t s_new_mode = PM_MODE_CPU_MAX;
/* Number of times each mode was locked */
static size_t s_mode_lock_counts[PM_MODE_COUNT];
/* Bit mask of locked modes. BIT(i) is set iff s_mode_lock_counts[i] > 0. */
static uint32_t s_mode_mask;
/* Divider and multiplier used to adjust (ccompare - ccount) duration.
* Only set to non-zero values when switch is in progress.
*/
static uint32_t s_ccount_div;
static uint32_t s_ccount_mul;
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
/* Indicates if light sleep entry was skipped in vApplicationSleep for given CPU.
* This in turn gets used in IDLE hook to decide if `waiti` needs
* to be invoked or not.
*/
static bool s_skipped_light_sleep[portNUM_PROCESSORS];
#if portNUM_PROCESSORS == 2
/* When light sleep is finished on one CPU, it is possible that the other CPU
* will enter light sleep again very soon, before interrupts on the first CPU
* get a chance to run. To avoid such situation, set a flag for the other CPU to
* skip light sleep attempt.
*/
static bool s_skip_light_sleep[portNUM_PROCESSORS];
#endif // portNUM_PROCESSORS == 2
#endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE
/* Indicates to the ISR hook that CCOMPARE needs to be updated on the given CPU.
* Used in conjunction with cross-core interrupt to update CCOMPARE on the other CPU.
*/
static volatile bool s_need_update_ccompare[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];
/* 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 frequency configs to be used in each mode.
* Initialized by esp_pm_impl_init and modified by esp_pm_configure.
*/
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;
/* When configuration is changed, current frequency may not match the
* newly configured frequency for the current mode. This is an indicator
* to the mode switch code to get the actual current frequency instead of
* relying on the current mode.
*/
static bool s_config_changed = false;
#ifdef WITH_PROFILING
/* Time, in microseconds, spent so far in each mode */
static pm_time_t s_time_in_mode[PM_MODE_COUNT];
/* Timestamp, in microseconds, when the mode switch last happened */
static pm_time_t s_last_mode_change_time;
/* User-readable mode names, used by esp_pm_impl_dump_stats */
static const char* s_mode_names[] = {
"SLEEP",
"APB_MIN",
"APB_MAX",
"CPU_MAX"
};
#endif // WITH_PROFILING
static const char* TAG = "pm_esp32";
static void update_ccompare(void);
static void do_switch(pm_mode_t new_mode);
static void leave_idle(void);
static void on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us);
pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
{
(void) arg;
if (type == ESP_PM_CPU_FREQ_MAX) {
return PM_MODE_CPU_MAX;
} else if (type == ESP_PM_APB_FREQ_MAX) {
return PM_MODE_APB_MAX;
} else if (type == ESP_PM_NO_LIGHT_SLEEP) {
return PM_MODE_APB_MIN;
} else {
// unsupported mode
abort();
}
}
esp_err_t esp_pm_configure(const void* vconfig)
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
const esp_pm_config_esp32_t* config = (const esp_pm_config_esp32_t*) vconfig;
#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE
if (config->light_sleep_enable) {
return ESP_ERR_NOT_SUPPORTED;
}
#endif
int min_freq_mhz = config->min_freq_mhz;
int max_freq_mhz = config->max_freq_mhz;
if (min_freq_mhz > max_freq_mhz) {
return ESP_ERR_INVALID_ARG;
}
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 = 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 = 80;
}
apb_max_freq = MAX(apb_max_freq, min_freq_mhz);
ESP_LOGI(TAG, "Frequency switching config: "
"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);
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);
return ESP_OK;
}
static pm_mode_t IRAM_ATTR get_lowest_allowed_mode(void)
{
/* TODO: optimize using ffs/clz */
if (s_mode_mask >= BIT(PM_MODE_CPU_MAX)) {
return PM_MODE_CPU_MAX;
} else if (s_mode_mask >= BIT(PM_MODE_APB_MAX)) {
return PM_MODE_APB_MAX;
} else if (s_mode_mask >= BIT(PM_MODE_APB_MIN) || !s_light_sleep_en) {
return PM_MODE_APB_MIN;
} else {
return PM_MODE_LIGHT_SLEEP;
}
}
void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode,
pm_mode_switch_t lock_or_unlock, pm_time_t now)
{
bool need_switch = false;
uint32_t mode_mask = BIT(mode);
portENTER_CRITICAL_SAFE(&s_switch_lock);
uint32_t count;
if (lock_or_unlock == MODE_LOCK) {
count = ++s_mode_lock_counts[mode];
} else {
count = s_mode_lock_counts[mode]--;
}
if (count == 1) {
if (lock_or_unlock == MODE_LOCK) {
s_mode_mask |= mode_mask;
} else {
s_mode_mask &= ~mode_mask;
}
need_switch = true;
}
pm_mode_t new_mode = s_mode;
if (need_switch) {
new_mode = get_lowest_allowed_mode();
#ifdef WITH_PROFILING
if (s_last_mode_change_time != 0) {
pm_time_t diff = now - s_last_mode_change_time;
s_time_in_mode[s_mode] += diff;
}
s_last_mode_change_time = now;
#endif // WITH_PROFILING
}
portEXIT_CRITICAL_SAFE(&s_switch_lock);
if (need_switch && new_mode != s_mode) {
do_switch(new_mode);
}
}
/**
* @brief Update clock dividers in esp_timer and FreeRTOS, and adjust CCOMPARE
* values on both CPUs.
* @param old_ticks_per_us old CPU frequency
* @param ticks_per_us new CPU frequency
*/
static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us)
{
uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80);
uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80);
/* Update APB frequency value used by the timer */
if (old_apb_ticks_per_us != apb_ticks_per_us) {
esp_timer_private_update_apb_freq(apb_ticks_per_us);
}
/* Calculate new tick divisor */
_xt_tick_divisor = ticks_per_us * MHZ / XT_TICK_PER_SEC;
int core_id = xPortGetCoreID();
if (s_rtos_lock_handle[core_id] != NULL) {
ESP_PM_TRACE_ENTER(CCOMPARE_UPDATE, core_id);
/* ccount_div and ccount_mul are used in esp_pm_impl_update_ccompare
* to calculate new CCOMPARE value.
*/
s_ccount_div = old_ticks_per_us;
s_ccount_mul = ticks_per_us;
/* Update CCOMPARE value on this CPU */
update_ccompare();
#if portNUM_PROCESSORS == 2
/* Send interrupt to the other CPU to update CCOMPARE value */
int other_core_id = (core_id == 0) ? 1 : 0;
s_need_update_ccompare[other_core_id] = true;
esp_crosscore_int_send_freq_switch(other_core_id);
int timeout = 0;
while (s_need_update_ccompare[other_core_id]) {
if (++timeout == CCOMPARE_UPDATE_TIMEOUT) {
assert(false && "failed to update CCOMPARE, possible deadlock");
}
}
#endif // portNUM_PROCESSORS == 2
s_ccount_mul = 0;
s_ccount_div = 0;
ESP_PM_TRACE_EXIT(CCOMPARE_UPDATE, core_id);
}
}
/**
* Perform the switch to new power mode.
* Currently only changes the CPU frequency and adjusts clock dividers.
* No light sleep yet.
* @param new_mode mode to switch to
*/
static void IRAM_ATTR do_switch(pm_mode_t new_mode)
{
const int core_id = xPortGetCoreID();
do {
portENTER_CRITICAL_ISR(&s_switch_lock);
if (!s_is_switching) {
break;
}
if (s_new_mode <= new_mode) {
portEXIT_CRITICAL_ISR(&s_switch_lock);
return;
}
if (s_need_update_ccompare[core_id]) {
s_need_update_ccompare[core_id] = false;
}
portEXIT_CRITICAL_ISR(&s_switch_lock);
} while (true);
s_new_mode = new_mode;
s_is_switching = true;
bool config_changed = s_config_changed;
s_config_changed = false;
portEXIT_CRITICAL_ISR(&s_switch_lock);
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_config = s_cpu_freq_by_mode[s_mode];
} else {
rtc_clk_cpu_freq_get_config(&old_config);
}
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;
ESP_PM_TRACE_ENTER(FREQ_SWITCH, core_id);
if (switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
rtc_clk_cpu_freq_set_config_fast(&new_config);
if (!switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
ESP_PM_TRACE_EXIT(FREQ_SWITCH, core_id);
}
portENTER_CRITICAL_ISR(&s_switch_lock);
s_mode = new_mode;
s_is_switching = false;
portEXIT_CRITICAL_ISR(&s_switch_lock);
}
/**
* @brief Calculate new CCOMPARE value based on s_ccount_{mul,div}
*
* Adjusts CCOMPARE value so that the interrupt happens at the same time as it
* would happen without the frequency change.
* Assumes that the new_frequency = old_frequency * s_ccount_mul / s_ccount_div.
*/
static void IRAM_ATTR update_ccompare(void)
{
uint32_t ccount = XTHAL_GET_CCOUNT();
uint32_t ccompare = XTHAL_GET_CCOMPARE(XT_TIMER_INDEX);
if ((ccompare - CCOMPARE_MIN_CYCLES_IN_FUTURE) - ccount < UINT32_MAX / 2) {
uint32_t diff = ccompare - ccount;
uint32_t diff_scaled = (diff * s_ccount_mul + s_ccount_div - 1) / s_ccount_div;
if (diff_scaled < _xt_tick_divisor) {
uint32_t new_ccompare = ccount + diff_scaled;
XTHAL_SET_CCOMPARE(XT_TIMER_INDEX, new_ccompare);
}
}
}
static void IRAM_ATTR leave_idle(void)
{
int core_id = xPortGetCoreID();
if (s_core_idle[core_id]) {
// TODO: possible optimization: raise frequency here first
esp_pm_lock_acquire(s_rtos_lock_handle[core_id]);
s_core_idle[core_id] = false;
}
}
void esp_pm_impl_idle_hook(void)
{
int core_id = xPortGetCoreID();
uint32_t state = portENTER_CRITICAL_NESTED();
if (!s_core_idle[core_id]) {
esp_pm_lock_release(s_rtos_lock_handle[core_id]);
s_core_idle[core_id] = true;
}
portEXIT_CRITICAL_NESTED(state);
ESP_PM_TRACE_ENTER(IDLE, core_id);
}
void IRAM_ATTR esp_pm_impl_isr_hook(void)
{
int core_id = xPortGetCoreID();
ESP_PM_TRACE_ENTER(ISR_HOOK, core_id);
/* Prevent higher level interrupts (than the one this function was called from)
* from happening in this section, since they will also call into esp_pm_impl_isr_hook.
*/
uint32_t state = portENTER_CRITICAL_NESTED();
#if portNUM_PROCESSORS == 2
if (s_need_update_ccompare[core_id]) {
update_ccompare();
s_need_update_ccompare[core_id] = false;
} else {
leave_idle();
}
#else
leave_idle();
#endif // portNUM_PROCESSORS == 2
portEXIT_CRITICAL_NESTED(state);
ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
}
void esp_pm_impl_waiti(void)
{
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
int core_id = xPortGetCoreID();
if (s_skipped_light_sleep[core_id]) {
asm("waiti 0");
/* Interrupt took the CPU out of waiti and s_rtos_lock_handle[core_id]
* is now taken. However since we are back to idle task, we can release
* the lock so that vApplicationSleep can attempt to enter light sleep.
*/
esp_pm_impl_idle_hook();
s_skipped_light_sleep[core_id] = false;
}
#else
asm("waiti 0");
#endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE
}
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
static inline bool IRAM_ATTR should_skip_light_sleep(int core_id)
{
#if portNUM_PROCESSORS == 2
if (s_skip_light_sleep[core_id]) {
s_skip_light_sleep[core_id] = false;
s_skipped_light_sleep[core_id] = true;
return true;
}
#endif // portNUM_PROCESSORS == 2
if (s_mode != PM_MODE_LIGHT_SLEEP || s_is_switching) {
s_skipped_light_sleep[core_id] = true;
} else {
s_skipped_light_sleep[core_id] = false;
}
return s_skipped_light_sleep[core_id];
}
static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
{
#if portNUM_PROCESSORS == 2
s_skip_light_sleep[!core_id] = true;
#endif
}
void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime )
{
portENTER_CRITICAL(&s_switch_lock);
int core_id = xPortGetCoreID();
if (!should_skip_light_sleep(core_id)) {
/* Calculate how much we can sleep */
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm();
int64_t now = esp_timer_get_time();
int64_t time_until_next_alarm = next_esp_timer_alarm - now;
int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime;
int64_t sleep_time_us = MIN(wakeup_delay_us, time_until_next_alarm);
if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) {
esp_sleep_enable_timer_wakeup(sleep_time_us - LIGHT_SLEEP_EARLY_WAKEUP_US);
#ifdef CONFIG_PM_TRACE
/* to force tracing GPIOs to keep state */
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
/* Enter sleep */
ESP_PM_TRACE_ENTER(SLEEP, core_id);
int64_t sleep_start = esp_timer_get_time();
esp_light_sleep_start();
int64_t slept_us = esp_timer_get_time() - sleep_start;
ESP_PM_TRACE_EXIT(SLEEP, core_id);
uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL);
if (slept_ticks > 0) {
/* Adjust RTOS tick count based on the amount of time spent in sleep */
vTaskStepTick(slept_ticks);
/* Trigger tick interrupt, since sleep time was longer
* than portTICK_PERIOD_MS. Note that setting INTSET does not
* work for timer interrupt, and changing CCOMPARE would clear
* the interrupt flag.
*/
XTHAL_SET_CCOUNT(XTHAL_GET_CCOMPARE(XT_TIMER_INDEX) - 16);
while (!(XTHAL_GET_INTERRUPT() & BIT(XT_TIMER_INTNUM))) {
;
}
}
other_core_should_skip_light_sleep(core_id);
}
}
portEXIT_CRITICAL(&s_switch_lock);
}
#endif //CONFIG_FREERTOS_USE_TICKLESS_IDLE
#ifdef WITH_PROFILING
void esp_pm_impl_dump_stats(FILE* out)
{
pm_time_t time_in_mode[PM_MODE_COUNT];
portENTER_CRITICAL_ISR(&s_switch_lock);
memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode));
pm_time_t last_mode_change_time = s_last_mode_change_time;
pm_mode_t cur_mode = s_mode;
pm_time_t now = pm_get_time();
portEXIT_CRITICAL_ISR(&s_switch_lock);
time_in_mode[cur_mode] += now - last_mode_change_time;
fprintf(out, "Mode stats:\n");
for (int i = 0; i < PM_MODE_COUNT; ++i) {
if (i == PM_MODE_LIGHT_SLEEP && !s_light_sleep_en) {
/* don't display light sleep mode if it's not enabled */
continue;
}
fprintf(out, "%8s %3dM %12lld %2d%%\n",
s_mode_names[i],
s_cpu_freq_by_mode[i].freq_mhz,
time_in_mode[i],
(int) (time_in_mode[i] * 100 / now));
}
}
#endif // WITH_PROFILING
void esp_pm_impl_init(void)
{
#ifdef CONFIG_PM_TRACE
esp_pm_trace_init();
#endif
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos0",
&s_rtos_lock_handle[0]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[0]));
#if portNUM_PROCESSORS == 2
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos1",
&s_rtos_lock_handle[1]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[1]));
#endif // portNUM_PROCESSORS == 2
/* Configure all modes to use the default CPU frequency.
* This will be modified later by a call to esp_pm_configure.
*/
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_config;
}
}

View File

@ -18,8 +18,6 @@ else()
"dport_access.c"
"hw_random.c"
"intr_alloc.c"
"pm_esp32s2.c"
"pm_trace.c"
"spiram.c"
"spiram_psram.c"
"system_api_esp32s2.c"

View File

@ -392,8 +392,10 @@ menu "ESP32S2-specific"
config ESP32S2_TIME_SYSCALL_USE_RTC_FRC1
bool "RTC and high-resolution timer"
select ESP_TIMER_RTC_USE
config ESP32S2_TIME_SYSCALL_USE_RTC
bool "RTC"
select ESP_TIMER_RTC_USE
config ESP32S2_TIME_SYSCALL_USE_FRC1
bool "High-resolution timer"
config ESP32S2_TIME_SYSCALL_USE_NONE
@ -518,58 +520,3 @@ menu "ESP32S2-specific"
wise RTC fast memory operates on APB clock and hence does not have much performance impact.
endmenu # ESP32S2-Specific
menu "Power Management"
# TODO: this component simply shouldn't be included
# in the build at the CMake level, but this is currently
# not working so we just hide all items here
visible if IDF_TARGET_ESP32S2
config PM_ENABLE
bool "Support for power management"
default n
help
If enabled, application is compiled with support for power management.
This option has run-time overhead (increased interrupt latency,
longer time to enter idle state), and it also reduces accuracy of
RTOS ticks and timers used for timekeeping.
Enable this option if application uses power management APIs.
config PM_DFS_INIT_AUTO
bool "Enable dynamic frequency scaling (DFS) at startup"
depends on PM_ENABLE
default n
help
If enabled, startup code configures dynamic frequency scaling.
Max CPU frequency is set to CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ setting,
min frequency is set to XTAL frequency.
If disabled, DFS will not be active until the application
configures it using esp_pm_configure function.
config PM_PROFILING
bool "Enable profiling counters for PM locks"
depends on PM_ENABLE
default n
help
If enabled, esp_pm_* functions will keep track of the amount of time
each of the power management locks has been held, and esp_pm_dump_locks
function will print this information.
This feature can be used to analyze which locks are preventing the chip
from going into a lower power state, and see what time the chip spends
in each power saving mode. This feature does incur some run-time
overhead, so should typically be disabled in production builds.
config PM_TRACE
bool "Enable debug tracing of PM using GPIOs"
depends on PM_ENABLE
default n
help
If enabled, some GPIOs will be used to signal events such as RTOS ticks,
frequency switching, entry/exit from idle state. Refer to pm_trace.c
file for the list of GPIOs.
This feature is intended to be used when analyzing/debugging behavior
of power management implementation, and should be kept disabled in
applications.
endmenu # "Power Management"

View File

@ -1,52 +0,0 @@
// Copyright 2016-2017 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 "esp_private/pm_trace.h"
#include "driver/gpio.h"
#include "soc/gpio_reg.h"
/* GPIOs to use for tracing of esp_pm events.
* Two entries in the array for each type, one for each CPU.
* Feel free to change when debugging.
*/
static const int DRAM_ATTR s_trace_io[] = {
BIT(2), 0, // ESP_PM_TRACE_IDLE
BIT(3), 0, // ESP_PM_TRACE_TICK
BIT(4), 0, // ESP_PM_TRACE_FREQ_SWITCH
BIT(5), 0, // ESP_PM_TRACE_CCOMPARE_UPDATE
BIT(6), 0, // ESP_PM_TRACE_ISR_HOOK
BIT(7), 0, // ESP_PM_TRACE_SLEEP
};
void esp_pm_trace_init(void)
{
for (size_t i = 0; i < sizeof(s_trace_io)/sizeof(s_trace_io[0]); ++i) {
int io = __builtin_ffs(s_trace_io[i]);
if (io == 0) {
continue;
}
gpio_set_direction(io - 1, GPIO_MODE_OUTPUT);
}
}
void IRAM_ATTR esp_pm_trace_enter(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TS_REG, s_trace_io[2 * event + core_id]);
}
void IRAM_ATTR esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TC_REG, s_trace_io[2 * event + core_id]);
}

View File

@ -19,8 +19,6 @@ else()
"hw_random.c"
"intr_alloc.c"
"memprot.c"
"pm_esp32s3.c"
"pm_trace.c"
"spiram.c"
"spiram_psram.c"
"system_api_esp32s3.c")

View File

@ -430,8 +430,10 @@ menu "ESP32S3-Specific"
config ESP32S3_TIME_SYSCALL_USE_RTC_FRC1
bool "RTC and high-resolution timer"
select ESP_TIMER_RTC_USE
config ESP32S3_TIME_SYSCALL_USE_RTC
bool "RTC"
select ESP_TIMER_RTC_USE
config ESP32S3_TIME_SYSCALL_USE_FRC1
bool "High-resolution timer"
config ESP32S3_TIME_SYSCALL_USE_NONE

View File

@ -1,643 +0,0 @@
// Copyright 2016-2020 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 <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_pm.h"
#include "esp_log.h"
#include "esp32s3/clk.h"
#include "esp_private/crosscore_int.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_timer.h"
#include "xtensa/core-macros.h"
#include "esp_private/pm_impl.h"
#include "esp_private/pm_trace.h"
#include "esp_private/esp_timer_private.h"
#include "esp32s3/pm.h"
#include "esp_sleep.h"
/* CCOMPARE update timeout, in CPU cycles. Any value above ~600 cycles will work
* for the purpose of detecting a deadlock.
*/
#define CCOMPARE_UPDATE_TIMEOUT 1000000
/* When changing CCOMPARE, don't allow changes if the difference is less
* than this. This is to prevent setting CCOMPARE below CCOUNT.
*/
#define CCOMPARE_MIN_CYCLES_IN_FUTURE 1000
/* When light sleep is used, wake this number of microseconds earlier than
* the next tick.
*/
#define LIGHT_SLEEP_EARLY_WAKEUP_US 100
/* Minimal divider at which REF_CLK_FREQ can be obtained */
#define REF_CLK_DIV_MIN 2
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#endif
static portMUX_TYPE s_switch_lock = portMUX_INITIALIZER_UNLOCKED;
/* The following state variables are protected using s_switch_lock: */
/* Current sleep mode; When switching, contains old mode until switch is complete */
static pm_mode_t s_mode = PM_MODE_CPU_MAX;
/* True when switch is in progress */
static volatile bool s_is_switching;
/* When switch is in progress, this is the mode we are switching into */
static pm_mode_t s_new_mode = PM_MODE_CPU_MAX;
/* Number of times each mode was locked */
static size_t s_mode_lock_counts[PM_MODE_COUNT];
/* Bit mask of locked modes. BIT(i) is set iff s_mode_lock_counts[i] > 0. */
static uint32_t s_mode_mask;
/* Divider and multiplier used to adjust (ccompare - ccount) duration.
* Only set to non-zero values when switch is in progress.
*/
static uint32_t s_ccount_div;
static uint32_t s_ccount_mul;
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
#define PERIPH_SKIP_LIGHT_SLEEP_NO 1
/* Indicates if light sleep shoule be skipped by peripherals. */
static skip_light_sleep_cb_t s_periph_skip_light_sleep_cb[PERIPH_SKIP_LIGHT_SLEEP_NO];
/* Indicates if light sleep entry was skipped in vApplicationSleep for given CPU.
* This in turn gets used in IDLE hook to decide if `waiti` needs
* to be invoked or not.
*/
static bool s_skipped_light_sleep[portNUM_PROCESSORS];
#if portNUM_PROCESSORS == 2
/* When light sleep is finished on one CPU, it is possible that the other CPU
* will enter light sleep again very soon, before interrupts on the first CPU
* get a chance to run. To avoid such situation, set a flag for the other CPU to
* skip light sleep attempt.
*/
static bool s_skip_light_sleep[portNUM_PROCESSORS];
#endif // portNUM_PROCESSORS == 2
#endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE
/* Indicates to the ISR hook that CCOMPARE needs to be updated on the given CPU.
* Used in conjunction with cross-core interrupt to update CCOMPARE on the other CPU.
*/
static volatile bool s_need_update_ccompare[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];
/* 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 frequency configs to be used in each mode.
* Initialized by esp_pm_impl_init and modified by esp_pm_configure.
*/
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;
/* When configuration is changed, current frequency may not match the
* newly configured frequency for the current mode. This is an indicator
* to the mode switch code to get the actual current frequency instead of
* relying on the current mode.
*/
static bool s_config_changed = false;
#ifdef WITH_PROFILING
/* Time, in microseconds, spent so far in each mode */
static pm_time_t s_time_in_mode[PM_MODE_COUNT];
/* Timestamp, in microseconds, when the mode switch last happened */
static pm_time_t s_last_mode_change_time;
/* User-readable mode names, used by esp_pm_impl_dump_stats */
static const char *s_mode_names[] = {
"SLEEP",
"APB_MIN",
"APB_MAX",
"CPU_MAX"
};
#endif // WITH_PROFILING
static const char *TAG = "pm_esp32s3";
static void update_ccompare(void);
static void do_switch(pm_mode_t new_mode);
static void leave_idle(void);
static void on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us);
pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
{
(void) arg;
if (type == ESP_PM_CPU_FREQ_MAX) {
return PM_MODE_CPU_MAX;
} else if (type == ESP_PM_APB_FREQ_MAX) {
return PM_MODE_APB_MAX;
} else if (type == ESP_PM_NO_LIGHT_SLEEP) {
return PM_MODE_APB_MIN;
} else {
// unsupported mode
abort();
}
}
esp_err_t esp_pm_configure(const void *vconfig)
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
const esp_pm_config_esp32s3_t *config = (const esp_pm_config_esp32s3_t *) vconfig;
#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE
if (config->light_sleep_enable) {
return ESP_ERR_NOT_SUPPORTED;
}
#endif
int min_freq_mhz = config->min_freq_mhz;
int max_freq_mhz = config->max_freq_mhz;
if (min_freq_mhz > max_freq_mhz) {
return ESP_ERR_INVALID_ARG;
}
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 = MIN(max_freq_mhz, 80); /* CPU frequency in APB_MAX mode */
apb_max_freq = MAX(apb_max_freq, min_freq_mhz);
ESP_LOGI(TAG, "Frequency switching config: "
"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);
bool res;
res = rtc_clk_cpu_freq_mhz_to_config(max_freq_mhz, &s_cpu_freq_by_mode[PM_MODE_CPU_MAX]);
assert(res);
res = rtc_clk_cpu_freq_mhz_to_config(apb_max_freq, &s_cpu_freq_by_mode[PM_MODE_APB_MAX]);
assert(res);
res = rtc_clk_cpu_freq_mhz_to_config(min_freq_mhz, &s_cpu_freq_by_mode[PM_MODE_APB_MIN]);
assert(res);
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);
return ESP_OK;
}
static pm_mode_t IRAM_ATTR get_lowest_allowed_mode(void)
{
/* TODO: optimize using ffs/clz */
if (s_mode_mask >= BIT(PM_MODE_CPU_MAX)) {
return PM_MODE_CPU_MAX;
} else if (s_mode_mask >= BIT(PM_MODE_APB_MAX)) {
return PM_MODE_APB_MAX;
} else if (s_mode_mask >= BIT(PM_MODE_APB_MIN) || !s_light_sleep_en) {
return PM_MODE_APB_MIN;
} else {
return PM_MODE_LIGHT_SLEEP;
}
}
void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode,
pm_mode_switch_t lock_or_unlock, pm_time_t now)
{
bool need_switch = false;
uint32_t mode_mask = BIT(mode);
portENTER_CRITICAL_SAFE(&s_switch_lock);
uint32_t count;
if (lock_or_unlock == MODE_LOCK) {
count = ++s_mode_lock_counts[mode];
} else {
count = s_mode_lock_counts[mode]--;
}
if (count == 1) {
if (lock_or_unlock == MODE_LOCK) {
s_mode_mask |= mode_mask;
} else {
s_mode_mask &= ~mode_mask;
}
need_switch = true;
}
pm_mode_t new_mode = s_mode;
if (need_switch) {
new_mode = get_lowest_allowed_mode();
#ifdef WITH_PROFILING
if (s_last_mode_change_time != 0) {
pm_time_t diff = now - s_last_mode_change_time;
s_time_in_mode[s_mode] += diff;
}
s_last_mode_change_time = now;
#endif // WITH_PROFILING
}
portEXIT_CRITICAL_SAFE(&s_switch_lock);
if (need_switch && new_mode != s_mode) {
do_switch(new_mode);
}
}
/**
* @brief Update clock dividers in esp_timer and FreeRTOS, and adjust CCOMPARE
* values on both CPUs.
* @param old_ticks_per_us old CPU frequency
* @param ticks_per_us new CPU frequency
*/
static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us)
{
uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80);
uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80);
/* Update APB frequency value used by the timer */
if (old_apb_ticks_per_us != apb_ticks_per_us) {
esp_timer_private_update_apb_freq(apb_ticks_per_us);
}
/* Calculate new tick divisor */
_xt_tick_divisor = ticks_per_us * MHZ / XT_TICK_PER_SEC;
int core_id = xPortGetCoreID();
if (s_rtos_lock_handle[core_id] != NULL) {
ESP_PM_TRACE_ENTER(CCOMPARE_UPDATE, core_id);
/* ccount_div and ccount_mul are used in esp_pm_impl_update_ccompare
* to calculate new CCOMPARE value.
*/
s_ccount_div = old_ticks_per_us;
s_ccount_mul = ticks_per_us;
/* Update CCOMPARE value on this CPU */
update_ccompare();
#if portNUM_PROCESSORS == 2
/* Send interrupt to the other CPU to update CCOMPARE value */
int other_core_id = (core_id == 0) ? 1 : 0;
s_need_update_ccompare[other_core_id] = true;
esp_crosscore_int_send_freq_switch(other_core_id);
int timeout = 0;
while (s_need_update_ccompare[other_core_id]) {
if (++timeout == CCOMPARE_UPDATE_TIMEOUT) {
assert(false && "failed to update CCOMPARE, possible deadlock");
}
}
#endif // portNUM_PROCESSORS == 2
s_ccount_mul = 0;
s_ccount_div = 0;
ESP_PM_TRACE_EXIT(CCOMPARE_UPDATE, core_id);
}
}
/**
* Perform the switch to new power mode.
* Currently only changes the CPU frequency and adjusts clock dividers.
* No light sleep yet.
* @param new_mode mode to switch to
*/
static void IRAM_ATTR do_switch(pm_mode_t new_mode)
{
const int core_id = xPortGetCoreID();
do {
portENTER_CRITICAL_ISR(&s_switch_lock);
if (!s_is_switching) {
break;
}
if (s_new_mode <= new_mode) {
portEXIT_CRITICAL_ISR(&s_switch_lock);
return;
}
if (s_need_update_ccompare[core_id]) {
s_need_update_ccompare[core_id] = false;
}
portEXIT_CRITICAL_ISR(&s_switch_lock);
} while (true);
s_new_mode = new_mode;
s_is_switching = true;
bool config_changed = s_config_changed;
s_config_changed = false;
portEXIT_CRITICAL_ISR(&s_switch_lock);
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_config = s_cpu_freq_by_mode[s_mode];
} else {
rtc_clk_cpu_freq_get_config(&old_config);
}
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;
ESP_PM_TRACE_ENTER(FREQ_SWITCH, core_id);
if (switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
rtc_clk_cpu_freq_set_config_fast(&new_config);
if (!switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
ESP_PM_TRACE_EXIT(FREQ_SWITCH, core_id);
}
portENTER_CRITICAL_ISR(&s_switch_lock);
s_mode = new_mode;
s_is_switching = false;
portEXIT_CRITICAL_ISR(&s_switch_lock);
}
/**
* @brief Calculate new CCOMPARE value based on s_ccount_{mul,div}
*
* Adjusts CCOMPARE value so that the interrupt happens at the same time as it
* would happen without the frequency change.
* Assumes that the new_frequency = old_frequency * s_ccount_mul / s_ccount_div.
*/
static void IRAM_ATTR update_ccompare(void)
{
uint32_t ccount = XTHAL_GET_CCOUNT();
uint32_t ccompare = XTHAL_GET_CCOMPARE(XT_TIMER_INDEX);
if ((ccompare - CCOMPARE_MIN_CYCLES_IN_FUTURE) - ccount < UINT32_MAX / 2) {
uint32_t diff = ccompare - ccount;
uint32_t diff_scaled = (diff * s_ccount_mul + s_ccount_div - 1) / s_ccount_div;
if (diff_scaled < _xt_tick_divisor) {
uint32_t new_ccompare = ccount + diff_scaled;
XTHAL_SET_CCOMPARE(XT_TIMER_INDEX, new_ccompare);
}
}
}
static void IRAM_ATTR leave_idle(void)
{
int core_id = xPortGetCoreID();
if (s_core_idle[core_id]) {
// TODO: possible optimization: raise frequency here first
esp_pm_lock_acquire(s_rtos_lock_handle[core_id]);
s_core_idle[core_id] = false;
}
}
void esp_pm_impl_idle_hook(void)
{
int core_id = xPortGetCoreID();
uint32_t state = portENTER_CRITICAL_NESTED();
if (!s_core_idle[core_id]) {
esp_pm_lock_release(s_rtos_lock_handle[core_id]);
s_core_idle[core_id] = true;
}
portEXIT_CRITICAL_NESTED(state);
ESP_PM_TRACE_ENTER(IDLE, core_id);
}
void IRAM_ATTR esp_pm_impl_isr_hook(void)
{
int core_id = xPortGetCoreID();
ESP_PM_TRACE_ENTER(ISR_HOOK, core_id);
/* Prevent higher level interrupts (than the one this function was called from)
* from happening in this section, since they will also call into esp_pm_impl_isr_hook.
*/
uint32_t state = portENTER_CRITICAL_NESTED();
#if portNUM_PROCESSORS == 2
if (s_need_update_ccompare[core_id]) {
update_ccompare();
s_need_update_ccompare[core_id] = false;
} else {
leave_idle();
}
#else
leave_idle();
#endif // portNUM_PROCESSORS == 2
portEXIT_CRITICAL_NESTED(state);
ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
}
void esp_pm_impl_waiti(void)
{
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
int core_id = xPortGetCoreID();
if (s_skipped_light_sleep[core_id]) {
asm("waiti 0");
/* Interrupt took the CPU out of waiti and s_rtos_lock_handle[core_id]
* is now taken. However since we are back to idle task, we can release
* the lock so that vApplicationSleep can attempt to enter light sleep.
*/
esp_pm_impl_idle_hook();
s_skipped_light_sleep[core_id] = false;
}
#else
asm("waiti 0");
#endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE
}
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
esp_err_t esp_pm_register_skip_light_sleep_callback(skip_light_sleep_cb_t cb)
{
for (int i = 0; i < PERIPH_SKIP_LIGHT_SLEEP_NO; i++) {
if (s_periph_skip_light_sleep_cb[i] == cb) {
return ESP_OK;
} else if (s_periph_skip_light_sleep_cb[i] == NULL) {
s_periph_skip_light_sleep_cb[i] = cb;
return ESP_OK;
}
}
return ESP_ERR_NO_MEM;
}
esp_err_t esp_pm_unregister_skip_light_sleep_callback(skip_light_sleep_cb_t cb)
{
for (int i = 0; i < PERIPH_SKIP_LIGHT_SLEEP_NO; i++) {
if (s_periph_skip_light_sleep_cb[i] == cb) {
s_periph_skip_light_sleep_cb[i] = NULL;
return ESP_OK;
}
}
return ESP_ERR_INVALID_STATE;
}
static inline bool IRAM_ATTR periph_should_skip_light_sleep(void)
{
for (int i = 0; i < PERIPH_SKIP_LIGHT_SLEEP_NO; i++) {
if (s_periph_skip_light_sleep_cb[i]) {
if (s_periph_skip_light_sleep_cb[i]() == true) {
return true;
}
}
}
return false;
}
static inline bool IRAM_ATTR should_skip_light_sleep(int core_id)
{
#if portNUM_PROCESSORS == 2
if (s_skip_light_sleep[core_id]) {
s_skip_light_sleep[core_id] = false;
s_skipped_light_sleep[core_id] = true;
return true;
}
#endif // portNUM_PROCESSORS == 2
if (s_mode != PM_MODE_LIGHT_SLEEP || s_is_switching || periph_should_skip_light_sleep()) {
s_skipped_light_sleep[core_id] = true;
} else {
s_skipped_light_sleep[core_id] = false;
}
return s_skipped_light_sleep[core_id];
}
static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
{
#if portNUM_PROCESSORS == 2
s_skip_light_sleep[!core_id] = true;
#endif
}
void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime )
{
portENTER_CRITICAL(&s_switch_lock);
int core_id = xPortGetCoreID();
if (!should_skip_light_sleep(core_id)) {
/* Calculate how much we can sleep */
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm();
int64_t now = esp_timer_get_time();
int64_t time_until_next_alarm = next_esp_timer_alarm - now;
int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime;
int64_t sleep_time_us = MIN(wakeup_delay_us, time_until_next_alarm);
if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) {
esp_sleep_enable_timer_wakeup(sleep_time_us - LIGHT_SLEEP_EARLY_WAKEUP_US);
#ifdef CONFIG_PM_TRACE
/* to force tracing GPIOs to keep state */
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
/* Enter sleep */
ESP_PM_TRACE_ENTER(SLEEP, core_id);
int64_t sleep_start = esp_timer_get_time();
esp_light_sleep_start();
int64_t slept_us = esp_timer_get_time() - sleep_start;
ESP_PM_TRACE_EXIT(SLEEP, core_id);
uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL);
if (slept_ticks > 0) {
/* Adjust RTOS tick count based on the amount of time spent in sleep */
vTaskStepTick(slept_ticks);
/* Trigger tick interrupt, since sleep time was longer
* than portTICK_PERIOD_MS. Note that setting INTSET does not
* work for timer interrupt, and changing CCOMPARE would clear
* the interrupt flag.
*/
XTHAL_SET_CCOUNT(XTHAL_GET_CCOMPARE(XT_TIMER_INDEX) - 16);
while (!(XTHAL_GET_INTERRUPT() & BIT(XT_TIMER_INTNUM))) {
;
}
}
other_core_should_skip_light_sleep(core_id);
}
}
portEXIT_CRITICAL(&s_switch_lock);
}
#endif //CONFIG_FREERTOS_USE_TICKLESS_IDLE
#ifdef WITH_PROFILING
void esp_pm_impl_dump_stats(FILE *out)
{
pm_time_t time_in_mode[PM_MODE_COUNT];
portENTER_CRITICAL_ISR(&s_switch_lock);
memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode));
pm_time_t last_mode_change_time = s_last_mode_change_time;
pm_mode_t cur_mode = s_mode;
pm_time_t now = pm_get_time();
portEXIT_CRITICAL_ISR(&s_switch_lock);
time_in_mode[cur_mode] += now - last_mode_change_time;
fprintf(out, "Mode stats:\n");
for (int i = 0; i < PM_MODE_COUNT; ++i) {
if (i == PM_MODE_LIGHT_SLEEP && !s_light_sleep_en) {
/* don't display light sleep mode if it's not enabled */
continue;
}
fprintf(out, "%8s %3dM %12lld %2d%%\n",
s_mode_names[i],
s_cpu_freq_by_mode[i].freq_mhz,
time_in_mode[i],
(int) (time_in_mode[i] * 100 / now));
}
}
#endif // WITH_PROFILING
void esp_pm_impl_init(void)
{
#ifdef CONFIG_PM_TRACE
esp_pm_trace_init();
#endif
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos0",
&s_rtos_lock_handle[0]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[0]));
#if portNUM_PROCESSORS == 2
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos1",
&s_rtos_lock_handle[1]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[1]));
#endif // portNUM_PROCESSORS == 2
/* Configure all modes to use the default CPU frequency.
* This will be modified later by a call to esp_pm_configure.
*/
rtc_cpu_freq_config_t default_config;
if (!rtc_clk_cpu_freq_mhz_to_config(CONFIG_ESP32S3_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_config;
}
}

View File

@ -1,51 +0,0 @@
// Copyright 2016-2020 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 "esp_private/pm_trace.h"
#include "driver/gpio.h"
#include "soc/gpio_reg.h"
/* GPIOs to use for tracing of esp_pm events.
* Two entries in the array for each type, one for each CPU.
* Feel free to change when debugging.
*/
static const int DRAM_ATTR s_trace_io[] = {
BIT(4), BIT(5), // ESP_PM_TRACE_IDLE
BIT(16), BIT(17), // ESP_PM_TRACE_TICK
BIT(18), BIT(18), // ESP_PM_TRACE_FREQ_SWITCH
BIT(19), BIT(19), // ESP_PM_TRACE_CCOMPARE_UPDATE
BIT(25), BIT(26), // ESP_PM_TRACE_ISR_HOOK
BIT(27), BIT(27), // ESP_PM_TRACE_SLEEP
};
void esp_pm_trace_init(void)
{
for (size_t i = 0; i < sizeof(s_trace_io) / sizeof(s_trace_io[0]); ++i) {
int io = __builtin_ffs(s_trace_io[i]);
if (io == 0) {
continue;
}
gpio_set_direction(io - 1, GPIO_MODE_OUTPUT);
}
}
void IRAM_ATTR esp_pm_trace_enter(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TS_REG, s_trace_io[2 * event + core_id]);
}
void IRAM_ATTR esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TC_REG, s_trace_io[2 * event + core_id]);
}

View File

@ -18,15 +18,14 @@ else()
"src/esp_err_to_name.c"
"src/freertos_hooks.c"
"src/mac_addr.c"
"src/pm_locks.c"
"src/stack_check.c"
"src/task_wdt.c"
"src/int_wdt.c")
# Note: esp_ipc added as a public requirement to keep compatibility as to be located here.
# Note: esp_ipc, esp_pm added as a public requirement to keep compatibility as to be located here.
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS include
REQUIRES ${target} espcoredump esp_timer esp_ipc
REQUIRES ${target} espcoredump esp_timer esp_ipc esp_pm
PRIV_REQUIRES soc
LDFRAGMENTS "linker.lf")

View File

@ -14,7 +14,7 @@ if(CONFIG_ETH_ENABLED)
# esp_netif related
if(esp_netif IN_LIST components_to_build)
list(APPEND srcs "src/esp_eth_netif_glue.c")
list(APPEND priv_requires "esp_netif")
list(APPEND priv_requires esp_netif esp_pm)
endif()
endif()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "pm_locks.c" "pm_trace.c" "pm_impl.c"
INCLUDE_DIRS include)

64
components/esp_pm/Kconfig Normal file
View File

@ -0,0 +1,64 @@
menu "Power Management"
config PM_ENABLE
bool "Support for power management"
default n
help
If enabled, application is compiled with support for power management.
This option has run-time overhead (increased interrupt latency,
longer time to enter idle state), and it also reduces accuracy of
RTOS ticks and timers used for timekeeping.
Enable this option if application uses power management APIs.
config PM_DFS_INIT_AUTO
bool "Enable dynamic frequency scaling (DFS) at startup"
depends on PM_ENABLE
default n
help
If enabled, startup code configures dynamic frequency scaling.
Max CPU frequency is set to DEFAULT_CPU_FREQ_MHZ setting,
min frequency is set to XTAL frequency.
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 && ESP_TIMER_IMPL_FRC2 && ESP_TIMER_RTC_USE)
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
default n
help
If enabled, esp_pm_* functions will keep track of the amount of time
each of the power management locks has been held, and esp_pm_dump_locks
function will print this information.
This feature can be used to analyze which locks are preventing the chip
from going into a lower power state, and see what time the chip spends
in each power saving mode. This feature does incur some run-time
overhead, so should typically be disabled in production builds.
config PM_TRACE
bool "Enable debug tracing of PM using GPIOs"
depends on PM_ENABLE
default n
help
If enabled, some GPIOs will be used to signal events such as RTOS ticks,
frequency switching, entry/exit from idle state. Refer to pm_trace.c
file for the list of GPIOs.
This feature is intended to be used when analyzing/debugging behavior
of power management implementation, and should be kept disabled in
applications.
endmenu # "Power Management"

View File

@ -0,0 +1,2 @@
COMPONENT_SRCDIRS := .
COMPONENT_ADD_INCLUDEDIRS := include

View File

@ -177,8 +177,6 @@ esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle);
*/
esp_err_t esp_pm_dump_locks(FILE* stream);
#ifdef __cplusplus
}
#endif

View File

@ -26,6 +26,9 @@
#include "esp_timer.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* This is an enum of possible power modes supported by the implementation
@ -151,3 +154,7 @@ static inline pm_time_t IRAM_ATTR pm_get_time(void)
return esp_timer_get_time();
}
#endif // WITH_PROFILING
#ifdef __cplusplus
}
#endif

View File

@ -16,6 +16,10 @@
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ESP_PM_TRACE_IDLE,
ESP_PM_TRACE_TICK,
@ -43,3 +47,7 @@ void esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id);
#define ESP_PM_TRACE_EXIT(type, core_id) do { (void) core_id; } while(0)
#endif // CONFIG_PM_TRACE
#ifdef __cplusplus
}
#endif

View File

@ -1,4 +1,4 @@
// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD
// Copyright 2016-2017 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.
@ -21,11 +21,14 @@
#include "esp_err.h"
#include "esp_pm.h"
#include "esp_log.h"
#include "esp32s2/clk.h"
#include "esp_private/crosscore_int.h"
#include "soc/rtc.h"
#include "hal/uart_ll.h"
#include "hal/uart_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_timer.h"
@ -34,9 +37,25 @@
#include "esp_private/pm_impl.h"
#include "esp_private/pm_trace.h"
#include "esp_private/esp_timer_private.h"
#include "esp32s2/pm.h"
#include "esp_sleep.h"
#include "sdkconfig.h"
// [refactor-todo] opportunity for further refactor
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/clk.h"
#include "esp32/pm.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/clk.h"
#include "esp32s2/pm.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/clk.h"
#include "esp32s3/pm.h"
#endif
#define MHZ (1000000)
/* CCOMPARE update timeout, in CPU cycles. Any value above ~600 cycles will work
* for the purpose of detecting a deadlock.
*/
@ -52,14 +71,24 @@
*/
#define LIGHT_SLEEP_EARLY_WAKEUP_US 100
#if CONFIG_IDF_TARGET_ESP32
/* Minimal divider at which REF_CLK_FREQ can be obtained */
#define REF_CLK_DIV_MIN 10
#define DEFAULT_CPU_FREQ CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ
#elif CONFIG_IDF_TARGET_ESP32S2
/* Minimal divider at which REF_CLK_FREQ can be obtained */
#define REF_CLK_DIV_MIN 2
#define DEFAULT_CPU_FREQ CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ
#elif CONFIG_IDF_TARGET_ESP32S3
/* Minimal divider at which REF_CLK_FREQ can be obtained */
#define REF_CLK_DIV_MIN 2
#define DEFAULT_CPU_FREQ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ
#endif
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#endif
static portMUX_TYPE s_switch_lock = portMUX_INITIALIZER_UNLOCKED;
/* The following state variables are protected using s_switch_lock: */
/* Current sleep mode; When switching, contains old mode until switch is complete */
@ -80,10 +109,13 @@ static uint32_t s_ccount_div;
static uint32_t s_ccount_mul;
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define PERIPH_SKIP_LIGHT_SLEEP_NO 1
/* Indicates if light sleep shoule be skipped by peripherals. */
static skip_light_sleep_cb_t s_periph_skip_light_sleep_cb[PERIPH_SKIP_LIGHT_SLEEP_NO];
#endif
/* Indicates if light sleep entry was skipped in vApplicationSleep for given CPU.
* This in turn gets used in IDLE hook to decide if `waiti` needs
@ -145,15 +177,13 @@ static const char* s_mode_names[] = {
};
#endif // WITH_PROFILING
static const char* TAG = "pm_esp32s2";
static const char* TAG = "pm_" CONFIG_IDF_TARGET;
static void update_ccompare(void);
static void do_switch(pm_mode_t new_mode);
static void leave_idle(void);
static void on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us);
pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
{
(void) arg;
@ -175,7 +205,14 @@ esp_err_t esp_pm_configure(const void* vconfig)
return ESP_ERR_NOT_SUPPORTED;
#endif
#if CONFIG_IDF_TARGET_ESP32
const esp_pm_config_esp32_t* config = (const esp_pm_config_esp32_t*) vconfig;
#elif CONFIG_IDF_TARGET_ESP32S2
const esp_pm_config_esp32s2_t* config = (const esp_pm_config_esp32s2_t*) vconfig;
#elif CONFIG_IDF_TARGET_ESP32S3
const esp_pm_config_esp32s3_t* config = (const esp_pm_config_esp32s3_t*) vconfig;
#endif
#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE
if (config->light_sleep_enable) {
return ESP_ERR_NOT_SUPPORTED;
@ -206,7 +243,23 @@ esp_err_t esp_pm_configure(const void* vconfig)
return ESP_ERR_INVALID_ARG;
}
#if CONFIG_IDF_TARGET_ESP32
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 = 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 = 80;
}
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
int apb_max_freq = MIN(max_freq_mhz, 80); /* CPU frequency in APB_MAX mode */
#endif
apb_max_freq = MAX(apb_max_freq, min_freq_mhz);
ESP_LOGI(TAG, "Frequency switching config: "
@ -217,8 +270,9 @@ esp_err_t esp_pm_configure(const void* vconfig)
config->light_sleep_enable ? "ENABLED" : "DISABLED");
portENTER_CRITICAL(&s_switch_lock);
bool res;
res = rtc_clk_cpu_freq_mhz_to_config(max_freq_mhz, &s_cpu_freq_by_mode[PM_MODE_CPU_MAX]);
bool res = false;
res = rtc_clk_cpu_freq_mhz_to_config(max_freq_mhz, &s_cpu_freq_by_mode[PM_MODE_CPU_MAX]);
assert(res);
res = rtc_clk_cpu_freq_mhz_to_config(apb_max_freq, &s_cpu_freq_by_mode[PM_MODE_APB_MAX]);
assert(res);
@ -482,6 +536,7 @@ void esp_pm_impl_waiti(void)
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
esp_err_t esp_pm_register_skip_light_sleep_callback(skip_light_sleep_cb_t cb)
{
for (int i = 0; i < PERIPH_SKIP_LIGHT_SLEEP_NO; i++) {
@ -517,6 +572,7 @@ static inline bool IRAM_ATTR periph_should_skip_light_sleep(void)
}
return false;
}
#endif
static inline bool IRAM_ATTR should_skip_light_sleep(int core_id)
{
@ -527,7 +583,11 @@ static inline bool IRAM_ATTR should_skip_light_sleep(int core_id)
return true;
}
#endif // portNUM_PROCESSORS == 2
#if CONFIG_IDF_TARGET_ESP32
if (s_mode != PM_MODE_LIGHT_SLEEP || s_is_switching) {
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
if (s_mode != PM_MODE_LIGHT_SLEEP || s_is_switching || periph_should_skip_light_sleep()) {
#endif
s_skipped_light_sleep[core_id] = true;
} else {
s_skipped_light_sleep[core_id] = false;
@ -619,12 +679,20 @@ void esp_pm_impl_dump_stats(FILE* out)
void esp_pm_impl_init(void)
{
#if defined(CONFIG_ESP_CONSOLE_UART)
while(!uart_ll_get_txfifo_len(UART_LL_GET_HW(CONFIG_ESP_CONSOLE_UART_NUM)));
/* When DFS is enabled, override system setting and use REFTICK as UART clock source */
uart_ll_set_baudrate(UART_LL_GET_HW(CONFIG_ESP_CONSOLE_UART_NUM), UART_SCLK_REF_TICK, CONFIG_ESP_CONSOLE_UART_BAUDRATE);
#endif // CONFIG_ESP_CONSOLE_UART
#ifdef CONFIG_PM_TRACE
esp_pm_trace_init();
#endif
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos0",
&s_rtos_lock_handle[0]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[0]));
#if portNUM_PROCESSORS == 2
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos1",
&s_rtos_lock_handle[1]));
@ -635,10 +703,26 @@ void esp_pm_impl_init(void)
* This will be modified later by a call to esp_pm_configure.
*/
rtc_cpu_freq_config_t default_config;
if (!rtc_clk_cpu_freq_mhz_to_config(CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ, &default_config)) {
if (!rtc_clk_cpu_freq_mhz_to_config(DEFAULT_CPU_FREQ, &default_config)) {
assert(false && "unsupported frequency");
}
for (size_t i = 0; i < PM_MODE_COUNT; ++i) {
s_cpu_freq_by_mode[i] = default_config;
}
#ifdef CONFIG_PM_DFS_INIT_AUTO
int xtal_freq = (int) rtc_clk_xtal_freq_get();
#if CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t cfg = {
#elif CONFIG_IDF_TARGET_ESP32S2
esp_pm_config_esp32s2_t cfg = {
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t cfg = {
#endif
.max_freq_mhz = DEFAULT_CPU_FREQ,
.min_freq_mhz = xtal_freq,
};
esp_pm_configure(&cfg);
#endif //CONFIG_PM_DFS_INIT_AUTO
}

View File

@ -48,4 +48,4 @@ void IRAM_ATTR esp_pm_trace_enter(esp_pm_trace_event_t event, int core_id)
void IRAM_ATTR esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TC_REG, s_trace_io[2 * event + core_id]);
}
}

View File

@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS .
PRIV_REQUIRES unity esp_pm ulp)

View File

@ -0,0 +1,5 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -5,18 +5,28 @@
#include <sys/param.h>
#include "unity.h"
#include "esp_pm.h"
#include "esp32/clk.h"
#include "esp_sleep.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "driver/timer.h"
#include "driver/rtc_io.h"
#include "esp32/ulp.h"
#include "soc/rtc_periph.h"
#include "esp_rom_sys.h"
#define MHZ 1000000
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/clk.h"
#include "esp32/ulp.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/clk.h"
#include "esp32s2/ulp.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/clk.h"
#include "esp32s3/ulp.h"
#endif
TEST_CASE("Can dump power management lock stats", "[pm]")
{
@ -28,7 +38,13 @@ TEST_CASE("Can dump power management lock stats", "[pm]")
static void switch_freq(int mhz)
{
int xtal_freq = rtc_clk_xtal_freq_get();
#if CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S2
esp_pm_config_esp32s2_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config = {
#endif
.max_freq_mhz = mhz,
.min_freq_mhz = MIN(mhz, xtal_freq),
};
@ -66,7 +82,13 @@ static void light_sleep_enable(void)
int cur_freq_mhz = esp_clk_cpu_freq() / MHZ;
int xtal_freq = (int) rtc_clk_xtal_freq_get();
const esp_pm_config_esp32_t pm_config = {
#if CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S2
esp_pm_config_esp32s2_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config = {
#endif
.max_freq_mhz = cur_freq_mhz,
.min_freq_mhz = xtal_freq,
.light_sleep_enable = true
@ -78,7 +100,13 @@ static void light_sleep_disable(void)
{
int cur_freq_mhz = esp_clk_cpu_freq() / MHZ;
const esp_pm_config_esp32_t pm_config = {
#if CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S2
esp_pm_config_esp32s2_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config = {
#endif
.max_freq_mhz = cur_freq_mhz,
.min_freq_mhz = cur_freq_mhz,
};
@ -125,10 +153,16 @@ TEST_CASE("Automatic light occurs when tasks are suspended", "[pm]")
light_sleep_disable();
}
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
TEST_CASE("Can wake up from automatic light sleep by GPIO", "[pm]")
{
#if CONFIG_IDF_TARGET_ESP32
assert(CONFIG_ESP32_ULP_COPROC_RESERVE_MEM >= 16 && "this test needs ESP32_ULP_COPROC_RESERVE_MEM option set in menuconfig");
#elif CONFIG_IDF_TARGET_ESP32S2
assert(CONFIG_ESP32S2_ULP_COPROC_RESERVE_MEM >= 16 && "this test needs ESP32_ULP_COPROC_RESERVE_MEM option set in menuconfig");
#elif CONFIG_IDF_TARGET_ESP32S3
assert(CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM >= 16 && "this test needs ESP32_ULP_COPROC_RESERVE_MEM option set in menuconfig");
#endif
/* Set up GPIO used to wake up RTC */
const int ext1_wakeup_gpio = 25;
@ -192,8 +226,7 @@ TEST_CASE("Can wake up from automatic light sleep by GPIO", "[pm]")
light_sleep_disable();
}
#endif
typedef struct {
int delay_us;
int result;

View File

@ -1,8 +1,10 @@
idf_component_register(SRCS "esp_async_memcpy.c" "panic.c" "system_api.c" "startup.c" "sleep_modes.c" "system_time.c"
INCLUDE_DIRS include
PRIV_REQUIRES spi_flash app_update
# requirements due to startup code
nvs_flash pthread app_trace
PRIV_REQUIRES spi_flash
# [refactor-todo] requirements due to init code,
# should be removable once using component init functions
# link-time registration is used.
esp_pm app_update nvs_flash pthread app_trace
LDFRAGMENTS "linker.lf")
add_subdirectory(port)

View File

@ -369,29 +369,9 @@ IRAM_ATTR ESP_SYSTEM_INIT_FN(init_components0, BIT(0))
{
esp_timer_init();
#if defined(CONFIG_PM_ENABLE) && defined(CONFIG_ESP_CONSOLE_UART)
/* When DFS is enabled, use REFTICK as UART clock source */
uart_ll_set_baudrate(UART_LL_GET_HW(CONFIG_ESP_CONSOLE_UART_NUM), UART_SCLK_REF_TICK, CONFIG_ESP_CONSOLE_UART_BAUDRATE);
#endif // CONFIG_ESP_CONSOLE_UART_NONE
#ifdef CONFIG_PM_ENABLE
#if defined(CONFIG_PM_ENABLE)
esp_pm_impl_init();
#ifdef CONFIG_PM_DFS_INIT_AUTO
int xtal_freq = (int) rtc_clk_xtal_freq_get();
#ifdef CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t cfg = {
.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ,
.min_freq_mhz = xtal_freq,
};
#else
esp_pm_config_esp32s2_t cfg = {
.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ,
.min_freq_mhz = xtal_freq,
};
#endif
esp_pm_configure(&cfg);
#endif //CONFIG_PM_DFS_INIT_AUTO
#endif //CONFIG_PM_ENABLE
#if CONFIG_IDF_TARGET_ESP32
#if CONFIG_ESP32_ENABLE_COREDUMP

View File

@ -9,6 +9,9 @@ menu "High resolution timer (esp_timer)"
This option has some effect on timer performance and the amount of memory used for timer
storage, and should only be used for debugging/testing purposes.
config ESP_TIMER_RTC_USE # [refactor-todo] remove when timekeeping and persistence are separate
bool
config ESP_TIMER_TASK_STACK_SIZE
int "High-resolution timer task stack size"
default 3584

View File

@ -35,7 +35,7 @@ idf_component_register(SRCS "src/coexist.c"
"${idf_target}/esp_adapter.c"
INCLUDE_DIRS "include" "${idf_target}/include"
REQUIRES esp_event
PRIV_REQUIRES wpa_supplicant nvs_flash esp_netif ${extra_priv_requires}
PRIV_REQUIRES esp_pm wpa_supplicant nvs_flash esp_netif ${extra_priv_requires}
LDFRAGMENTS "${ldfragments}")
idf_build_get_property(build_dir BUILD_DIR)

View File

@ -4,7 +4,7 @@ idf_build_get_property(python PYTHON)
idf_component_register(SRCS "esp_crt_bundle/esp_crt_bundle.c"
INCLUDE_DIRS "port/include" "mbedtls/include" "esp_crt_bundle/include"
REQUIRES lwip
PRIV_REQUIRES soc
PRIV_REQUIRES esp_pm soc
)
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)

View File

@ -19,6 +19,8 @@
#include "soc/soc.h"
#include "soc/rtc_periph.h"
#define MHZ (1000000)
#ifdef __cplusplus
extern "C" {
#endif

View File

@ -277,8 +277,8 @@ INPUT = \
$(IDF_PATH)/components/app_trace/include/esp_app_trace.h \
$(IDF_PATH)/components/app_trace/include/esp_sysview_trace.h \
### Power management
$(IDF_PATH)/components/esp_common/include/esp_pm.h \
$(IDF_PATH)/components/$(IDF_TARGET)/include/$(IDF_TARGET)/pm.h \
$(IDF_PATH)/components/esp_pm/include/esp_pm.h \
$(IDF_PATH)/components/esp_pm/include/$(IDF_TARGET)/pm.h \
### esp_timer, High Resolution Timer
$(IDF_PATH)/components/esp_timer/include/esp_timer.h \
### esp_event, Event Loop Library

View File

@ -1,3 +1,3 @@
# This config is split between targets since different component needs to be excluded (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32 esp_ipc esp_system esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32 esp_ipc esp_pm esp_system esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component

View File

@ -1,3 +1,3 @@
# This config is split between targets since different component needs to be excluded (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32s2"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32s2 esp_ipc esp_system esp_timer driver heap pthread soc spi_flash vfs experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32s2 esp_ipc esp_pm esp_system esp_timer driver heap pthread soc spi_flash vfs experimental_cpp_component

View File

@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32"
TEST_COMPONENTS=esp_pm
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y

View File

@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32s2"
TEST_COMPONENTS=esp_pm
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y

View File

@ -1,5 +1,5 @@
CONFIG_IDF_TARGET="esp32"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update driver esp32 esp_ipc esp_system esp_timer mbedtls spi_flash test_utils heap pthread soc experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update driver esp32 esp_ipc esp_pm esp_system esp_timer mbedtls spi_flash test_utils heap pthread soc experimental_cpp_component
CONFIG_ESP32_SPIRAM_SUPPORT=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=800
CONFIG_SPIRAM_OCCUPY_NO_HOST=y

View File

@ -1,6 +1,6 @@
# This config is split between targets since different component needs to be included (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32 esp_ipc esp_system esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32 esp_ipc esp_pm esp_system esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

View File

@ -1,6 +1,6 @@
# This config is split between targets since different component needs to be excluded (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32s2"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32s2 esp_ipc esp_system esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32s2 esp_ipc esp_pm esp_system esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

View File

@ -1,6 +1,6 @@
# This config is split between targets since different component needs to be excluded (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32 esp_system esp_ipc esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32 esp_system esp_pm esp_ipc esp_timer driver heap pthread soc spi_flash vfs test_utils experimental_cpp_component
CONFIG_MEMMAP_SMP=n
CONFIG_FREERTOS_UNICORE=y
CONFIG_ESP32_RTCDATA_IN_FAST_MEM=y

View File

@ -1,6 +1,6 @@
# This config is split between targets since different component needs to be excluded (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32s2"
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32s2 esp_ipc esp_system esp_timer driver heap pthread soc spi_flash vfs experimental_cpp_component
TEST_EXCLUDE_COMPONENTS=libsodium bt app_update freertos esp32s2 esp_ipc esp_system esp_pm esp_timer driver heap pthread soc spi_flash vfs experimental_cpp_component
CONFIG_MEMMAP_SMP=n
CONFIG_FREERTOS_UNICORE=y
CONFIG_ESP32S2_RTCDATA_IN_FAST_MEM=y