xtensa/esp32: Add power management of deep-sleep

This commit is contained in:
chenwen 2020-10-13 14:18:31 +08:00 committed by Alan Carvalho de Assis
parent 3108233b8a
commit 67c0af650f
6 changed files with 427 additions and 15 deletions

View File

@ -49,6 +49,14 @@
# define CONFIG_PM_ALARM_NSEC 0
#endif
#ifndef CONFIG_PM_SLEEP_WAKEUP_SEC
# define CONFIG_PM_SLEEP_WAKEUP_SEC 20
#endif
#ifndef CONFIG_PM_SLEEP_WAKEUP_NSEC
# define CONFIG_PM_SLEEP_WAKEUP_NSEC 0
#endif
#define PM_IDLE_DOMAIN 0 /* Revisit */
#endif
@ -102,6 +110,8 @@ static void up_idlepm(void)
oldstate = newstate;
}
spin_unlock_irqrestore(flags);
/* MCU-specific power management logic */
switch (newstate)
@ -114,29 +124,34 @@ static void up_idlepm(void)
case PM_STANDBY:
{
/* Configure the RTC alarm to Auto Wake the system */
/* Enter Force-sleep mode */
esp32_pmstart(CONFIG_PM_ALARM_SEC * 1000000 +
esp32_pmstandby(CONFIG_PM_ALARM_SEC * 1000000 +
CONFIG_PM_ALARM_NSEC / 1000);
/* Resume normal operation */
pm_relax(PM_IDLE_DOMAIN, PM_STANDBY);
}
break;
case PM_SLEEP:
{
pm_changestate(PM_IDLE_DOMAIN, PM_NORMAL);
newstate = PM_NORMAL;
/* Enter Deep-sleep mode */
esp32_pmsleep(CONFIG_PM_SLEEP_WAKEUP_SEC * 1000000 +
CONFIG_PM_SLEEP_WAKEUP_NSEC / 1000);
}
break;
default:
break;
}
}
else
{
if (oldstate == PM_NORMAL)
{
/* Relax normal operation */
spin_unlock_irqrestore(flags);
pm_relax(PM_IDLE_DOMAIN, PM_NORMAL);
}
}
}
#else

View File

