feat(driver_spi): slave driver support sleep retention

This commit is contained in:
wanckl
2024-09-27 10:55:47 +08:00
parent d54390ec97
commit 9b7bbb1f0c
15 changed files with 224 additions and 30 deletions

View File

@@ -157,9 +157,10 @@
.flags=0,\
}
//default device config for slave hd devices
//default device config for slave hd devices, DMA is always required for slave hd
#define SPI_SLOT_TEST_DEFAULT_CONFIG() {\
.spics_io_num = PIN_NUM_CS, \
.dma_chan = SPI_DMA_CH_AUTO, \
.flags = 0, \
.mode = 0, \
.command_bits = 8,\
@@ -283,4 +284,9 @@ void spitest_gpio_input_sel(uint32_t gpio_num, int func, uint32_t signal_idx);
//then the cs_num of the 1st and 2nd devices are 0 and 1 respectively.
void same_pin_func_sel(spi_bus_config_t bus, spi_device_interface_config_t dev, uint8_t cs_num);
// Soft simulated spi master host for slave testing
// TODO: `speed_hz` is not implemented yet, temp to max 500Hz
// TODO: mode 0 only
void spi_master_trans_impl_gpio(spi_bus_config_t bus, uint8_t cs_pin, uint8_t speed_hz, void *tx, void *rx, uint32_t len);
#endif //_TEST_COMMON_SPI_H_

View File

@@ -111,7 +111,7 @@ void slave_pull_up(const spi_bus_config_t* cfg, int spics_io_num)
}
/**********************************************************************************
* functions for slave task
* functions for master task
*********************************************************************************/
static int test_len[] = {1, 3, 5, 7, 9, 11, 33, 64};
@@ -232,7 +232,6 @@ void spitest_gpio_input_sel(uint32_t gpio_num, int func, uint32_t signal_idx)
esp_rom_gpio_connect_in_signal(gpio_num, signal_idx, 0);
}
#if (TEST_SPI_PERIPH_NUM >= 2)
//Note this cs_dev_id is the ID of the connected devices' ID, e.g. if 2 devices are connected to the bus,
//then the cs_dev_id of the 1st and 2nd devices are 0 and 1 respectively.
void same_pin_func_sel(spi_bus_config_t bus, spi_device_interface_config_t dev, uint8_t cs_dev_id)
@@ -249,4 +248,32 @@ void same_pin_func_sel(spi_bus_config_t bus, spi_device_interface_config_t dev,
spitest_gpio_output_sel(bus.sclk_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spiclk_out);
spitest_gpio_input_sel(bus.sclk_io_num, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiclk_in);
}
#endif //(TEST_SPI_PERIPH_NUM >= 2)
void spi_master_trans_impl_gpio(spi_bus_config_t bus, uint8_t cs_pin, uint8_t speed_hz, void *tx, void *rx, uint32_t len)
{
uint8_t *u8_tx = tx, *u8_rx = rx;
esp_rom_gpio_connect_out_signal(cs_pin, SIG_GPIO_OUT_IDX, 0, 0);
esp_rom_gpio_connect_out_signal(bus.sclk_io_num, SIG_GPIO_OUT_IDX, 0, 0);
esp_rom_gpio_connect_out_signal(bus.mosi_io_num, SIG_GPIO_OUT_IDX, 0, 0);
esp_rom_gpio_connect_in_signal(bus.miso_io_num, SIG_GPIO_OUT_IDX, 0);
gpio_set_level(cs_pin, 0);
vTaskDelay(1); // cs_ena_pre_trans
for (uint32_t index = 0; index < len; index ++) {
uint8_t rx_data = 0;
for (uint8_t bit = 0x80; bit > 0; bit >>= 1) {
// mode 0, output data first
gpio_set_level(bus.mosi_io_num, (u8_tx) ? (u8_tx[index] & bit) : 0);
vTaskDelay(1);
gpio_set_level(bus.sclk_io_num, 1);
rx_data <<= 1;
rx_data |= gpio_get_level(bus.miso_io_num);
vTaskDelay(1);
gpio_set_level(bus.sclk_io_num, 0);
}
if (u8_rx) {
u8_rx[index] = rx_data;
}
}
gpio_set_level(cs_pin, 1);
}

