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 # define CONFIG_PM_ALARM_NSEC 0
#endif #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 */ #define PM_IDLE_DOMAIN 0 /* Revisit */
#endif #endif
@ -102,6 +110,8 @@ static void up_idlepm(void)
oldstate = newstate; oldstate = newstate;
} }
spin_unlock_irqrestore(flags);
/* MCU-specific power management logic */ /* MCU-specific power management logic */
switch (newstate) switch (newstate)
@ -114,29 +124,34 @@ static void up_idlepm(void)
case PM_STANDBY: 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); CONFIG_PM_ALARM_NSEC / 1000);
/* Resume normal operation */
pm_relax(PM_IDLE_DOMAIN, PM_STANDBY);
} }
break; break;
case PM_SLEEP: case PM_SLEEP:
{ {
pm_changestate(PM_IDLE_DOMAIN, PM_NORMAL); /* Enter Deep-sleep mode */
newstate = PM_NORMAL;
esp32_pmsleep(CONFIG_PM_SLEEP_WAKEUP_SEC * 1000000 +
CONFIG_PM_SLEEP_WAKEUP_NSEC / 1000);
} }
break; break;
default: default:
break; break;
} }
}
else
{
if (oldstate == PM_NORMAL)
{
/* Relax normal operation */
spin_unlock_irqrestore(flags); pm_relax(PM_IDLE_DOMAIN, PM_NORMAL);
}
} }
} }
#else #else

View File

@ -34,10 +34,13 @@
#include <sys/time.h> #include <sys/time.h>
#include "hardware/esp32_rtccntl.h" #include "hardware/esp32_rtccntl.h"
#include "hardware/esp32_uart.h" #include "hardware/esp32_uart.h"
#include "hardware/esp32_dport.h"
#include "xtensa.h" #include "xtensa.h"
#include "xtensa_attr.h" #include "xtensa_attr.h"
#include "esp32_rtc.h" #include "esp32_rtc.h"
#include "esp32_clockconfig.h" #include "esp32_clockconfig.h"
#include "esp32_pm.h"
#include "esp32_resetcause.h"
/**************************************************************************** /****************************************************************************
* Pre-processor Definitions * Pre-processor Definitions
@ -62,6 +65,7 @@
/* Extra time it takes to enter and exit light sleep and deep sleep */ /* 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 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 #define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 2000
@ -80,6 +84,9 @@
#define RTC_ULP_TRIG_EN BIT(9) /* ULP wakeup */ #define RTC_ULP_TRIG_EN BIT(9) /* ULP wakeup */
#define RTC_BT_TRIG_EN BIT(10) /* BT wakeup (light sleep only) */ #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 * Private Types
****************************************************************************/ ****************************************************************************/
@ -141,6 +148,10 @@ struct rtc_vddsdio_config_s
uint32_t drefl : 2; /* Tuning parameter for VDDSDIO regulator */ 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 * 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, int IRAM_ATTR esp32_light_sleep_inner(uint32_t pd_flags,
uint32_t time_us, struct rtc_vddsdio_config_s config); uint32_t time_us, struct rtc_vddsdio_config_s config);
static int IRAM_ATTR esp32_configure_cpu_freq(uint32_t cpu_freq_mhz); 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 * Public Data
@ -180,8 +196,24 @@ static struct esp32_sleep_config_t s_config =
* Private Functions * Private Functions
****************************************************************************/ ****************************************************************************/
/* CPU do while loop for some time. */
extern void ets_delay_us(uint32_t us); 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 * 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; 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 * Public Functions
****************************************************************************/ ****************************************************************************/
@ -762,10 +919,10 @@ void esp32_pminit(void)
} }
/**************************************************************************** /****************************************************************************
* Name: esp32_pmstart * Name: esp32_pmstandby
* *
* Description: * Description:
* Enter force sleep time interval. * Enter force sleep.
* *
* Input Parameters: * Input Parameters:
* time_in_us - force sleep time interval * 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. */ /* 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(); esp32_light_sleep_start();
} }
/****************************************************************************
* 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 */ #endif /* CONFIG_PM */

View File

@ -44,6 +44,53 @@ extern "C"
#ifdef CONFIG_PM #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 * Public Function Prototypes
****************************************************************************/ ****************************************************************************/
@ -97,7 +144,7 @@ int esp32_light_sleep_start(void);
void esp32_pminit(void); void esp32_pminit(void);
/**************************************************************************** /****************************************************************************
* Name: esp32_pmstart * Name: esp32_pmstandby
* *
* Description: * Description:
* Enter force sleep time interval. * 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 */ #endif /* CONFIG_PM */

View File

@ -79,6 +79,20 @@ config PM_ALARM_NSEC
---help--- ---help---
Number of additional nanoseconds to wait in PM_STANDBY before going to PM_STANDBY mode. 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 endif # PM
if ESP32_SPIRAM if ESP32_SPIRAM

View File

@ -33,6 +33,7 @@ CONFIG_NSH_FILEIOSIZE=512
CONFIG_NSH_LINELEN=64 CONFIG_NSH_LINELEN=64
CONFIG_NSH_READLINE=y CONFIG_NSH_READLINE=y
CONFIG_PM=y CONFIG_PM=y
CONFIG_PM_GOVERNOR_EXPLICIT_RELAX=y
CONFIG_PM_GOVERNOR_GREEDY=y CONFIG_PM_GOVERNOR_GREEDY=y
CONFIG_PREALLOC_TIMERS=4 CONFIG_PREALLOC_TIMERS=4
CONFIG_RAM_SIZE=114688 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_cpu_frequency = 0x4000855c );
PROVIDE ( ets_get_detected_xtal_freq = 0x40008588 ); PROVIDE ( ets_get_detected_xtal_freq = 0x40008588 );
PROVIDE ( ets_get_xtal_scale = 0x4000856c ); PROVIDE ( ets_get_xtal_scale = 0x4000856c );
PROVIDE ( ets_update_cpu_frequency_rom = 0x40008550 );
PROVIDE ( ets_install_putc1 = 0x40007d18 ); PROVIDE ( ets_install_putc1 = 0x40007d18 );
PROVIDE ( ets_install_putc2 = 0x40007d38 ); PROVIDE ( ets_install_putc2 = 0x40007d38 );
PROVIDE ( ets_install_uart_printf = 0x40007d28 ); PROVIDE ( ets_install_uart_printf = 0x40007d28 );