Merge branch 'feat/driver_twai_sleep_retention_support' into 'master'

feat(driver_twai): new driver sleep retention supports

Closes IDF-13001

See merge request espressif/esp-idf!45418
This commit is contained in:
Wan Lei
2026-02-27 14:29:06 +08:00
17 changed files with 478 additions and 18 deletions
+45 -5
View File
@@ -286,6 +286,16 @@ static void _node_destroy(twai_onchip_ctx_t *twai_ctx)
if (twai_ctx->pm_lock) { if (twai_ctx->pm_lock) {
esp_pm_lock_delete(twai_ctx->pm_lock); esp_pm_lock_delete(twai_ctx->pm_lock);
} }
#endif
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
const sleep_retention_module_t retention_id = twai_reg_retention_info[twai_ctx->ctrlr_id].module_id;
if (sleep_retention_is_module_created(retention_id)) {
assert(sleep_retention_is_module_inited(retention_id));
sleep_retention_module_free(retention_id);
}
if (sleep_retention_is_module_inited(retention_id)) {
sleep_retention_module_deinit(retention_id);
}
#endif #endif
if (twai_ctx->intr_hdl) { if (twai_ctx->intr_hdl) {
esp_intr_free(twai_ctx->intr_hdl); esp_intr_free(twai_ctx->intr_hdl);
@@ -632,13 +642,25 @@ static esp_err_t _node_parse_rx(twai_node_handle_t node, twai_frame_t *rx_frame)
return ESP_OK; return ESP_OK;
} }
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
static esp_err_t _node_create_sleep_retention_cb(void *obj)
{
twai_onchip_ctx_t *twai_ctx = (twai_onchip_ctx_t *)obj;
return sleep_retention_entries_create(twai_reg_retention_info[twai_ctx->ctrlr_id].entry_array,
twai_reg_retention_info[twai_ctx->ctrlr_id].array_size,
REGDMA_LINK_PRI_TWAI,
twai_reg_retention_info[twai_ctx->ctrlr_id].module_id);
}
#endif
/* --------------------------------- Public --------------------------------- */ /* --------------------------------- Public --------------------------------- */
esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twai_node_handle_t *node_ret) esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twai_node_handle_t *node_ret)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE((node_config->tx_queue_depth > 0) || node_config->flags.enable_listen_only, ESP_ERR_INVALID_ARG, TAG, "tx_queue_depth at least 1"); ESP_RETURN_ON_FALSE((node_config->tx_queue_depth > 0) || node_config->flags.enable_listen_only, ESP_ERR_INVALID_ARG, TAG, "tx_queue_depth at least 1");
ESP_RETURN_ON_FALSE(!node_config->intr_priority || (BIT(node_config->intr_priority) & ESP_INTR_FLAG_LOWMED), ESP_ERR_INVALID_ARG, TAG, "Invalid intr_priority level"); ESP_RETURN_ON_FALSE(!node_config->intr_priority || (BIT(node_config->intr_priority) & ESP_INTR_FLAG_LOWMED), ESP_ERR_INVALID_ARG, TAG, "Invalid intr_priority level");
#if !SOC_TWAI_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(!node_config->flags.sleep_allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "sleep retention is not supported on this target");
#endif
// Allocate TWAI node from internal memory because it contains atomic variable // Allocate TWAI node from internal memory because it contains atomic variable
twai_onchip_ctx_t *node = heap_caps_calloc(1, sizeof(twai_onchip_ctx_t) + twai_hal_get_mem_requirment(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); twai_onchip_ctx_t *node = heap_caps_calloc(1, sizeof(twai_onchip_ctx_t) + twai_hal_get_mem_requirment(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(node, ESP_ERR_NO_MEM, TAG, "No mem"); ESP_RETURN_ON_FALSE(node, ESP_ERR_NO_MEM, TAG, "No mem");
@@ -679,6 +701,25 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
#endif #endif
node->timestamp_freq_hz = node_config->timestamp_resolution_hz; node->timestamp_freq_hz = node_config->timestamp_resolution_hz;
} }
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
const sleep_retention_module_t retention_id = twai_reg_retention_info[node->ctrlr_id].module_id;
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
.handle = _node_create_sleep_retention_cb,
.arg = node,
},
},
.depends = RETENTION_MODULE_BITMAP_INIT(CLOCK_SYSTEM)
};
if (sleep_retention_module_init(retention_id, &init_param) == ESP_OK) {
if ((node_config->flags.sleep_allow_pd) && (sleep_retention_module_allocate(retention_id) != ESP_OK)) {
ESP_LOGW(TAG, "sleep retention prepare failed, power will hold on");
}
} else {
ESP_LOGW(TAG, "sleep retention init failed, twai may offline after sleep");
}
#endif // SOC_TWAI_SUPPORT_SLEEP_RETENTION
_lock_release(&s_platform.intr_mutex); _lock_release(&s_platform.intr_mutex);
#if CONFIG_PM_ENABLE #if CONFIG_PM_ENABLE
@@ -732,10 +773,9 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
*node_ret = &node->api_base; *node_ret = &node->api_base;
return ESP_OK; return ESP_OK;
err: err:
if (node) { _lock_release(&s_platform.intr_mutex);
_lock_release(&s_platform.intr_mutex); _node_destroy(node);
_node_destroy(node);
}
return ret; return ret;
} }
@@ -35,6 +35,7 @@ typedef struct {
uint32_t enable_loopback: 1; /**< The TWAI controller receives back frames that it sends out, but does not acknowledge them */ uint32_t enable_loopback: 1; /**< The TWAI controller receives back frames that it sends out, but does not acknowledge them */
uint32_t enable_listen_only: 1; /**< No transmissions or acknowledgements. The controller only monitors the bus without participating */ uint32_t enable_listen_only: 1; /**< No transmissions or acknowledgements. The controller only monitors the bus without participating */
uint32_t no_receive_rtr: 1; /**< Don't receive remote frames */ uint32_t no_receive_rtr: 1; /**< Don't receive remote frames */
uint32_t sleep_allow_pd: 1; /**< Allow power down during sleep to save power, driver will backup/restore the TWAI registers to guarantee the peripheral features. */
} flags; /**< Misc configuration flags */ } flags; /**< Misc configuration flags */
} twai_onchip_node_config_t; } twai_onchip_node_config_t;
@@ -2,6 +2,9 @@ set(srcs "test_app_main.c")
if(CONFIG_SOC_TWAI_SUPPORTED) if(CONFIG_SOC_TWAI_SUPPORTED)
list(APPEND srcs "test_twai_common.cpp" "test_twai_network.cpp") list(APPEND srcs "test_twai_common.cpp" "test_twai_network.cpp")
if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED)
list(APPEND srcs "test_twai_sleep.c")
endif()
endif() endif()
if(CONFIG_SOC_TWAI_FD_SUPPORTED) if(CONFIG_SOC_TWAI_FD_SUPPORTED)
@@ -10,6 +13,6 @@ endif()
idf_component_register( idf_component_register(
SRCS ${srcs} SRCS ${srcs}
PRIV_REQUIRES esp_driver_twai esp_timer esp_driver_uart esp_psram esp_driver_gpio PRIV_REQUIRES esp_driver_twai esp_timer esp_driver_uart esp_psram esp_driver_gpio esp_pm
WHOLE_ARCHIVE WHOLE_ARCHIVE
) )
@@ -19,12 +19,17 @@
#include "esp_twai.h" #include "esp_twai.h"
#include "esp_twai_onchip.h" #include "esp_twai_onchip.h"
#include "hal/twai_periph.h" #include "hal/twai_periph.h"
#include "esp_private/gpio.h"
#include "soc/gpio_sig_map.h" #include "soc/gpio_sig_map.h"
#include "esp_private/gpio.h"
#include "driver/uart.h" // for baudrate detection #include "driver/uart.h" // for baudrate detection
#if CONFIG_IDF_TARGET_ESP32H4
#define TEST_TX_GPIO GPIO_NUM_2
#define TEST_RX_GPIO GPIO_NUM_3
#else
#define TEST_TX_GPIO GPIO_NUM_4 #define TEST_TX_GPIO GPIO_NUM_4
#define TEST_RX_GPIO GPIO_NUM_5 #define TEST_RX_GPIO GPIO_NUM_5
#endif
#define TEST_TWAI_QUEUE_DEPTH 5 #define TEST_TWAI_QUEUE_DEPTH 5
#define TEST_TRANS_LEN 100 #define TEST_TRANS_LEN 100
#define TEST_FRAME_LEN 7 #define TEST_FRAME_LEN 7
@@ -0,0 +1,226 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/param.h>
#include "unity.h"
#include "test_utils.h"
#include "esp_log.h"
#include "esp_pm.h"
#include "esp_timer.h"
#include "esp_rom_sys.h"
#include "esp_private/esp_pmu.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/gpio.h"
#include "hal/twai_periph.h"
#include "esp_twai.h"
#include "esp_twai_onchip.h"
#if CONFIG_IDF_TARGET_ESP32H4
#define TEST_TX_GPIO GPIO_NUM_2
#define TEST_RX_GPIO GPIO_NUM_3
#else
#define TEST_TX_GPIO GPIO_NUM_4
#define TEST_RX_GPIO GPIO_NUM_5
#endif
#define TEST_TWAI_QUEUE_DEPTH 5
#define TEST_TRANS_LEN 100
#define TEST_FRAME_LEN 7
#define TEST_FRAME_NUM howmany(TEST_TRANS_LEN, TEST_FRAME_LEN)
static IRAM_ATTR bool test_sleep_rx_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
twai_frame_t *rx_frame = (twai_frame_t *)user_ctx;
if (ESP_OK == twai_node_receive_from_isr(handle, rx_frame)) {
ESP_EARLY_LOGI("RX", "timestamp %llu frame %x [%d] %s", rx_frame->header.timestamp, rx_frame->header.id, rx_frame->header.dlc, rx_frame->buffer);
}
return false;
}
TEST_CASE("twai sleep retention", "[twai]")
{
// Prepare a TOP PD sleep
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1 * 1000 * 1000));
#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(true));
#endif
esp_sleep_context_t sleep_ctx = {};
esp_sleep_set_sleep_context(&sleep_ctx);
twai_node_handle_t node_hdl[SOC_TWAI_CONTROLLER_NUM];
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 800000;
node_config.timestamp_resolution_hz = 1000000;
node_config.tx_queue_depth = TEST_FRAME_NUM;
node_config.flags.enable_loopback = true;
node_config.flags.enable_self_test = true;
twai_frame_t tx_frame = {};
tx_frame.buffer = (uint8_t *)"sleep";
tx_frame.buffer_len = strlen((const char *)tx_frame.buffer);
uint8_t rx_buffer[TWAI_FRAME_MAX_LEN] = {0};
twai_frame_t rx_frame = {};
rx_frame.buffer = rx_buffer;
rx_frame.buffer_len = sizeof(rx_buffer);
twai_event_callbacks_t user_cbs = {};
user_cbs.on_rx_done = test_sleep_rx_cb;
for (int power = 0; power < 2; power++) {
node_config.flags.sleep_allow_pd = power;
#if !SOC_TWAI_SUPPORT_SLEEP_RETENTION
node_config.flags.sleep_allow_pd = false;
#endif
printf("\n===> Testing sleep power %s\n", node_config.flags.sleep_allow_pd ? "DOWN" : "HOLD");
for (int node = 0; node < SOC_TWAI_CONTROLLER_NUM; node++) {
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl[node]));
TEST_ESP_OK(twai_node_register_event_callbacks(node_hdl[node], &user_cbs, &rx_frame));
}
for (int node = 0; node < SOC_TWAI_CONTROLLER_NUM; node++) {
printf("\nTWAI%d\n", node);
// fix signal connections after all nodes are created
gpio_matrix_input(TEST_TX_GPIO, twai_periph_signals[node].rx_sig, false);
gpio_matrix_output(TEST_TX_GPIO, twai_periph_signals[node].tx_sig, false, false);
// test with 2 transactions
for (int i = 1; i < 3; i++) {
printf("Go to sleep ...\n");
uint64_t time_before = esp_timer_get_time();
TEST_ESP_OK(esp_light_sleep_start());
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
printf("Waked up! sleep %d power down %ld\n", !sleep_ctx.sleep_request_result, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
// check if the peripheral is powered down as expected
TEST_ASSERT_EQUAL(node_config.flags.sleep_allow_pd ? PMU_SLEEP_PD_TOP : 0, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
#endif
TEST_ESP_OK(twai_node_enable(node_hdl[node]));
tx_frame.header.id = i + node * 0x10;
memset(&rx_frame.header, 0, sizeof(rx_frame.header));
memset(rx_buffer, 0, sizeof(rx_buffer));
// Waiting 1 second after node_enable to check timestamp correctness after wakeup
esp_rom_delay_us(1000 * 1000);
TEST_ESP_OK(twai_node_transmit(node_hdl[node], &tx_frame, 100));
TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl[node], -1));
TEST_ASSERT_EQUAL_HEX8_ARRAY(tx_frame.buffer, rx_buffer, tx_frame.buffer_len);
TEST_ASSERT_EQUAL_UINT32(tx_frame.header.id, rx_frame.header.id);
// sleep 1s, wait 1s, so diff should be 2000000us
printf("Time diff: %llu, expect 2000000\n", rx_frame.header.timestamp - time_before);
TEST_ASSERT_INT32_WITHIN(10000, 2000000, rx_frame.header.timestamp - time_before);
time_before = rx_frame.header.timestamp;
TEST_ESP_OK(twai_node_disable(node_hdl[node]));
}
}
for (int node = 0; node < SOC_TWAI_CONTROLLER_NUM; node++) {
TEST_ESP_OK(twai_node_delete(node_hdl[node]));
}
}
esp_sleep_set_sleep_context(NULL);
#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(false));
#endif
}
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION
static void test_random_trans_generator(twai_node_handle_t node_hdl, uint32_t trans_num)
{
uint8_t send_pkg_ptr[TWAI_FRAME_MAX_LEN];
twai_frame_t tx_msg = {};
tx_msg.buffer = send_pkg_ptr;
printf("Sending %ld random trans ...\n", trans_num);
for (uint32_t tx_cnt = 0; tx_cnt < trans_num; tx_cnt++) {
tx_msg.header.id = tx_cnt | 0xf000 | (tx_cnt << 16);
tx_msg.header.ide = !!(tx_cnt % 2);
tx_msg.header.rtr = !!(tx_cnt % 3);
tx_msg.buffer_len = tx_cnt % TWAI_FRAME_MAX_LEN;
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_msg, 0));
TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, 8));
}
}
static IRAM_ATTR bool test_filter_rx_done_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
uint8_t *test_ctrl = (uint8_t *)user_ctx;
uint8_t recv_pkg_ptr[TWAI_FRAME_MAX_LEN];
twai_frame_t rx_frame = {};
rx_frame.buffer = recv_pkg_ptr;
rx_frame.buffer_len = TWAI_FRAME_MAX_LEN;
if (ESP_OK == twai_node_receive_from_isr(handle, &rx_frame)) {
ESP_EARLY_LOGI("Recv", "RX id 0x%4x len %2d ext %d rmt %d", rx_frame.header.id, twaifd_dlc2len(rx_frame.header.dlc), rx_frame.header.ide, rx_frame.header.rtr);
switch (test_ctrl[0]) {
case 0: // receive something
TEST_ASSERT(rx_frame.header.id >= 0x10);
TEST_ASSERT(!rx_frame.header.ide);
break;
case 1: // receive all
case 2: break; // receive none
default: TEST_ASSERT(false);
}
test_ctrl[1] ++;
}
return false;
}
TEST_CASE("twai mask filter sleep retention", "[twai]")
{
// Prepare a TOP PD sleep
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1 * 1000 * 1000));
#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(true));
#endif
esp_sleep_context_t sleep_ctx = {};
esp_sleep_set_sleep_context(&sleep_ctx);
twai_node_handle_t node_hdl;
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 1000000;
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
node_config.flags.enable_self_test = true;
node_config.flags.enable_loopback = true;
node_config.flags.sleep_allow_pd = true;
TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl));
uint8_t test_ctrl[2] = {0, 0};
twai_event_callbacks_t user_cbs = {};
user_cbs.on_rx_done = test_filter_rx_done_cb;
TEST_ESP_OK(twai_node_register_event_callbacks(node_hdl, &user_cbs, test_ctrl));
twai_mask_filter_config_t mfilter_cfg = {};
mfilter_cfg.id = 0x10,
mfilter_cfg.mask = 0xf0,
mfilter_cfg.is_ext = false,
printf("Testing mask filter: id 0x%lx mask 0x%lx is_ext %d\n", mfilter_cfg.id, mfilter_cfg.mask, mfilter_cfg.is_ext);
TEST_ESP_OK(twai_node_config_mask_filter(node_hdl, 0, &mfilter_cfg));
printf("Go to sleep ...\n");
TEST_ESP_OK(esp_light_sleep_start());
printf("Waked up! sleep %d power down %ld\n", !sleep_ctx.sleep_request_result, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
TEST_ASSERT_EQUAL(PMU_SLEEP_PD_TOP, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
#endif
TEST_ESP_OK(twai_node_enable(node_hdl));
test_random_trans_generator(node_hdl, 30);
TEST_ASSERT_EQUAL(7, test_ctrl[1]); // must receive 7 of 30 frames under filter config
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
esp_sleep_set_sleep_context(NULL);
#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(false));
#endif
}
#endif //SOC_TWAI_SUPPORT_SLEEP_RETENTION
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import subprocess import subprocess
@@ -17,11 +17,21 @@ from pytest_embedded_idf.utils import soc_filtered_targets
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@pytest.mark.generic @pytest.mark.generic
@pytest.mark.parametrize('config', ['release', 'cache_safe'], indirect=True) @pytest.mark.parametrize('config', ['release', 'cache_safe'], indirect=True)
@idf_parametrize('target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1'), indirect=['target']) @idf_parametrize(
'target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1 and IDF_TARGET not in ["esp32c5"]'), indirect=['target']
)
def test_driver_twai_loopbk(dut: Dut) -> None: def test_driver_twai_loopbk(dut: Dut) -> None:
dut.run_all_single_board_cases(group='twai', reset=True) dut.run_all_single_board_cases(group='twai', reset=True)
@pytest.mark.generic
@pytest.mark.esp32c5_eco3
@pytest.mark.parametrize('config', ['release', 'cache_safe'], indirect=True)
@idf_parametrize('target', ['esp32c5'], indirect=['target'])
def test_driver_twai_loopbk_c5eco3(dut: Dut) -> None:
dut.run_all_single_board_cases(group='twai', reset=True)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helper Functions # Helper Functions
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -1,2 +1,4 @@
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT_INIT=n CONFIG_ESP_TASK_WDT_INIT=n
# primitives for checking sleep internal state
CONFIG_ESP_SLEEP_DEBUG=y
+73 -1
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -30,3 +30,75 @@ const twai_signal_conn_t twai_periph_signals[2] = {
.stand_by_sig = -1, .stand_by_sig = -1,
}, },
}; };
/**
* TWAI Registers to be saved during sleep retention
* - TWAIFD_MODE_SETTINGS_REG
* - TWAIFD_STATUS_REG
* - TWAIFD_COMMAND_REG
* - TWAIFD_INT_ENA_SET_REG
* - TWAIFD_INT_ENA_CLR_REG
* - TWAIFD_BTR_REG
* - TWAIFD_BTR_FD_REG
* - TWAIFD_EWL_ERP_FAULT_STATE_REG
* - TWAIFD_REC_TEC_REG
* - TWAIFD_ERR_NORM_ERR_FD_REG
* - TWAIFD_FILTER_A_MASK_REG
* - TWAIFD_FILTER_A_VAL_REG
* - TWAIFD_FILTER_B_MASK_REG
* - TWAIFD_FILTER_B_VAL_REG
* - TWAIFD_FILTER_C_MASK_REG
* - TWAIFD_FILTER_C_VAL_REG
* - TWAIFD_FILTER_RAN_LOW_REG
* - TWAIFD_FILTER_RAN_HIGH_REG
* - TWAIFD_FILTER_CONTROL_FILTER_STATUS_REG
* - TWAIFD_RX_STATUS_RX_SETTINGS_REG
* - TWAIFD_TX_COMMAND_TXTB_INFO_REG
* - TWAIFD_TX_PRIORITY_REG
* - TWAIFD_TRV_DELAY_SSP_CFG_REG
* - TWAIFD_RX_FR_CTR_REG
* - TWAIFD_TX_FR_CTR_REG
*/
#define TWAI_RETENTION_REGS_CNT 25
#define TWAI_RETENTION_REGS_BASE(i) REG_TWAI_BASE(i)
static const uint32_t twai_regs_map[4] = {0xb27fdf37, 0x3, 0x0, 0x0};
// twai timer registers is far away from twai registers, so we need a separate link
// - TWAIFD_TIMER_INT_ENA_REG
// - TWAIFD_TIMER_CFG_REG
#define TWAI_TIMER_RETENTION_REGS_CNT 2
#define TWAI_TIMER_RETENTION_REGS_BASE(i) (REG_TWAI_BASE(i) + 0xfe0)
static const uint32_t twai_timer_regs_map[4] = {0x5, 0x0, 0x0, 0x0};
#define TWAI_SLEEP_RETENTION_ENTRIES(id) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT( \
REGDMA_TWAI_LINK(0x00), \
TWAI_RETENTION_REGS_BASE(id), TWAI_RETENTION_REGS_BASE(id), \
TWAI_RETENTION_REGS_CNT, 0, 0, \
twai_regs_map[0], twai_regs_map[1], \
twai_regs_map[2], twai_regs_map[3]), \
.owner = ENTRY(0) | ENTRY(2) }, \
[1] = { .config = REGDMA_LINK_ADDR_MAP_INIT( \
REGDMA_TWAI_LINK(0x01), \
TWAI_TIMER_RETENTION_REGS_BASE(id), TWAI_TIMER_RETENTION_REGS_BASE(id), \
TWAI_TIMER_RETENTION_REGS_CNT, 0, 0, \
twai_timer_regs_map[0], twai_timer_regs_map[1], \
twai_timer_regs_map[2], twai_timer_regs_map[3]), \
.owner = ENTRY(0) | ENTRY(2) }, \
}
static const regdma_entries_config_t twai0_regs_retention[] = TWAI_SLEEP_RETENTION_ENTRIES(0);
static const regdma_entries_config_t twai1_regs_retention[] = TWAI_SLEEP_RETENTION_ENTRIES(1);
const twai_reg_retention_info_t twai_reg_retention_info[2] = {
[0] = {
.module_id = SLEEP_RETENTION_MODULE_TWAI0,
.entry_array = twai0_regs_retention,
.array_size = ARRAY_SIZE(twai0_regs_retention)
},
[1] = {
.module_id = SLEEP_RETENTION_MODULE_TWAI1,
.entry_array = twai1_regs_retention,
.array_size = ARRAY_SIZE(twai1_regs_retention)
},
};
+67 -1
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -20,3 +20,69 @@ const twai_signal_conn_t twai_periph_signals[1] = {
.stand_by_sig = -1, .stand_by_sig = -1,
} }
}; };
/**
* TWAI Registers to be saved during sleep retention
* - TWAIFD_MODE_SETTINGS_REG
* - TWAIFD_STATUS_REG
* - TWAIFD_COMMAND_REG
* - TWAIFD_INT_ENA_SET_REG
* - TWAIFD_INT_ENA_CLR_REG
* - TWAIFD_BTR_REG
* - TWAIFD_BTR_FD_REG
* - TWAIFD_EWL_ERP_FAULT_STATE_REG
* - TWAIFD_REC_TEC_REG
* - TWAIFD_ERR_NORM_ERR_FD_REG
* - TWAIFD_FILTER_A_MASK_REG
* - TWAIFD_FILTER_A_VAL_REG
* - TWAIFD_FILTER_B_MASK_REG
* - TWAIFD_FILTER_B_VAL_REG
* - TWAIFD_FILTER_C_MASK_REG
* - TWAIFD_FILTER_C_VAL_REG
* - TWAIFD_FILTER_RAN_LOW_REG
* - TWAIFD_FILTER_RAN_HIGH_REG
* - TWAIFD_FILTER_CONTROL_FILTER_STATUS_REG
* - TWAIFD_RX_STATUS_RX_SETTINGS_REG
* - TWAIFD_TX_COMMAND_TXTB_INFO_REG
* - TWAIFD_TX_PRIORITY_REG
* - TWAIFD_TRV_DELAY_SSP_CFG_REG
* - TWAIFD_RX_FR_CTR_REG
* - TWAIFD_TX_FR_CTR_REG
*/
#define TWAI_RETENTION_REGS_CNT 25
#define TWAI_RETENTION_REGS_BASE(i) DR_REG_TWAIFD_BASE
static const uint32_t twai_regs_map[4] = {0xb27fdf37, 0x3, 0x0, 0x0};
// twai timer registers is far away from twai registers, so we need a separate link
// - TWAIFD_TIMER_INT_ENA_REG
// - TWAIFD_TIMER_CFG_REG
#define TWAI_TIMER_RETENTION_REGS_CNT 2
#define TWAI_TIMER_RETENTION_REGS_BASE(i) (DR_REG_TWAIFD_BASE + 0xfe0)
static const uint32_t twai_timer_regs_map[4] = {0x5, 0x0, 0x0, 0x0};
#define TWAI_SLEEP_RETENTION_ENTRIES(id) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT( \
REGDMA_TWAI_LINK(0x00), \
TWAI_RETENTION_REGS_BASE(id), TWAI_RETENTION_REGS_BASE(id), \
TWAI_RETENTION_REGS_CNT, 0, 0, \
twai_regs_map[0], twai_regs_map[1], \
twai_regs_map[2], twai_regs_map[3]), \
.owner = ENTRY(0) | ENTRY(2) }, \
[1] = { .config = REGDMA_LINK_ADDR_MAP_INIT( \
REGDMA_TWAI_LINK(0x01), \
TWAI_TIMER_RETENTION_REGS_BASE(id), TWAI_TIMER_RETENTION_REGS_BASE(id), \
TWAI_TIMER_RETENTION_REGS_CNT, 0, 0, \
twai_timer_regs_map[0], twai_timer_regs_map[1], \
twai_timer_regs_map[2], twai_timer_regs_map[3]), \
.owner = ENTRY(0) | ENTRY(2) }, \
}
static const regdma_entries_config_t twai0_regs_retention[] = TWAI_SLEEP_RETENTION_ENTRIES(0);
const twai_reg_retention_info_t twai_reg_retention_info[1] = {
[0] = {
.module_id = SLEEP_RETENTION_MODULE_TWAI0,
.entry_array = twai0_regs_retention,
.array_size = ARRAY_SIZE(twai0_regs_retention)
},
};
@@ -59,7 +59,10 @@ bool peripheral_domain_pd_allowed(void)
// ESP32C5 supports temperature sensor sleep retention // ESP32C5 supports temperature sensor sleep retention
RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_TEMP_SENSOR); RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_TEMP_SENSOR);
// ESP32C5 does not support TWAI sleep retention yet: IDF-13001 // ESP32C5 supports TWAI sleep retention
RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_TWAI0);
RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_TWAI1);
/* ESP32C5 only supports TWAI0 and TWAI1 */
// ESP32C5 supports PARLIO sleep retention // ESP32C5 supports PARLIO sleep retention
RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_PARLIO0); RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_PARLIO0);
@@ -1079,6 +1079,10 @@ config SOC_TWAI_RANGE_FILTER_NUM
int int
default 1 default 1
config SOC_TWAI_SUPPORT_SLEEP_RETENTION
bool
default y
config SOC_EFUSE_DIS_PAD_JTAG config SOC_EFUSE_DIS_PAD_JTAG
bool bool
default y default y
@@ -45,6 +45,8 @@ typedef enum periph_retention_module {
SLEEP_RETENTION_MODULE_LEDC = 21, SLEEP_RETENTION_MODULE_LEDC = 21,
SLEEP_RETENTION_MODULE_MCPWM0 = 22, SLEEP_RETENTION_MODULE_MCPWM0 = 22,
SLEEP_RETENTION_MODULE_SDM0 = 23, SLEEP_RETENTION_MODULE_SDM0 = 23,
SLEEP_RETENTION_MODULE_TWAI0 = 24,
SLEEP_RETENTION_MODULE_TWAI1 = 25,
/* modem module, which includes WiFi, BLE and 802.15.4 */ /* modem module, which includes WiFi, BLE and 802.15.4 */
SLEEP_RETENTION_MODULE_WIFI_MAC = 26, SLEEP_RETENTION_MODULE_WIFI_MAC = 26,
@@ -82,6 +84,8 @@ typedef enum periph_retention_module {
: ((m) == SLEEP_RETENTION_MODULE_LEDC) ? true \ : ((m) == SLEEP_RETENTION_MODULE_LEDC) ? true \
: ((m) == SLEEP_RETENTION_MODULE_MCPWM0) ? true \ : ((m) == SLEEP_RETENTION_MODULE_MCPWM0) ? true \
: ((m) == SLEEP_RETENTION_MODULE_SDM0) ? true \ : ((m) == SLEEP_RETENTION_MODULE_SDM0) ? true \
: ((m) == SLEEP_RETENTION_MODULE_TWAI0) ? true \
: ((m) == SLEEP_RETENTION_MODULE_TWAI1) ? true \
: false) : false)
#ifdef __cplusplus #ifdef __cplusplus
@@ -445,9 +445,10 @@
#define SOC_MWDT_SUPPORT_SLEEP_RETENTION (1) #define SOC_MWDT_SUPPORT_SLEEP_RETENTION (1)
/*-------------------------- TWAI CAPS ---------------------------------------*/ /*-------------------------- TWAI CAPS ---------------------------------------*/
#define SOC_TWAI_CONTROLLER_NUM 2 #define SOC_TWAI_CONTROLLER_NUM 2
#define SOC_TWAI_MASK_FILTER_NUM 3 #define SOC_TWAI_MASK_FILTER_NUM 3
#define SOC_TWAI_RANGE_FILTER_NUM 1U #define SOC_TWAI_RANGE_FILTER_NUM 1U
#define SOC_TWAI_SUPPORT_SLEEP_RETENTION 1
/*-------------------------- eFuse CAPS----------------------------*/ /*-------------------------- eFuse CAPS----------------------------*/
#define SOC_EFUSE_DIS_PAD_JTAG 1 #define SOC_EFUSE_DIS_PAD_JTAG 1
@@ -811,6 +811,10 @@ config SOC_TWAI_RANGE_FILTER_NUM
int int
default 1 default 1
config SOC_TWAI_SUPPORT_SLEEP_RETENTION
bool
default y
config SOC_EFUSE_DIS_DOWNLOAD_ICACHE config SOC_EFUSE_DIS_DOWNLOAD_ICACHE
bool bool
default n default n
@@ -400,9 +400,10 @@
#define SOC_MWDT_SUPPORT_XTAL (1) #define SOC_MWDT_SUPPORT_XTAL (1)
/*-------------------------- TWAI CAPS ---------------------------------------*/ /*-------------------------- TWAI CAPS ---------------------------------------*/
#define SOC_TWAI_CONTROLLER_NUM 1U #define SOC_TWAI_CONTROLLER_NUM 1U
#define SOC_TWAI_MASK_FILTER_NUM 3 #define SOC_TWAI_MASK_FILTER_NUM 3U
#define SOC_TWAI_RANGE_FILTER_NUM 1U #define SOC_TWAI_RANGE_FILTER_NUM 1U
#define SOC_TWAI_SUPPORT_SLEEP_RETENTION 1
/*-------------------------- eFuse CAPS----------------------------*/ /*-------------------------- eFuse CAPS----------------------------*/
#define SOC_EFUSE_DIS_DOWNLOAD_ICACHE 0 #define SOC_EFUSE_DIS_DOWNLOAD_ICACHE 0
@@ -334,6 +334,15 @@ Power Management
When power management is enabled via :ref:`CONFIG_PM_ENABLE`, the system may adjust or disable clock sources before entering sleep mode, which could cause TWAI to malfunction. To prevent this, the driver manages a power management lock internally. This lock is acquired when calling :cpp:func:`twai_node_enable`, ensuring the system does not enter sleep mode and TWAI remains functional. To allow the system to enter a low-power state, call :cpp:func:`twai_node_disable` to release the lock. During sleep, the TWAI controller will also stop functioning. When power management is enabled via :ref:`CONFIG_PM_ENABLE`, the system may adjust or disable clock sources before entering sleep mode, which could cause TWAI to malfunction. To prevent this, the driver manages a power management lock internally. This lock is acquired when calling :cpp:func:`twai_node_enable`, ensuring the system does not enter sleep mode and TWAI remains functional. To allow the system to enter a low-power state, call :cpp:func:`twai_node_disable` to release the lock. During sleep, the TWAI controller will also stop functioning.
.. only:: SOC_TWAI_SUPPORT_SLEEP_RETENTION
About Sleep Retention
^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports powering down the TWAI controller during **Light Sleep** to further reduce power consumption and automatically restore after waking up. This means the application does not need to reconfigure TWAI after **Light Sleep** wake up.
Enable the option :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP`, and set :cpp:member:`twai_onchip_node_config_t::flags::sleep_allow_pd` to ``true`` when initializing the TWAI node to enable this feature. Otherwise, the TWAI controller will remain powered during **Light Sleep**. This feature helps reduce power consumption during light sleep but requires additional storage to save register configurations.
Cache Safety Cache Safety
------------ ------------
@@ -334,6 +334,15 @@ TWAI控制器能够检测由于总线干扰产生的/损坏的不符合帧格式
当启用电源管理 :ref:`CONFIG_PM_ENABLE` 时,系统在进入睡眠模式前可能会调整或关闭时钟源,从而导致 TWAI 出错。为了防止这种情况发生,驱动内部使用电源锁管理。当调用 :cpp:func:`twai_node_enable` 函数后,该锁将被激活,确保系统不会进入睡眠模式,从而保持 TWAI 功能正常。如果需要降低功耗,可以调用 :cpp:func:`twai_node_disable` 函数来释放电源管理锁,使系统能够进入睡眠模式,睡眠期间 TWAI 控制器也将停止工作。 当启用电源管理 :ref:`CONFIG_PM_ENABLE` 时,系统在进入睡眠模式前可能会调整或关闭时钟源,从而导致 TWAI 出错。为了防止这种情况发生,驱动内部使用电源锁管理。当调用 :cpp:func:`twai_node_enable` 函数后,该锁将被激活,确保系统不会进入睡眠模式,从而保持 TWAI 功能正常。如果需要降低功耗,可以调用 :cpp:func:`twai_node_disable` 函数来释放电源管理锁,使系统能够进入睡眠模式,睡眠期间 TWAI 控制器也将停止工作。
.. only:: SOC_TWAI_SUPPORT_SLEEP_RETENTION
关于睡眠保留
^^^^^^^^^^^^
{IDF_TARGET_NAME} 支持在 **Light Sleep** 期间将 TWAI 控制器断电以进一步降低功耗,并在唤醒后自动恢复。即程序不需要在 **Light Sleep** 唤醒后重新配置 TWAI。
启用选项 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP`,并在初始化 TWAI 节点时,将 :cpp:member:`twai_onchip_node_config_t::flags::sleep_allow_pd` 设置为 ``true`` 即可启用该功能,否则 TWAI 控制器在 **Light Sleep** 期间将保持供电。它可以帮助降低轻度睡眠时的功耗,但需要花费一些额外的存储来保存寄存器的配置。
关于 Cache 安全 关于 Cache 安全
--------------- ---------------