View File

@@ -28,6 +28,7 @@
#include "driver/spi_slave.h"
#include "hal/gpio_hal.h"
#include "hal/spi_slave_hal.h"
#include "esp_private/sleep_retention.h"
#include "esp_private/spi_slave_internal.h"
#include "esp_private/spi_common_internal.h"
#include "esp_private/esp_cache_private.h"
@@ -129,6 +130,17 @@ static void ipc_isr_reg_to_core(void *args)
}
#endif
#if SOC_SPI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
static esp_err_t s_spi_create_sleep_retention_cb(void *arg)
{
spi_slave_t *context = arg;
return sleep_retention_entries_create(spi_reg_retention_info[context->id - 1].entry_array,
spi_reg_retention_info[context->id - 1].array_size,
REGDMA_LINK_PRI_GPSPI,
spi_reg_retention_info[context->id - 1].module_id);
}
#endif // SOC_SPI_SUPPORT_SLEEP_RETENTION
esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, const spi_slave_interface_config_t *slave_config, spi_dma_chan_t dma_chan)
{
bool spi_chan_claimed;
@@ -222,6 +234,32 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b
esp_pm_lock_acquire(spihost[host]->pm_lock);
#endif //CONFIG_PM_ENABLE
#if SOC_SPI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
.handle = s_spi_create_sleep_retention_cb,
.arg = spihost[host],
},
},
.depends = RETENTION_MODULE_BITMAP_INIT(CLOCK_SYSTEM),
};
if (ESP_OK == sleep_retention_module_init(spi_reg_retention_info[host - 1].module_id, &init_param)) {
if ((bus_config->flags & SPICOMMON_BUSFLAG_SLP_ALLOW_PD) && (sleep_retention_module_allocate(spi_reg_retention_info[host - 1].module_id) != ESP_OK)) {
// even though the sleep retention create failed, SPI driver should still work, so just warning here
ESP_LOGW(SPI_TAG, "Alloc sleep recover failed, spi may hold power on");
}
} else {
// even the sleep retention init failed, SPI driver should still work, so just warning here
ESP_LOGW(SPI_TAG, "Init sleep recover failed, spi may offline after sleep");
}
#else
if (bus_config->flags & SPICOMMON_BUSFLAG_SLP_ALLOW_PD) {
ESP_LOGE(SPI_TAG, "power down peripheral in sleep is not enabled or not supported on your target");
}
#endif // SOC_SPI_SUPPORT_SLEEP_RETENTION
//Create queues
spihost[host]->trans_queue = xQueueCreate(slave_config->queue_size, sizeof(spi_slave_trans_priv_t));
if (!spihost[host]->trans_queue) {
@@ -290,6 +328,17 @@ esp_err_t spi_slave_free(spi_host_device_t host)
}
spicommon_bus_free_io_cfg(&spihost[host]->bus_config);
esp_intr_free(spihost[host]->intr);
#if SOC_SPI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
const periph_retention_module_t retention_id = spi_reg_retention_info[spihost[host]->id - 1].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
#ifdef CONFIG_PM_ENABLE
if (spihost[host]->pm_lock) {
esp_pm_lock_release(spihost[host]->pm_lock);

View File

@@ -1527,10 +1527,16 @@ static void test_master_hd_dma(void)
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
for (uint8_t speed_level = 0; speed_level < sizeof(s_spi_bus_freq) / sizeof(int); speed_level++) {
spi_device_interface_config_t devcfg = SPI_SLOT_TEST_DEFAULT_CONFIG();
devcfg.mode = mode;
devcfg.flags = SPI_DEVICE_HALFDUPLEX;
devcfg.clock_speed_hz = s_spi_bus_freq[speed_level];
spi_device_interface_config_t devcfg = {
.spics_io_num = PIN_NUM_CS,
.clock_speed_hz = s_spi_bus_freq[speed_level],
.mode = mode,
.flags = SPI_DEVICE_HALFDUPLEX,
.command_bits = 8,
.address_bits = 8,
.dummy_bits = 8,
.queue_size = 10,
};
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev0));
printf("Next trans: %s\tmode:%d\t@%.2f MHz\n", (is_gpio) ? "GPIO_Matrix" : "IOMUX", mode, s_spi_bus_freq[speed_level] / 1000000.f);
@@ -1629,10 +1635,16 @@ static void test_master_hd_no_dma(void)
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_DISABLED));
for (uint8_t speed_level = 0; speed_level < sizeof(s_spi_bus_freq) / sizeof(int); speed_level++) {
spi_device_interface_config_t devcfg = SPI_SLOT_TEST_DEFAULT_CONFIG();
devcfg.mode = mode;
devcfg.flags = SPI_DEVICE_HALFDUPLEX;
devcfg.clock_speed_hz = s_spi_bus_freq[speed_level];
spi_device_interface_config_t devcfg = {
.spics_io_num = PIN_NUM_CS,
.clock_speed_hz = s_spi_bus_freq[speed_level],
.mode = mode,
.flags = SPI_DEVICE_HALFDUPLEX,
.command_bits = 8,
.address_bits = 8,
.dummy_bits = 8,
.queue_size = 10,
};
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev0));
printf("Next trans: %s\tmode:%d\t@%.2f MHz\n", (is_gpio) ? "GPIO_Matrix" : "IOMUX", mode, s_spi_bus_freq[speed_level] / 1000000.f);