@ -34,10 +34,13 @@
#include <sys/time.h>
#include "hardware/esp32_rtccntl.h"
#include "hardware/esp32_uart.h"
#include "hardware/esp32_dport.h"
#include "xtensa.h"
#include "xtensa_attr.h"
#include "esp32_rtc.h"
#include "esp32_clockconfig.h"
#include "esp32_pm.h"
#include "esp32_resetcause.h"
/****************************************************************************
* Pre-processor Definitions
@ -62,6 +65,7 @@
/* Extra time it takes to enter and exit light sleep and deep sleep */
#define LIGHT_SLEEP_TIME_OVERHEAD_US (250 + 30 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
#define DEEP_SLEEP_TIME_OVERHEAD_US (250 + 100 * 240 / CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ)
#define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 2000
@ -80,6 +84,9 @@
#define RTC_ULP_TRIG_EN BIT(9) /* ULP wakeup */
#define RTC_BT_TRIG_EN BIT(10) /* BT wakeup (light sleep only) */
#define RTC_ENTRY_ADDR_REG RTC_CNTL_STORE6_REG
#define RTC_MEMORY_CRC_REG RTC_CNTL_STORE7_REG
/****************************************************************************
* Private Types
****************************************************************************/
@ -141,6 +148,10 @@ struct rtc_vddsdio_config_s
uint32_t drefl : 2; /* Tuning parameter for VDDSDIO regulator */
};
/* Function type for stub to run on wake from sleep */
typedef void (*esp_deep_sleep_wake_stub_fn_t)(void);
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
@ -158,6 +169,11 @@ static int IRAM_ATTR esp32_get_vddsdio_config(
int IRAM_ATTR esp32_light_sleep_inner(uint32_t pd_flags,
uint32_t time_us, struct rtc_vddsdio_config_s config);
static int IRAM_ATTR esp32_configure_cpu_freq(uint32_t cpu_freq_mhz);
static inline bool IRAM_ATTR esp32_ptr_executable(const void *p);
static void esp32_set_deep_sleep_wake_stub(
esp_deep_sleep_wake_stub_fn_t new_stub);
static void RTC_IRAM_ATTR esp32_wake_deep_sleep(void);
static esp_deep_sleep_wake_stub_fn_t esp32_get_deep_sleep_wake_stub(void);
/****************************************************************************
* Public Data
@ -180,8 +196,24 @@ static struct esp32_sleep_config_t s_config =
* Private Functions
****************************************************************************/
/* CPU do while loop for some time. */
extern void ets_delay_us(uint32_t us);
/* Set CRC of Fast RTC memory 0-0x7ff into RTC STORE7. */
extern void set_rtc_memory_crc(void);
/* Set the real CPU ticks per us to the ets,
* so that ets_delay_us will be accurate.
*/
extern void ets_update_cpu_frequency_rom(uint32_t ticks_per_us);
/* Get xtal_freq value, If value not stored in RTC_STORE5, than store. */
extern uint32_t ets_get_detected_xtal_freq(void);
/****************************************************************************
* Name: esp32_uart_tx_wait_idle
*
@ -654,6 +686,131 @@ static int IRAM_ATTR esp32_configure_cpu_freq(uint32_t cpu_freq_mhz)
return OK;
}
/****************************************************************************
* Name: esp32_ptr_executable
*
* Description:
* Check if point p in a compatible memory area of IRAM
*
* Input Parameters:
* p - Memory address
*
* Returned Value:
* True if in memory area or false if not.
*
****************************************************************************/
static inline bool IRAM_ATTR esp32_ptr_executable(const void *p)
{
intptr_t ip = (intptr_t) p;
return (ip >= SOC_IROM_LOW && ip < SOC_IROM_HIGH)
|| (ip >= SOC_IRAM_LOW && ip < SOC_IRAM_HIGH)
|| (ip >= SOC_IROM_MASK_LOW && ip < SOC_IROM_MASK_HIGH)
#if defined(SOC_CACHE_APP_LOW)
|| (ip >= SOC_CACHE_APP_LOW && ip < SOC_CACHE_APP_HIGH)
#endif
|| (ip >= SOC_RTC_IRAM_LOW && ip < SOC_RTC_IRAM_HIGH);
}
/****************************************************************************
* Name: esp32_set_deep_sleep_wake_stub
*
* Description:
* Install a new stub at runtime to run on wake from deep sleep.
*
* Input Parameters:
* new_stub - Function type for stub to run on wake from sleep
*
* Returned Value:
* None.
*
****************************************************************************/
static void esp32_set_deep_sleep_wake_stub(
esp_deep_sleep_wake_stub_fn_t new_stub)
{
putreg32((uint32_t)new_stub, RTC_ENTRY_ADDR_REG);
set_rtc_memory_crc();
}
/****************************************************************************
* Name: esp32_get_deep_sleep_wake_stub
*
* Description:
* Get current wake from deep sleep stub.
*
* Input Parameters:
* None
*
* Returned Value:
* Current wake from deep sleep stub, or NULL if no stub is installed.
*
****************************************************************************/
static esp_deep_sleep_wake_stub_fn_t esp32_get_deep_sleep_wake_stub(void)
{
uint32_t stored_crc = 0;
uint32_t calc_crc = 0;
esp_deep_sleep_wake_stub_fn_t stub_ptr = NULL;
stored_crc = getreg32(RTC_MEMORY_CRC_REG);
set_rtc_memory_crc();
calc_crc = getreg32(RTC_MEMORY_CRC_REG);
putreg32(stored_crc, RTC_MEMORY_CRC_REG);
if (stored_crc != calc_crc)
{
return NULL;
}
stub_ptr = (esp_deep_sleep_wake_stub_fn_t) getreg32(RTC_ENTRY_ADDR_REG);
if (!esp32_ptr_executable(stub_ptr))
{
return NULL;
}
return stub_ptr;
}
/****************************************************************************
* Name: esp32_wake_deep_sleep
*
* Description:
* Default stub to run on wake from deep sleep.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void RTC_IRAM_ATTR esp32_wake_deep_sleep(void)
{
/* Clear MMU for CPU 0 */
putreg32(getreg32(DPORT_PRO_CACHE_CTRL1_REG) | DPORT_PRO_CACHE_MMU_IA_CLR,
DPORT_PRO_CACHE_CTRL1_REG);
putreg32(getreg32(DPORT_PRO_CACHE_CTRL1_REG) &
(~DPORT_PRO_CACHE_MMU_IA_CLR), DPORT_PRO_CACHE_CTRL1_REG);
#if CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY > 0
/* ROM code has not started yet, so we need to set delay factor
* used by ets_delay_us first.
*/
ets_update_cpu_frequency_rom(ets_get_detected_xtal_freq() / 1000000);
/* This delay is configured in menuconfig, it can be used to give
* the flash chip some time to become ready.
*/
ets_delay_us(CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY);
#endif
}
/****************************************************************************
* Public Functions
****************************************************************************/
@ -762,10 +919,10 @@ void esp32_pminit(void)
}
/****************************************************************************
* Name: esp32_pmstart
* Name: esp32_pmstandby
*
* Description:
* Enter force sleep time interval.
* Enter force sleep.
*
* Input Parameters:
* time_in_us - force sleep time interval
@ -775,7 +932,7 @@ void esp32_pminit(void)
*
****************************************************************************/
void esp32_pmstart(uint64_t time_in_us)
void esp32_pmstandby(uint64_t time_in_us)
{
/* don't power down XTAL — powering it up takes different time on. */
@ -784,4 +941,133 @@ void esp32_pmstart(uint64_t time_in_us)
esp32_light_sleep_start();
}
#endif /* CONFIG_PM */
/****************************************************************************
* Name: esp32_sleep_get_wakeup_cause
*
* Description:
* Get the wakeup source which caused wakeup from sleep.
*
* Input Parameters:
* None
*
* Returned Value:
* Cause of wake up from last sleep
* (one of enum esp32_sleep_source_e values).
*
****************************************************************************/
enum esp32_sleep_source_e esp32_sleep_get_wakeup_cause(void)
{
uint32_t wakeup_cause;
if (esp32_resetcause(0) != ESP32_RESETCAUSE_CORE_DPSP)
{
return ESP_SLEEP_WAKEUP_UNDEFINED;
}
wakeup_cause = REG_GET_FIELD(RTC_CNTL_WAKEUP_STATE_REG,
RTC_CNTL_WAKEUP_CAUSE);
if (wakeup_cause & RTC_EXT0_TRIG_EN)
{
return ESP_SLEEP_WAKEUP_EXT0;
}
else if (wakeup_cause & RTC_EXT1_TRIG_EN)
{
return ESP_SLEEP_WAKEUP_EXT1;
}
else if (wakeup_cause & RTC_TIMER_TRIG_EN)
{
return ESP_SLEEP_WAKEUP_TIMER;
}
else if (wakeup_cause & RTC_TOUCH_TRIG_EN)
{
return ESP_SLEEP_WAKEUP_TOUCHPAD;
}
else if (wakeup_cause & RTC_ULP_TRIG_EN)
{
return ESP_SLEEP_WAKEUP_ULP;
}
else if (wakeup_cause & RTC_GPIO_TRIG_EN)
{
return ESP_SLEEP_WAKEUP_GPIO;
}
else if (wakeup_cause & (RTC_UART0_TRIG_EN | RTC_UART1_TRIG_EN))
{
return ESP_SLEEP_WAKEUP_UART;
}
else
{
return ESP_SLEEP_WAKEUP_UNDEFINED;
}
}
/****************************************************************************
* Name: esp32_deep_sleep_start
*
* Description:
* Enter deep sleep mode
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void esp32_deep_sleep_start(void)
{
uint32_t pd_flags;
/* record current RTC time */
s_config.rtc_ticks_at_sleep_start = esp32_rtc_time_get();
/* Configure wake stub */
if (esp32_get_deep_sleep_wake_stub() == NULL)
{
esp32_set_deep_sleep_wake_stub(esp32_wake_deep_sleep);
}
/* Decide which power domains can be powered down */
pd_flags = esp32_get_power_down_flags();
/* Correct the sleep time */
s_config.sleep_time_adjustment = DEEP_SLEEP_TIME_OVERHEAD_US;
/* Enter deep sleep */
esp32_sleep_start(RTC_SLEEP_PD_DIG | RTC_SLEEP_PD_VDDSDIO
| RTC_SLEEP_PD_XTAL | pd_flags);
/* Because RTC is in a slower clock domain than the CPU, it
* can take several CPU cycles for the sleep mode to start.
*/
while (1);
}
/****************************************************************************
* Name: esp32_pmsleep
*
* Description:
* Enter deep sleep.
*
* Input Parameters:
* time_in_us - deep sleep time interval
*
* Returned Value:
* None
*
****************************************************************************/
void esp32_pmsleep(uint64_t time_in_us)
{
fflush(stdout);
esp32_sleep_enable_timer_wakeup(time_in_us);
esp32_deep_sleep_start();
}
#endif /* CONFIG_PM */

