From b814abc9e0ae14078bdb515593ad9cbd03ac8bb0 Mon Sep 17 00:00:00 2001 From: wanckl Date: Mon, 26 Jan 2026 20:17:40 +0800 Subject: [PATCH] feat(driver_twai): new driver sleep retention support --- components/esp_driver_twai/esp_twai_onchip.c | 50 +++- .../esp_driver_twai/include/esp_twai_onchip.h | 1 + .../test_apps/test_twai/main/CMakeLists.txt | 5 +- .../test_twai/main/test_twai_common.cpp | 7 +- .../test_twai/main/test_twai_sleep.c | 226 ++++++++++++++++++ .../test_apps/test_twai/pytest_driver_twai.py | 14 +- .../test_apps/test_twai/sdkconfig.defaults | 2 + components/esp_hal_twai/esp32c5/twai_periph.c | 74 +++++- components/esp_hal_twai/esp32h4/twai_periph.c | 68 +++++- .../port/esp32c5/peripheral_domain_pd.c | 5 +- .../esp32c5/include/soc/Kconfig.soc_caps.in | 4 + .../include/soc/retention_periph_defs.h | 4 + components/soc/esp32c5/include/soc/soc_caps.h | 7 +- .../esp32h4/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32h4/include/soc/soc_caps.h | 7 +- docs/en/api-reference/peripherals/twai.rst | 9 + docs/zh_CN/api-reference/peripherals/twai.rst | 9 + 17 files changed, 478 insertions(+), 18 deletions(-) create mode 100644 components/esp_driver_twai/test_apps/test_twai/main/test_twai_sleep.c diff --git a/components/esp_driver_twai/esp_twai_onchip.c b/components/esp_driver_twai/esp_twai_onchip.c index 5a2ab16358..deb2b097e4 100644 --- a/components/esp_driver_twai/esp_twai_onchip.c +++ b/components/esp_driver_twai/esp_twai_onchip.c @@ -286,6 +286,16 @@ static void _node_destroy(twai_onchip_ctx_t *twai_ctx) if (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 if (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; } +#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 --------------------------------- */ 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_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"); - +#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 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"); @@ -679,6 +701,25 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa #endif 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); #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; return ESP_OK; + err: - if (node) { - _lock_release(&s_platform.intr_mutex); - _node_destroy(node); - } + _lock_release(&s_platform.intr_mutex); + _node_destroy(node); return ret; } diff --git a/components/esp_driver_twai/include/esp_twai_onchip.h b/components/esp_driver_twai/include/esp_twai_onchip.h index 84fc8bd50b..383cf9b495 100644 --- a/components/esp_driver_twai/include/esp_twai_onchip.h +++ b/components/esp_driver_twai/include/esp_twai_onchip.h @@ -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_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 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 */ } twai_onchip_node_config_t; diff --git a/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt b/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt index aac256b989..24c3db7b0f 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt +++ b/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt @@ -2,6 +2,9 @@ set(srcs "test_app_main.c") if(CONFIG_SOC_TWAI_SUPPORTED) 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() if(CONFIG_SOC_TWAI_FD_SUPPORTED) @@ -10,6 +13,6 @@ endif() idf_component_register( 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 ) diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp index 6b8f9c498e..f23bae4ce2 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp @@ -19,12 +19,17 @@ #include "esp_twai.h" #include "esp_twai_onchip.h" #include "hal/twai_periph.h" -#include "esp_private/gpio.h" #include "soc/gpio_sig_map.h" +#include "esp_private/gpio.h" #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_RX_GPIO GPIO_NUM_5 +#endif #define TEST_TWAI_QUEUE_DEPTH 5 #define TEST_TRANS_LEN 100 #define TEST_FRAME_LEN 7 diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_sleep.c b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_sleep.c new file mode 100644 index 0000000000..6dc88ca8c6 --- /dev/null +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_sleep.c @@ -0,0 +1,226 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 diff --git a/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py b/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py index da8f6a814e..62e945d92d 100644 --- a/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py +++ b/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py @@ -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 import subprocess @@ -17,11 +17,21 @@ from pytest_embedded_idf.utils import soc_filtered_targets # --------------------------------------------------------------------------- @pytest.mark.generic @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: 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 # --------------------------------------------------------------------------- diff --git a/components/esp_driver_twai/test_apps/test_twai/sdkconfig.defaults b/components/esp_driver_twai/test_apps/test_twai/sdkconfig.defaults index 8e326e32e1..fff8c78591 100644 --- a/components/esp_driver_twai/test_apps/test_twai/sdkconfig.defaults +++ b/components/esp_driver_twai/test_apps/test_twai/sdkconfig.defaults @@ -1,2 +1,4 @@ CONFIG_FREERTOS_HZ=1000 CONFIG_ESP_TASK_WDT_INIT=n +# primitives for checking sleep internal state +CONFIG_ESP_SLEEP_DEBUG=y diff --git a/components/esp_hal_twai/esp32c5/twai_periph.c b/components/esp_hal_twai/esp32c5/twai_periph.c index 5d770948e4..103591f960 100644 --- a/components/esp_hal_twai/esp32c5/twai_periph.c +++ b/components/esp_hal_twai/esp32c5/twai_periph.c @@ -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 */ @@ -30,3 +30,75 @@ const twai_signal_conn_t twai_periph_signals[2] = { .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) + }, +}; diff --git a/components/esp_hal_twai/esp32h4/twai_periph.c b/components/esp_hal_twai/esp32h4/twai_periph.c index a27443fbdd..0102739461 100644 --- a/components/esp_hal_twai/esp32h4/twai_periph.c +++ b/components/esp_hal_twai/esp32h4/twai_periph.c @@ -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 */ @@ -20,3 +20,69 @@ const twai_signal_conn_t twai_periph_signals[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) + }, +}; diff --git a/components/esp_hw_support/port/esp32c5/peripheral_domain_pd.c b/components/esp_hw_support/port/esp32c5/peripheral_domain_pd.c index 0f96516745..7f75dca639 100644 --- a/components/esp_hw_support/port/esp32c5/peripheral_domain_pd.c +++ b/components/esp_hw_support/port/esp32c5/peripheral_domain_pd.c @@ -59,7 +59,10 @@ bool peripheral_domain_pd_allowed(void) // ESP32C5 supports temperature sensor sleep retention 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 RETENTION_MODULE_BITMAP_SET(&mask, SLEEP_RETENTION_MODULE_PARLIO0); diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index 92c120e371..0b839f568f 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -1075,6 +1075,10 @@ config SOC_TWAI_RANGE_FILTER_NUM int default 1 +config SOC_TWAI_SUPPORT_SLEEP_RETENTION + bool + default y + config SOC_EFUSE_DIS_PAD_JTAG bool default y diff --git a/components/soc/esp32c5/include/soc/retention_periph_defs.h b/components/soc/esp32c5/include/soc/retention_periph_defs.h index 0855b2f5f7..6382ad901d 100644 --- a/components/soc/esp32c5/include/soc/retention_periph_defs.h +++ b/components/soc/esp32c5/include/soc/retention_periph_defs.h @@ -45,6 +45,8 @@ typedef enum periph_retention_module { SLEEP_RETENTION_MODULE_LEDC = 21, SLEEP_RETENTION_MODULE_MCPWM0 = 22, 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 */ 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_MCPWM0) ? true \ : ((m) == SLEEP_RETENTION_MODULE_SDM0) ? true \ + : ((m) == SLEEP_RETENTION_MODULE_TWAI0) ? true \ + : ((m) == SLEEP_RETENTION_MODULE_TWAI1) ? true \ : false) #ifdef __cplusplus diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index 949938b247..3287f0f41d 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -443,9 +443,10 @@ #define SOC_MWDT_SUPPORT_SLEEP_RETENTION (1) /*-------------------------- TWAI CAPS ---------------------------------------*/ -#define SOC_TWAI_CONTROLLER_NUM 2 -#define SOC_TWAI_MASK_FILTER_NUM 3 -#define SOC_TWAI_RANGE_FILTER_NUM 1U +#define SOC_TWAI_CONTROLLER_NUM 2 +#define SOC_TWAI_MASK_FILTER_NUM 3 +#define SOC_TWAI_RANGE_FILTER_NUM 1U +#define SOC_TWAI_SUPPORT_SLEEP_RETENTION 1 /*-------------------------- eFuse CAPS----------------------------*/ #define SOC_EFUSE_DIS_PAD_JTAG 1 diff --git a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in index 42d14197aa..f2205fedda 100644 --- a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in @@ -807,6 +807,10 @@ config SOC_TWAI_RANGE_FILTER_NUM int default 1 +config SOC_TWAI_SUPPORT_SLEEP_RETENTION + bool + default y + config SOC_EFUSE_DIS_DOWNLOAD_ICACHE bool default n diff --git a/components/soc/esp32h4/include/soc/soc_caps.h b/components/soc/esp32h4/include/soc/soc_caps.h index 22a9adfccf..57df60b6b2 100644 --- a/components/soc/esp32h4/include/soc/soc_caps.h +++ b/components/soc/esp32h4/include/soc/soc_caps.h @@ -398,9 +398,10 @@ #define SOC_MWDT_SUPPORT_XTAL (1) /*-------------------------- TWAI CAPS ---------------------------------------*/ -#define SOC_TWAI_CONTROLLER_NUM 1U -#define SOC_TWAI_MASK_FILTER_NUM 3 -#define SOC_TWAI_RANGE_FILTER_NUM 1U +#define SOC_TWAI_CONTROLLER_NUM 1U +#define SOC_TWAI_MASK_FILTER_NUM 3U +#define SOC_TWAI_RANGE_FILTER_NUM 1U +#define SOC_TWAI_SUPPORT_SLEEP_RETENTION 1 /*-------------------------- eFuse CAPS----------------------------*/ #define SOC_EFUSE_DIS_DOWNLOAD_ICACHE 0 diff --git a/docs/en/api-reference/peripherals/twai.rst b/docs/en/api-reference/peripherals/twai.rst index 6c7bed53c7..7bae2f93fa 100644 --- a/docs/en/api-reference/peripherals/twai.rst +++ b/docs/en/api-reference/peripherals/twai.rst @@ -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. +.. 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 ------------ diff --git a/docs/zh_CN/api-reference/peripherals/twai.rst b/docs/zh_CN/api-reference/peripherals/twai.rst index 9ee4876301..30c4266471 100644 --- a/docs/zh_CN/api-reference/peripherals/twai.rst +++ b/docs/zh_CN/api-reference/peripherals/twai.rst @@ -334,6 +334,15 @@ 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 安全 ---------------