blob: 28abfcd2207bbcb7bf428b35953b521511840e48 [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/esp32/esp32_pm.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/power/pm.h>
#ifdef CONFIG_PM
#include <arch/chip/chip.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <sys/param.h>
#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"
#ifdef CONFIG_ESP32_RT_TIMER
#include "esp32_rt_timer.h"
#endif
#ifdef CONFIG_SCHED_TICKLESS
#include "esp32_tickless.h"
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* If light sleep time is less than that, don't power down flash */
#define FLASH_PD_MIN_SLEEP_TIME_US 2000
/* Minimal amount of time we can sleep for. */
#define LIGHT_SLEEP_MIN_TIME_US 200
/* Time from VDD_SDIO power up to first flash read in ROM code */
#define VDD_SDIO_POWERUP_TO_FLASH_READ_US 700
/* 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
#define RTC_VDDSDIO_TIEH_1_8V 0 /* TIEH field value for 1.8V VDDSDIO */
#define RTC_VDDSDIO_TIEH_3_3V 1 /* TIEH field value for 3.3V VDDSDIO */
#define RTC_EXT0_TRIG_EN BIT(0) /* EXT0 GPIO wakeup */
#define RTC_EXT1_TRIG_EN BIT(1) /* EXT1 GPIO wakeup */
#define RTC_GPIO_TRIG_EN BIT(2) /* GPIO wakeup (light sleep only) */
#define RTC_TIMER_TRIG_EN BIT(3) /* Timer wakeup */
#define RTC_SDIO_TRIG_EN BIT(4) /* SDIO wakeup (light sleep only) */
#define RTC_MAC_TRIG_EN BIT(5) /* MAC wakeup (light sleep only) */
#define RTC_UART0_TRIG_EN BIT(6) /* UART0 wakeup (light sleep only) */
#define RTC_UART1_TRIG_EN BIT(7) /* UART1 wakeup (light sleep only) */
#define RTC_TOUCH_TRIG_EN BIT(8) /* Touch wakeup */
#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
****************************************************************************/
/* Power down options */
enum esp32_sleep_pd_option_e
{
/* Power down the power domain in sleep mode */
ESP_PD_OPTION_OFF,
/* Keep power domain enabled during sleep mode */
ESP_PD_OPTION_ON,
/* Keep power domain enabled in sleep mode if it is needed
* by one of the wakeup options, otherwise power it down.
*/
ESP_PD_OPTION_AUTO
};
/* Power domains which can be powered down in sleep mode. */
enum esp_sleep_pd_domain_e
{
ESP_PD_DOMAIN_RTC_PERIPH, /* RTC IO, sensors and ULP co-processor */
ESP_PD_DOMAIN_RTC_SLOW_MEM, /* RTC slow memory */
ESP_PD_DOMAIN_RTC_FAST_MEM, /* RTC fast memory */
ESP_PD_DOMAIN_XTAL, /* XTAL oscillator */
ESP_PD_DOMAIN_MAX /* Number of domains */
};
/* Internal structure which holds all requested deep sleep parameters. */
struct esp32_sleep_config_t
{
enum esp32_sleep_pd_option_e pd_options[ESP_PD_DOMAIN_MAX];
uint64_t sleep_duration;
uint32_t wakeup_triggers : 11;
uint32_t ext1_trigger_mode : 1;
uint32_t ext1_rtc_gpio_mask : 18;
uint32_t ext0_trigger_level : 1;
uint32_t ext0_rtc_gpio_num : 5;
uint32_t sleep_time_adjustment;
uint64_t rtc_ticks_at_sleep_start;
};
/* Structure describing vddsdio configuration. */
struct rtc_vddsdio_config_s
{
uint32_t force : 1; /* If 1, use configuration from RTC registers; if 0, use EFUSE/bootstrapping pins. */
uint32_t enable : 1; /* Enable VDDSDIO regulator */
uint32_t tieh : 1; /* Select VDDSDIO voltage. One of RTC_VDDSDIO_TIEH_1_8V, RTC_VDDSDIO_TIEH_3_3V */
uint32_t drefh : 2; /* Tuning parameter for VDDSDIO regulator */
uint32_t drefm : 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
****************************************************************************/
static void esp32_timer_wakeup_prepare(void);
static void IRAM_ATTR esp32_flush_uarts(void);
static void IRAM_ATTR esp32_suspend_uarts(void);
static void IRAM_ATTR esp32_resume_uarts(void);
static uint32_t esp32_get_power_down_flags(void);
static inline void esp32_uart_tx_wait_idle(uint8_t uart_no);
static void IRAM_ATTR esp32_set_vddsdio_config(
struct rtc_vddsdio_config_s config);
static int IRAM_ATTR esp32_get_vddsdio_config(
struct rtc_vddsdio_config_s *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
****************************************************************************/
extern uint32_t g_ticks_per_us_pro;
/****************************************************************************
* Private Data
****************************************************************************/
static struct esp32_sleep_config_t s_config =
{
.pd_options =
{ ESP_PD_OPTION_AUTO, ESP_PD_OPTION_AUTO, ESP_PD_OPTION_AUTO },
.wakeup_triggers = 0
};
static _Atomic uint32_t pm_wakelock = 0;
/****************************************************************************
* 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
*
* Description:
* Wait until uart tx full empty and the last char send ok.
*
* Input Parameters:
* uart_no - 0 for UART0, 1 for UART1, 2 for UART2
*
* Returned Value:
* None
*
****************************************************************************/
static inline void esp32_uart_tx_wait_idle(uint8_t uart_no)
{
uint32_t status;
do
{
status = getreg32(UART_STATUS_REG(uart_no));
/* either tx count or state is non-zero */
}
while ((status & (UART_ST_UTX_OUT_M | UART_TXFIFO_CNT_M)) != 0);
}
/****************************************************************************
* Name: esp32_flush_uarts
*
* Description:
* Wait until UART0/UART1/UART2 tx full empty and the last char send ok
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void IRAM_ATTR esp32_flush_uarts(void)
{
int i;
for (i = 0; i < ESP32_NUARTS; ++i)
{
esp32_uart_tx_wait_idle(i);
}
}
/****************************************************************************
* Name: esp32_suspend_uarts
*
* Description:
* Suspend UART0/UART1/UART2 output
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void IRAM_ATTR esp32_suspend_uarts(void)
{
int i;
for (i = 0; i < ESP32_NUARTS; ++i)
{
modifyreg32(UART_FLOW_CONF_REG(i), 0, UART_FORCE_XOFF);
while (REG_GET_FIELD(UART_STATUS_REG(i), UART_ST_UTX_OUT) != 0);
}
}
/****************************************************************************
* Name: esp32_resume_uarts
*
* Description:
* Re-enable UART0/UART1/UART2 output
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void IRAM_ATTR esp32_resume_uarts(void)
{
int i;
for (i = 0; i < ESP32_NUARTS; ++i)
{
modifyreg32(UART_FLOW_CONF_REG(i), UART_FORCE_XOFF, 0);
modifyreg32(UART_FLOW_CONF_REG(i), 0, UART_FORCE_XON);
modifyreg32(UART_FLOW_CONF_REG(i), UART_FORCE_XON, 0);
}
}
/****************************************************************************
* Name: esp32_get_power_down_flags
*
* Description:
* Get power domains that can be powered down
*
* Input Parameters:
* None
*
* Returned Value:
* Power domains
*
****************************************************************************/
static uint32_t esp32_get_power_down_flags(void)
{
uint32_t pd_flags = 0;
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_FAST_MEM] == ESP_PD_OPTION_AUTO)
{
s_config.pd_options[ESP_PD_DOMAIN_RTC_FAST_MEM] = ESP_PD_OPTION_ON;
}
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] == ESP_PD_OPTION_AUTO)
{
if (s_config.wakeup_triggers & (RTC_EXT0_TRIG_EN | RTC_GPIO_TRIG_EN))
{
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] = ESP_PD_OPTION_ON;
}
else if (s_config.wakeup_triggers &
(RTC_TOUCH_TRIG_EN | RTC_ULP_TRIG_EN))
{
/* In both rev. 0 and rev. 1 of ESP32,
* forcing power up of prevents ULP timer
* and touch FSMs from working correctly.
*/
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] = ESP_PD_OPTION_OFF;
}
}
if (s_config.pd_options[ESP_PD_DOMAIN_XTAL] == ESP_PD_OPTION_AUTO)
{
s_config.pd_options[ESP_PD_DOMAIN_XTAL] = ESP_PD_OPTION_OFF;
}
/* Prepare flags based on the selected options */
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_FAST_MEM] != ESP_PD_OPTION_ON)
{
pd_flags |= RTC_SLEEP_PD_RTC_FAST_MEM;
}
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_SLOW_MEM] != ESP_PD_OPTION_ON)
{
pd_flags |= RTC_SLEEP_PD_RTC_SLOW_MEM;
}
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] != ESP_PD_OPTION_ON)
{
pd_flags |= RTC_SLEEP_PD_RTC_PERIPH;
}
if (s_config.pd_options[ESP_PD_DOMAIN_XTAL] != ESP_PD_OPTION_ON)
{
pd_flags |= RTC_SLEEP_PD_XTAL;
}
return pd_flags;
}
/****************************************************************************
* Name: esp32_timer_wakeup_prepare
*
* Description:
* Configure timer to wake-up
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32_timer_wakeup_prepare(void)
{
uint32_t period;
int64_t sleep_duration;
int64_t rtc_count_delta;
period = getreg32(RTC_SLOW_CLK_CAL_REG);
sleep_duration = (int64_t) s_config.sleep_duration -
(int64_t) s_config.sleep_time_adjustment;
if (sleep_duration < 0)
{
sleep_duration = 0;
}
rtc_count_delta = esp32_rtc_time_us_to_slowclk(sleep_duration, period);
esp32_rtc_sleep_set_wakeup_time(s_config.rtc_ticks_at_sleep_start +
rtc_count_delta);
}
/****************************************************************************
* Name: esp32_set_vddsdio_config
*
* Description:
* Set new VDDSDIO configuration using RTC registers.
*
* Input Parameters:
* New VDDSDIO configuration
*
* Returned Value:
* None
*
****************************************************************************/
static void IRAM_ATTR esp32_set_vddsdio_config(
struct rtc_vddsdio_config_s config)
{
uint32_t val = 0;
val |= (config.force << RTC_CNTL_SDIO_FORCE_S);
val |= (config.enable << RTC_CNTL_XPD_SDIO_REG_S);
val |= (config.drefh << RTC_CNTL_DREFH_SDIO_S);
val |= (config.drefm << RTC_CNTL_DREFM_SDIO_S);
val |= (config.drefl << RTC_CNTL_DREFL_SDIO_S);
val |= (config.tieh << RTC_CNTL_SDIO_TIEH_S);
val |= RTC_CNTL_SDIO_PD_EN;
putreg32((uint32_t)val, RTC_CNTL_SDIO_CONF_REG);
}
/****************************************************************************
* Name: esp32_get_vddsdio_config
*
* Description:
* Get current VDDSDIO configuration.
*
* Input Parameters:
* Incoming parameter address of VDDSDIO configuration to be saved
*
* Returned Value:
* Zero (OK) is returned on success.
*
****************************************************************************/
static int IRAM_ATTR esp32_get_vddsdio_config(
struct rtc_vddsdio_config_s *config)
{
struct rtc_vddsdio_config_s *result = config;
uint32_t efuse_reg;
uint32_t strap_reg;
uint32_t sdio_conf_reg = getreg32(RTC_CNTL_SDIO_CONF_REG);
result->drefh = (sdio_conf_reg & RTC_CNTL_DREFH_SDIO_M)
>> RTC_CNTL_DREFH_SDIO_S;
result->drefm = (sdio_conf_reg & RTC_CNTL_DREFM_SDIO_M)
>> RTC_CNTL_DREFM_SDIO_S;
result->drefl = (sdio_conf_reg & RTC_CNTL_DREFL_SDIO_M)
>> RTC_CNTL_DREFL_SDIO_S;
if (sdio_conf_reg & RTC_CNTL_SDIO_FORCE)
{
/* Get configuration from RTC */
result->force = 1;
result->enable = (sdio_conf_reg & RTC_CNTL_XPD_SDIO_REG_M)
>> RTC_CNTL_XPD_SDIO_REG_S;
result->tieh = (sdio_conf_reg & RTC_CNTL_SDIO_TIEH_M)
>> RTC_CNTL_SDIO_TIEH_S;
return OK;
}
efuse_reg = getreg32(EFUSE_BLK0_RDATA4_REG);
if (efuse_reg & EFUSE_RD_SDIO_FORCE)
{
/* Get configuration from EFUSE */
result->force = 0;
result->enable = (efuse_reg & EFUSE_RD_XPD_SDIO_REG_M)
>> EFUSE_RD_XPD_SDIO_REG_S;
result->tieh = (efuse_reg & EFUSE_RD_SDIO_TIEH_M)
>> EFUSE_RD_SDIO_TIEH_S;
if (REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG,
EFUSE_RD_BLK3_PART_RESERVE) == 0)
{
result->drefh = (efuse_reg & EFUSE_RD_SDIO_DREFH_M)
>> EFUSE_RD_SDIO_DREFH_S;
result->drefm = (efuse_reg & EFUSE_RD_SDIO_DREFM_M)
>> EFUSE_RD_SDIO_DREFM_S;
result->drefl = (efuse_reg & EFUSE_RD_SDIO_DREFL_M)
>> EFUSE_RD_SDIO_DREFL_S;
}
return OK;
}
/* Otherwise, VDD_SDIO is controlled by bootstrapping pin */
strap_reg = getreg32(GPIO_STRAP_REG);
result->force = 0;
result->tieh = (strap_reg & BIT(5)) ? RTC_VDDSDIO_TIEH_1_8V
: RTC_VDDSDIO_TIEH_3_3V;
result->enable = 1;
return OK;
}
/****************************************************************************
* Name: esp32_sleep_start
*
* Description:
* Enter low power mode.
*
* Input Parameters:
* Power domains
*
* Returned Value:
* 0 is returned on success or a negated errno value is returned
*
****************************************************************************/
static int IRAM_ATTR esp32_sleep_start(uint32_t pd_flags)
{
int result;
uint32_t cur_freq;
/* Stop UART output so that output is not lost due to APB frequency change.
* For light sleep, suspend UART output - it will resume after wakeup.
* For deep sleep, wait for the contents of UART FIFO to be sent.
*/
if (pd_flags & RTC_SLEEP_PD_DIG)
{
esp32_flush_uarts();
}
else
{
esp32_suspend_uarts();
}
/* Save current frequency and switch to XTAL */
cur_freq = esp_rtc_clk_get_cpu_freq();
esp32_rtc_cpu_freq_set_xtal();
/* Enter sleep */
esp32_rtc_sleep_init(pd_flags);
/* Configure timer wakeup */
if ((s_config.wakeup_triggers & RTC_TIMER_TRIG_EN)
&& s_config.sleep_duration > 0)
{
esp32_timer_wakeup_prepare();
}
result = esp32_rtc_sleep_start(s_config.wakeup_triggers, 0);
/* Restore CPU frequency */
if (esp32_configure_cpu_freq(cur_freq) != OK)
{
pwrwarn("WARNING: Failed to restore CPU frequency"
"Configure cpu frequency %d.\n", cur_freq);
}
/* Re-enable UART output */
esp32_resume_uarts();
return result;
}
/****************************************************************************
* Name: esp32_light_sleep_inner
*
* Description:
* Enter low power mode, then wait for flash to be ready on wakeup
*
* Input Parameters:
* pd_flags - Power domains
* time_us - Time to wait for spi_flash become ready
* config - VDDSDIO configuration
*
* Returned Value:
* 0 is returned on success or a negated errno value is returned
*
****************************************************************************/
int esp32_light_sleep_inner(uint32_t pd_flags,
uint32_t time_us, struct rtc_vddsdio_config_s config)
{
/* Enter sleep */
int err = esp32_sleep_start(pd_flags);
/* If VDDSDIO regulator was controlled by RTC registers before sleep.
* restore the configuration.
*/
if (config.force)
{
esp32_set_vddsdio_config(config);
}
/* If SPI flash was powered down, wait for it to become ready. */
if (pd_flags & RTC_SLEEP_PD_VDDSDIO)
{
/* Wait for the flash chip to start up. */
ets_delay_us(time_us);
}
return err;
}
/****************************************************************************
* Name: esp32_configure_cpu_freq
*
* Description:
* Switch to new CPU frequencies.
*
* Input Parameters:
* cpu_freq_mhz - new CPU frequency
*
* Returned Value:
* 0 is returned on success or a negated errno value is returned
*
****************************************************************************/
static int IRAM_ATTR esp32_configure_cpu_freq(uint32_t cpu_freq_mhz)
{
uint32_t soc_clk_sel;
uint32_t source_freq_mhz;
enum esp32_rtc_xtal_freq_e xtal_freq;
if (cpu_freq_mhz == 240)
{
source_freq_mhz = RTC_PLL_FREQ_480M;
}
else if(cpu_freq_mhz == 80 || cpu_freq_mhz == 160)
{
source_freq_mhz = RTC_PLL_FREQ_320M;
}
else
{
return -EINVAL;
}
xtal_freq = esp32_rtc_clk_xtal_freq_get();
soc_clk_sel = REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL);
if (soc_clk_sel != RTC_CNTL_SOC_CLK_SEL_XTL)
{
esp32_rtc_update_to_xtal(xtal_freq, 1);
esp32_rtc_wait_for_slow_cycle();
}
if (soc_clk_sel == RTC_CNTL_SOC_CLK_SEL_PLL)
{
esp32_rtc_bbpll_disable();
}
esp32_rtc_bbpll_enable();
esp32_rtc_wait_for_slow_cycle();
esp32_rtc_bbpll_configure(xtal_freq, source_freq_mhz);
esp32_set_cpu_freq(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
****************************************************************************/
/****************************************************************************
* Name: esp32_sleep_enable_timer_wakeup
*
* Description:
* Configure wake-up interval
*
* Input Parameters:
* time_in_us - Configure wake-up time interval
*
* Returned Value:
* None
*
****************************************************************************/
void IRAM_ATTR esp32_sleep_enable_timer_wakeup(uint64_t time_in_us)
{
s_config.wakeup_triggers |= RTC_TIMER_TRIG_EN;
s_config.sleep_duration = time_in_us;
}
/****************************************************************************
* Name: esp32_light_sleep_start
*
* Description:
* Enter sleep mode
*
* Input Parameters:
* sleep_time - Actual sleep time
*
* Returned Value:
* 0 is returned on success or a negated errno value is returned
*
****************************************************************************/
int esp32_light_sleep_start(uint64_t *sleep_time)
{
irqstate_t flags;
uint32_t pd_flags;
uint32_t flash_enable_time_us;
#ifndef CONFIG_ESP32_SPIRAM
uint32_t vddsdio_pd_sleep_duration;
#endif
struct rtc_vddsdio_config_s vddsdio_config;
int ret = OK;
flags = enter_critical_section();
s_config.rtc_ticks_at_sleep_start = esp32_rtc_time_get();
/* Decide which power domains can be powered down */
pd_flags = esp32_get_power_down_flags();
/* Amount of time to subtract from actual sleep time.
* This is spent on entering and leaving light sleep.
*/
s_config.sleep_time_adjustment = LIGHT_SLEEP_TIME_OVERHEAD_US;
/* Decide if VDD_SDIO needs to be powered down;
* If it needs to be powered down, adjust sleep time.
*/
flash_enable_time_us = VDD_SDIO_POWERUP_TO_FLASH_READ_US
+ CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY;
#ifndef CONFIG_ESP32_SPIRAM
vddsdio_pd_sleep_duration = MAX(FLASH_PD_MIN_SLEEP_TIME_US,
flash_enable_time_us + LIGHT_SLEEP_TIME_OVERHEAD_US
+ LIGHT_SLEEP_MIN_TIME_US);
if (s_config.sleep_duration > vddsdio_pd_sleep_duration)
{
pd_flags |= RTC_SLEEP_PD_VDDSDIO;
s_config.sleep_time_adjustment += flash_enable_time_us;
}
#endif
esp32_get_vddsdio_config(&vddsdio_config);
/* Enter sleep, then wait for flash to be ready on wakeup */
ret = esp32_light_sleep_inner(pd_flags, flash_enable_time_us,
vddsdio_config);
if (sleep_time != NULL)
{
*sleep_time = esp32_rtc_time_slowclk_to_us(esp32_rtc_time_get() -
s_config.rtc_ticks_at_sleep_start, esp32_clk_slowclk_cal_get());
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: esp32_pminit
*
* Description:
* Initialize force sleep parameters.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void esp32_pminit(void)
{
/* Initialize RTC parameters */
esp32_rtc_init();
esp32_rtc_clk_set();
}
/****************************************************************************
* Name: esp32_pmstandby
*
* Description:
* Enter force sleep.
*
* Input Parameters:
* time_in_us - force sleep time interval
*
* Returned Value:
* None
*
****************************************************************************/
void esp32_pmstandby(uint64_t time_in_us)
{
uint64_t rtc_diff_us;
#ifdef CONFIG_ESP32_RT_TIMER
uint64_t hw_start_us;
uint64_t hw_end_us;
uint64_t hw_diff_us;
#endif
/* don't power down XTAL - powering it up takes different time on. */
esp32_sleep_enable_timer_wakeup(time_in_us);
#ifdef CONFIG_ESP32_RT_TIMER
/* Get rt-timer timestamp before entering sleep */
hw_start_us = rt_timer_time_us();
#endif
esp32_light_sleep_start(&rtc_diff_us);
#ifdef CONFIG_ESP32_RT_TIMER
/* Get rt-timer timestamp after waking up from sleep */
hw_end_us = rt_timer_time_us();
hw_diff_us = hw_end_us - hw_start_us;
DEBUGASSERT(rtc_diff_us > hw_diff_us);
rt_timer_calibration(rtc_diff_us - hw_diff_us);
#endif
#ifdef CONFIG_SCHED_TICKLESS
up_step_idletime((uint32_t)time_in_us);
#endif
pwrinfo("Returned from auto-sleep, slept for %" PRIu32 " ms\n",
(uint32_t)(rtc_diff_us) / 1000);
}
/****************************************************************************
* 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)
{
esp32_sleep_enable_timer_wakeup(time_in_us);
esp32_deep_sleep_start();
}
/****************************************************************************
* Name: esp32_pm_lockacquire
*
* Description:
* Take a power management lock
*
****************************************************************************/
void IRAM_ATTR esp32_pm_lockacquire(void)
{
++pm_wakelock;
}
/****************************************************************************
* Name: esp32_pm_lockrelease
*
* Description:
* Release the lock taken using esp32_pm_lockacquire.
*
****************************************************************************/
void IRAM_ATTR esp32_pm_lockrelease(void)
{
--pm_wakelock;
}
/****************************************************************************
* Name: esp32_pm_lockstatus
*
* Description:
* Return power management lock status.
*
****************************************************************************/
uint32_t IRAM_ATTR esp32_pm_lockstatus(void)
{
return pm_wakelock;
}
#endif /* CONFIG_PM */