Skip to content

Commit 16d99f3

Browse files
committed
Merge branch 'feature/support_i2s_retention' into 'master'
feat(i2s): support sleep retention Closes IDF-8468, IDF-9754, IDF-9783, and IDF-10343 See merge request espressif/esp-idf!33529
2 parents ad6fba3 + 4ed1b87 commit 16d99f3

File tree

38 files changed

+488
-30
lines changed

38 files changed

+488
-30
lines changed

components/esp_driver_i2s/i2s_common.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141

4242
#include "esp_private/i2s_platform.h"
4343
#include "esp_private/esp_clk.h"
44+
#if SOC_I2S_SUPPORT_SLEEP_RETENTION
45+
#include "esp_private/sleep_retention.h"
46+
#endif
4447

4548
#include "driver/gpio.h"
4649
#include "esp_private/gpio.h"
@@ -85,6 +88,34 @@ inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size)
8588
Scope: This file only
8689
----------------------------------------------------------------------------*/
8790

91+
#if I2S_USE_RETENTION_LINK
92+
static esp_err_t s_i2s_create_sleep_retention_link_cb(void *arg)
93+
{
94+
i2s_controller_t *i2s_obj = (i2s_controller_t *)arg;
95+
ESP_RETURN_ON_ERROR(sleep_retention_entries_create(i2s_reg_retention_info[i2s_obj->id].entry_array,
96+
i2s_reg_retention_info[i2s_obj->id].array_size,
97+
REGDMA_LINK_PRI_I2S, i2s_obj->slp_retention_mod),
98+
TAG, "create retention link failed");
99+
return ESP_OK;
100+
}
101+
102+
static void s_i2s_create_retention_module(i2s_controller_t *i2s_obj)
103+
{
104+
sleep_retention_module_t module = i2s_obj->slp_retention_mod;
105+
106+
_lock_acquire(&i2s_obj->mutex);
107+
if (i2s_obj->retention_link_created == false) {
108+
if (sleep_retention_module_allocate(module) != ESP_OK) {
109+
// even though the sleep retention module create failed, I2S driver should still work, so just warning here
110+
ESP_LOGW(TAG, "create retention module failed, power domain can't turn off");
111+
} else {
112+
i2s_obj->retention_link_created = true;
113+
}
114+
}
115+
_lock_release(&i2s_obj->mutex);
116+
}
117+
#endif // I2S_USE_RETENTION_LINK
118+
88119
static void i2s_tx_channel_start(i2s_chan_handle_t handle)
89120
{
90121
i2s_hal_tx_reset(&(handle->controller->hal));
@@ -175,6 +206,14 @@ static esp_err_t i2s_destroy_controller_obj(i2s_controller_t **i2s_obj)
175206
#if SOC_I2S_HW_VERSION_1
176207
i2s_ll_enable_dma((*i2s_obj)->hal.dev, false);
177208
#endif
209+
#if I2S_USE_RETENTION_LINK
210+
if ((*i2s_obj)->slp_retention_mod) {
211+
if ((*i2s_obj)->retention_link_created) {
212+
sleep_retention_module_free((*i2s_obj)->slp_retention_mod);
213+
}
214+
sleep_retention_module_deinit((*i2s_obj)->slp_retention_mod);
215+
}
216+
#endif // I2S_USE_RETENTION_LINK
178217
free(*i2s_obj);
179218
*i2s_obj = NULL;
180219
return i2s_platform_release_occupation(I2S_CTLR_HP, id);
@@ -219,6 +258,25 @@ static i2s_controller_t *i2s_acquire_controller_obj(int id)
219258
adc_ll_digi_set_data_source(0);
220259
}
221260
#endif
261+
262+
#if I2S_USE_RETENTION_LINK
263+
sleep_retention_module_t module = i2s_reg_retention_info[id].retention_module;
264+
sleep_retention_module_init_param_t init_param = {
265+
.cbs = {
266+
.create = {
267+
.handle = s_i2s_create_sleep_retention_link_cb,
268+
.arg = i2s_obj,
269+
},
270+
},
271+
.depends = BIT(SLEEP_RETENTION_MODULE_CLOCK_SYSTEM)
272+
};
273+
if (sleep_retention_module_init(module, &init_param) == ESP_OK) {
274+
i2s_obj->slp_retention_mod = module;
275+
} else {
276+
// even the sleep retention module init failed, I2S driver should still work, so just warning here
277+
ESP_LOGW(TAG, "init sleep retention failed for I2S%d, power domain may be turned off during sleep", id);
278+
}
279+
#endif // I2S_USE_RETENTION_LINK
222280
} else {
223281
free(pre_alloc);
224282
portENTER_CRITICAL(&g_i2s.spinlock);
@@ -879,6 +937,9 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *
879937
ESP_RETURN_ON_FALSE(chan_cfg->id < SOC_I2S_NUM || chan_cfg->id == I2S_NUM_AUTO, ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id");
880938
ESP_RETURN_ON_FALSE(chan_cfg->dma_desc_num >= 2, ESP_ERR_INVALID_ARG, TAG, "there should be at least 2 DMA buffers");
881939
ESP_RETURN_ON_FALSE(chan_cfg->intr_priority >= 0 && chan_cfg->intr_priority <= 7, ESP_ERR_INVALID_ARG, TAG, "intr_priority should be within 0~7");
940+
#if !SOC_I2S_SUPPORT_SLEEP_RETENTION
941+
ESP_RETURN_ON_FALSE(!chan_cfg->allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
942+
#endif
882943

883944
esp_err_t ret = ESP_OK;
884945
i2s_controller_t *i2s_obj = NULL;
@@ -937,6 +998,11 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *
937998
if ((tx_handle != NULL) && (rx_handle != NULL)) {
938999
i2s_obj->full_duplex = true;
9391000
}
1001+
#if I2S_USE_RETENTION_LINK
1002+
if (chan_cfg->allow_pd) {
1003+
s_i2s_create_retention_module(i2s_obj);
1004+
}
1005+
#endif
9401006

9411007
return ESP_OK;
9421008
/* i2s_obj allocated but register channel failed */

components/esp_driver_i2s/i2s_private.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#pragma once
88

9+
#include <sys/lock.h>
910
#include "freertos/FreeRTOS.h"
1011
#include "freertos/semphr.h"
1112
#include "freertos/queue.h"
@@ -25,6 +26,9 @@
2526
#endif
2627
#include "esp_private/periph_ctrl.h"
2728
#include "esp_private/esp_gpio_reserve.h"
29+
#if SOC_I2S_SUPPORT_SLEEP_RETENTION
30+
#include "esp_private/sleep_retention.h"
31+
#endif
2832
#include "esp_pm.h"
2933
#include "esp_err.h"
3034
#include "sdkconfig.h"
@@ -56,6 +60,8 @@ extern "C" {
5660
#define I2S_RCC_ATOMIC()
5761
#endif
5862

63+
#define I2S_USE_RETENTION_LINK (SOC_I2S_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)
64+
5965
#define I2S_NULL_POINTER_CHECK(tag, p) ESP_RETURN_ON_FALSE((p), ESP_ERR_INVALID_ARG, tag, "input parameter '"#p"' is NULL")
6066

6167
/**
@@ -130,6 +136,11 @@ typedef struct {
130136
bool full_duplex; /*!< is full_duplex */
131137
i2s_chan_handle_t tx_chan; /*!< tx channel handler */
132138
i2s_chan_handle_t rx_chan; /*!< rx channel handler */
139+
_lock_t mutex; /*!< mutex for controller */
140+
#if SOC_I2S_SUPPORT_SLEEP_RETENTION
141+
sleep_retention_module_t slp_retention_mod; /*!< Sleep retention module */
142+
bool retention_link_created; /*!< Whether the retention link is created */
143+
#endif
133144
int mclk; /*!< MCK out pin, shared by tx/rx*/
134145
#if CONFIG_IDF_TARGET_ESP32
135146
esp_clock_output_mapping_handle_t mclk_out_hdl; /*!< The handle of MCLK output signal */

components/esp_driver_i2s/include/driver/i2s_common.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern "C" {
2626
.dma_frame_num = 240, \
2727
.auto_clear_after_cb = false, \
2828
.auto_clear_before_cb = false, \
29+
.allow_pd = false, \
2930
.intr_priority = 0, \
3031
}
3132

@@ -73,6 +74,10 @@ typedef struct {
7374
bool auto_clear_before_cb; /*!< Set to auto clear DMA TX buffer before `on_sent` callback, I2S will always send zero automatically if no data to send
7475
* So that user can access data in the callback that just finished to send.
7576
*/
77+
bool allow_pd; /*!< Set to allow power down. When this flag set, the driver will backup/restore the I2S registers before/after entering/exist sleep mode.
78+
* By this approach, the system can power off I2S's power domain.
79+
* This can save power, but at the expense of more RAM being consumed.
80+
*/
7681
int intr_priority; /*!< I2S interrupt priority, range [0, 7], if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
7782
} i2s_chan_config_t;
7883

components/esp_driver_i2s/test_apps/.build-test-rules.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
components/esp_driver_i2s/test_apps/i2s:
44
disable:
55
- if: SOC_I2S_SUPPORTED != 1
6-
disable_test:
7-
- if: IDF_TARGET == "esp32c5"
8-
temporary: true
9-
reason: target test failed # TODO [ESP32C5] IDF-10343
106
depends_components:
117
- esp_driver_i2s
128
- esp_driver_pcnt
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
set(srcs "test_app_main.c"
22
"test_i2s.c"
3-
"test_i2s_iram.c")
3+
"test_i2s_iram.c"
4+
"test_i2s_sleep.c")
45

56
if(CONFIG_SOC_I2S_SUPPORTS_ETM AND CONFIG_SOC_GPIO_SUPPORT_ETM)
67
set(srcs ${srcs} "test_i2s_etm.c")
78
endif()
89

910
idf_component_register(SRCS ${srcs}
10-
PRIV_REQUIRES unity esp_driver_pcnt spi_flash esp_driver_gpio esp_driver_i2s
11+
PRIV_REQUIRES unity esp_driver_pcnt spi_flash esp_driver_gpio esp_driver_i2s esp_driver_uart
1112
WHOLE_ARCHIVE)

components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ static void i2s_test_io_config(int mode)
9494
}
9595
}
9696

97-
static void i2s_read_write_test(i2s_chan_handle_t tx_chan, i2s_chan_handle_t rx_chan)
97+
void i2s_read_write_test(i2s_chan_handle_t tx_chan, i2s_chan_handle_t rx_chan)
9898
{
9999
#define I2S_SEND_BUF_LEN 100
100100
#define I2S_RECV_BUF_LEN 10000
@@ -776,7 +776,7 @@ static void i2s_test_common_sample_rate(i2s_chan_handle_t rx_chan, i2s_std_clk_c
776776
printf("[%"PRIu32" Hz] %d pulses, expected %d, err %d\n", test_freq[i], real_pulse, expt_pulse, real_pulse - expt_pulse);
777777
TEST_ESP_OK(i2s_channel_disable(rx_chan));
778778
// Check if the error between real pulse number and expected pulse number is within 1%
779-
TEST_ASSERT_INT_WITHIN(expt_pulse * 0.01, expt_pulse, real_pulse);
779+
TEST_ASSERT_INT_WITHIN(expt_pulse * 0.02, expt_pulse, real_pulse);
780780
}
781781
TEST_ESP_OK(pcnt_del_channel(pcnt_chan));
782782
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#include <stdio.h>
7+
#include <string.h>
8+
#include "sdkconfig.h"
9+
#include "freertos/FreeRTOS.h"
10+
#include "freertos/task.h"
11+
#include "unity.h"
12+
#include "unity_test_utils.h"
13+
#include "driver/i2s_std.h"
14+
#include "driver/uart.h"
15+
#include "soc/i2s_struct.h"
16+
#include "esp_sleep.h"
17+
#include "esp_private/sleep_cpu.h"
18+
#include "esp_private/esp_sleep_internal.h"
19+
#include "esp_private/esp_pmu.h"
20+
#include "../../test_inc/test_i2s.h"
21+
22+
#define TEST_I2S_PD_SLEEP (SOC_I2S_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)
23+
24+
extern void i2s_read_write_test(i2s_chan_handle_t tx_chan, i2s_chan_handle_t rx_chan);
25+
26+
static void s_test_i2s_enter_light_sleep(int sec, bool allow_power_down)
27+
{
28+
esp_sleep_context_t sleep_ctx;
29+
esp_sleep_set_sleep_context(&sleep_ctx);
30+
printf("Entering light sleep for %d seconds\n", sec);
31+
#if ESP_SLEEP_POWER_DOWN_CPU
32+
printf("Enable CPU power down\n");
33+
TEST_ESP_OK(sleep_cpu_configure(true));
34+
#endif
35+
uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM);
36+
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(sec * 1000 * 1000));
37+
TEST_ESP_OK(esp_light_sleep_start());
38+
39+
#if ESP_SLEEP_POWER_DOWN_CPU
40+
TEST_ESP_OK(sleep_cpu_configure(false));
41+
#endif
42+
printf("Woke up from light sleep\n");
43+
44+
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
45+
#if SOC_I2S_SUPPORT_SLEEP_RETENTION
46+
// check if the power domain also is powered down
47+
TEST_ASSERT_EQUAL(allow_power_down ? PMU_SLEEP_PD_TOP : 0, (sleep_ctx.sleep_flags) & PMU_SLEEP_PD_TOP);
48+
#endif
49+
esp_sleep_set_sleep_context(NULL);
50+
}
51+
52+
static void s_test_i2s_sleep(i2s_chan_handle_t tx_handle, i2s_chan_handle_t rx_handle, bool allow_power_down)
53+
{
54+
/* Enter light sleep before I2S channel enabled and wake up after 1 second */
55+
s_test_i2s_enter_light_sleep(1, allow_power_down);
56+
/* Check whether I2S can work correctly after light sleep */
57+
TEST_ESP_OK(i2s_channel_enable(tx_handle));
58+
TEST_ESP_OK(i2s_channel_enable(rx_handle));
59+
i2s_read_write_test(tx_handle, rx_handle);
60+
}
61+
62+
static void s_test_i2s_power_on_sleep(i2s_chan_handle_t tx_handle, i2s_chan_handle_t rx_handle)
63+
{
64+
s_test_i2s_sleep(tx_handle, rx_handle, false);
65+
}
66+
67+
#if TEST_I2S_PD_SLEEP
68+
static void s_test_i2s_power_down_sleep(i2s_chan_handle_t tx_handle, i2s_chan_handle_t rx_handle)
69+
{
70+
#if SOC_GDMA_SUPPORT_SLEEP_RETENTION
71+
s_test_i2s_sleep(tx_handle, rx_handle, true);
72+
#else
73+
/* I2S retention is depended on GDMA retention.
74+
* Only take two registers as sample to check the I2S retention when GDMA retention has not been supported. */
75+
i2s_tx_conf_reg_t tx_reg_before_slp = I2S0.tx_conf;
76+
i2s_rx_conf_reg_t rx_reg_before_slp = I2S0.rx_conf;
77+
/* Enter light sleep before I2S channel enabled and wake up after 1 second */
78+
s_test_i2s_enter_light_sleep(1, true);
79+
/* Only check whether the register values are restored if GDMA retention has not been supported */
80+
i2s_tx_conf_reg_t tx_reg_after_slp = I2S0.tx_conf;
81+
i2s_rx_conf_reg_t rx_reg_after_slp = I2S0.rx_conf;
82+
83+
TEST_ASSERT_EQUAL_UINT32(tx_reg_before_slp.val, tx_reg_after_slp.val);
84+
TEST_ASSERT_EQUAL_UINT32(rx_reg_before_slp.val, rx_reg_after_slp.val);
85+
86+
TEST_ESP_OK(i2s_channel_enable(tx_handle));
87+
TEST_ESP_OK(i2s_channel_enable(rx_handle));
88+
89+
tx_reg_before_slp.val = I2S0.tx_conf.val;
90+
rx_reg_before_slp.val = I2S0.rx_conf.val;
91+
/* Enter light sleep before I2S channel enabled and wake up after 1 second */
92+
s_test_i2s_enter_light_sleep(1, true);
93+
/* Only check whether the register values are restored if GDMA retention has not been supported */
94+
tx_reg_after_slp.val = I2S0.tx_conf.val;
95+
rx_reg_after_slp.val = I2S0.rx_conf.val;
96+
97+
TEST_ASSERT_EQUAL_UINT32(tx_reg_before_slp.val, tx_reg_after_slp.val);
98+
TEST_ASSERT_EQUAL_UINT32(rx_reg_before_slp.val, rx_reg_after_slp.val);
99+
#endif // SOC_GDMA_SUPPORT_SLEEP_RETENTION
100+
}
101+
#endif // TEST_I2S_PD_SLEEP
102+
103+
void test_i2s_sleep_usability(bool allow_power_down)
104+
{
105+
i2s_chan_handle_t tx_handle;
106+
i2s_chan_handle_t rx_handle;
107+
108+
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
109+
chan_cfg.allow_pd = allow_power_down;
110+
i2s_std_config_t std_cfg = {
111+
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
112+
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO),
113+
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
114+
};
115+
std_cfg.gpio_cfg.din = std_cfg.gpio_cfg.dout;
116+
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
117+
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
118+
TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
119+
120+
if (!allow_power_down) {
121+
s_test_i2s_power_on_sleep(tx_handle, rx_handle);
122+
}
123+
#if TEST_I2S_PD_SLEEP
124+
else {
125+
s_test_i2s_power_down_sleep(tx_handle, rx_handle);
126+
}
127+
#endif
128+
129+
printf("I2S works as expected after light sleep\n");
130+
131+
TEST_ESP_OK(i2s_channel_disable(tx_handle));
132+
TEST_ESP_OK(i2s_channel_disable(rx_handle));
133+
TEST_ESP_OK(i2s_del_channel(tx_handle));
134+
TEST_ESP_OK(i2s_del_channel(rx_handle));
135+
}
136+
137+
TEST_CASE("I2S_light_sleep_usability_test", "[i2s]")
138+
{
139+
printf("\nTesting I2S power on light sleep...\n");
140+
test_i2s_sleep_usability(false);
141+
#if TEST_I2S_PD_SLEEP
142+
printf("\nTesting I2S power down light sleep...\n");
143+
test_i2s_sleep_usability(true);
144+
#endif
145+
}

components/esp_driver_i2s/test_apps/i2s/pytest_i2s.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33
import pytest
44
from pytest_embedded import Dut
@@ -7,7 +7,7 @@
77
@pytest.mark.esp32
88
@pytest.mark.esp32s2
99
@pytest.mark.esp32c3
10-
# @pytest.mark.esp32c5 # TODO: [ESP32C5] IDF-10343
10+
@pytest.mark.esp32c5
1111
@pytest.mark.esp32c6
1212
@pytest.mark.esp32s3
1313
@pytest.mark.esp32h2

components/esp_driver_i2s/test_apps/i2s/sdkconfig.ci.release

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
33
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
44
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
55
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
6+
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
CONFIG_I2S_ENABLE_DEBUG_LOG=y
22
CONFIG_ESP_TASK_WDT_EN=n
3+
# primitives for checking sleep internal state
4+
CONFIG_ESP_SLEEP_DEBUG=y

0 commit comments

Comments
 (0)