1197 lines
33 KiB
C
1197 lines
33 KiB
C
/****************************************************************************
|
|
* arch/risc-v/src/esp32c3/esp32c3_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/arch.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/power/pm.h>
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "chip.h"
|
|
#include "riscv_arch.h"
|
|
|
|
#include "hardware/esp32c3_rtccntl.h"
|
|
#include "hardware/esp32c3_system.h"
|
|
#include "hardware/esp32c3_syscon.h"
|
|
#include "hardware/esp32c3_soc.h"
|
|
#include "hardware/esp32c3_uart.h"
|
|
#include "hardware/esp32c3_gpio.h"
|
|
#include "hardware/apb_ctrl_reg.h"
|
|
|
|
#include "esp32c3_attr.h"
|
|
#include "esp32c3_rtc.h"
|
|
#include "esp32c3_clockconfig.h"
|
|
#include "esp32c3_pm.h"
|
|
|
|
#ifdef CONFIG_ESP32C3_RT_TIMER
|
|
#include "esp32c3_rt_timer.h"
|
|
#endif
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
#include "esp32c3_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
|
|
|
|
#define RTC_MODULE_SLEEP_PREPARE_CYCLES (6)
|
|
|
|
#ifndef MAX
|
|
#define MAX(a,b) a > b ? a : b
|
|
#endif
|
|
|
|
/* 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 (37)
|
|
|
|
#ifdef CONFIG_ESP32C3_CPU_FREQ_MHZ
|
|
#define DEFAULT_CPU_FREQ_MHZ CONFIG_ESP32C3_CPU_FREQ_MHZ
|
|
#else
|
|
#define DEFAULT_CPU_FREQ_MHZ (160)
|
|
#endif
|
|
|
|
#define DEFAULT_SLEEP_OUT_OVERHEAD_US (105)
|
|
#define DEEP_SLEEP_TIME_OVERHEAD_US (250 + 100 * 240 / DEFAULT_CPU_FREQ_MHZ)
|
|
|
|
#define DEEP_SLEEP_WAKEUP_DELAY 0
|
|
|
|
#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_GPIO_TRIG_EN BIT(2) /* GPIO wakeup */
|
|
#define RTC_TIMER_TRIG_EN BIT(3) /* Timer wakeup */
|
|
#define RTC_WIFI_TRIG_EN BIT(5) /* Wi-Fi 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_BT_TRIG_EN BIT(10) /* BT wakeup (light sleep only) */
|
|
#define RTC_XTAL32K_DEAD_TRIG_EN BIT(12)
|
|
#define RTC_USB_TRIG_EN BIT(14)
|
|
#define RTC_BROWNOUT_DET_TRIG_EN BIT(16)
|
|
|
|
#define PERIPH_INFORM_OUT_SLEEP_OVERHEAD_NO (1)
|
|
#define PERIPH_SKIP_SLEEP_NO (1)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* Power down options */
|
|
|
|
enum esp32c3_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 esp32c3_sleep_pd_domain_e
|
|
{
|
|
ESP_PD_DOMAIN_RTC_PERIPH = 0, /* RTC IO, sensors */
|
|
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_CPU, /* CPU core */
|
|
ESP_PD_DOMAIN_MAX /* Number of domains */
|
|
};
|
|
|
|
/* Internal structure which holds all requested deep sleep parameters. */
|
|
|
|
struct esp32c3_sleep_config_s
|
|
{
|
|
enum esp32c3_sleep_pd_option_e pd_options[ESP_PD_DOMAIN_MAX];
|
|
uint64_t sleep_duration;
|
|
uint32_t wakeup_triggers : 15;
|
|
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 gpio_wakeup_mask : 6;
|
|
uint32_t gpio_trigger_mode : 6;
|
|
uint32_t sleep_time_adjustment;
|
|
uint32_t ccount_ticks_record;
|
|
uint32_t sleep_time_overhead_out;
|
|
uint32_t rtc_clk_cal_period;
|
|
uint64_t rtc_ticks_at_sleep_start;
|
|
void *cpu_pd_mem;
|
|
};
|
|
|
|
/* Structure describing vddsdio configuration. */
|
|
|
|
struct esp32c3_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 */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static inline void IRAM_ATTR esp32c3_uart_tx_wait_idle(uint8_t uart_no);
|
|
static void IRAM_ATTR esp32c3_flush_uarts(void);
|
|
static void IRAM_ATTR esp32c3_suspend_uarts(void);
|
|
static void IRAM_ATTR esp32c3_resume_uarts(void);
|
|
static void IRAM_ATTR esp32c3_timer_wakeup_prepare(void);
|
|
static uint32_t IRAM_ATTR esp32c3_get_power_down_flags(void);
|
|
static void IRAM_ATTR esp32c3_set_vddsdio_config(
|
|
struct esp32c3_rtc_vddsdio_config_s config);
|
|
static int IRAM_ATTR esp32c3_get_vddsdio_config(
|
|
struct esp32c3_rtc_vddsdio_config_s *config);
|
|
static int IRAM_ATTR esp32c3_light_sleep_inner(uint32_t pd_flags,
|
|
uint32_t time_us, struct esp32c3_rtc_vddsdio_config_s config);
|
|
static int IRAM_ATTR esp32c3_sleep_start(uint32_t pd_flags);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct esp32c3_sleep_config_s s_config =
|
|
{
|
|
.pd_options =
|
|
{ ESP_PD_OPTION_AUTO, ESP_PD_OPTION_AUTO,
|
|
ESP_PD_OPTION_AUTO, ESP_PD_OPTION_AUTO,
|
|
ESP_PD_OPTION_AUTO
|
|
},
|
|
.ccount_ticks_record = 0,
|
|
.sleep_time_overhead_out = DEFAULT_SLEEP_OUT_OVERHEAD_US,
|
|
.wakeup_triggers = 0
|
|
};
|
|
|
|
static _Atomic uint32_t pm_wakelock = 0;
|
|
|
|
/* Inform peripherals of light sleep wakeup overhead time */
|
|
|
|
inform_out_sleep_overhead_cb_t
|
|
g_periph_inform_out_sleep_overhead_cb[PERIPH_INFORM_OUT_SLEEP_OVERHEAD_NO];
|
|
|
|
/* Indicates if light sleep shoule be skipped by peripherals. */
|
|
|
|
skip_light_sleep_cb_t g_periph_skip_sleep_cb[PERIPH_SKIP_SLEEP_NO];
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/* Set the real CPU ticks per us to the ets,
|
|
* so that ets_delay_us will be accurate.
|
|
*/
|
|
|
|
extern void ets_update_cpu_frequency(uint32_t ticks_per_us);
|
|
|
|
/* Pauses execution for us microseconds. */
|
|
|
|
extern void esp_rom_delay_us(uint32_t us);
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_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 IRAM_ATTR esp32c3_uart_tx_wait_idle(uint8_t uart_no)
|
|
{
|
|
uint32_t status;
|
|
do
|
|
{
|
|
status = getreg32(UART_STATUS_REG(uart_no));
|
|
}
|
|
while ((status & (UART_ST_UTX_OUT_M | UART_TXFIFO_CNT_M)) != 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_flush_uarts
|
|
*
|
|
* Description:
|
|
* Wait until UART0/UART1 tx full empty and the last char send ok
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void IRAM_ATTR esp32c3_flush_uarts(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ESP32C3_NUARTS; ++i)
|
|
{
|
|
if (esp32c3_periph_ll_periph_enabled(PERIPH_UART0_MODULE + i))
|
|
{
|
|
esp32c3_uart_tx_wait_idle(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_suspend_uarts
|
|
*
|
|
* Description:
|
|
* Suspend UART0/UART1 output
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void IRAM_ATTR esp32c3_suspend_uarts(void)
|
|
{
|
|
int i;
|
|
uint32_t uart_fsm = 0;
|
|
|
|
for (i = 0; i < ESP32C3_NUARTS; ++i)
|
|
{
|
|
if (!esp32c3_periph_ll_periph_enabled(PERIPH_UART0_MODULE + i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
modifyreg32(UART_FLOW_CONF_REG(i), 0, UART_FORCE_XOFF);
|
|
do
|
|
{
|
|
uart_fsm = REG_GET_FIELD(UART_STATUS_REG(i), UART_ST_UTX_OUT);
|
|
}
|
|
while (!(uart_fsm == UART_FSM_IDLE ||
|
|
uart_fsm == UART_FSM_TX_WAIT_SEND));
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_resume_uarts
|
|
*
|
|
* Description:
|
|
* Re-enable UART0/UART1/UART2 output
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void IRAM_ATTR esp32c3_resume_uarts(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ESP32C3_NUARTS; ++i)
|
|
{
|
|
if (!esp32c3_periph_ll_periph_enabled(PERIPH_UART0_MODULE + i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
REG_CLR_BIT(UART_FLOW_CONF_REG(i), UART_FORCE_XOFF);
|
|
REG_SET_BIT(UART_FLOW_CONF_REG(i), UART_FORCE_XON);
|
|
REG_CLR_BIT(UART_FLOW_CONF_REG(i),
|
|
UART_SW_FLOW_CON_EN | UART_FORCE_XON);
|
|
REG_SET_BIT(UART_ID_REG(i), UART_UPDATE);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_get_power_down_flags
|
|
*
|
|
* Description:
|
|
* Get power domains that can be powered down
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* Power domains
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t IRAM_ATTR esp32c3_get_power_down_flags(void)
|
|
{
|
|
uint32_t pd_flags = 0;
|
|
|
|
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_GPIO_TRIG_EN)
|
|
{
|
|
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] =
|
|
ESP_PD_OPTION_ON;
|
|
}
|
|
else
|
|
{
|
|
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] =
|
|
ESP_PD_OPTION_OFF;
|
|
}
|
|
}
|
|
|
|
if (s_config.cpu_pd_mem == NULL)
|
|
{
|
|
s_config.pd_options[ESP_PD_DOMAIN_CPU] = ESP_PD_OPTION_ON;
|
|
}
|
|
|
|
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_PERIPH] != ESP_PD_OPTION_ON)
|
|
{
|
|
pd_flags |= RTC_SLEEP_PD_RTC_PERIPH;
|
|
}
|
|
|
|
if (s_config.pd_options[ESP_PD_DOMAIN_CPU] != ESP_PD_OPTION_ON)
|
|
{
|
|
pd_flags |= RTC_SLEEP_PD_CPU;
|
|
}
|
|
|
|
return pd_flags;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_timer_wakeup_prepare
|
|
*
|
|
* Description:
|
|
* Configure timer to wake-up
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void IRAM_ATTR esp32c3_timer_wakeup_prepare(void)
|
|
{
|
|
int64_t ticks;
|
|
int64_t sleep_duration = (int64_t)s_config.sleep_duration -
|
|
(int64_t) s_config.sleep_time_adjustment;
|
|
if (sleep_duration < 0)
|
|
{
|
|
sleep_duration = 0;
|
|
}
|
|
|
|
ticks = esp32c3_rtc_time_us_to_slowclk(sleep_duration,
|
|
s_config.rtc_clk_cal_period);
|
|
esp32c3_rtc_sleep_set_wakeup_time(s_config.rtc_ticks_at_sleep_start
|
|
+ ticks);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_set_vddsdio_config
|
|
*
|
|
* Description:
|
|
* Set new VDDSDIO configuration using RTC registers.
|
|
*
|
|
* Input Parameters:
|
|
* New VDDSDIO configuration
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void IRAM_ATTR esp32c3_set_vddsdio_config(
|
|
struct esp32c3_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: esp32c3_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 esp32c3_get_vddsdio_config(
|
|
struct esp32c3_rtc_vddsdio_config_s *config)
|
|
{
|
|
struct esp32c3_rtc_vddsdio_config_s *result = config;
|
|
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;
|
|
}
|
|
|
|
/* 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: esp32c3_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 esp32c3_sleep_start(uint32_t pd_flags)
|
|
{
|
|
int result;
|
|
struct esp32c3_cpu_freq_config_s cpu_freq_config;
|
|
bool deep_sleep = pd_flags & RTC_SLEEP_PD_DIG;
|
|
|
|
/* 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 (deep_sleep)
|
|
{
|
|
esp32c3_flush_uarts();
|
|
}
|
|
else
|
|
{
|
|
esp32c3_suspend_uarts();
|
|
}
|
|
|
|
/* Save current frequency and switch to XTAL */
|
|
|
|
esp32c3_rtc_clk_cpu_freq_get_config(&cpu_freq_config);
|
|
esp32c3_rtc_cpu_freq_set_xtal();
|
|
|
|
/* Enter sleep */
|
|
|
|
esp32c3_rtc_sleep_init(pd_flags);
|
|
|
|
esp32c3_rtc_sleep_low_init(s_config.rtc_clk_cal_period);
|
|
|
|
/* Set state machine time for light sleep */
|
|
|
|
if (deep_sleep == false)
|
|
{
|
|
esp32c3_rtc_sleep_low_init(s_config.rtc_clk_cal_period);
|
|
}
|
|
|
|
/* Configure timer wakeup */
|
|
|
|
if ((s_config.wakeup_triggers & RTC_TIMER_TRIG_EN)
|
|
&& s_config.sleep_duration > 0)
|
|
{
|
|
esp32c3_timer_wakeup_prepare();
|
|
}
|
|
|
|
if (deep_sleep)
|
|
{
|
|
/* Otherwise, need to call the dedicated soc function for this */
|
|
|
|
result = esp32c3_rtc_deep_sleep_start(s_config.wakeup_triggers, 0);
|
|
}
|
|
else
|
|
{
|
|
result = esp32c3_rtc_sleep_start(s_config.wakeup_triggers, 0, 1);
|
|
}
|
|
|
|
/* Restore CPU frequency */
|
|
|
|
esp32c3_rtc_clk_cpu_freq_set_config(&cpu_freq_config);
|
|
|
|
if (!deep_sleep)
|
|
{
|
|
s_config.ccount_ticks_record = esp32c3_cpu_cycle_count();
|
|
}
|
|
|
|
REG_CLR_BIT(RTC_CNTL_RETENTION_CTRL_REG, RTC_CNTL_RETENTION_EN);
|
|
|
|
/* Re-enable UART output */
|
|
|
|
esp32c3_resume_uarts();
|
|
|
|
return result;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_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
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int IRAM_ATTR esp32c3_light_sleep_inner(uint32_t pd_flags,
|
|
uint32_t time_us, struct esp32c3_rtc_vddsdio_config_s config)
|
|
{
|
|
/* Enter sleep */
|
|
|
|
int err = esp32c3_sleep_start(pd_flags);
|
|
|
|
/* If VDDSDIO regulator was controlled by RTC registers before sleep.
|
|
* restore the configuration.
|
|
*/
|
|
|
|
if (config.force)
|
|
{
|
|
esp32c3_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. */
|
|
|
|
esp_rom_delay_us(time_us);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_periph_should_skip_sleep
|
|
*
|
|
* Description:
|
|
* Indicates if light sleep shoule be skipped by peripherals
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* True is returned on success. Otherwise false.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline bool IRAM_ATTR esp32c3_periph_should_skip_sleep(void)
|
|
{
|
|
for (int i = 0; i < PERIPH_SKIP_SLEEP_NO; i++)
|
|
{
|
|
if (g_periph_skip_sleep_cb[i])
|
|
{
|
|
if (g_periph_skip_sleep_cb[i]() == true)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_register_skip_sleep_callback
|
|
*
|
|
* Description:
|
|
* Unregister callback function of skipping light sleep.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success. Otherwise -1 (ERROR).
|
|
*
|
|
****************************************************************************/
|
|
|
|
int esp32c3_pm_register_skip_sleep_callback(skip_light_sleep_cb_t cb)
|
|
{
|
|
for (int i = 0; i < PERIPH_SKIP_SLEEP_NO; i++)
|
|
{
|
|
if (g_periph_skip_sleep_cb[i] == cb)
|
|
{
|
|
return OK;
|
|
}
|
|
else if (g_periph_skip_sleep_cb[i] == NULL)
|
|
{
|
|
g_periph_skip_sleep_cb[i] = cb;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_unregister_skip_sleep_callback
|
|
*
|
|
* Description:
|
|
* Register callback function of skipping light sleep.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success. Otherwise -1 (ERROR).
|
|
*
|
|
****************************************************************************/
|
|
|
|
int esp32c3_pm_unregister_skip_sleep_callback(skip_light_sleep_cb_t cb)
|
|
{
|
|
for (int i = 0; i < PERIPH_SKIP_SLEEP_NO; i++)
|
|
{
|
|
if (g_periph_skip_sleep_cb[i] == cb)
|
|
{
|
|
g_periph_skip_sleep_cb[i] = NULL;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_should_skip_light_sleep
|
|
*
|
|
* Description:
|
|
* Indicates if light sleep shoule be skipped.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* True is returned on success. Otherwise false.
|
|
*
|
|
****************************************************************************/
|
|
|
|
bool IRAM_ATTR esp32c3_should_skip_light_sleep(void)
|
|
{
|
|
if (esp32c3_periph_should_skip_sleep() == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_register_inform_out_sleep_overhead_callback
|
|
*
|
|
* Description:
|
|
* Register informing peripherals of light sleep wakeup overhead time
|
|
* callback function.
|
|
*
|
|
* Input Parameters:
|
|
* cb - callback function
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success. Otherwise -1 (ERROR).
|
|
*
|
|
****************************************************************************/
|
|
|
|
int esp32c3_pm_register_inform_out_sleep_overhead_callback(
|
|
inform_out_sleep_overhead_cb_t cb)
|
|
{
|
|
for (int i = 0; i < PERIPH_INFORM_OUT_SLEEP_OVERHEAD_NO; i++)
|
|
{
|
|
if (g_periph_inform_out_sleep_overhead_cb[i] == cb)
|
|
{
|
|
return ERROR;
|
|
}
|
|
else if (g_periph_inform_out_sleep_overhead_cb[i] == NULL)
|
|
{
|
|
g_periph_inform_out_sleep_overhead_cb[i] = cb;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_unregister_inform_out_sleep_overhead_callback
|
|
*
|
|
* Description:
|
|
* Unregister informing peripherals of light sleep wakeup overhead time
|
|
* callback function.
|
|
*
|
|
* Input Parameters:
|
|
* cb - callback function
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
int esp32c3_pm_unregister_inform_out_sleep_overhead_callback(
|
|
inform_out_sleep_overhead_cb_t cb)
|
|
{
|
|
for (int i = 0; i < PERIPH_INFORM_OUT_SLEEP_OVERHEAD_NO; i++)
|
|
{
|
|
if (g_periph_inform_out_sleep_overhead_cb[i] == cb)
|
|
{
|
|
g_periph_inform_out_sleep_overhead_cb[i] = NULL;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_periph_inform_out_sleep_overhead
|
|
*
|
|
* Description:
|
|
* Inform peripherals of light sleep wakeup overhead time
|
|
*
|
|
* Input Parameters:
|
|
* us - light sleep wakeup overhead time
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void IRAM_ATTR esp32c3_periph_inform_out_sleep_overhead(uint32_t us)
|
|
{
|
|
for (int i = 0; i < PERIPH_INFORM_OUT_SLEEP_OVERHEAD_NO; i++)
|
|
{
|
|
if (g_periph_inform_out_sleep_overhead_cb[i])
|
|
{
|
|
g_periph_inform_out_sleep_overhead_cb[i](us);
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_sleep_enable_rtc_timer_wakeup
|
|
*
|
|
* Description:
|
|
* Configure RTC TIMER wake-up interval
|
|
*
|
|
* Input Parameters:
|
|
* time_in_us - Configure wake-up time interval
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void IRAM_ATTR esp32c3_sleep_enable_rtc_timer_wakeup(uint64_t time_in_us)
|
|
{
|
|
s_config.wakeup_triggers |= RTC_TIMER_TRIG_EN;
|
|
s_config.sleep_duration = time_in_us;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_sleep_enable_wifi_wakeup
|
|
*
|
|
* Description:
|
|
* Configure Wi-Fi wake-up source
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void esp32c3_sleep_enable_wifi_wakeup(void)
|
|
{
|
|
s_config.wakeup_triggers |= RTC_WIFI_TRIG_EN;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_light_sleep_start
|
|
*
|
|
* Description:
|
|
* Enter light sleep mode
|
|
*
|
|
* Input Parameters:
|
|
* sleep_time - Actual sleep time
|
|
*
|
|
* Returned Value:
|
|
* 0 is returned on success or a negated errno value is returned
|
|
*
|
|
****************************************************************************/
|
|
|
|
int IRAM_ATTR esp32c3_light_sleep_start(uint64_t *sleep_time)
|
|
{
|
|
int ret = OK;
|
|
irqstate_t flags;
|
|
uint32_t pd_flags;
|
|
uint32_t flash_enable_time_us;
|
|
uint32_t vddsdio_pd_sleep_duration;
|
|
struct esp32c3_rtc_vddsdio_config_s vddsdio_config;
|
|
uint32_t rtc_cntl_xtl_buf_wait_cycles;
|
|
uint32_t sleep_time_overhead_in;
|
|
uint32_t ccount_at_sleep_start;
|
|
|
|
flags = enter_critical_section();
|
|
|
|
s_config.ccount_ticks_record = esp32c3_cpu_cycle_count();
|
|
s_config.rtc_ticks_at_sleep_start = esp32c3_rtc_time_get();
|
|
ccount_at_sleep_start = esp32c3_cpu_cycle_count();
|
|
sleep_time_overhead_in = (ccount_at_sleep_start -
|
|
s_config.ccount_ticks_record) / (esp32c3_clk_cpu_freq() / 1000000ULL);
|
|
|
|
/* Decide which power domains can be powered down */
|
|
|
|
pd_flags = esp32c3_get_power_down_flags();
|
|
|
|
s_config.rtc_clk_cal_period =
|
|
esp32c3_rtc_clk_cal(RTC_CAL_RTC_MUX, RTC_CLK_SRC_CAL_CYCLES);
|
|
|
|
/* Adjustment time consists of parts below:
|
|
* 1. Hardware time waiting for internal 8M oscilate clock and XTAL;
|
|
* 2. Hardware state swithing time of the rtc main state machine;
|
|
* 3. Code execution time when clock is not stable;
|
|
* 4. Code execution time which can be measured;
|
|
*/
|
|
|
|
rtc_cntl_xtl_buf_wait_cycles = esp32c3_rtc_time_us_to_slowclk(
|
|
RTC_CNTL_XTL_BUF_WAIT_SLP_US, s_config.rtc_clk_cal_period);
|
|
|
|
s_config.sleep_time_adjustment = LIGHT_SLEEP_TIME_OVERHEAD_US +
|
|
sleep_time_overhead_in + s_config.sleep_time_overhead_out
|
|
+ esp32c3_rtc_time_slowclk_to_us(rtc_cntl_xtl_buf_wait_cycles +
|
|
RTC_CNTL_CK8M_WAIT_SLP_CYCLES + RTC_CNTL_WAKEUP_DELAY_CYCLES,
|
|
s_config.rtc_clk_cal_period);
|
|
|
|
/* 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
|
|
+ DEEP_SLEEP_WAKEUP_DELAY;
|
|
|
|
/* When SPIRAM is disabled in menuconfig, the minimum sleep time of the
|
|
* system needs to meet the sum below:
|
|
* 1. Wait time for the flash power-on after waking up;
|
|
* 2. The execution time of codes between RTC Timer get start time
|
|
* with hardware starts to switch state to sleep;
|
|
* 3. The hardware state switching time of the rtc state machine during
|
|
* sleep and wake-up. This process requires 6 cycles to complete.
|
|
* The specific hardware state switching process and the cycles
|
|
* consumed are rtc_cpu_run_stall(1), cut_pll_rtl(2), cut_8m(1),
|
|
* min_protect(2);
|
|
* 4. All the adjustment time which is
|
|
* s_config.sleep_time_adjustment below.
|
|
*/
|
|
|
|
vddsdio_pd_sleep_duration = MAX(FLASH_PD_MIN_SLEEP_TIME_US,
|
|
flash_enable_time_us + LIGHT_SLEEP_MIN_TIME_US +
|
|
s_config.sleep_time_adjustment + esp32c3_rtc_time_slowclk_to_us(
|
|
RTC_MODULE_SLEEP_PREPARE_CYCLES, s_config.rtc_clk_cal_period));
|
|
|
|
if (s_config.sleep_duration > vddsdio_pd_sleep_duration)
|
|
{
|
|
pd_flags |= RTC_SLEEP_PD_VDDSDIO;
|
|
if (s_config.sleep_time_overhead_out < flash_enable_time_us)
|
|
{
|
|
s_config.sleep_time_adjustment += flash_enable_time_us;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (s_config.sleep_time_overhead_out > flash_enable_time_us)
|
|
{
|
|
s_config.sleep_time_adjustment -= flash_enable_time_us;
|
|
}
|
|
}
|
|
|
|
esp32c3_periph_inform_out_sleep_overhead(
|
|
s_config.sleep_time_adjustment - sleep_time_overhead_in);
|
|
|
|
esp32c3_get_vddsdio_config(&vddsdio_config);
|
|
|
|
/* Enter sleep, then wait for flash to be ready on wakeup */
|
|
|
|
ret = esp32c3_light_sleep_inner(pd_flags, flash_enable_time_us,
|
|
vddsdio_config);
|
|
|
|
if (sleep_time != NULL)
|
|
{
|
|
*sleep_time = esp32c3_rtc_time_slowclk_to_us(esp32c3_rtc_time_get() -
|
|
s_config.rtc_ticks_at_sleep_start, s_config.rtc_clk_cal_period);
|
|
}
|
|
|
|
s_config.sleep_time_overhead_out = (esp32c3_cpu_cycle_count() -
|
|
s_config.ccount_ticks_record) / (esp32c3_clk_cpu_freq() / 1000000ULL);
|
|
|
|
leave_critical_section(flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pmstandby
|
|
*
|
|
* Description:
|
|
* Enter force sleep.
|
|
*
|
|
* Input Parameters:
|
|
* time_in_us - force sleep time interval
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void esp32c3_pmstandby(uint64_t time_in_us)
|
|
{
|
|
uint64_t rtc_diff_us;
|
|
#ifdef CONFIG_ESP32C3_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. */
|
|
|
|
esp32c3_sleep_enable_rtc_timer_wakeup(time_in_us);
|
|
#ifdef CONFIG_ESP32C3_RT_TIMER
|
|
/* Get rt-timer timestamp before entering sleep */
|
|
|
|
hw_start_us = rt_timer_time_us();
|
|
#endif
|
|
|
|
esp32c3_light_sleep_start(&rtc_diff_us);
|
|
|
|
#ifdef CONFIG_ESP32C3_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: esp32c3_deep_sleep_start
|
|
*
|
|
* Description:
|
|
* Enter deep sleep mode
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void IRAM_ATTR esp32c3_deep_sleep_start(void)
|
|
{
|
|
uint32_t pd_flags;
|
|
|
|
/* record current RTC time */
|
|
|
|
s_config.rtc_ticks_at_sleep_start = esp32c3_rtc_time_get();
|
|
|
|
/* Decide which power domains can be powered down */
|
|
|
|
pd_flags = esp32c3_get_power_down_flags();
|
|
s_config.rtc_clk_cal_period = getreg32(RTC_SLOW_CLK_CAL_REG);
|
|
|
|
/* Correct the sleep time */
|
|
|
|
s_config.sleep_time_adjustment = DEEP_SLEEP_TIME_OVERHEAD_US;
|
|
|
|
/* Enter deep sleep */
|
|
|
|
esp32c3_sleep_start(RTC_SLEEP_PD_DIG | RTC_SLEEP_PD_VDDSDIO | 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: esp32c3_pmsleep
|
|
*
|
|
* Description:
|
|
* Enter deep sleep.
|
|
*
|
|
* Input Parameters:
|
|
* time_in_us - deep sleep time interval
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void esp32c3_pmsleep(uint64_t time_in_us)
|
|
{
|
|
esp32c3_sleep_enable_rtc_timer_wakeup(time_in_us);
|
|
esp32c3_deep_sleep_start();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_lockacquire
|
|
*
|
|
* Description:
|
|
* Take a power management lock
|
|
*
|
|
****************************************************************************/
|
|
|
|
void IRAM_ATTR esp32c3_pm_lockacquire(void)
|
|
{
|
|
++pm_wakelock;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_lockrelease
|
|
*
|
|
* Description:
|
|
* Release the lock taken using esp32c3_pm_lockacquire.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void IRAM_ATTR esp32c3_pm_lockrelease(void)
|
|
{
|
|
--pm_wakelock;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: esp32c3_pm_lockstatus
|
|
*
|
|
* Description:
|
|
* Return power management lock status.
|
|
*
|
|
****************************************************************************/
|
|
|
|
uint32_t IRAM_ATTR esp32c3_pm_lockstatus(void)
|
|
{
|
|
return pm_wakelock;
|
|
}
|
|
|
|
#endif /* CONFIG_PM */ |