forked from espressif/esp-idf
Merge branch 'feature/dfs' into 'master'
Dynamic frequency scaling See merge request !1189
This commit is contained in:
@@ -945,3 +945,68 @@ config ESP32_PHY_MAX_TX_POWER
|
||||
default ESP32_PHY_MAX_WIFI_TX_POWER
|
||||
|
||||
endmenu # PHY
|
||||
|
||||
|
||||
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 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 && (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"
|
||||
@@ -15,10 +15,12 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/param.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_clk.h"
|
||||
#include "esp_clk_internal.h"
|
||||
#include "rom/ets_sys.h"
|
||||
#include "rom/uart.h"
|
||||
#include "rom/rtc.h"
|
||||
@@ -40,14 +42,13 @@
|
||||
|
||||
static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk);
|
||||
|
||||
// g_ticks_us defined in ROMs for PRO and APP CPU
|
||||
extern uint32_t g_ticks_per_us_pro;
|
||||
extern uint32_t g_ticks_per_us_app;
|
||||
|
||||
static const char* TAG = "clk";
|
||||
/*
|
||||
* This function is not exposed as an API at this point,
|
||||
* because FreeRTOS doesn't yet support dynamic changing of
|
||||
* CPU frequency. Also we need to implement hooks for
|
||||
* components which want to be notified of CPU frequency
|
||||
* changes.
|
||||
*/
|
||||
|
||||
|
||||
void esp_clk_init(void)
|
||||
{
|
||||
rtc_config_t cfg = RTC_CONFIG_DEFAULT();
|
||||
@@ -90,10 +91,19 @@ void esp_clk_init(void)
|
||||
XTHAL_SET_CCOUNT( XTHAL_GET_CCOUNT() * freq_after / freq_before );
|
||||
}
|
||||
|
||||
int IRAM_ATTR esp_clk_cpu_freq(void)
|
||||
{
|
||||
return g_ticks_per_us_pro * 1000000;
|
||||
}
|
||||
|
||||
int IRAM_ATTR esp_clk_apb_freq(void)
|
||||
{
|
||||
return MIN(g_ticks_per_us_pro, 80) * 1000000;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us)
|
||||
{
|
||||
extern uint32_t g_ticks_per_us_pro; // g_ticks_us defined in ROM for PRO CPU
|
||||
extern uint32_t g_ticks_per_us_app; // same defined for APP CPU
|
||||
/* Update scale factors used by ets_delay_us */
|
||||
g_ticks_per_us_pro = ticks_per_us;
|
||||
g_ticks_per_us_app = ticks_per_us;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "esp_panic.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_clk.h"
|
||||
|
||||
#if CONFIG_ESP32_ENABLE_COREDUMP
|
||||
#define LOG_LOCAL_LEVEL CONFIG_ESP32_CORE_DUMP_LOG_LEVEL
|
||||
@@ -522,10 +523,11 @@ void esp_core_dump_to_uart(XtExcFrame *frame)
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD);
|
||||
|
||||
ESP_COREDUMP_LOGI("Press Enter to print core dump to UART...");
|
||||
tm_end = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000) + CONFIG_ESP32_CORE_DUMP_UART_DELAY;
|
||||
const int cpu_ticks_per_ms = esp_clk_cpu_freq() / 1000;
|
||||
tm_end = xthal_get_ccount() / cpu_ticks_per_ms + CONFIG_ESP32_CORE_DUMP_UART_DELAY;
|
||||
ch = esp_core_dump_uart_get_char();
|
||||
while (!(ch == '\n' || ch == '\r')) {
|
||||
tm_cur = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000);
|
||||
tm_cur = xthal_get_ccount() / cpu_ticks_per_ms;
|
||||
if (tm_cur >= tm_end)
|
||||
break;
|
||||
ch = esp_core_dump_uart_get_char();
|
||||
|
||||
@@ -64,8 +64,10 @@
|
||||
#include "esp_app_trace.h"
|
||||
#include "esp_efuse.h"
|
||||
#include "esp_spiram.h"
|
||||
#include "esp_clk.h"
|
||||
#include "esp_clk_internal.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_pm.h"
|
||||
#include "pm_impl.h"
|
||||
#include "trax.h"
|
||||
|
||||
#define STRINGIFY(s) STRINGIFY2(s)
|
||||
@@ -287,9 +289,18 @@ void start_cpu0_default(void)
|
||||
esp_clk_init();
|
||||
esp_perip_clk_init();
|
||||
intr_matrix_clear();
|
||||
|
||||
#ifndef CONFIG_CONSOLE_UART_NONE
|
||||
uart_div_modify(CONFIG_CONSOLE_UART_NUM, (rtc_clk_apb_freq_get() << 4) / CONFIG_CONSOLE_UART_BAUDRATE);
|
||||
#endif
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
const int uart_clk_freq = REF_CLK_FREQ;
|
||||
/* When DFS is enabled, use REFTICK as UART clock source */
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0_REG(CONFIG_CONSOLE_UART_NUM), UART_TICK_REF_ALWAYS_ON);
|
||||
#else
|
||||
const int uart_clk_freq = APB_CLK_FREQ;
|
||||
#endif // CONFIG_PM_DFS_ENABLE
|
||||
uart_div_modify(CONFIG_CONSOLE_UART_NUM, (uart_clk_freq << 4) / CONFIG_CONSOLE_UART_BAUDRATE);
|
||||
#endif // CONFIG_CONSOLE_UART_NONE
|
||||
|
||||
#if CONFIG_BROWNOUT_DET
|
||||
esp_brownout_init();
|
||||
#endif
|
||||
@@ -337,6 +348,18 @@ void start_cpu0_default(void)
|
||||
spi_flash_init();
|
||||
/* init default OS-aware flash access critical section */
|
||||
spi_flash_guard_set(&g_flash_guard_default_ops);
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_impl_init();
|
||||
#ifdef CONFIG_PM_DFS_INIT_AUTO
|
||||
rtc_cpu_freq_t max_freq;
|
||||
rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &max_freq);
|
||||
esp_pm_config_esp32_t cfg = {
|
||||
.max_cpu_freq = max_freq,
|
||||
.min_cpu_freq = RTC_CPU_FREQ_XTAL
|
||||
};
|
||||
esp_pm_configure(&cfg);
|
||||
#endif //CONFIG_PM_DFS_INIT_AUTO
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
|
||||
#if CONFIG_ESP32_ENABLE_COREDUMP
|
||||
esp_core_dump_init();
|
||||
|
||||
@@ -34,20 +34,25 @@
|
||||
#include "freertos/portmacro.h"
|
||||
|
||||
|
||||
#define REASON_YIELD (1<<0)
|
||||
#define REASON_YIELD BIT(0)
|
||||
#define REASON_FREQ_SWITCH BIT(1)
|
||||
|
||||
static portMUX_TYPE reasonSpinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static portMUX_TYPE reason_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static volatile uint32_t reason[ portNUM_PROCESSORS ];
|
||||
|
||||
|
||||
/*
|
||||
ToDo: There is a small chance the CPU already has yielded when this ISR is serviced. In that case, it's running the intended task but
|
||||
the ISR will cause it to switch _away_ from it. portYIELD_FROM_ISR will probably just schedule the task again, but have to check that.
|
||||
*/
|
||||
static void esp_crosscore_isr_handle_yield()
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
static void IRAM_ATTR esp_crosscore_isr(void *arg) {
|
||||
uint32_t myReasonVal;
|
||||
uint32_t my_reason_val;
|
||||
//A pointer to the correct reason array item is passed to this ISR.
|
||||
volatile uint32_t *myReason=arg;
|
||||
volatile uint32_t *my_reason=arg;
|
||||
|
||||
//Clear the interrupt first.
|
||||
if (xPortGetCoreID()==0) {
|
||||
@@ -56,43 +61,59 @@ static void IRAM_ATTR esp_crosscore_isr(void *arg) {
|
||||
DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0);
|
||||
}
|
||||
//Grab the reason and clear it.
|
||||
portENTER_CRITICAL(&reasonSpinlock);
|
||||
myReasonVal=*myReason;
|
||||
*myReason=0;
|
||||
portEXIT_CRITICAL(&reasonSpinlock);
|
||||
portENTER_CRITICAL(&reason_spinlock);
|
||||
my_reason_val=*my_reason;
|
||||
*my_reason=0;
|
||||
portEXIT_CRITICAL(&reason_spinlock);
|
||||
|
||||
//Check what we need to do.
|
||||
if (myReasonVal&REASON_YIELD) {
|
||||
portYIELD_FROM_ISR();
|
||||
if (my_reason_val & REASON_YIELD) {
|
||||
esp_crosscore_isr_handle_yield();
|
||||
}
|
||||
if (my_reason_val & REASON_FREQ_SWITCH) {
|
||||
/* Nothing to do here; the frequency switch event was already
|
||||
* handled by a hook in xtensa_vectors.S. Could be used in the future
|
||||
* to allow DFS features without the extra latency of the ISR hook.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
//Initialize the crosscore interrupt on this core. Call this once
|
||||
//on each active core.
|
||||
void esp_crosscore_int_init() {
|
||||
portENTER_CRITICAL(&reasonSpinlock);
|
||||
portENTER_CRITICAL(&reason_spinlock);
|
||||
reason[xPortGetCoreID()]=0;
|
||||
portEXIT_CRITICAL(&reasonSpinlock);
|
||||
portEXIT_CRITICAL(&reason_spinlock);
|
||||
esp_err_t err;
|
||||
if (xPortGetCoreID()==0) {
|
||||
err = esp_intr_alloc(ETS_FROM_CPU_INTR0_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL);
|
||||
err = esp_intr_alloc(ETS_FROM_CPU_INTR0_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[0], NULL);
|
||||
} else {
|
||||
err = esp_intr_alloc(ETS_FROM_CPU_INTR1_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL);
|
||||
err = esp_intr_alloc(ETS_FROM_CPU_INTR1_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[1], NULL);
|
||||
}
|
||||
assert(err == ESP_OK);
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_crosscore_int_send_yield(int coreId) {
|
||||
assert(coreId<portNUM_PROCESSORS);
|
||||
static void IRAM_ATTR esp_crosscore_int_send(int core_id, uint32_t reason_mask) {
|
||||
assert(core_id<portNUM_PROCESSORS);
|
||||
//Mark the reason we interrupt the other CPU
|
||||
portENTER_CRITICAL(&reasonSpinlock);
|
||||
reason[coreId]|=REASON_YIELD;
|
||||
portEXIT_CRITICAL(&reasonSpinlock);
|
||||
portENTER_CRITICAL(&reason_spinlock);
|
||||
reason[core_id] |= reason_mask;
|
||||
portEXIT_CRITICAL(&reason_spinlock);
|
||||
//Poke the other CPU.
|
||||
if (coreId==0) {
|
||||
if (core_id==0) {
|
||||
DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, DPORT_CPU_INTR_FROM_CPU_0);
|
||||
} else {
|
||||
DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, DPORT_CPU_INTR_FROM_CPU_1);
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_crosscore_int_send_yield(int core_id)
|
||||
{
|
||||
esp_crosscore_int_send(core_id, REASON_YIELD);
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_crosscore_int_send_freq_switch(int core_id)
|
||||
{
|
||||
esp_crosscore_int_send(core_id, REASON_FREQ_SWITCH);
|
||||
}
|
||||
|
||||
|
||||
39
components/esp32/esp_clk_internal.h
Normal file
39
components/esp32/esp_clk_internal.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2015-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.
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file esp_clk_internal.h
|
||||
*
|
||||
* Private clock-related functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Initialize clock-related settings
|
||||
*
|
||||
* Called from cpu_start.c, not intended to be called from other places.
|
||||
* This function configures the CPU clock, RTC slow and fast clocks, and
|
||||
* performs RTC slow clock calibration.
|
||||
*/
|
||||
void esp_clk_init(void);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Disables clock of some peripherals
|
||||
*
|
||||
* Called from cpu_start.c, not intended to be called from other places.
|
||||
* This function disables clock of useless peripherals when cpu starts.
|
||||
*/
|
||||
void esp_perip_clk_init(void);
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
#include "esp_attr.h"
|
||||
#include "esp_freertos_hooks.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_pm.h"
|
||||
#include "pm_impl.h"
|
||||
|
||||
//We use just a static array here because it's not expected many components will need
|
||||
//an idle or tick hook.
|
||||
#define MAX_HOOKS 8
|
||||
@@ -41,20 +45,21 @@ void IRAM_ATTR esp_vApplicationTickHook()
|
||||
|
||||
void esp_vApplicationIdleHook()
|
||||
{
|
||||
bool doWait=true;
|
||||
bool r;
|
||||
int n;
|
||||
bool can_go_idle=true;
|
||||
int core = xPortGetCoreID();
|
||||
for (n=0; n<MAX_HOOKS; n++) {
|
||||
if (idle_cb[core][n]!=NULL) {
|
||||
r=idle_cb[core][n]();
|
||||
if (!r) doWait=false;
|
||||
for (int n = 0; n < MAX_HOOKS; n++) {
|
||||
if (idle_cb[core][n] != NULL && !idle_cb[core][n]()) {
|
||||
can_go_idle = false;
|
||||
}
|
||||
}
|
||||
if (doWait) {
|
||||
//Wait for whatever interrupt comes next... this should save some power.
|
||||
asm("waiti 0");
|
||||
if (!can_go_idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_impl_idle_hook();
|
||||
#endif
|
||||
asm("waiti 0");
|
||||
}
|
||||
|
||||
esp_err_t esp_register_freertos_idle_hook_for_cpu(esp_freertos_idle_cb_t new_idle_cb, UBaseType_t cpuid)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "esp_attr.h"
|
||||
#include "esp_clk.h"
|
||||
#include "soc/wdev_reg.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "xtensa/core-macros.h"
|
||||
@@ -35,13 +36,21 @@ uint32_t IRAM_ATTR esp_random(void)
|
||||
* WDEV_RND_REG reads while waiting.
|
||||
*/
|
||||
|
||||
/* This code does not run in a critical section, so CPU frequency switch may
|
||||
* happens while this code runs (this will not happen in the current
|
||||
* implementation, but possible in the future). However if that happens,
|
||||
* the number of cycles spent on frequency switching will certainly be more
|
||||
* than the number of cycles we need to wait here.
|
||||
*/
|
||||
uint32_t cpu_to_apb_freq_ratio = esp_clk_cpu_freq() / esp_clk_apb_freq();
|
||||
|
||||
static uint32_t last_ccount = 0;
|
||||
uint32_t ccount;
|
||||
uint32_t result = 0;
|
||||
do {
|
||||
ccount = XTHAL_GET_CCOUNT();
|
||||
result ^= REG_READ(WDEV_RND_REG);
|
||||
} while (ccount - last_ccount < XT_CLOCK_FREQ / APB_CLK_FREQ * 16);
|
||||
} while (ccount - last_ccount < cpu_to_apb_freq_ratio * 16);
|
||||
last_ccount = ccount;
|
||||
return result ^ REG_READ(WDEV_RND_REG);
|
||||
}
|
||||
|
||||
42
components/esp32/include/esp32/pm.h
Normal file
42
components/esp32/include/esp32/pm.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "soc/rtc.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief Power management config for ESP32
|
||||
*
|
||||
* 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 */
|
||||
bool light_sleep_enable; /*!< Enter light sleep when no locks are taken */
|
||||
} esp_pm_config_esp32_t;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -18,20 +18,8 @@
|
||||
* @file esp_clk.h
|
||||
*
|
||||
* This file contains declarations of clock related functions.
|
||||
* These functions are used in ESP-IDF components, but should not be considered
|
||||
* to be part of public API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Initialize clock-related settings
|
||||
*
|
||||
* Called from cpu_start.c, not intended to be called from other places.
|
||||
* This function configures the CPU clock, RTC slow and fast clocks, and
|
||||
* performs RTC slow clock calibration.
|
||||
*/
|
||||
void esp_clk_init(void);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the calibration value of RTC slow clock
|
||||
*
|
||||
@@ -42,7 +30,6 @@ void esp_clk_init(void);
|
||||
*/
|
||||
uint32_t esp_clk_slowclk_cal_get();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Update the calibration value of RTC slow clock
|
||||
*
|
||||
@@ -55,10 +42,34 @@ uint32_t esp_clk_slowclk_cal_get();
|
||||
void esp_clk_slowclk_cal_set(uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Disables clock of some peripherals
|
||||
* @brief Return current CPU clock frequency
|
||||
* When frequency switching is performed, this frequency may change.
|
||||
* However it is guaranteed that the frequency never changes with a critical
|
||||
* section.
|
||||
*
|
||||
* Called from cpu_start.c, not intended to be called from other places.
|
||||
* This function disables clock of useless peripherals when cpu starts.
|
||||
* @return CPU clock frequency, in Hz
|
||||
*/
|
||||
void esp_perip_clk_init(void);
|
||||
int esp_clk_cpu_freq(void);
|
||||
|
||||
/**
|
||||
* @brief Return current APB clock frequency
|
||||
*
|
||||
* When frequency switching is performed, this frequency may change.
|
||||
* However it is guaranteed that the frequency never changes with a critical
|
||||
* section.
|
||||
*
|
||||
* @return APB clock frequency, in Hz
|
||||
*/
|
||||
int esp_clk_apb_freq(void);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Read value of RTC counter, converting it to microseconds
|
||||
* @attention The value returned by this function may change abruptly when
|
||||
* calibration value of RTC counter is updated via esp_clk_slowclk_cal_set
|
||||
* function. This should not happen unless application calls esp_clk_slowclk_cal_set.
|
||||
* In ESP-IDF, esp_clk_slowclk_cal_set is only called in startup code.
|
||||
*
|
||||
* @return Value or RTC counter, expressed in microseconds
|
||||
*/
|
||||
uint64_t esp_clk_rtc_time();
|
||||
|
||||
@@ -35,8 +35,20 @@ void esp_crosscore_int_init();
|
||||
* This is used internally by FreeRTOS in multicore mode
|
||||
* and should not be called by the user.
|
||||
*
|
||||
* @param coreID Core that should do the yielding
|
||||
* @param core_id Core that should do the yielding
|
||||
*/
|
||||
void esp_crosscore_int_send_yield(int coreId);
|
||||
void esp_crosscore_int_send_yield(int core_id);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Send an interrupt to a CPU indicating it should update its
|
||||
* CCOMPARE1 value due to a frequency switch.
|
||||
*
|
||||
* This is used internally when dynamic frequency switching is
|
||||
* enabled, and should not be called from application code.
|
||||
*
|
||||
* @param core_id Core that should update its CCOMPARE1 value
|
||||
*/
|
||||
void esp_crosscore_int_send_freq_switch(int core_id);
|
||||
|
||||
#endif
|
||||
|
||||
179
components/esp32/include/esp_pm.h
Normal file
179
components/esp32/include/esp_pm.h
Normal file
@@ -0,0 +1,179 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
// Include SoC-specific definitions. Only ESP32 supported for now.
|
||||
#include "esp32/pm.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Power management constraints
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Require CPU frequency to be at the maximum value set via esp_pm_configure.
|
||||
* Argument is unused and should be set to 0.
|
||||
*/
|
||||
ESP_PM_CPU_FREQ_MAX,
|
||||
/**
|
||||
* Require APB frequency to be at the maximum value supported by the chip.
|
||||
* Argument is unused and should be set to 0.
|
||||
*/
|
||||
ESP_PM_APB_FREQ_MAX,
|
||||
/**
|
||||
* Prevent the system from going into light sleep.
|
||||
* Argument is unused and should be set to 0.
|
||||
*/
|
||||
ESP_PM_NO_LIGHT_SLEEP,
|
||||
} esp_pm_lock_type_t;
|
||||
|
||||
/**
|
||||
* @brief Set implementation-specific power management configuration
|
||||
* @param config pointer to implementation-specific configuration structure (e.g. esp_pm_config_esp32)
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if the configuration values are not correct
|
||||
* - ESP_ERR_NOT_SUPPORTED if certain combination of values is not supported,
|
||||
* or if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_configure(const void* config);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to the power management lock
|
||||
*/
|
||||
typedef struct esp_pm_lock* esp_pm_lock_handle_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize a lock handle for certain power management parameter
|
||||
*
|
||||
* When lock is created, initially it is not taken.
|
||||
* Call esp_pm_lock_acquire to take the lock.
|
||||
*
|
||||
* This function must not be called from an ISR.
|
||||
*
|
||||
* @param lock_type Power management constraint which the lock should control
|
||||
* @param arg argument, value depends on lock_type, see esp_pm_lock_type_t
|
||||
* @param name arbitrary string identifying the lock (e.g. "wifi" or "spi").
|
||||
* Used by the esp_pm_dump_locks function to list existing locks.
|
||||
* May be set to NULL. If not set to NULL, must point to a string which is valid
|
||||
* for the lifetime of the lock.
|
||||
* @param[out] out_handle handle returned from this function. Use this handle when calling
|
||||
* esp_pm_lock_delete, esp_pm_lock_acquire, esp_pm_lock_release.
|
||||
* Must not be NULL.
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NO_MEM if the lock structure can not be allocated
|
||||
* - ESP_ERR_INVALID_ARG if out_handle is NULL or type argument is not valid
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_lock_create(esp_pm_lock_type_t lock_type, int arg,
|
||||
const char* name, esp_pm_lock_handle_t* out_handle);
|
||||
|
||||
/**
|
||||
* @brief Take a power management lock
|
||||
*
|
||||
* Once the lock is taken, power management algorithm will not switch to the
|
||||
* mode specified in a call to esp_pm_lock_create, or any of the lower power
|
||||
* modes (higher numeric values of 'mode').
|
||||
*
|
||||
* The lock is recursive, in the sense that if esp_pm_lock_acquire is called
|
||||
* a number of times, esp_pm_lock_release has to be called the same number of
|
||||
* times in order to release the lock.
|
||||
*
|
||||
* This function may be called from an ISR.
|
||||
*
|
||||
* This function is not thread-safe w.r.t. calls to other esp_pm_lock_*
|
||||
* functions for the same handle.
|
||||
*
|
||||
* @param handle handle obtained from esp_pm_lock_create function
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if the handle is invalid
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_lock_acquire(esp_pm_lock_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Release the lock taken using esp_pm_lock_acquire.
|
||||
*
|
||||
* Call to this functions removes power management restrictions placed when
|
||||
* taking the lock.
|
||||
*
|
||||
* Locks are recursive, so if esp_pm_lock_acquire is called a number of times,
|
||||
* esp_pm_lock_release has to be called the same number of times in order to
|
||||
* actually release the lock.
|
||||
*
|
||||
* This function may be called from an ISR.
|
||||
*
|
||||
* This function is not thread-safe w.r.t. calls to other esp_pm_lock_*
|
||||
* functions for the same handle.
|
||||
*
|
||||
* @param handle handle obtained from esp_pm_lock_create function
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if the handle is invalid
|
||||
* - ESP_ERR_INVALID_STATE if lock is not acquired
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_lock_release(esp_pm_lock_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Delete a lock created using esp_pm_lock
|
||||
*
|
||||
* The lock must be released before calling this function.
|
||||
*
|
||||
* This function must not be called from an ISR.
|
||||
*
|
||||
* @param handle handle obtained from esp_pm_lock_create function
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if the handle argument is NULL
|
||||
* - ESP_ERR_INVALID_STATE if the lock is still acquired
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle);
|
||||
|
||||
/**
|
||||
* Dump the list of all locks to stderr
|
||||
*
|
||||
* This function dumps debugging information about locks created using
|
||||
* esp_pm_lock_create to an output stream.
|
||||
*
|
||||
* This function must not be called from an ISR. If esp_pm_lock_acquire/release
|
||||
* are called while this function is running, inconsistent results may be
|
||||
* reported.
|
||||
*
|
||||
* @param stream stream to print information to; use stdout or stderr to print
|
||||
* to the console; use fmemopen/open_memstream to print to a
|
||||
* string buffer.
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
|
||||
*/
|
||||
esp_err_t esp_pm_dump_locks(FILE* stream);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -18,9 +18,6 @@
|
||||
* @file esp_timer.h
|
||||
* @brief microsecond-precision 64-bit timer API, replacement for ets_timer
|
||||
*
|
||||
* Not a public header yet. To be moved into include/ directory when it is made
|
||||
* public.
|
||||
*
|
||||
* esp_timer APIs allow components to receive callbacks when a hardware timer
|
||||
* reaches certain value. The timer provides microsecond accuracy and
|
||||
* up to 64 bit range. Note that while the timer itself provides microsecond
|
||||
456
components/esp32/pm_esp32.c
Normal file
456
components/esp32/pm_esp32.c
Normal file
@@ -0,0 +1,456 @@
|
||||
// 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 "esp_crosscore_int.h"
|
||||
|
||||
#include "soc/rtc.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/xtensa_timer.h"
|
||||
#include "xtensa/core-macros.h"
|
||||
|
||||
#include "pm_impl.h"
|
||||
#include "pm_trace.h"
|
||||
#include "esp_timer_impl.h"
|
||||
#include "esp32/pm.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
|
||||
|
||||
#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;
|
||||
|
||||
/* 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];
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Lookup table of CPU frequencies to be used in each mode.
|
||||
* Modified by esp_pm_configure.
|
||||
*/
|
||||
rtc_cpu_freq_t s_cpu_freq_by_mode[PM_MODE_COUNT] = {
|
||||
[PM_MODE_LIGHT_SLEEP] = (rtc_cpu_freq_t) -1, /* unused */
|
||||
[PM_MODE_APB_MIN] = RTC_CPU_FREQ_XTAL,
|
||||
[PM_MODE_APB_MAX] = RTC_CPU_FREQ_80M,
|
||||
[PM_MODE_CPU_MAX] = RTC_CPU_FREQ_80M,
|
||||
};
|
||||
|
||||
/* 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"
|
||||
};
|
||||
|
||||
/* Whether automatic light sleep is enabled. Currently always false */
|
||||
static bool s_light_sleep_en = 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();
|
||||
static void do_switch(pm_mode_t new_mode);
|
||||
static void leave_idle();
|
||||
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;
|
||||
if (config->light_sleep_enable) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
rtc_cpu_freq_t min_freq = config->min_cpu_freq;
|
||||
rtc_cpu_freq_t max_freq = config->max_cpu_freq;
|
||||
|
||||
rtc_cpu_freq_t apb_max_freq; /* CPU frequency in APB_MAX mode */
|
||||
if (max_freq == RTC_CPU_FREQ_240M) {
|
||||
/* 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 {
|
||||
/* Otherwise (max CPU frequency is 80MHz or 160MHz), can use 80MHz
|
||||
* CPU frequency when 80MHz APB frequency is requested.
|
||||
*/
|
||||
apb_max_freq = RTC_CPU_FREQ_80M;
|
||||
}
|
||||
|
||||
apb_max_freq = MAX(apb_max_freq, min_freq);
|
||||
|
||||
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],
|
||||
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_light_sleep_en = config->light_sleep_enable;
|
||||
portEXIT_CRITICAL(&s_switch_lock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static pm_mode_t IRAM_ATTR get_lowest_allowed_mode()
|
||||
{
|
||||
/* 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(&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(&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_impl_update_apb_freq(apb_ticks_per_us);
|
||||
}
|
||||
|
||||
/* Calculate new tick divisor */
|
||||
_xt_tick_divisor = ticks_per_us * 1000000 / 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;
|
||||
portEXIT_CRITICAL_ISR(&s_switch_lock);
|
||||
|
||||
rtc_cpu_freq_t old_freq = s_cpu_freq_by_mode[s_mode];
|
||||
rtc_cpu_freq_t new_freq = s_cpu_freq_by_mode[new_mode];
|
||||
|
||||
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];
|
||||
|
||||
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_fast(new_freq);
|
||||
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()
|
||||
{
|
||||
const uint32_t ccompare_min_cycles_in_future = 1000;
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
int core_id = xPortGetCoreID();
|
||||
ESP_PM_TRACE_ENTER(ISR_HOOK, core_id);
|
||||
#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
|
||||
ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
for (int i = 0; i < PM_MODE_COUNT; ++i) {
|
||||
fprintf(out, "%8s %12lld %2d%%\n",
|
||||
s_mode_names[i],
|
||||
time_in_mode[i],
|
||||
(int) (time_in_mode[i] * 100 / now));
|
||||
}
|
||||
}
|
||||
#endif // WITH_PROFILING
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
117
components/esp32/pm_impl.h
Normal file
117
components/esp32/pm_impl.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file pm_impl.h
|
||||
*
|
||||
* This header file defines interface between PM lock functions (pm_locks.c)
|
||||
* and the chip-specific power management (DFS/light sleep) implementation.
|
||||
*/
|
||||
|
||||
#include "soc/rtc.h"
|
||||
#include "esp_pm.h"
|
||||
#include "esp_timer.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
|
||||
/**
|
||||
* This is an enum of possible power modes supported by the implementation
|
||||
*/
|
||||
typedef enum {
|
||||
PM_MODE_LIGHT_SLEEP,//!< Light sleep
|
||||
PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks)
|
||||
PM_MODE_APB_MAX, //!< Maximum APB frequency mode
|
||||
PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode
|
||||
PM_MODE_COUNT //!< Number of items
|
||||
} pm_mode_t;
|
||||
|
||||
/**
|
||||
* @brief Get the mode corresponding to a certain lock
|
||||
* @param type lock type
|
||||
* @param arg argument value for this lock (passed to esp_pm_lock_create)
|
||||
* @return lowest power consumption mode which meets the constraints of the lock
|
||||
*/
|
||||
pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg);
|
||||
|
||||
/**
|
||||
* If profiling is enabled, this data type will be used to store microsecond
|
||||
* timestamps.
|
||||
*/
|
||||
typedef int64_t pm_time_t;
|
||||
|
||||
/**
|
||||
* See \ref esp_pm_impl_switch_mode
|
||||
*/
|
||||
typedef enum {
|
||||
MODE_LOCK,
|
||||
MODE_UNLOCK
|
||||
} pm_mode_switch_t;
|
||||
|
||||
/**
|
||||
* @brief Switch between power modes when lock is taken or released
|
||||
* @param mode pm_mode_t corresponding to the lock being taken or released,
|
||||
* as returned by \ref esp_pm_impl_get_mode
|
||||
* @param lock_or_unlock
|
||||
* - MODE_LOCK: lock was taken. Implementation needs to make sure
|
||||
* that the constraints of the lock are met by switching to the
|
||||
* given 'mode' or any of the higher power ones.
|
||||
* - MODE_UNLOCK: lock was released. If all the locks for given
|
||||
* mode are released, and no locks for higher power modes are
|
||||
* taken, implementation can switch to one of lower power modes.
|
||||
* @param now timestamp when the lock was taken or released. Passed as
|
||||
* a minor optimization, so that the implementation does not need to
|
||||
* call pm_get_time again.
|
||||
*/
|
||||
void esp_pm_impl_switch_mode(pm_mode_t mode, pm_mode_switch_t lock_or_unlock, pm_time_t now);
|
||||
|
||||
/**
|
||||
* @brief Call once at startup to initialize pm implementation
|
||||
*/
|
||||
void esp_pm_impl_init();
|
||||
|
||||
/**
|
||||
* @brief Hook function for the idle task
|
||||
* Must be called from the IDLE task on each CPU before entering waiti state.
|
||||
*/
|
||||
void esp_pm_impl_idle_hook();
|
||||
|
||||
/**
|
||||
* @brief Hook function for the interrupt dispatcher
|
||||
* Must be called soon after entering the ISR
|
||||
*/
|
||||
void esp_pm_impl_isr_hook();
|
||||
|
||||
/**
|
||||
* @brief Dump the information about time spent in each of the pm modes.
|
||||
*
|
||||
* Prints three columns:
|
||||
* mode name, total time in mode (in microseconds), percentage of time in mode
|
||||
*
|
||||
* @param out stream to dump the information to
|
||||
*/
|
||||
void esp_pm_impl_dump_stats(FILE* out);
|
||||
|
||||
|
||||
#ifdef CONFIG_PM_PROFILING
|
||||
#define WITH_PROFILING
|
||||
#endif
|
||||
|
||||
#ifdef WITH_PROFILING
|
||||
static inline pm_time_t IRAM_ATTR pm_get_time()
|
||||
{
|
||||
return esp_timer_get_time();
|
||||
}
|
||||
#endif // WITH_PROFILING
|
||||
205
components/esp32/pm_locks.c
Normal file
205
components/esp32/pm_locks.c
Normal file
@@ -0,0 +1,205 @@
|
||||
// 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 <string.h>
|
||||
#include <sys/lock.h>
|
||||
#include "esp_pm.h"
|
||||
#include "esp_system.h"
|
||||
#include "rom/queue.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "pm_impl.h"
|
||||
#include "esp_timer.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
|
||||
typedef struct esp_pm_lock {
|
||||
esp_pm_lock_type_t type; /*!< type passed to esp_pm_lock_create */
|
||||
int arg; /*!< argument passed to esp_pm_lock_create */
|
||||
pm_mode_t mode; /*!< implementation-defined mode for this type of lock*/
|
||||
const char* name; /*!< used to identify the lock */
|
||||
SLIST_ENTRY(esp_pm_lock) next; /*!< linked list pointer */
|
||||
size_t count; /*!< lock count */
|
||||
portMUX_TYPE spinlock; /*!< spinlock used when operating on 'count' */
|
||||
#ifdef WITH_PROFILING
|
||||
pm_time_t last_taken; /*!< time what the lock was taken (valid if count > 0) */
|
||||
pm_time_t time_held; /*!< total time the lock was taken.
|
||||
If count > 0, this doesn't include the time since last_taken */
|
||||
size_t times_taken; /*!< number of times the lock was ever taken */
|
||||
#endif
|
||||
} esp_pm_lock_t;
|
||||
|
||||
|
||||
static const char* s_lock_type_names[] = {
|
||||
"CPU_FREQ_MAX",
|
||||
"APB_FREQ_MAX",
|
||||
"NO_LIGHT_SLEEP"
|
||||
};
|
||||
|
||||
/* List of all existing locks, used for esp_pm_dump_locks */
|
||||
static SLIST_HEAD(esp_pm_locks_head, esp_pm_lock) s_list =
|
||||
SLIST_HEAD_INITIALIZER(s_head);
|
||||
/* Protects the above list */
|
||||
static _lock_t s_list_lock;
|
||||
|
||||
|
||||
esp_err_t esp_pm_lock_create(esp_pm_lock_type_t lock_type, int arg,
|
||||
const char* name, esp_pm_lock_handle_t* out_handle)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
if (out_handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_pm_lock_t* new_lock = (esp_pm_lock_t*) calloc(1, sizeof(*new_lock));
|
||||
if (!new_lock) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
new_lock->type = lock_type;
|
||||
new_lock->arg = arg;
|
||||
new_lock->mode = esp_pm_impl_get_mode(lock_type, arg);
|
||||
new_lock->name = name;
|
||||
new_lock->spinlock = (portMUX_TYPE) portMUX_INITIALIZER_UNLOCKED;
|
||||
*out_handle = new_lock;
|
||||
|
||||
_lock_acquire(&s_list_lock);
|
||||
SLIST_INSERT_HEAD(&s_list, new_lock, next);
|
||||
_lock_release(&s_list_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
if (handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (handle->count > 0) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
_lock_acquire(&s_list_lock);
|
||||
SLIST_REMOVE(&s_list, handle, esp_pm_lock, next);
|
||||
_lock_release(&s_list_lock);
|
||||
free(handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t IRAM_ATTR esp_pm_lock_acquire(esp_pm_lock_handle_t handle)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
if (handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&handle->spinlock);
|
||||
if (handle->count++ == 0) {
|
||||
pm_time_t now = 0;
|
||||
#ifdef WITH_PROFILING
|
||||
now = pm_get_time();
|
||||
#endif
|
||||
esp_pm_impl_switch_mode(handle->mode, MODE_LOCK, now);
|
||||
#ifdef WITH_PROFILING
|
||||
handle->last_taken = now;
|
||||
handle->times_taken++;
|
||||
#endif
|
||||
}
|
||||
portEXIT_CRITICAL(&handle->spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t IRAM_ATTR esp_pm_lock_release(esp_pm_lock_handle_t handle)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
if (handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_err_t ret = ESP_OK;
|
||||
portENTER_CRITICAL(&handle->spinlock);
|
||||
if (handle->count == 0) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
goto out;
|
||||
}
|
||||
if (--handle->count == 0) {
|
||||
pm_time_t now = 0;
|
||||
#ifdef WITH_PROFILING
|
||||
now = pm_get_time();
|
||||
handle->time_held += now - handle->last_taken;
|
||||
#endif
|
||||
esp_pm_impl_switch_mode(handle->mode, MODE_UNLOCK, now);
|
||||
}
|
||||
out:
|
||||
portEXIT_CRITICAL(&handle->spinlock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t esp_pm_dump_locks(FILE* stream)
|
||||
{
|
||||
#ifndef CONFIG_PM_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_PROFILING
|
||||
pm_time_t cur_time = pm_get_time();
|
||||
pm_time_t cur_time_d100 = cur_time / 100;
|
||||
#endif // WITH_PROFILING
|
||||
|
||||
_lock_acquire(&s_list_lock);
|
||||
#ifdef WITH_PROFILING
|
||||
fprintf(stream, "Time: %lld\n", cur_time);
|
||||
#endif
|
||||
|
||||
fprintf(stream, "Lock stats:\n");
|
||||
esp_pm_lock_t* it;
|
||||
SLIST_FOREACH(it, &s_list, next) {
|
||||
portENTER_CRITICAL(&it->spinlock);
|
||||
if (it->name == NULL) {
|
||||
fprintf(stream, "lock@%p ", it);
|
||||
} else {
|
||||
fprintf(stream, "%-15s ", it->name);
|
||||
}
|
||||
#ifdef WITH_PROFILING
|
||||
pm_time_t time_held = it->time_held;
|
||||
if (it->count > 0) {
|
||||
time_held += cur_time - it->last_taken;
|
||||
}
|
||||
fprintf(stream, "%10s %3d %3d %9d %9lld %3lld%%\n",
|
||||
s_lock_type_names[it->type], it->arg,
|
||||
it->count, it->times_taken, time_held,
|
||||
(time_held + cur_time_d100 - 1) / cur_time_d100);
|
||||
#else
|
||||
fprintf(stream, "%10s %3d %3d\n", s_lock_type_names[it->type], it->arg, it->count);
|
||||
#endif // WITH_PROFILING
|
||||
portEXIT_CRITICAL(&it->spinlock);
|
||||
}
|
||||
_lock_release(&s_list_lock);
|
||||
#ifdef WITH_PROFILING
|
||||
esp_pm_impl_dump_stats(stream);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
51
components/esp32/pm_trace.c
Normal file
51
components/esp32/pm_trace.c
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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 "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
|
||||
};
|
||||
|
||||
void esp_pm_trace_init()
|
||||
{
|
||||
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]);
|
||||
}
|
||||
44
components/esp32/pm_trace.h
Normal file
44
components/esp32/pm_trace.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
typedef enum {
|
||||
ESP_PM_TRACE_IDLE,
|
||||
ESP_PM_TRACE_TICK,
|
||||
ESP_PM_TRACE_FREQ_SWITCH,
|
||||
ESP_PM_TRACE_CCOMPARE_UPDATE,
|
||||
ESP_PM_TRACE_ISR_HOOK,
|
||||
ESP_PM_TRACE_TYPE_MAX
|
||||
} esp_pm_trace_event_t;
|
||||
|
||||
void esp_pm_trace_init();
|
||||
void esp_pm_trace_enter(esp_pm_trace_event_t event, int core_id);
|
||||
void esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id);
|
||||
|
||||
#ifdef CONFIG_PM_TRACE
|
||||
|
||||
#define ESP_PM_TRACE_ENTER(event, core_id) \
|
||||
esp_pm_trace_enter(ESP_PM_TRACE_ ## event, core_id)
|
||||
#define ESP_PM_TRACE_EXIT(event, core_id) \
|
||||
esp_pm_trace_exit(ESP_PM_TRACE_ ## event, core_id)
|
||||
|
||||
#else // CONFIG_PM_TRACE
|
||||
|
||||
#define ESP_PM_TRACE_ENTER(type, core_id) do { (void) core_id; } while(0);
|
||||
#define ESP_PM_TRACE_EXIT(type, core_id) do { (void) core_id; } while(0);
|
||||
|
||||
#endif // CONFIG_PM_TRACE
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include "unity.h"
|
||||
#include "../esp_timer.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
@@ -113,6 +113,7 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
|
||||
size_t cur_interval;
|
||||
int intervals[NUM_INTERVALS];
|
||||
int64_t t_start;
|
||||
SemaphoreHandle_t done;
|
||||
} test_args_t;
|
||||
|
||||
void timer_func(void* arg)
|
||||
@@ -128,6 +129,7 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
|
||||
if (p_args->cur_interval == NUM_INTERVALS) {
|
||||
printf("done\n");
|
||||
TEST_ESP_OK(esp_timer_stop(p_args->timer));
|
||||
xSemaphoreGive(p_args->done);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,15 +139,16 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
|
||||
esp_timer_create_args_t create_args = {
|
||||
.callback = &timer_func,
|
||||
.arg = &args,
|
||||
.name = "timer1"
|
||||
.name = "timer1",
|
||||
};
|
||||
TEST_ESP_OK(esp_timer_create(&create_args, &timer1));
|
||||
ref_clock_init();
|
||||
args.timer = timer1;
|
||||
args.t_start = ref_clock_get();
|
||||
args.done = xSemaphoreCreateBinary();
|
||||
TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000));
|
||||
|
||||
vTaskDelay(delay_ms * (NUM_INTERVALS + 1));
|
||||
TEST_ASSERT(xSemaphoreTake(args.done, delay_ms * NUM_INTERVALS * 2));
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval);
|
||||
for (size_t i = 0; i < NUM_INTERVALS; ++i) {
|
||||
@@ -155,6 +158,7 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
|
||||
TEST_ESP_OK( esp_timer_dump(stdout) );
|
||||
|
||||
TEST_ESP_OK( esp_timer_delete(timer1) );
|
||||
vSemaphoreDelete(args.done);
|
||||
#undef NUM_INTERVALS
|
||||
}
|
||||
|
||||
@@ -370,3 +374,8 @@ TEST_CASE("esp_timer_get_time returns monotonic values", "[esp_timer][ignore]")
|
||||
vSemaphoreDelete(done_2);
|
||||
ref_clock_deinit();
|
||||
}
|
||||
|
||||
TEST_CASE("Can dump esp_timer stats", "[esp_timer]")
|
||||
{
|
||||
esp_timer_dump(stdout);
|
||||
}
|
||||
|
||||
12
components/esp32/test/test_pm.c
Normal file
12
components/esp32/test/test_pm.c
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include "unity.h"
|
||||
#include "esp_pm.h"
|
||||
|
||||
|
||||
TEST_CASE("Can dump power management lock stats", "[pm]")
|
||||
{
|
||||
esp_pm_dump_locks(stdout);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
// Copyright 2015-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
|
||||
@@ -14,10 +14,43 @@
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_wifi.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi_internal.h"
|
||||
#include "esp_pm.h"
|
||||
#include "soc/rtc.h"
|
||||
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
static esp_pm_lock_handle_t s_wifi_modem_sleep_lock;
|
||||
#endif
|
||||
|
||||
esp_err_t esp_wifi_init(const wifi_init_config_t *config)
|
||||
{
|
||||
esp_event_set_default_wifi_handlers();
|
||||
return esp_wifi_init_internal(config);
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
if (s_wifi_modem_sleep_lock == NULL) {
|
||||
esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "wifi",
|
||||
&s_wifi_modem_sleep_lock);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
esp_event_set_default_wifi_handlers();
|
||||
return esp_wifi_init_internal(config);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
void wifi_apb80m_request(void)
|
||||
{
|
||||
assert(s_wifi_modem_sleep_lock);
|
||||
esp_pm_lock_acquire(s_wifi_modem_sleep_lock);
|
||||
if (rtc_clk_apb_freq_get() != APB_CLK_FREQ) {
|
||||
ESP_LOGE(__func__, "WiFi needs 80MHz APB frequency to work, but got %dHz", rtc_clk_apb_freq_get());
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_apb80m_release(void)
|
||||
{
|
||||
assert(s_wifi_modem_sleep_lock);
|
||||
esp_pm_lock_release(s_wifi_modem_sleep_lock);
|
||||
}
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
|
||||
Reference in New Issue
Block a user