From 566f01899678c23d1137779e4010b5c6552da335 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 21 Aug 2017 22:29:08 +0800 Subject: [PATCH 01/26] crosscore_int: add support for FREQ_SWITCH event --- components/esp32/crosscore_int.c | 63 +++++++++++++------- components/esp32/include/esp_crosscore_int.h | 18 +++++- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index 0d5ccb35f7..4a57a2b197 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -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 Date: Mon, 16 Oct 2017 00:37:00 +0800 Subject: [PATCH 02/26] make esp_timer.h public --- components/esp32/{ => include}/esp_timer.h | 3 --- components/esp32/test/test_esp_timer.c | 2 +- components/newlib/time.c | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) rename components/esp32/{ => include}/esp_timer.h (98%) diff --git a/components/esp32/esp_timer.h b/components/esp32/include/esp_timer.h similarity index 98% rename from components/esp32/esp_timer.h rename to components/esp32/include/esp_timer.h index f5ba21967e..e3e8b48128 100644 --- a/components/esp32/esp_timer.h +++ b/components/esp32/include/esp_timer.h @@ -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 diff --git a/components/esp32/test/test_esp_timer.c b/components/esp32/test/test_esp_timer.c index 246aac9d86..9206d312d2 100644 --- a/components/esp32/test/test_esp_timer.c +++ b/components/esp32/test/test_esp_timer.c @@ -3,7 +3,7 @@ #include #include #include "unity.h" -#include "../esp_timer.h" +#include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" diff --git a/components/newlib/time.c b/components/newlib/time.c index 1427dcfdea..0357f6412e 100644 --- a/components/newlib/time.c +++ b/components/newlib/time.c @@ -26,7 +26,7 @@ #include "esp_attr.h" #include "esp_intr_alloc.h" #include "esp_clk.h" -#include "../esp32/esp_timer.h" +#include "esp_timer.h" #include "soc/soc.h" #include "soc/rtc.h" #include "soc/rtc_cntl_reg.h" From 37e9bc715c47cfc036c876c92cd971c670eb3efe Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:04:16 +0800 Subject: [PATCH 03/26] esp_clk.h: make public, add getters for RTC time, CPU/APB freq --- components/esp32/clk.c | 28 ++++++++++++------ components/esp32/cpu_start.c | 2 +- components/esp32/esp_clk_internal.h | 39 +++++++++++++++++++++++++ components/esp32/include/esp_clk.h | 45 ++++++++++++++++++----------- components/newlib/time.c | 9 ++++++ 5 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 components/esp32/esp_clk_internal.h diff --git a/components/esp32/clk.c b/components/esp32/clk.c index 319b31fbcd..6e01cc2ea1 100644 --- a/components/esp32/clk.c +++ b/components/esp32/clk.c @@ -15,10 +15,12 @@ #include #include #include +#include #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; } diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 3aefdce0a4..ccf8a1d4bc 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -64,7 +64,7 @@ #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 "trax.h" diff --git a/components/esp32/esp_clk_internal.h b/components/esp32/esp_clk_internal.h new file mode 100644 index 0000000000..d1fd434cc1 --- /dev/null +++ b/components/esp32/esp_clk_internal.h @@ -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); diff --git a/components/esp32/include/esp_clk.h b/components/esp32/include/esp_clk.h index 5c6f5cb871..6526aa9272 100644 --- a/components/esp32/include/esp_clk.h +++ b/components/esp32/include/esp_clk.h @@ -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(); diff --git a/components/newlib/time.c b/components/newlib/time.c index 0357f6412e..edb9a59fe0 100644 --- a/components/newlib/time.c +++ b/components/newlib/time.c @@ -145,6 +145,15 @@ void esp_set_time_from_rtc() #endif // WITH_FRC1 && WITH_RTC } +uint64_t esp_clk_rtc_time(void) +{ +#ifdef WITH_RTC + return get_rtc_time_us(); +#else + return 0; +#endif +} + clock_t IRAM_ATTR _times_r(struct _reent *r, struct tms *ptms) { clock_t t = xTaskGetTickCount() * (portTICK_PERIOD_MS * CLK_TCK / 1000); From a242ae6d0b785cc5c14905bef1fdbdf28ceaa6b1 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 21 Aug 2017 22:34:42 +0800 Subject: [PATCH 04/26] soc/rtc: add fast paths for switching between PLL and XTAL --- components/soc/esp32/include/soc/rtc.h | 19 +++++++ components/soc/esp32/rtc_clk.c | 75 +++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/components/soc/esp32/include/soc/rtc.h b/components/soc/esp32/include/soc/rtc.h index b47e39e1e7..2c4b4d320c 100644 --- a/components/soc/esp32/include/soc/rtc.h +++ b/components/soc/esp32/include/soc/rtc.h @@ -276,6 +276,25 @@ rtc_fast_freq_t rtc_clk_fast_freq_get(); */ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq); +/** + * @brief Switch CPU frequency + * + * This is a faster version of rtc_clk_cpu_freq_set, which can handle some of + * the frequency switch paths (XTAL -> PLL, PLL -> XTAL). + * When switching from PLL to XTAL, PLL is not disabled (unlike rtc_clk_cpu_freq_set). + * When switching back from XTAL to PLL, only the same PLL can be used. + * Therefore it is not possible to switch 240 -> XTAL -> (80 or 160) using this + * function. + * + * For unsupported cases, this function falls back to rtc_clk_cpu_freq_set. + * + * Unlike rtc_clk_cpu_freq_set, this function relies on static data, so it is + * less safe to use it e.g. from a panic handler (when memory might be corrupted). + * + * @param cpu_freq new CPU frequency + */ +void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq); + /** * @brief Get the currently selected CPU frequency * diff --git a/components/soc/esp32/rtc_clk.c b/components/soc/esp32/rtc_clk.c index e3e007898d..0b1f902517 100644 --- a/components/soc/esp32/rtc_clk.c +++ b/components/soc/esp32/rtc_clk.c @@ -72,9 +72,9 @@ static const char* TAG = "rtc_clk"; * All values are in microseconds. * TODO: some of these are excessive, and should be reduced. */ -#define DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_150K 80 +#define DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_150K 20 #define DELAY_CPU_FREQ_SWITCH_TO_XTAL_WITH_32K 160 -#define DELAY_CPU_FREQ_SWITCH_TO_PLL 10 +#define DELAY_CPU_FREQ_SWITCH_TO_PLL 20 #define DELAY_PLL_DBIAS_RAISE 3 #define DELAY_PLL_ENABLE_WITH_150K 80 #define DELAY_PLL_ENABLE_WITH_32K 160 @@ -87,6 +87,8 @@ static const char* TAG = "rtc_clk"; */ #define XTAL_FREQ_EST_CYCLES 10 +static rtc_cpu_freq_t s_cur_freq = RTC_CPU_FREQ_XTAL; +static int s_pll_freq = 0; static void rtc_clk_32k_enable_internal(int dac, int dres, int dbias) { @@ -323,6 +325,70 @@ void rtc_clk_bbpll_set(rtc_xtal_freq_t xtal_freq, rtc_cpu_freq_t cpu_freq) ets_delay_us(delay_pll_en); } +/** + * Switch to XTAL frequency. Does not disable the PLL. + */ +static void rtc_clk_cpu_freq_to_xtal() +{ + rtc_xtal_freq_t xtal_freq = rtc_clk_xtal_freq_get(); + ets_update_cpu_frequency(xtal_freq); + REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DBIAS_1V10); + REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, 0); + REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_XTL); + DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 0); // clear DPORT_CPUPERIOD_SEL + + rtc_clk_apb_freq_update(xtal_freq * MHZ); + s_cur_freq = RTC_CPU_FREQ_XTAL; +} + +/** + * Switch to one of PLL-based frequencies. Current frequency can be XTAL or PLL. + * PLL must already be enabled. + * If switching between frequencies derived from different PLLs (320M and 480M), + * fall back to rtc_clk_cpu_freq_set. + * @param cpu_freq new CPU frequency + */ +static void rtc_clk_cpu_freq_to_pll(rtc_cpu_freq_t cpu_freq) +{ + int freq = 0; + if ((cpu_freq == RTC_CPU_FREQ_240M && s_pll_freq == 320) || + (cpu_freq != RTC_CPU_FREQ_240M && s_pll_freq == 240)) { + /* need to switch PLLs, fall back to full implementation */ + rtc_clk_cpu_freq_set(cpu_freq); + return; + } + + if (cpu_freq == RTC_CPU_FREQ_80M) { + DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 0); + freq = 80; + } else if (cpu_freq == RTC_CPU_FREQ_160M) { + DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 1); + freq = 160; + } else if (cpu_freq == RTC_CPU_FREQ_240M) { + REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, RTC_CNTL_DBIAS_1V25); + DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, 2); + freq = 240; + } + REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL); + rtc_clk_apb_freq_update(80 * MHZ); + ets_update_cpu_frequency(freq); + s_cur_freq = cpu_freq; +} + +void rtc_clk_cpu_freq_set_fast(rtc_cpu_freq_t cpu_freq) +{ + if (cpu_freq == s_cur_freq) { + return; + } else if (cpu_freq == RTC_CPU_FREQ_2M || s_cur_freq == RTC_CPU_FREQ_2M) { + /* fall back to full implementation if switch to/from 2M is needed */ + rtc_clk_cpu_freq_set(cpu_freq); + } else if (cpu_freq == RTC_CPU_FREQ_XTAL) { + rtc_clk_cpu_freq_to_xtal(); + } else if (cpu_freq > RTC_CPU_FREQ_XTAL) { + rtc_clk_cpu_freq_to_pll(cpu_freq); + } +} + void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq) { rtc_xtal_freq_t xtal_freq = rtc_clk_xtal_freq_get(); @@ -338,6 +404,7 @@ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq) SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BB_I2C_FORCE_PD | RTC_CNTL_BBPLL_FORCE_PD | RTC_CNTL_BBPLL_I2C_FORCE_PD); + s_pll_freq = 0; rtc_clk_apb_freq_update(xtal_freq * MHZ); /* is APLL under force power down? */ @@ -365,17 +432,21 @@ void rtc_clk_cpu_freq_set(rtc_cpu_freq_t cpu_freq) if (cpu_freq == RTC_CPU_FREQ_80M) { DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 0); ets_update_cpu_frequency(80); + s_pll_freq = 320; } else if (cpu_freq == RTC_CPU_FREQ_160M) { DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 1); ets_update_cpu_frequency(160); + s_pll_freq = 320; } else if (cpu_freq == RTC_CPU_FREQ_240M) { DPORT_REG_SET_FIELD(DPORT_CPU_PER_CONF_REG, DPORT_CPUPERIOD_SEL, 2); ets_update_cpu_frequency(240); + s_pll_freq = 480; } REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL); ets_delay_us(DELAY_CPU_FREQ_SWITCH_TO_PLL); rtc_clk_apb_freq_update(80 * MHZ); } + s_cur_freq = cpu_freq; } rtc_cpu_freq_t rtc_clk_cpu_freq_get() From b03e08dc4e4de399336d26478ff8649b4f0fe9f8 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sat, 14 Oct 2017 00:27:56 +0800 Subject: [PATCH 05/26] soc/rtc: add function to convert CPU frequency in MHz to rtc_cpu_freq_t --- components/soc/esp32/include/soc/rtc.h | 8 ++++++++ components/soc/esp32/rtc_clk.c | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/components/soc/esp32/include/soc/rtc.h b/components/soc/esp32/include/soc/rtc.h index 2c4b4d320c..f13c113b4e 100644 --- a/components/soc/esp32/include/soc/rtc.h +++ b/components/soc/esp32/include/soc/rtc.h @@ -315,6 +315,14 @@ rtc_cpu_freq_t rtc_clk_cpu_freq_get(); */ uint32_t rtc_clk_cpu_freq_value(rtc_cpu_freq_t cpu_freq); +/** + * @brief Get rtc_cpu_freq_t enum value for given CPU frequency + * @param cpu_freq_mhz CPU frequency, one of 80, 160, 240, 2, and XTAL frequency + * @param[out] out_val output, rtc_cpu_freq_t value corresponding to the frequency + * @return true if the given frequency value matches one of enum values + */ + bool rtc_clk_cpu_freq_from_mhz(int cpu_freq_mhz, rtc_cpu_freq_t* out_val); + /** * @brief Store new APB frequency value into RTC_APB_FREQ_REG * diff --git a/components/soc/esp32/rtc_clk.c b/components/soc/esp32/rtc_clk.c index 0b1f902517..6e2b0909d0 100644 --- a/components/soc/esp32/rtc_clk.c +++ b/components/soc/esp32/rtc_clk.c @@ -504,6 +504,24 @@ uint32_t rtc_clk_cpu_freq_value(rtc_cpu_freq_t cpu_freq) } } +bool rtc_clk_cpu_freq_from_mhz(int mhz, rtc_cpu_freq_t* out_val) +{ + if (mhz == 240) { + *out_val = RTC_CPU_FREQ_240M; + } else if (mhz == 160) { + *out_val = RTC_CPU_FREQ_160M; + } else if (mhz == 80) { + *out_val = RTC_CPU_FREQ_80M; + } else if (mhz == (int) rtc_clk_xtal_freq_get()) { + *out_val = RTC_CPU_FREQ_XTAL; + } else if (mhz == 2) { + *out_val = RTC_CPU_FREQ_2M; + } else { + return false; + } + return true; +} + /* Values of RTC_XTAL_FREQ_REG and RTC_APB_FREQ_REG are stored as two copies in * lower and upper 16-bit halves. These are the routines to work with such a * representation. From 2b3325b1abc58221f48bf809d24274e3ef24ad25 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 8 Sep 2017 01:33:20 +0800 Subject: [PATCH 06/26] unit tests: fix ref_clock value obtained due to overflow --- tools/unit-test-app/components/unity/ref_clock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/unit-test-app/components/unity/ref_clock.c b/tools/unit-test-app/components/unity/ref_clock.c index 696bd0a4d3..94afe01bd7 100644 --- a/tools/unit-test-app/components/unity/ref_clock.c +++ b/tools/unit-test-app/components/unity/ref_clock.c @@ -159,6 +159,8 @@ uint64_t ref_clock_get() uint32_t microseconds = PCNT.cnt_unit[REF_CLOCK_PCNT_UNIT].cnt_val; uint32_t milliseconds = s_milliseconds; if (PCNT.int_st.val & BIT(REF_CLOCK_PCNT_UNIT)) { + // refresh counter value, in case the overflow has happened after reading cnt_val + microseconds = PCNT.cnt_unit[REF_CLOCK_PCNT_UNIT].cnt_val; milliseconds += REF_CLOCK_PRESCALER_MS; } portEXIT_CRITICAL(&s_lock); From 3788b35f87c37f8f0a0f7d0953f91499fb338a32 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 8 Sep 2017 01:32:18 +0800 Subject: [PATCH 07/26] unit tests: more robust esp_timer test --- components/esp32/test/test_esp_timer.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/esp32/test/test_esp_timer.c b/components/esp32/test/test_esp_timer.c index 9206d312d2..6113395f87 100644 --- a/components/esp32/test/test_esp_timer.c +++ b/components/esp32/test/test_esp_timer.c @@ -103,7 +103,7 @@ TEST_CASE("esp_timer produces correct delay", "[esp_timer]") esp_timer_delete(timer1); } -TEST_CASE("periodic ets_timer produces correct delays", "[esp_timer]") +TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]") { // no, we can't make this a const size_t (§6.7.5.2) #define NUM_INTERVALS 16 @@ -113,6 +113,7 @@ TEST_CASE("periodic ets_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 ets_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 ets_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 ets_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 } From 4798b7d7759dd3a533edb469cfee5785643e5074 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:16:52 +0800 Subject: [PATCH 08/26] unit tests: add test to dump esp_timer stats --- components/esp32/test/test_esp_timer.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/esp32/test/test_esp_timer.c b/components/esp32/test/test_esp_timer.c index 6113395f87..061b86aa49 100644 --- a/components/esp32/test/test_esp_timer.c +++ b/components/esp32/test/test_esp_timer.c @@ -374,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); +} From 47e3c9dd4bd4c1de5078075a45e097f1fd0d1417 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:29:33 +0800 Subject: [PATCH 09/26] pm: initial version of power management APIs --- components/esp32/include/esp_pm.h | 179 ++++++++++++++++++++++++++ components/esp32/pm_locks.c | 205 ++++++++++++++++++++++++++++++ components/esp32/test/test_pm.c | 12 ++ 3 files changed, 396 insertions(+) create mode 100644 components/esp32/include/esp_pm.h create mode 100644 components/esp32/pm_locks.c create mode 100644 components/esp32/test/test_pm.c diff --git a/components/esp32/include/esp_pm.h b/components/esp32/include/esp_pm.h new file mode 100644 index 0000000000..3887e77a92 --- /dev/null +++ b/components/esp32/include/esp_pm.h @@ -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 +#include +#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 diff --git a/components/esp32/pm_locks.c b/components/esp32/pm_locks.c new file mode 100644 index 0000000000..bbd8f04d6d --- /dev/null +++ b/components/esp32/pm_locks.c @@ -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 +#include +#include +#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; +} + + diff --git a/components/esp32/test/test_pm.c b/components/esp32/test/test_pm.c new file mode 100644 index 0000000000..2214cec591 --- /dev/null +++ b/components/esp32/test/test_pm.c @@ -0,0 +1,12 @@ +#include +#include +#include +#include +#include "unity.h" +#include "esp_pm.h" + + +TEST_CASE("Can dump power management lock stats", "[pm]") +{ + esp_pm_dump_locks(stdout); +} From 68e1751b780b3e01858f09cde4cdce28bbf451c2 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:30:13 +0800 Subject: [PATCH 10/26] pm: initial implementation for ESP32 --- components/esp32/include/esp32/pm.h | 42 +++ components/esp32/pm_esp32.c | 456 ++++++++++++++++++++++++++++ components/esp32/pm_impl.h | 117 +++++++ 3 files changed, 615 insertions(+) create mode 100644 components/esp32/include/esp32/pm.h create mode 100644 components/esp32/pm_esp32.c create mode 100644 components/esp32/pm_impl.h diff --git a/components/esp32/include/esp32/pm.h b/components/esp32/include/esp32/pm.h new file mode 100644 index 0000000000..a7cbf0eac7 --- /dev/null +++ b/components/esp32/include/esp32/pm.h @@ -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 +#include +#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 diff --git a/components/esp32/pm_esp32.c b/components/esp32/pm_esp32.c new file mode 100644 index 0000000000..5866484a7a --- /dev/null +++ b/components/esp32/pm_esp32.c @@ -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 +#include +#include +#include + +#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 +} diff --git a/components/esp32/pm_impl.h b/components/esp32/pm_impl.h new file mode 100644 index 0000000000..7f1bf09cbe --- /dev/null +++ b/components/esp32/pm_impl.h @@ -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 From 42d51a42240c8a490954d20a7f6ddc7f6c575a77 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:02:58 +0800 Subject: [PATCH 11/26] esp32: initialize PM at startup, add Kconfig options --- components/esp32/Kconfig | 52 ++++++++++++++++++++++++++++++++++++ components/esp32/cpu_start.c | 14 ++++++++++ 2 files changed, 66 insertions(+) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 4867912c9d..06ab28d69f 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -945,3 +945,55 @@ 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_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" \ No newline at end of file diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index ccf8a1d4bc..4a56143cfd 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -66,6 +66,8 @@ #include "esp_spiram.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) @@ -337,6 +339,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(); From 535695f0b97527a1496d31c1e522a34469f8e0bc Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:06:52 +0800 Subject: [PATCH 12/26] freertos: add frequency switching hooks to ISR and idle task --- components/esp32/freertos_hooks.c | 25 +++++++++++++++---------- components/freertos/xtensa_vectors.S | 4 ++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/components/esp32/freertos_hooks.c b/components/esp32/freertos_hooks.c index ed88be8835..abe88a97e1 100644 --- a/components/esp32/freertos_hooks.c +++ b/components/esp32/freertos_hooks.c @@ -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 Date: Fri, 22 Sep 2017 23:26:09 +0800 Subject: [PATCH 13/26] pm: support for tracing using GPIOs --- components/esp32/pm_trace.c | 51 ++++++++++++++++++++++++++++ components/esp32/pm_trace.h | 44 ++++++++++++++++++++++++ components/freertos/portasm.S | 13 +++++++ components/freertos/xtensa_vectors.S | 8 ++++- 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 components/esp32/pm_trace.c create mode 100644 components/esp32/pm_trace.h diff --git a/components/esp32/pm_trace.c b/components/esp32/pm_trace.c new file mode 100644 index 0000000000..c1f240017e --- /dev/null +++ b/components/esp32/pm_trace.c @@ -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]); +} diff --git a/components/esp32/pm_trace.h b/components/esp32/pm_trace.h new file mode 100644 index 0000000000..1aff1ac806 --- /dev/null +++ b/components/esp32/pm_trace.h @@ -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 diff --git a/components/freertos/portasm.S b/components/freertos/portasm.S index de4b0d5581..f5b280fd85 100644 --- a/components/freertos/portasm.S +++ b/components/freertos/portasm.S @@ -24,6 +24,7 @@ */ #include "xtensa_rtos.h" +#include "sdkconfig.h" #define TOPOFSTACK_OFFS 0x00 /* StackType_t *pxTopOfStack */ #define CP_TOPOFSTACK_OFFS 0x04 /* xMPU_SETTINGS.coproc_area */ @@ -269,6 +270,12 @@ _frxt_timer_int: ENTRY(16) + #ifdef CONFIG_PM_TRACE + movi a6, 1 /* = ESP_PM_TRACE_TICK */ + getcoreid a7 + call4 esp_pm_trace_enter + #endif // CONFIG_PM_TRACE + .L_xt_timer_int_catchup: /* Update the timer comparator for the next tick. */ @@ -308,6 +315,12 @@ _frxt_timer_int: sub a4, a4, a3 /* diff = ccount - old comparator */ blt a2, a4, .L_xt_timer_int_catchup /* repeat while diff > divisor */ +#ifdef CONFIG_PM_TRACE + movi a6, 1 /* = ESP_PM_TRACE_TICK */ + getcoreid a7 + call4 esp_pm_trace_exit +#endif // CONFIG_PM_TRACE + RET(16) /* diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index 46750081e3..b0238375a8 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -175,7 +175,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. NOTE: For CALL0 ABI, a12-a15 have not yet been saved. - NOTE: This macro will use registers a0 and a2-a6. The arguments are: + NOTE: This macro will use registers a0 and a2-a7. The arguments are: level -- interrupt level mask -- interrupt bitmask for this level -------------------------------------------------------------------------------- @@ -183,6 +183,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .macro dispatch_c_isr level mask + #ifdef CONFIG_PM_TRACE + movi a6, 0 /* = ESP_PM_TRACE_IDLE */ + getcoreid a7 + call4 esp_pm_trace_exit + #endif // CONFIG_PM_TRACE + /* Get mask of pending, enabled interrupts at this level into a2. */ .L_xt_user_int_&level&: From eb0c34e5c9515ed637ddfbdbea068d8878ebbd7c Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Oct 2017 15:24:51 +0800 Subject: [PATCH 14/26] esp_timer: add support for frequency scaling --- components/esp32/Kconfig | 13 +++++++ components/esp32/esp_timer_esp32.c | 58 ++++++++++++++++++++++++++++++ components/esp32/esp_timer_impl.h | 9 +++++ 3 files changed, 80 insertions(+) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 06ab28d69f..1b219cf2eb 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -970,6 +970,19 @@ config PM_DFS_INIT_AUTO If disabled, DFS will not be active until the application configures it using esp_pm_configure function. +config PM_USE_RTC_TIMER_REF + bool "Use RTC timer to prevent time drift (EXPERIMENTAL)" + depends on PM_ENABLE && (ESP32_TIME_SYSCALL_USE_RTC || ESP32_TIME_SYSCALL_USE_RTC_FRC1) + default n + help + When APB clock frequency changes, high-resolution timer (esp_timer) + scale and base value need to be adjusted. Each adjustment may cause + small error, and over time such small errors may cause time drift. + If this option is enabled, RTC timer will be used as a reference to + compensate for the drift. + It is recommended that this option is only used if 32k XTAL is selected + as RTC clock source. + config PM_PROFILING bool "Enable profiling counters for PM locks" depends on PM_ENABLE diff --git a/components/esp32/esp_timer_esp32.c b/components/esp32/esp_timer_esp32.c index 365a11dbcb..e2d271186b 100644 --- a/components/esp32/esp_timer_esp32.c +++ b/components/esp32/esp_timer_esp32.c @@ -19,6 +19,7 @@ #include "esp_attr.h" #include "esp_intr_alloc.h" #include "esp_log.h" +#include "esp_clk.h" #include "esp_timer_impl.h" #include "soc/frc_timer_reg.h" #include "soc/rtc.h" @@ -112,6 +113,14 @@ static uint32_t s_timer_us_per_overflow; // will not increment s_time_base_us if this flag is set. static bool s_mask_overflow; +#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF +// If DFS is enabled, upon the first frequency change this value is set to the +// difference between esp_timer value and RTC timer value. On every subsequent +// frequency change, s_time_base_us is adjusted to maintain the same difference +// between esp_timer and RTC timer. (All mentioned values are in microseconds.) +static uint64_t s_rtc_time_diff = 0; +#endif + // Spinlock used to protect access to static variables above and to the hardware // registers. portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED; @@ -208,6 +217,55 @@ static void IRAM_ATTR timer_alarm_isr(void *arg) (*s_alarm_handler)(arg); } +void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us) +{ + portENTER_CRITICAL(&s_time_update_lock); + /* Bail out if the timer is not initialized yet */ + if (s_timer_interrupt_handle == NULL) { + portEXIT_CRITICAL(&s_time_update_lock); + return; + } + + uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV; + uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1)); + uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1)); + uint64_t ticks_to_alarm = alarm - count; + uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us; + uint32_t new_alarm_val; + if (alarm > count && new_ticks <= FRC_TIMER_LOAD_VALUE(1)) { + new_alarm_val = new_ticks; + } else { + new_alarm_val = ALARM_OVERFLOW_VAL; + if (alarm != ALARM_OVERFLOW_VAL) { + s_mask_overflow = true; + } + } + REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val); + REG_WRITE(FRC_TIMER_LOAD_REG(1), 0); + + s_time_base_us += count / s_timer_ticks_per_us; + +#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF + // Due to the extra time required to read RTC time, don't attempt this + // adjustment when switching to a higher frequency (which usually + // happens in an interrupt). + if (new_ticks_per_us < s_timer_ticks_per_us) { + uint64_t rtc_time = esp_clk_rtc_time(); + uint64_t new_rtc_time_diff = s_time_base_us - rtc_time; + if (s_rtc_time_diff != 0) { + uint64_t correction = new_rtc_time_diff - s_rtc_time_diff; + s_time_base_us -= correction; + } else { + s_rtc_time_diff = new_rtc_time_diff; + } + } +#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF + + s_timer_ticks_per_us = new_ticks_per_us; + s_timer_us_per_overflow = FRC_TIMER_LOAD_VALUE(1) / new_ticks_per_us; + + portEXIT_CRITICAL(&s_time_update_lock); +} esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler) { diff --git a/components/esp32/esp_timer_impl.h b/components/esp32/esp_timer_impl.h index f5d73f5a3b..7d49e3eeb0 100644 --- a/components/esp32/esp_timer_impl.h +++ b/components/esp32/esp_timer_impl.h @@ -51,6 +51,15 @@ void esp_timer_impl_deinit(); */ void esp_timer_impl_set_alarm(uint64_t timestamp); +/** + * @brief Notify esp_timer implementation that APB frequency has changed + * + * Called by the frequency switching code. + * + * @param apb_ticks_per_us new number of APB clock ticks per microsecond + */ +void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us); + /** * @brief Get time, in microseconds, since esp_timer_impl_init was called * @return timestamp in microseconds From df3c857a3015a81db900047f5e62a4cfa19427ea Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:34:52 +0800 Subject: [PATCH 15/26] esp32: add power management hooks for WiFi library --- components/esp32/wifi_init.c | 41 ++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/components/esp32/wifi_init.c b/components/esp32/wifi_init.c index e0f21b615c..84bb4fc559 100644 --- a/components/esp32/wifi_init.c +++ b/components/esp32/wifi_init.c @@ -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 #include +#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 From bfeecd2b56d45961b1585e7b32a931c5830183a5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 22 Sep 2017 23:09:16 +0800 Subject: [PATCH 16/26] freertos: deprecate XT_CLOCK_FREQ - freertos: add deprecated definition for XT_CLOCK_FREQ - flash_ops: don't use XT_CLOCK_FREQ - unity: don't use XT_CLOCK_FREQ - hw_random: don't use XT_CLOCK_FREQ - core_dump: don't use XT_CLOCK_FREQ - app_trace: don't use XT_CLOCK_FREQ - xtensa_init: init xt_tick_divisor --- components/app_trace/app_trace.c | 2 -- components/app_trace/app_trace_util.c | 11 +++++---- components/app_trace/test/test_trace.c | 2 -- components/esp32/core_dump.c | 6 +++-- components/esp32/hw_random.c | 11 ++++++++- .../include/freertos/FreeRTOSConfig.h | 16 +++++++++++-- components/freertos/xtensa_init.c | 24 ++++++------------- components/spi_flash/flash_ops.c | 2 +- .../components/unity/unity_platform.c | 3 ++- 9 files changed, 44 insertions(+), 33 deletions(-) diff --git a/components/app_trace/app_trace.c b/components/app_trace/app_trace.c index c46f71b5af..1e792dd862 100644 --- a/components/app_trace/app_trace.c +++ b/components/app_trace/app_trace.c @@ -203,8 +203,6 @@ const static char *TAG = "esp_apptrace"; #define ESP_APPTRACE_LOGV( format, ... ) ESP_APPTRACE_LOG_LEV(V, ESP_LOG_VERBOSE, format, ##__VA_ARGS__) #define ESP_APPTRACE_LOGO( format, ... ) ESP_APPTRACE_LOG_LEV(E, ESP_LOG_NONE, format, ##__VA_ARGS__) -#define ESP_APPTRACE_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000)) - // TODO: move these (and same definitions in trax.c to dport_reg.h) #define TRACEMEM_MUX_PROBLK0_APPBLK1 0 #define TRACEMEM_MUX_BLK0_ONLY 1 diff --git a/components/app_trace/app_trace_util.c b/components/app_trace/app_trace_util.c index 2af3cd4d9e..da0a8d58a4 100644 --- a/components/app_trace/app_trace_util.c +++ b/components/app_trace/app_trace_util.c @@ -15,23 +15,24 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_app_trace_util.h" +#include "esp_clk.h" /////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// TIMEOUT ///////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -// TODO: get actual clock from PLL config -#define ESP_APPTRACE_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000)) -#define ESP_APPTRACE_US2CPUTICKS(_t_) ((_t_)*(XT_CLOCK_FREQ/1000000)) +#define ESP_APPTRACE_CPUTICKS2US(_t_, _cpu_freq_) ((_t_)/(_cpu_freq_/1000000)) +#define ESP_APPTRACE_US2CPUTICKS(_t_, _cpu_freq_) ((_t_)*(_cpu_freq_/1000000)) esp_err_t esp_apptrace_tmo_check(esp_apptrace_tmo_t *tmo) { + int cpu_freq = esp_clk_cpu_freq(); if (tmo->tmo != ESP_APPTRACE_TMO_INFINITE) { unsigned cur = portGET_RUN_TIME_COUNTER_VALUE(); if (tmo->start <= cur) { - tmo->elapsed = ESP_APPTRACE_CPUTICKS2US(cur - tmo->start); + tmo->elapsed = ESP_APPTRACE_CPUTICKS2US(cur - tmo->start, cpu_freq); } else { - tmo->elapsed = ESP_APPTRACE_CPUTICKS2US(0xFFFFFFFF - tmo->start + cur); + tmo->elapsed = ESP_APPTRACE_CPUTICKS2US(0xFFFFFFFF - tmo->start + cur, cpu_freq); } if (tmo->elapsed >= tmo->tmo) { return ESP_ERR_TIMEOUT; diff --git a/components/app_trace/test/test_trace.c b/components/app_trace/test/test_trace.c index e92b6ffc6a..6846ea50a2 100644 --- a/components/app_trace/test/test_trace.c +++ b/components/app_trace/test/test_trace.c @@ -87,8 +87,6 @@ static void esp_apptrace_test_timer_init(int timer_group, int timer_idx, uint32_ #define ESP_APPTRACE_TEST_WRITE_FROM_ISR(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 0UL) #define ESP_APPTRACE_TEST_WRITE_NOWAIT(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 0) -#define ESP_APPTRACE_TEST_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000)) - typedef struct { uint8_t *buf; uint32_t buf_sz; diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c index 12f63a4420..56ce08b64a 100644 --- a/components/esp32/core_dump.c +++ b/components/esp32/core_dump.c @@ -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(); diff --git a/components/esp32/hw_random.c b/components/esp32/hw_random.c index 4f63ecd989..78ad542fc2 100644 --- a/components/esp32/hw_random.c +++ b/components/esp32/hw_random.c @@ -17,6 +17,7 @@ #include #include #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); } diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index 3ccd3fda69..f830a45209 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -95,8 +95,20 @@ #define configNUM_THREAD_LOCAL_STORAGE_POINTERS CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS #define configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS 1 -/* TODO: config freq by menuconfig */ -#define XT_CLOCK_FREQ (CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ * 1000000) +#ifndef __ASSEMBLER__ + +/** + * This function is defined to provide a deprecation warning whenever + * XT_CLOCK_FREQ macro is used. + * Update the code to use esp_clk_cpu_freq function instead. + * @return current CPU clock frequency, in Hz + */ +int xt_clock_freq(void) __attribute__((deprecated)); + +#define XT_CLOCK_FREQ (xt_clock_freq()) + +#endif // __ASSEMBLER__ + /* Required for configuration-dependent settings */ #include "xtensa_config.h" diff --git a/components/freertos/xtensa_init.c b/components/freertos/xtensa_init.c index ded8df1098..624f242b1e 100644 --- a/components/freertos/xtensa_init.c +++ b/components/freertos/xtensa_init.c @@ -34,31 +34,21 @@ that are implemented in C. #endif #include "xtensa_rtos.h" +#include "esp_clk.h" #ifdef XT_RTOS_TIMER_INT unsigned _xt_tick_divisor = 0; /* cached number of cycles per tick */ -/* -Compute and initialize at run-time the tick divisor (the number of -processor clock cycles in an RTOS tick, used to set the tick timer). -Called when the processor clock frequency is not known at compile-time. -*/ void _xt_tick_divisor_init(void) { -#ifdef XT_CLOCK_FREQ + _xt_tick_divisor = esp_clk_cpu_freq() / XT_TICK_PER_SEC; +} - _xt_tick_divisor = (XT_CLOCK_FREQ / XT_TICK_PER_SEC); - -#else - - #ifdef XT_BOARD - _xt_tick_divisor = xtbsp_clock_freq_hz() / XT_TICK_PER_SEC; - #else - #error "No way to obtain processor clock frequency" - #endif /* XT_BOARD */ - -#endif /* XT_CLOCK_FREQ */ +/* Deprecated, to be removed */ +int xt_clock_freq(void) +{ + return esp_clk_cpu_freq(); } #endif /* XT_RTOS_TIMER_INT */ diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index afb8f407be..2302bf1d40 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -49,7 +49,7 @@ static spi_flash_counters_t s_flash_stats; #define COUNTER_STOP(counter) \ do{ \ s_flash_stats.counter.count++; \ - s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); \ + s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (esp_clk_cpu_freq() / 1000000); \ } while(0) #define COUNTER_ADD_BYTES(counter, size) \ diff --git a/tools/unit-test-app/components/unity/unity_platform.c b/tools/unit-test-app/components/unity/unity_platform.c index 8e73fd5257..2eeb66048b 100644 --- a/tools/unit-test-app/components/unity/unity_platform.c +++ b/tools/unit-test-app/components/unity/unity_platform.c @@ -8,6 +8,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" +#include "esp_clk.h" #include "soc/cpu.h" #include "esp_heap_caps.h" #include "test_utils.h" @@ -150,7 +151,7 @@ static void unity_run_single_test_by_index_parse(const char* filter, int index_m unity_run_single_test_by_index(test_index - 1); uint32_t end; RSR(CCOUNT, end); - uint32_t ms = (end - start) / (XT_CLOCK_FREQ / 1000); + uint32_t ms = (end - start) / (esp_clk_cpu_freq() / 1000); printf("Test ran in %dms\n", ms); } } From fba9678c5660b463a7fbd2102d4b67d0d2442e18 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 21 Aug 2017 22:30:23 +0800 Subject: [PATCH 17/26] uart: add support for REF_TICK --- components/driver/include/driver/uart.h | 3 +- components/driver/uart.c | 44 ++++++++++++++++--------- components/esp32/cpu_start.c | 13 ++++++-- components/soc/esp32/include/soc/soc.h | 1 + 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/components/driver/include/driver/uart.h b/components/driver/include/driver/uart.h index 6c17fd6566..a4227b3481 100644 --- a/components/driver/include/driver/uart.h +++ b/components/driver/include/driver/uart.h @@ -106,7 +106,8 @@ typedef struct { uart_parity_t parity; /*!< UART parity mode*/ uart_stop_bits_t stop_bits; /*!< UART stop bits*/ uart_hw_flowcontrol_t flow_ctrl; /*!< UART HW flow control mode (cts/rts)*/ - uint8_t rx_flow_ctrl_thresh ; /*!< UART HW RTS threshold*/ + uint8_t rx_flow_ctrl_thresh; /*!< UART HW RTS threshold*/ + bool use_ref_tick; /*!< Set to true if UART should be clocked from REF_TICK */ } uart_config_t; /** diff --git a/components/driver/uart.c b/components/driver/uart.c index 1d4503e97a..300a19ce8b 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -18,6 +18,7 @@ #include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_err.h" +#include "esp_clk.h" #include "malloc.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -172,13 +173,25 @@ esp_err_t uart_get_parity(uart_port_t uart_num, uart_parity_t* parity_mode) esp_err_t uart_set_baudrate(uart_port_t uart_num, uint32_t baud_rate) { UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); - UART_CHECK((baud_rate <= UART_BITRATE_MAX), "baud_rate error", ESP_FAIL); - uint32_t clk_div = (((UART_CLK_FREQ) << 4) / baud_rate); + esp_err_t ret = ESP_OK; UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); - UART[uart_num]->clk_div.div_int = clk_div >> 4; - UART[uart_num]->clk_div.div_frag = clk_div & 0xf; + int uart_clk_freq; + if (UART[uart_num]->conf0.tick_ref_always_on == 0) { + /* this UART has been configured to use REF_TICK */ + uart_clk_freq = REF_CLK_FREQ; + } else { + uart_clk_freq = esp_clk_apb_freq(); + } + uint32_t clk_div = (((uart_clk_freq) << 4) / baud_rate); + if (clk_div < 16) { + /* baud rate is too high for this clock frequency */ + ret = ESP_ERR_INVALID_ARG; + } else { + UART[uart_num]->clk_div.div_int = clk_div >> 4; + UART[uart_num]->clk_div.div_frag = clk_div & 0xf; + } UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); - return ESP_OK; + return ret; } esp_err_t uart_get_baudrate(uart_port_t uart_num, uint32_t* baudrate) @@ -468,17 +481,18 @@ esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_conf } else if(uart_num == UART_NUM_2) { periph_module_enable(PERIPH_UART2_MODULE); } - r=uart_set_hw_flow_ctrl(uart_num, uart_config->flow_ctrl, uart_config->rx_flow_ctrl_thresh); - if (r!=ESP_OK) return r; - r=uart_set_baudrate(uart_num, uart_config->baud_rate); - if (r!=ESP_OK) return r; + r = uart_set_hw_flow_ctrl(uart_num, uart_config->flow_ctrl, uart_config->rx_flow_ctrl_thresh); + if (r != ESP_OK) return r; - UART[uart_num]->conf0.val = ( - (uart_config->parity << UART_PARITY_S) - | (uart_config->data_bits << UART_BIT_NUM_S) - | ((uart_config->flow_ctrl & UART_HW_FLOWCTRL_CTS) ? UART_TX_FLOW_EN : 0x0) - | UART_TICK_REF_ALWAYS_ON_M); - r=uart_set_stop_bits(uart_num, uart_config->stop_bits); + UART[uart_num]->conf0.val = + (uart_config->parity << UART_PARITY_S) + | (uart_config->data_bits << UART_BIT_NUM_S) + | ((uart_config->flow_ctrl & UART_HW_FLOWCTRL_CTS) ? UART_TX_FLOW_EN : 0x0) + | (uart_config->use_ref_tick ? 0 : UART_TICK_REF_ALWAYS_ON_M); + + r = uart_set_baudrate(uart_num, uart_config->baud_rate); + if (r != ESP_OK) return r; + r = uart_set_stop_bits(uart_num, uart_config->stop_bits); return r; } diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 4a56143cfd..c839c14dde 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -289,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 diff --git a/components/soc/esp32/include/soc/soc.h b/components/soc/esp32/include/soc/soc.h index 0e633821fd..d402262df9 100644 --- a/components/soc/esp32/include/soc/soc.h +++ b/components/soc/esp32/include/soc/soc.h @@ -266,6 +266,7 @@ #define CPU_CLK_FREQ_ROM APB_CLK_FREQ_ROM #define CPU_CLK_FREQ APB_CLK_FREQ #define APB_CLK_FREQ ( 80*1000000 ) //unit: Hz +#define REF_CLK_FREQ ( 1000000 ) #define UART_CLK_FREQ APB_CLK_FREQ #define WDT_CLK_FREQ APB_CLK_FREQ #define TIMER_CLK_FREQ (80000000>>4) //80MHz divided by 16 From c2fff997ea63f3bc688781bd966f39b725440c2e Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 29 Aug 2017 18:34:43 +0800 Subject: [PATCH 18/26] sysview: always use TG as timestamp source --- components/app_trace/Kconfig | 5 ++--- .../Config/SEGGER_SYSVIEW_Config_FreeRTOS.c | 19 ++++++------------- .../sys_view/esp32/SEGGER_RTT_esp32.c | 10 ---------- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/components/app_trace/Kconfig b/components/app_trace/Kconfig index 101fa97865..6883db57ef 100644 --- a/components/app_trace/Kconfig +++ b/components/app_trace/Kconfig @@ -63,13 +63,13 @@ config SYSVIEW_ENABLE help Enables supporrt for SEGGER SystemView tracing functionality. -if !FREERTOS_UNICORE choice SYSVIEW_TS_SOURCE prompt "ESP32 timer to use as SystemView timestamp source" depends on SYSVIEW_ENABLE default SYSVIEW_TS_SOURCE_TIMER_00 help - SystemView needs one source for timestamps when tracing events from both cores. + SystemView needs to use a hardware timer as the source of timestamps + when tracing This option selects HW timer for it. config SYSVIEW_TS_SOURCE_TIMER_00 @@ -93,7 +93,6 @@ config SYSVIEW_TS_SOURCE_TIMER_11 Select this to use timer 1 of group 1 endchoice -endif #FREERTOS_UNICORE config SYSVIEW_EVT_OVERFLOW_ENABLE bool "Trace Buffer Overflow Event" diff --git a/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c b/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c index 87d76f32ab..728879b6a2 100644 --- a/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c +++ b/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c @@ -70,6 +70,7 @@ Revision: $Rev: 3734 $ #include "esp_app_trace.h" #include "esp_app_trace_util.h" #include "esp_intr_alloc.h" +#include "esp_clk.h" extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; @@ -85,14 +86,12 @@ extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; // The target device name #define SYSVIEW_DEVICE_NAME "ESP32" +// Timer group timer divisor +#define SYSVIEW_TIMER_DIV 2 // Frequency of the timestamp. -#if CONFIG_FREERTOS_UNICORE == 0 -#define SYSVIEW_TIMESTAMP_FREQ (TIMER_BASE_CLK/2) -#else -#define SYSVIEW_TIMESTAMP_FREQ (XT_CLOCK_FREQ) -#endif +#define SYSVIEW_TIMESTAMP_FREQ (esp_clk_apb_freq() / SYSVIEW_TIMER_DIV) // System Frequency. -#define SYSVIEW_CPU_FREQ (XT_CLOCK_FREQ) +#define SYSVIEW_CPU_FREQ (esp_clk_cpu_freq()) // The lowest RAM address used for IDs (pointers) #define SYSVIEW_RAM_BASE (0x3F400000) @@ -104,10 +103,8 @@ extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; #define SYSTICK_INTR_ID (ETS_INTERNAL_TIMER1_INTR_SOURCE+ETS_INTERNAL_INTR_SOURCE_OFF) #endif -#if CONFIG_FREERTOS_UNICORE == 0 static timer_idx_t s_ts_timer_idx; static timer_group_t s_ts_timer_group; -#endif // SystemView is single core specific: it implies that SEGGER_SYSVIEW_LOCK() // disables IRQs (disables rescheduling globaly). So we can not use finite timeouts for locks and return error @@ -214,7 +211,6 @@ static void _cbSendSystemDesc(void) { * ********************************************************************** */ -#if CONFIG_FREERTOS_UNICORE == 0 static void SEGGER_SYSVIEW_TS_Init() { timer_config_t config; @@ -238,7 +234,7 @@ static void SEGGER_SYSVIEW_TS_Init() config.alarm_en = 0; config.auto_reload = 0; config.counter_dir = TIMER_COUNT_UP; - config.divider = 2; + config.divider = SYSVIEW_TIMER_DIV; config.counter_en = 0; /*Configure timer*/ timer_init(s_ts_timer_group, s_ts_timer_idx, &config); @@ -247,14 +243,11 @@ static void SEGGER_SYSVIEW_TS_Init() /*Enable timer interrupt*/ timer_start(s_ts_timer_group, s_ts_timer_idx); } -#endif void SEGGER_SYSVIEW_Conf(void) { U32 disable_evts = 0; -#if CONFIG_FREERTOS_UNICORE == 0 SEGGER_SYSVIEW_TS_Init(); -#endif SEGGER_SYSVIEW_Init(SYSVIEW_TIMESTAMP_FREQ, SYSVIEW_CPU_FREQ, &SYSVIEW_X_OS_TraceAPI, _cbSendSystemDesc); SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE); diff --git a/components/app_trace/sys_view/esp32/SEGGER_RTT_esp32.c b/components/app_trace/sys_view/esp32/SEGGER_RTT_esp32.c index fd674bbb6c..c8b199540e 100644 --- a/components/app_trace/sys_view/esp32/SEGGER_RTT_esp32.c +++ b/components/app_trace/sys_view/esp32/SEGGER_RTT_esp32.c @@ -26,16 +26,6 @@ const static char *TAG = "segger_rtt"; #define SYSVIEW_EVENTS_BUF_SZ 255U -#if SYSVIEW_RTT_MAX_DATA_RATE > 0 -#include "SEGGER_SYSVIEW_Conf.h" -#if CONFIG_FREERTOS_UNICORE == 0 -#include "driver/timer.h" -#define SYSVIEW_TIMESTAMP_FREQ (TIMER_BASE_CLK/2) -#else -#define SYSVIEW_TIMESTAMP_FREQ (XT_CLOCK_FREQ) -#endif -#endif - // size of down channel data buf #define SYSVIEW_DOWN_BUF_SIZE 32 #define SEGGER_HOST_WAIT_TMO 500 //us From 330b6bd668aedc8f28b8a6398894e6afd264015d Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sun, 24 Sep 2017 14:37:37 +0800 Subject: [PATCH 19/26] sdmmc: lock APB frequency while in transaction --- components/driver/sdmmc_transaction.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/components/driver/sdmmc_transaction.c b/components/driver/sdmmc_transaction.c index a0ff102dba..16a27d5a1f 100644 --- a/components/driver/sdmmc_transaction.c +++ b/components/driver/sdmmc_transaction.c @@ -15,6 +15,7 @@ #include #include "esp_err.h" #include "esp_log.h" +#include "esp_pm.h" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "freertos/semphr.h" @@ -67,6 +68,9 @@ static sdmmc_desc_t s_dma_desc[SDMMC_DMA_DESC_CNT]; static sdmmc_transfer_state_t s_cur_transfer = { 0 }; static QueueHandle_t s_request_mutex; static bool s_is_app_cmd; // This flag is set if the next command is an APP command +#ifdef CONFIG_PM_ENABLE +static esp_pm_lock_handle_t s_pm_lock; +#endif static esp_err_t handle_idle_state_events(); static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd); @@ -83,12 +87,24 @@ esp_err_t sdmmc_host_transaction_handler_init() return ESP_ERR_NO_MEM; } s_is_app_cmd = false; +#ifdef CONFIG_PM_ENABLE + esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "sdmmc", &s_pm_lock); + if (err != ESP_OK) { + vSemaphoreDelete(s_request_mutex); + s_request_mutex = NULL; + return err; + } +#endif return ESP_OK; } void sdmmc_host_transaction_handler_deinit() { assert(s_request_mutex); +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_delete(s_pm_lock); + s_pm_lock = NULL; +#endif vSemaphoreDelete(s_request_mutex); s_request_mutex = NULL; } @@ -96,6 +112,9 @@ void sdmmc_host_transaction_handler_deinit() esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) { xSemaphoreTake(s_request_mutex, portMAX_DELAY); +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_acquire(s_pm_lock); +#endif // dispose of any events which happened asynchronously handle_idle_state_events(); // convert cmdinfo to hardware register value @@ -141,6 +160,9 @@ esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) } } s_is_app_cmd = (ret == ESP_OK && cmdinfo->opcode == MMC_APP_CMD); +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(s_pm_lock); +#endif xSemaphoreGive(s_request_mutex); return ret; } From e4616588b7baa94bc797405ad0deee439c68c282 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sun, 24 Sep 2017 14:59:15 +0800 Subject: [PATCH 20/26] spi master: lock APB frequency while in transaction --- components/driver/spi_master.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index 089a65d2b2..8809d2e048 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -47,6 +47,7 @@ queue and re-enabling the interrupt will trigger the interrupt again, which can #include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_err.h" +#include "esp_pm.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/xtensa_api.h" @@ -84,6 +85,9 @@ typedef struct { bool no_gpio_matrix; int dma_chan; int max_transfer_sz; +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_handle_t pm_lock; +#endif } spi_host_t; struct spi_device_t { @@ -129,6 +133,13 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus spihost[host]=malloc(sizeof(spi_host_t)); if (spihost[host]==NULL) goto nomem; memset(spihost[host], 0, sizeof(spi_host_t)); +#ifdef CONFIG_PM_ENABLE + esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "spi_master", + &spihost[host]->pm_lock); + if (err != ESP_OK) { + goto nomem; + } +#endif //CONFIG_PM_ENABLE spicommon_bus_initialize_io(host, bus_config, dma_chan, SPICOMMON_BUSFLAG_MASTER|SPICOMMON_BUSFLAG_QUAD, &native); spihost[host]->no_gpio_matrix=native; @@ -180,6 +191,11 @@ nomem: if (spihost[host]) { free(spihost[host]->dmadesc_tx); free(spihost[host]->dmadesc_rx); +#ifdef CONFIG_PM_ENABLE + if (spihost[host]->pm_lock) { + esp_pm_lock_delete(spihost[host]->pm_lock); + } +#endif } free(spihost[host]); spicommon_periph_free(host); @@ -199,6 +215,9 @@ esp_err_t spi_bus_free(spi_host_device_t host) if ( spihost[host]->dma_chan > 0 ) { spicommon_dma_chan_free ( spihost[host]->dma_chan ); } +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_delete(spihost[host]->pm_lock); +#endif spihost[host]->hw->slave.trans_inten=0; spihost[host]->hw->slave.trans_done=0; esp_intr_free(spihost[host]->intr); @@ -412,6 +431,10 @@ static void IRAM_ATTR spi_intr(void *arg) if (i==NO_CS) { //No packet waiting. Disable interrupt. esp_intr_disable(host->intr); +#ifdef CONFIG_PM_ENABLE + //Release APB frequency lock + esp_pm_lock_release(host->pm_lock); +#endif } else { host->hw->slave.trans_done=0; //clear int bit //We have a transaction. Send it. @@ -649,6 +672,9 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t * // else use the original buffer (forced-conversion) or assign to NULL trans_buf.buffer_to_send = (uint32_t*)txdata; } +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_acquire(handle->host->pm_lock); +#endif r=xQueueSend(handle->trans_queue, (void*)&trans_buf, ticks_to_wait); if (!r) return ESP_ERR_TIMEOUT; From 5048d38ce7ed4512684fe666fbe91e11be6734c1 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sun, 24 Sep 2017 15:05:35 +0800 Subject: [PATCH 21/26] spi slave: lock APB frequency while driver is in use --- components/driver/spi_slave.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/components/driver/spi_slave.c b/components/driver/spi_slave.c index 204dc6b992..bfc700cea7 100644 --- a/components/driver/spi_slave.c +++ b/components/driver/spi_slave.c @@ -26,6 +26,7 @@ #include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_err.h" +#include "esp_pm.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/xtensa_api.h" @@ -60,6 +61,9 @@ typedef struct { QueueHandle_t trans_queue; QueueHandle_t ret_queue; int dma_chan; +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_handle_t pm_lock; +#endif } spi_slave_t; static spi_slave_t *spihost[3]; @@ -106,6 +110,15 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b //We're limited to non-DMA transfers: the SPI work registers can hold 64 bytes at most. spihost[host]->max_transfer_sz = 16 * 4; } +#ifdef CONFIG_PM_ENABLE + esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "spi_slave", + &spihost[host]->pm_lock); + if (err != ESP_OK) { + goto nomem; + } + // Lock APB frequency while SPI slave driver is in use + esp_pm_lock_acquire(spihost[host]->pm_lock); +#endif //CONFIG_PM_ENABLE //Create queues spihost[host]->trans_queue = xQueueCreate(slave_config->queue_size, sizeof(spi_slave_transaction_t *)); @@ -184,6 +197,12 @@ nomem: if (spihost[host]->ret_queue) vQueueDelete(spihost[host]->ret_queue); free(spihost[host]->dmadesc_tx); free(spihost[host]->dmadesc_rx); +#ifdef CONFIG_PM_ENABLE + if (spihost[host]->pm_lock) { + esp_pm_lock_release(spihost[host]->pm_lock); + esp_pm_lock_delete(spihost[host]->pm_lock); + } +#endif } free(spihost[host]); spihost[host] = NULL; @@ -203,6 +222,10 @@ esp_err_t spi_slave_free(spi_host_device_t host) } free(spihost[host]->dmadesc_tx); free(spihost[host]->dmadesc_rx); +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(spihost[host]->pm_lock); + esp_pm_lock_delete(spihost[host]->pm_lock); +#endif //CONFIG_PM_ENABLE free(spihost[host]); spihost[host] = NULL; spicommon_periph_free(host); From 266d2b3e4f9f22958b85d3079b32714eb899a7be Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sun, 24 Sep 2017 15:18:37 +0800 Subject: [PATCH 22/26] ethernet: lock APB frequency while ethernet is enabled --- components/ethernet/emac_main.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/components/ethernet/emac_main.c b/components/ethernet/emac_main.c index 522ec57a95..e0a5428294 100644 --- a/components/ethernet/emac_main.c +++ b/components/ethernet/emac_main.c @@ -36,6 +36,7 @@ #include "esp_log.h" #include "esp_eth.h" #include "esp_intr_alloc.h" +#include "esp_pm.h" #include "driver/periph_ctrl.h" @@ -71,6 +72,9 @@ static SemaphoreHandle_t emac_rx_xMutex = NULL; static SemaphoreHandle_t emac_tx_xMutex = NULL; static const char *TAG = "emac"; static bool pause_send = false; +#ifdef CONFIG_PM_ENABLE +static esp_pm_lock_handle_t s_pm_lock; +#endif static esp_err_t emac_ioctl(emac_sig_t sig, emac_par_t par); esp_err_t emac_post(emac_sig_t sig, emac_par_t par); @@ -804,13 +808,31 @@ esp_err_t esp_eth_enable(void) return open_cmd.err; } +#ifdef CONFIG_PM_ENABLE + esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "ethernet", &s_pm_lock); + if (err != ESP_OK) { + return err; + } + esp_pm_lock_acquire(s_pm_lock); +#endif //CONFIG_PM_ENABLE + if (emac_config.emac_status != EMAC_RUNTIME_NOT_INIT) { if (emac_ioctl(SIG_EMAC_START, (emac_par_t)(&post_cmd)) != 0) { open_cmd.err = EMAC_CMD_FAIL; + goto cleanup; } } else { open_cmd.err = EMAC_CMD_FAIL; + goto cleanup; } + return EMAC_CMD_OK; + +cleanup: +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(s_pm_lock); + esp_pm_lock_delete(s_pm_lock); + s_pm_lock = NULL; +#endif //CONFIG_PM_ENABLE return open_cmd.err; } @@ -854,6 +876,12 @@ esp_err_t esp_eth_disable(void) return close_cmd.err; } +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(s_pm_lock); + esp_pm_lock_delete(s_pm_lock); + s_pm_lock = NULL; +#endif // CONFIG_PM_ENABLE + if (emac_config.emac_status == EMAC_RUNTIME_START) { if (emac_ioctl(SIG_EMAC_STOP, (emac_par_t)(&post_cmd)) != 0) { close_cmd.err = EMAC_CMD_FAIL; From 373d85dd9fd2b722ed06e55222ed764922ed4af0 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sun, 24 Sep 2017 15:46:59 +0800 Subject: [PATCH 23/26] freertos: fix error when including xtensa-timer.h from other components --- components/freertos/include/freertos/xtensa_rtos.h | 2 +- components/freertos/include/freertos/xtensa_timer.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/freertos/include/freertos/xtensa_rtos.h b/components/freertos/include/freertos/xtensa_rtos.h index 980cc92e0d..e5982b83a5 100644 --- a/components/freertos/include/freertos/xtensa_rtos.h +++ b/components/freertos/include/freertos/xtensa_rtos.h @@ -55,7 +55,7 @@ Should be included by all Xtensa generic and RTOS port-specific sources. /* Include any RTOS specific definitions that are needed by this header. */ -#include +#include "FreeRTOSConfig.h" /* Convert FreeRTOSConfig definitions to XTENSA definitions. diff --git a/components/freertos/include/freertos/xtensa_timer.h b/components/freertos/include/freertos/xtensa_timer.h index 9bb8648f69..fa4f96098c 100644 --- a/components/freertos/include/freertos/xtensa_timer.h +++ b/components/freertos/include/freertos/xtensa_timer.h @@ -49,7 +49,7 @@ and the Xtensa core configuration need not have a timer. #include "xtensa_rtos.h" /* in case this wasn't included directly */ -#include +#include "FreeRTOSConfig.h" /* Select timer to use for periodic tick, and determine its interrupt number From d503b991b4fd1ba5cea5b91d984d7e8976b074b1 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Oct 2017 15:34:31 +0800 Subject: [PATCH 24/26] bt: lock APB frequency while BT controller is enabled --- components/bt/bt.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/components/bt/bt.c b/components/bt/bt.c index 140877d33a..a0539a19a6 100644 --- a/components/bt/bt.c +++ b/components/bt/bt.c @@ -33,6 +33,7 @@ #include "bt.h" #include "esp_err.h" #include "esp_log.h" +#include "esp_pm.h" #if CONFIG_BT_ENABLED @@ -147,6 +148,10 @@ static esp_bt_controller_status_t btdm_controller_status = ESP_BT_CONTROLLER_STA static portMUX_TYPE global_int_mux = portMUX_INITIALIZER_UNLOCKED; +#ifdef CONFIG_PM_ENABLE +static esp_pm_lock_handle_t s_pm_lock; +#endif + static void IRAM_ATTR interrupt_disable(void) { portENTER_CRITICAL(&global_int_mux); @@ -442,6 +447,13 @@ esp_err_t esp_bt_controller_init(esp_bt_controller_config_t *cfg) return ESP_ERR_INVALID_ARG; } +#ifdef CONFIG_PM_ENABLE + esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "bt", &s_pm_lock); + if (err != ESP_OK) { + return err; + } +#endif + btdm_osi_funcs_register(&osi_funcs); btdm_controller_mem_init(); @@ -450,6 +462,10 @@ esp_err_t esp_bt_controller_init(esp_bt_controller_config_t *cfg) ret = btdm_controller_init(btdm_cfg_mask, cfg); if (ret) { +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_delete(s_pm_lock); + s_pm_lock = NULL; +#endif return ESP_ERR_NO_MEM; } @@ -468,6 +484,12 @@ esp_err_t esp_bt_controller_deinit(void) } btdm_controller_status = ESP_BT_CONTROLLER_STATUS_IDLE; + +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_delete(s_pm_lock); + s_pm_lock = NULL; +#endif + return ESP_OK; } @@ -484,6 +506,10 @@ esp_err_t esp_bt_controller_enable(esp_bt_mode_t mode) return ESP_ERR_INVALID_ARG; } +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_acquire(s_pm_lock); +#endif + esp_phy_load_cal_and_init(); if (btdm_bb_init_flag == false) { @@ -519,6 +545,10 @@ esp_err_t esp_bt_controller_disable(void) btdm_controller_status = ESP_BT_CONTROLLER_STATUS_INITED; } +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(s_pm_lock); +#endif + return ESP_OK; } From 062be56c3cac4accca0be9f2eeb5eaff60590f4f Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Oct 2017 14:38:41 +0800 Subject: [PATCH 25/26] docs: add power management API reference --- docs/Doxyfile | 5 +- docs/api-reference/system/index.rst | 1 + .../api-reference/system/power_management.rst | 123 ++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 docs/api-reference/system/power_management.rst diff --git a/docs/Doxyfile b/docs/Doxyfile index dee628ef44..3d642afdaa 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -144,7 +144,10 @@ INPUT = \ ## ## Application Level Tracing - API Reference ## - ../components/app_trace/include/esp_app_trace.h + ../components/app_trace/include/esp_app_trace.h \ + ### Power management + ../components/esp32/include/esp_pm.h \ + ../components/esp32/include/esp32/pm.h ## Get warnings for functions that have no documentation for their parameters or return value diff --git a/docs/api-reference/system/index.rst b/docs/api-reference/system/index.rst index e86cd12626..d4f08528d8 100644 --- a/docs/api-reference/system/index.rst +++ b/docs/api-reference/system/index.rst @@ -10,6 +10,7 @@ System API Watchdogs Over The Air Updates (OTA) Sleep Modes + Power Management Logging Base MAC address Application Level Tracing diff --git a/docs/api-reference/system/power_management.rst b/docs/api-reference/system/power_management.rst new file mode 100644 index 0000000000..581d42b00f --- /dev/null +++ b/docs/api-reference/system/power_management.rst @@ -0,0 +1,123 @@ +Power Management +================ + +Overview +-------- + +Power management algorithm included in ESP-IDF can adjust APB frequency, CPU frequency, and put the chip into light sleep mode to run the application +at smallest possible power consumption, given the requirements of application components. + +Application components can express their requirements by creating and acquiring power management locks. + +For instance, a driver for a peripheral clocked from APB can request the APB frequency to be set to 80 MHz, for the duration while the peripheral is used. Another example is that the RTOS will request the CPU to run at the highest configured frequency while there are tasks ready to run. Yet another example is a peripheral driver which needs interrupts to be enabled. Such driver can request light sleep to be disabled. + +Naturally, requesting higher APB or CPU frequency or disabling light sleep causes higher current consumption. Components should try to limit usage of power management locks to the shortest amount of time possible. + +Configuration +------------- + +Power management can be enabled at compile time, using :ref:`CONFIG_PM_ENABLE` option. + +Enabling power management features comes at the cost of increased interrupt latency. Extra latency depends on a number of factors, among which are CPU frequency, single/dual core mode, whether frequency switch needs to be performed or not. Minimal extra latency is 0.2us (when CPU frequency is 240MHz, and frequency scaling is not enabled), maximum extra latency is 40us (when frequency scaling is enabled, and a switch from 40MHz to 80MHz is performed on interrupt entry). + +Dynamic frequency scaling (DFS) can be enabled in the application by calling :cpp:func:`esp_pm_configure` function. Its argument is a structure defining frequency scaling settings (for ESP32, minimum and maximum CPU frequencies). Alternatively, :ref:`CONFIG_PM_DFS_INIT_AUTO` option can be enabled in menuconfig. If enabled, maximal CPU frequency is determined by :ref:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ` setting, and minimal CPU frequency is set to the XTAL frequency. + +.. note:: + + :cpp:func:`esp_pm_configure` function also has provisions for enabling automatic light sleep mode. However this feature is not fully supported yet, so `esp_pm_configure` will return an `ESP_ERR_NOT_SUPPORTED` if automatic light sleep is requested. + +Power Management Locks +---------------------- + +As mentioned in the overview, applications can acquire/release locks to control the power management algorithm. When application takes a lock, power management algorithm operation is restricted in a way described below, for each lock. When the lock is released, such restriction is removed. + +Different parts of the application can take the same lock. In this case, lock mush be released the same number of times as it was acquired, in order for power managment algorithm to resume. + +In ESP32, three types of locks are supported: + +``ESP_PM_CPU_FREQ_MAX`` + Requests CPU frequency to be at the maximal value set via :cpp:func:`esp_pm_configure`. For ESP32, this value can be set to 80, 160, or 240MHz. + +``ESP_PM_APB_FREQ_MAX`` + Requests APB frequency to be at the maximal supported value. For ESP32, this is 80 MHz. + +``ESP_PM_NO_LIGHT_SLEEP`` + Prevents automatic light sleep from being used. Note: currently taking this lock has no effect, as automatic light sleep is never used. + + +Power Management Algorithm for the ESP32 +---------------------------------------- + +When dynamic frequency scaling is enabled, CPU frequency will be switched as follows: + +- If maximal CPU frequency (set using :cpp:func:`esp_pm_configure` or :ref:`CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ`) is 240 MHz: + + 1. When ``ESP_PM_CPU_FREQ_MAX`` or ``ESP_PM_APB_FREQ_MAX`` locks are acquired, CPU frequency will be 240 MHz, and APB frequency will be 80 MHz. + + 2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL). + +- If maximal CPU frequency is 160 MHz: + + 1. When ``ESP_PM_CPU_FREQ_MAX`` is acquired, CPU frequency is set to 160 MHz, and APB frequency to 80 MHz. + + 2. When ``ESP_PM_CPU_FREQ_MAX`` is not acquired, but ``ESP_PM_APB_FREQ_MAX`` is, CPU and APB frequencies are set to 80 MHz. + + 3. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL). + +- If maximal CPU frequency is 80 MHz: + + 1. When ``ESP_PM_CPU_FREQ_MAX`` or ``ESP_PM_APB_FREQ_MAX`` locks are acquired, CPU and APB frequencies will be 80 MHz. + + 2. Otherwise, frequency will be switched to the minimal value set using :cpp:func:`esp_pm_configure` (usually, XTAL). + + +Dynamic Frequency Scaling and Peripheral Drivers +------------------------------------------------ + +When DFS is enabled, APB frequency can be changed several times within a single RTOS tick. Some peripherals can work normally even when APB frequency changes; some can not. + +The following peripherals can work even when APB frequency is changing: + +- UART: if REF_TICK is used as clock source (see `use_ref_tick` member of `uart_config_t`). + +- LEDC: if REF_TICK is used as clock source (see :cpp:func:`ledc_timer_config` function). + +- RMT: if REF_TICK is used as clock source. Currently the driver does not support REF_TICK, but it can be enabled by clearing ``RMT_REF_ALWAYS_ON_CHx`` bit for the respective channel. + +Currently, the following peripheral drivers are aware of DFS and will use ``ESP_PM_APB_FREQ_MAX`` lock for the duration of the transaction: + +- SPI master + +- SDMMC + +The following drivers will hold ``ESP_PM_APB_FREQ_MAX`` lock while the driver is enabled: + +- SPI slave — between calls to :cpp:func:`spi_slave_initialize` and cpp:func:`spi_slave_free`. + +- Ethernet — between calls to :cpp:func:`esp_eth_enable` and :cpp:func:`esp_eth_disable`. + +- WiFi — between calls to :cpp:func:`esp_wifi_start` and :cpp:func:`esp_wifi_stop`. If modem sleep is enabled, lock will be released for thte periods of time when radio is disabled. + +- Bluetooth — between calls to :cpp:func:`esp_bt_controller_enable` and :cpp:func:`esp_bt_controller_disable`. + +The following peripheral drivers are not aware of DFS yet. Applications need to acquire/release locks when necessary: + +- I2C + +- I2S + +- MCPWM + +- PCNT + +- Sigma-delta + +- Timer group + + +API Reference +------------- + +.. include:: /_build/inc/esp_pm.inc +.. include:: /_build/inc/pm.inc + From f6ef536fe921b1ec0f5a62bd556806d838bed298 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 10 Oct 2017 12:33:47 +0800 Subject: [PATCH 26/26] examples/power_save: enable modem sleep and DFS by default --- examples/wifi/power_save/main/Kconfig.projbuild | 2 +- examples/wifi/power_save/main/power_save.c | 13 +++++++++++++ examples/wifi/power_save/sdkconfig.defaults | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 examples/wifi/power_save/sdkconfig.defaults diff --git a/examples/wifi/power_save/main/Kconfig.projbuild b/examples/wifi/power_save/main/Kconfig.projbuild index 96e28d0b99..70c87c86b1 100644 --- a/examples/wifi/power_save/main/Kconfig.projbuild +++ b/examples/wifi/power_save/main/Kconfig.projbuild @@ -14,7 +14,7 @@ config WIFI_PASSWORD choice POWER_SAVE_MODE prompt "power save mode" - default POWER_SAVE_NONE + default POWER_SAVE_MODEM help Power save mode for the esp32 to use. diff --git a/examples/wifi/power_save/main/power_save.c b/examples/wifi/power_save/main/power_save.c index dae26fb9a5..f1d665fb91 100644 --- a/examples/wifi/power_save/main/power_save.c +++ b/examples/wifi/power_save/main/power_save.c @@ -17,6 +17,7 @@ #include "esp_wifi.h" #include "esp_log.h" #include "esp_event_loop.h" +#include "esp_pm.h" #include "nvs_flash.h" /*set the ssid and password via "make menuconfig"*/ @@ -89,5 +90,17 @@ void app_main() } ESP_ERROR_CHECK( ret ); +#if CONFIG_PM_ENABLE + // Configure dynamic frequency scaling: maximum frequency is set in sdkconfig, + // minimum frequency is XTAL. + rtc_cpu_freq_t max_freq; + rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &max_freq); + esp_pm_config_esp32_t pm_config = { + .max_cpu_freq = max_freq, + .min_cpu_freq = RTC_CPU_FREQ_XTAL + }; + ESP_ERROR_CHECK( esp_pm_configure(&pm_config) ); +#endif // CONFIG_PM_ENABLE + wifi_power_save(); } diff --git a/examples/wifi/power_save/sdkconfig.defaults b/examples/wifi/power_save/sdkconfig.defaults new file mode 100644 index 0000000000..0c212b1df6 --- /dev/null +++ b/examples/wifi/power_save/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Use lower CPU frequency +CONFIG_ESP32_DEFAULT_CPU_FREQ_80=y +# Enable support for power management +CONFIG_PM_ENABLE=y +# Use RTC timer as reference +CONFIG_PM_USE_RTC_TIMER_REF=y