View File

@@ -18,6 +18,9 @@
#include "driver/gpio.h"
#include "esp_private/cache_utils.h"
#include "esp_private/spi_slave_internal.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/esp_pmu.h"
#include "esp_log.h"
#include "esp_rom_gpio.h"
@@ -778,3 +781,62 @@ TEST_CASE("test_slave_isr_pin_to_core", "[spi]")
TEST_ASSERT_EQUAL_UINT32(TEST_ISR_CNT, slave_expect);
}
#endif
TEST_CASE("test spi slave sleep retention", "[spi]")
{
// Prepare a TOP PD sleep
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1 * 1000 * 1000));
#if ESP_SLEEP_POWER_DOWN_CPU
sleep_cpu_configure(true);
#endif
esp_sleep_context_t sleep_ctx;
esp_sleep_set_sleep_context(&sleep_ctx);
uint8_t slv_send[14] = "I'm slave x\n", slv_rexcv[14];
uint8_t mst_send[14] = "I'm master x\n", mst_rexcv[14];
spi_slave_transaction_t *ret_trans, trans_cfg = {
.tx_buffer = slv_send,
.rx_buffer = slv_rexcv,
.length = sizeof(slv_send) * 8,
};
for (uint8_t allow_pd = 0; allow_pd < 2; allow_pd ++) {
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.flags = (allow_pd) ? SPICOMMON_BUSFLAG_SLP_ALLOW_PD : 0;
buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS;
spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG();
TEST_ESP_OK(spi_slave_initialize(TEST_SPI_HOST, &buscfg, &slvcfg, SPI_DMA_DISABLED));
gpio_pullup_en(slvcfg.spics_io_num);
vTaskDelay(1);
for (uint8_t cnt = 0; cnt < 3; cnt ++) {
printf("Going into sleep with power %s ...\n", (buscfg.flags & SPICOMMON_BUSFLAG_SLP_ALLOW_PD) ? "down" : "hold");
TEST_ESP_OK(esp_light_sleep_start());
printf("Waked up!\n");
// check if the sleep happened as expected
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
#if SOC_SPI_SUPPORT_SLEEP_RETENTION
// check if the power domain also is powered down
TEST_ASSERT_EQUAL((buscfg.flags & SPICOMMON_BUSFLAG_SLP_ALLOW_PD) ? PMU_SLEEP_PD_TOP : 0, (sleep_ctx.sleep_flags) & PMU_SLEEP_PD_TOP);
#endif
slv_send[11] = cnt + '0';
mst_send[11] = cnt + 'A';
memset(mst_rexcv, 0, sizeof(mst_rexcv));
memset(slv_rexcv, 0, sizeof(slv_rexcv));
TEST_ESP_OK(spi_slave_queue_trans(TEST_SPI_HOST, &trans_cfg, portMAX_DELAY));
spi_master_trans_impl_gpio(buscfg, slvcfg.spics_io_num, 0, mst_send, mst_rexcv, sizeof(mst_send));
TEST_ESP_OK(spi_slave_get_trans_result(TEST_SPI_HOST, &ret_trans, portMAX_DELAY));
spitest_cmp_or_dump(slv_send, mst_rexcv, sizeof(mst_rexcv));
spitest_cmp_or_dump(mst_send, slv_rexcv, sizeof(slv_rexcv));
}
TEST_ESP_OK(spi_slave_free(TEST_SPI_HOST));
}
esp_sleep_set_sleep_context(NULL);
#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(false));
#endif
}