View File

@ -44,6 +44,53 @@ extern "C"
#ifdef CONFIG_PM
/****************************************************************************
* Public Types
****************************************************************************/
/* Sleep wakeup cause */
enum esp32_sleep_source_e
{
/* In case of deep sleep, reset was not caused by exit from deep sleep */
ESP_SLEEP_WAKEUP_UNDEFINED,
/* Not a wakeup cause, used to disable all wakeup sources with
* esp_sleep_disable_wakeup_source
*/
ESP_SLEEP_WAKEUP_ALL,
/* Wakeup caused by external signal using RTC_IO */
ESP_SLEEP_WAKEUP_EXT0,
/* Wakeup caused by external signal using RTC_CNTL */
ESP_SLEEP_WAKEUP_EXT1,
/* Wakeup caused by timer */
ESP_SLEEP_WAKEUP_TIMER,
/* Wakeup caused by touchpad */
ESP_SLEEP_WAKEUP_TOUCHPAD,
/* Wakeup caused by ULP program */
ESP_SLEEP_WAKEUP_ULP,
/* Wakeup caused by GPIO (light sleep only) */
ESP_SLEEP_WAKEUP_GPIO,
/* Wakeup caused by UART (light sleep only) */
ESP_SLEEP_WAKEUP_UART,
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
@ -97,7 +144,7 @@ int esp32_light_sleep_start(void);
void esp32_pminit(void);
/****************************************************************************
* Name: esp32_pmstart
* Name: esp32_pmstandby
*
* Description:
* Enter force sleep time interval.
@ -110,7 +157,55 @@ void esp32_pminit(void);
*
****************************************************************************/
void esp32_pmstart(uint64_t time_in_us);
void esp32_pmstandby(uint64_t time_in_us);
/****************************************************************************
* Name: esp32_sleep_get_wakeup_cause
*
* Description:
* Get the wakeup source which caused wakeup from sleep.
*
* Input Parameters:
* None
*
* Returned Value:
* enum esp32_sleep_source_e - Cause of wake up from last sleep.
*
****************************************************************************/
enum esp32_sleep_source_e esp32_sleep_get_wakeup_cause(void);
/****************************************************************************
* Name: esp32_deep_sleep_start
*
* Description:
* Enter deep sleep mode
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void esp32_deep_sleep_start(void);
/****************************************************************************
* Name: esp32_pmsleep
*
* Description:
* Enter deep sleep.
*
* Input Parameters:
* time_in_us - deep sleep time interval
*
* Returned Value:
* None
*
****************************************************************************/
void esp32_pmsleep(uint64_t time_in_us);
#endif /* CONFIG_PM */

View File

@ -79,6 +79,20 @@ config PM_ALARM_NSEC
---help---
Number of additional nanoseconds to wait in PM_STANDBY before going to PM_STANDBY mode.
config PM_SLEEP_WAKEUP_SEC
int "PM_SLEEP delay (seconds)"
default 20
depends on PM
---help---
Number of seconds to wait in PM_SLEEP.
config PM_SLEEP_WAKEUP_NSEC
int "PM_SLEEP delay (nanoseconds)"
default 0
depends on PM
---help---
Number of additional nanoseconds to wait in PM_SLEEP.
endif # PM
if ESP32_SPIRAM

View File

@ -33,6 +33,7 @@ CONFIG_NSH_FILEIOSIZE=512
CONFIG_NSH_LINELEN=64
CONFIG_NSH_READLINE=y
CONFIG_PM=y
CONFIG_PM_GOVERNOR_EXPLICIT_RELAX=y
CONFIG_PM_GOVERNOR_GREEDY=y
CONFIG_PREALLOC_TIMERS=4
CONFIG_RAM_SIZE=114688

View File

@ -175,6 +175,7 @@ PROVIDE ( ets_efuse_read_op = 0x40008600 );
PROVIDE ( ets_get_cpu_frequency = 0x4000855c );
PROVIDE ( ets_get_detected_xtal_freq = 0x40008588 );
PROVIDE ( ets_get_xtal_scale = 0x4000856c );
PROVIDE ( ets_update_cpu_frequency_rom = 0x40008550 );
PROVIDE ( ets_install_putc1 = 0x40007d18 );
PROVIDE ( ets_install_putc2 = 0x40007d38 );
PROVIDE ( ets_install_uart_printf = 0x40007d28 );