View File

@@ -1,2 +0,0 @@
# don't delete.
# used for CI to compile a default config when 'sdkconfig.ci.xxxx' is exist

View File

@@ -0,0 +1,6 @@
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

View File

@@ -1,2 +1,4 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT=n
CONFIG_ESP_TASK_WDT_INIT=n
# primitives for checking sleep internal state
CONFIG_ESP_SLEEP_DEBUG=y

View File

@@ -40,7 +40,7 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
};
/**
* Backup registers in Light sleep: (total cnt 12)
* Backup registers in Light sleep: (total cnt 29)
*
* cmd
* addr
@@ -53,10 +53,12 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
* misc
* dma_conf
* dma_int_ena
* data_buf[0-15] // slave driver only
* slave
* slave1
*/
#define SPI_RETENTION_REGS_CNT 12
static const uint32_t spi_regs_map[4] = {0x31ff, 0x1000000, 0x0, 0x0};
#define SPI_RETENTION_REGS_CNT 29
static const uint32_t spi_regs_map[4] = {0x31ff, 0x33fffc0, 0x0, 0x0};
#define SPI_REG_RETENTION_ENTRIES(num) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT(REGDMA_GPSPI_LINK(0), \
REG_SPI_BASE(num), REG_SPI_BASE(num), \

View File

@@ -41,7 +41,7 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
/**
* Backup registers in Light sleep: (total cnt 12)
* Backup registers in Light sleep: (total cnt 29)
*
* cmd
* addr
@@ -54,10 +54,12 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
* misc
* dma_conf
* dma_int_ena
* data_buf[0-15] // slave driver only
* slave
* slave1
*/
#define SPI_RETENTION_REGS_CNT 12
static const uint32_t spi_regs_map[4] = {0x31ff, 0x1000000, 0x0, 0x0};
#define SPI_RETENTION_REGS_CNT 29
static const uint32_t spi_regs_map[4] = {0x31ff, 0x33fffc0, 0x0, 0x0};
#define SPI_REG_RETENTION_ENTRIES(num) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT(REGDMA_GPSPI_LINK(0), \
REG_SPI_BASE(num), REG_SPI_BASE(num), \

View File

@@ -40,7 +40,7 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
};
/**
* Backup registers in Light sleep: (total cnt 12)
* Backup registers in Light sleep: (total cnt 29)
*
* cmd
* addr
@@ -53,10 +53,12 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
* misc
* dma_conf
* dma_int_ena
* data_buf[0-15] // slave driver only
* slave
* slave1
*/
#define SPI_RETENTION_REGS_CNT 12
static const uint32_t spi_regs_map[4] = {0x31ff, 0x1000000, 0x0, 0x0};
#define SPI_RETENTION_REGS_CNT 29
static const uint32_t spi_regs_map[4] = {0x31ff, 0x33fffc0, 0x0, 0x0};
#define SPI_REG_RETENTION_ENTRIES(num) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT(REGDMA_GPSPI_LINK(0), \
REG_SPI_BASE(num), REG_SPI_BASE(num), \

View File

@@ -40,7 +40,7 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
};
/**
* Backup registers in Light sleep: (total cnt 12)
* Backup registers in Light sleep: (total cnt 29)
*
* cmd
* addr
@@ -53,10 +53,12 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
* misc
* dma_conf
* dma_int_ena
* data_buf[0-15] // slave driver only
* slave
* slave1
*/
#define SPI_RETENTION_REGS_CNT 12
static const uint32_t spi_regs_map[4] = {0x31ff, 0x1000000, 0x0, 0x0};
#define SPI_RETENTION_REGS_CNT 29
static const uint32_t spi_regs_map[4] = {0x31ff, 0x33fffc0, 0x0, 0x0};
#define SPI_REG_RETENTION_ENTRIES(num) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT(REGDMA_GPSPI_LINK(0), \
REG_SPI_BASE(num), REG_SPI_BASE(num), \

View File

@@ -72,7 +72,7 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
};
/**
* Backup registers in Light sleep: (total cnt 12)
* Backup registers in Light sleep: (total cnt 29)
*
* cmd
* addr
@@ -85,10 +85,12 @@ const spi_signal_conn_t spi_periph_signal[SOC_SPI_PERIPH_NUM] = {
* misc
* dma_conf
* dma_int_ena
* data_buf[0-15] // slave driver only
* slave
* slave1
*/
#define SPI_RETENTION_REGS_CNT 12
static const uint32_t spi_regs_map[4] = {0x31ff, 0x1000000, 0x0, 0x0};
#define SPI_RETENTION_REGS_CNT 29
static const uint32_t spi_regs_map[4] = {0x31ff, 0x33fffc0, 0x0, 0x0};
#define SPI_REG_RETENTION_ENTRIES(num) { \
[0] = { .config = REGDMA_LINK_ADDR_MAP_INIT(REGDMA_GPSPI_LINK(0), \
REG_SPI_BASE(num), REG_SPI_BASE(num), \

View File

@@ -55,6 +55,17 @@ The SPI slave driver allows using the SPI peripherals as full-duplex Devices. Th
The SPI slave driver supports registering the SPI ISR to a certain CPU core. If multiple tasks try to access the same SPI Device simultaneously, it is recommended that your application be refactored so that each SPI peripheral is only accessed by a single task at a time. Please also use :cpp:member:`spi_bus_config_t::isr_cpu_id` to register the SPI ISR to the same core as SPI peripheral related tasks to ensure thread safety.
.. only:: SOC_SPI_SUPPORT_SLEEP_RETENTION
Sleep Retention
^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports to retain the SPI register context before entering **light sleep** and restore them after waking up. This means you don't have to re-init the SPI driver after the light sleep.
This feature can be enabled by setting the flag :c:macro:`SPICOMMON_BUSFLAG_SLP_ALLOW_PD`. It will allow the system to power down the SPI in light sleep, meanwhile save the register context. It can help to save more power consumption with some extra cost of the memory.
Notice that when GPSPI is working as a slave, it is **not** support to enter sleep when any transaction (including TX and RX) is not finished.
SPI Transactions
----------------

View File

@@ -55,6 +55,17 @@ SPI 从机驱动程序允许将 SPI 外设作为全双工设备使用。驱动
SPI 从机驱动程序支持将 SPI ISR 注册至指定 CPU 内核。如果多个任务同时尝试访问一个 SPI 设备,建议重构应用程序,以使每个 SPI 外设一次只由一个任务访问。此外,请使用 :cpp:member:`spi_bus_config_t::isr_cpu_id` 将 SPI ISR 注册至与 SPI 外设相关任务相同的内核,确保线程安全。
.. only:: SOC_SPI_SUPPORT_SLEEP_RETENTION
睡眠保留
^^^^^^^^
{IDF_TARGET_NAME} 支持在进入 **Light Sleep** 之前保留 SPI 寄存器中的内容,并在唤醒后恢复。即程序不需要在 **Light Sleep** 唤醒后重新配置 SPI。
该特性可以通过置位配置中的 :c:macro:`SPICOMMON_BUSFLAG_SLP_ALLOW_PD` 标志位启用。启用后驱动允许系统在 Light Sleep 时对 SPI 掉电,同时保存寄存器配置。它可以帮助降低轻度睡眠时的功耗,但需要花费一些额外的存储来保存寄存器的配置。
注意在 Slave 角色下,不支持在所有传输(发送和接收)未完成时进入睡眠,否则将会出错。
SPI 传输事务
----------------