Skip to content

Commit

Permalink
Merge branch 'feat/rmt_regdma_link_esp32c5' into 'master'
Browse files Browse the repository at this point in the history
feat(rmt): support sleep retention by regdma on esp32c5

Closes IDF-10917, IDF-11123, DOC-9018, and DOC-9019

See merge request espressif/esp-idf!33690
  • Loading branch information
suda-morris committed Sep 23, 2024
2 parents bd48184 + 7e4ce00 commit f91ea4d
Show file tree
Hide file tree
Showing 20 changed files with 138 additions and 90 deletions.
2 changes: 1 addition & 1 deletion components/esp_driver_gpio/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GPIO API usage in Peripheral Drivers

When a peripheral driver initializes IOs, the general rule is to append necessary configurations onto the IO, instead of using any GPIO APIs that overwrite the current IO setting, such as `gpio_config`, `gpio_set_direction`, `gpio_set_pull_mode`.
When a peripheral driver initializes IOs, the general rule is to append necessary configurations onto the IO, instead of using any GPIO APIs that overwrite the current IO setting, such as `gpio_config`, `gpio_reset_pin`, `gpio_set_direction`, `gpio_set_pull_mode`.

Use `gpio_pullup_en` and `gpio_pulldown_en` to enable the internal pull-up/pull-down resistors if necessary.

Expand Down
5 changes: 2 additions & 3 deletions components/esp_driver_rmt/include/driver/rmt_rx.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ typedef struct {
uint32_t invert_in: 1; /*!< Whether to invert the incoming RMT channel signal */
uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
uint32_t backup_before_sleep: 1; /*!< If set, the driver will backup/restore the RMT registers before/after entering/exist sleep mode.
By this approach, the system can power off RMT's power domain.
This can save power, but at the expense of more RAM being consumed */
uint32_t allow_pd: 1; /*!< If set, driver allows the power domain to be powered off when system enters sleep mode.
This can save power, but at the expense of more RAM being consumed to save register context. */
} flags; /*!< RX channel config flags */
} rmt_rx_channel_config_t;

Expand Down
5 changes: 2 additions & 3 deletions components/esp_driver_rmt/include/driver/rmt_tx.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ typedef struct {
uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */
uint32_t io_loop_back: 1; /*!< The signal output from the GPIO will be fed to the input path as well */
uint32_t io_od_mode: 1; /*!< Configure the GPIO as open-drain mode */
uint32_t backup_before_sleep: 1; /*!< If set, the driver will backup/restore the RMT registers before/after entering/exist sleep mode.
By this approach, the system can power off RMT's power domain.
This can save power, but at the expense of more RAM being consumed */
uint32_t allow_pd: 1; /*!< If set, driver allows the power domain to be powered off when system enters sleep mode.
This can save power, but at the expense of more RAM being consumed to save register context. */
} flags; /*!< TX channel config flags */
} rmt_tx_channel_config_t;

Expand Down
41 changes: 20 additions & 21 deletions components/esp_driver_rmt/src/rmt_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ rmt_group_t *rmt_acquire_group_handle(int group_id)
rmt_ll_enable_bus_clock(group_id, true);
rmt_ll_reset_register(group_id);
}
#if RMT_USE_RETENTION_LINK
sleep_retention_module_t module = RMT_LL_SLEEP_RETENTION_MODULE_ID(group_id);
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
.handle = rmt_create_sleep_retention_link_cb,
.arg = group,
},
},
.depends = BIT(SLEEP_RETENTION_MODULE_CLOCK_SYSTEM)
};
if (sleep_retention_module_init(module, &init_param) == ESP_OK) {
group->sleep_retention_module = module;
} else {
// even though the sleep retention module init failed, RMT driver should still work, so just warning here
ESP_LOGW(TAG, "init sleep retention failed %d, power domain may be turned off during sleep", group_id);
}
#endif // RMT_USE_RETENTION_LINK
// hal layer initialize
rmt_hal_init(&group->hal);
}
Expand All @@ -87,24 +105,6 @@ rmt_group_t *rmt_acquire_group_handle(int group_id)
_lock_release(&s_platform.mutex);

if (new_group) {
#if RMT_USE_RETENTION_LINK
sleep_retention_module_t module = RMT_LL_SLEEP_RETENTION_MODULE_ID(group_id);
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
.handle = rmt_create_sleep_retention_link_cb,
.arg = group,
},
},
.depends = BIT(SLEEP_RETENTION_MODULE_CLOCK_SYSTEM)
};
if (sleep_retention_module_init(module, &init_param) == ESP_OK) {
group->sleep_retention_module = module;
} else {
// even though the sleep retention module init failed, RMT driver should still work, so just warning here
ESP_LOGW(TAG, "init sleep retention failed %d, power domain may be turned off during sleep", group_id);
}
#endif // RMT_USE_RETENTION_LINK
ESP_LOGD(TAG, "new group(%d) at %p, occupy=%"PRIx32, group_id, group, group->occupy_mask);
}
return group;
Expand Down Expand Up @@ -307,8 +307,7 @@ static esp_err_t rmt_create_sleep_retention_link_cb(void *arg)
esp_err_t err = sleep_retention_entries_create(rmt_reg_retention_info[group_id].regdma_entry_array,
rmt_reg_retention_info[group_id].array_size,
REGDMA_LINK_PRI_RMT, module);
ESP_RETURN_ON_ERROR(err, TAG, "create retention link failed");
return ESP_OK;
return err;
}

void rmt_create_retention_module(rmt_group_t *group)
Expand All @@ -319,7 +318,7 @@ void rmt_create_retention_module(rmt_group_t *group)
if (group->retention_link_created == false) {
if (sleep_retention_module_allocate(module) != ESP_OK) {
// even though the sleep retention module create failed, RMT driver should still work, so just warning here
ESP_LOGW(TAG, "create retention module failed, power domain can't turn off");
ESP_LOGW(TAG, "create retention link failed, power domain can't be turned off");
} else {
group->retention_link_created = true;
}
Expand Down
32 changes: 17 additions & 15 deletions components/esp_driver_rmt/src/rmt_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ static void rmt_rx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *gr
static esp_err_t rmt_rx_destroy(rmt_rx_channel_t *rx_channel)
{
if (rx_channel->base.gpio_num >= 0) {
gpio_reset_pin(rx_channel->base.gpio_num);
int group_id = rx_channel->base.group->group_id;
int channel_id = rx_channel->base.channel_id;
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT,
rmt_periph_signals.groups[group_id].channels[channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP].rx_sig,
false);
}
if (rx_channel->base.intr) {
ESP_RETURN_ON_ERROR(esp_intr_free(rx_channel->base.intr), TAG, "delete interrupt service failed");
Expand Down Expand Up @@ -197,7 +201,7 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
#endif // SOC_RMT_SUPPORT_DMA

#if !SOC_RMT_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(config->flags.backup_before_sleep == 0, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
ESP_RETURN_ON_FALSE(config->flags.allow_pd == 0, ESP_ERR_NOT_SUPPORTED, TAG, "not able to power down in light sleep");
#endif // SOC_RMT_SUPPORT_SLEEP_RETENTION

// malloc channel memory
Expand Down Expand Up @@ -240,7 +244,7 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
int group_id = group->group_id;

#if RMT_USE_RETENTION_LINK
if (config->flags.backup_before_sleep != 0) {
if (config->flags.allow_pd != 0) {
rmt_create_retention_module(group);
}
#endif // RMT_USE_RETENTION_LINK
Expand Down Expand Up @@ -303,20 +307,18 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
#endif

// GPIO Matrix/MUX configuration
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
// also enable the input path is `io_loop_back` is on, this is useful for debug
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0),
.pull_down_en = false,
.pull_up_en = true,
.pin_bit_mask = BIT64(config->gpio_num),
};
// gpio_config also connects the IO_MUX to the GPIO matrix
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed");
rx_channel->base.gpio_num = config->gpio_num;
gpio_func_sel(config->gpio_num, PIN_FUNC_GPIO);
gpio_input_enable(config->gpio_num);
gpio_pullup_en(config->gpio_num);
esp_rom_gpio_connect_in_signal(config->gpio_num,
rmt_periph_signals.groups[group_id].channels[channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP].rx_sig,
config->flags.invert_in);
rx_channel->base.gpio_num = config->gpio_num;

// deprecated, to be removed in in esp-idf v6.0
if (config->flags.io_loop_back) {
gpio_ll_output_enable(&GPIO, config->gpio_num);
}

// initialize other members of rx channel
portMUX_INITIALIZE(&rx_channel->base.spinlock);
Expand Down Expand Up @@ -400,7 +402,7 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_

// check buffer alignment
uint32_t align_check_mask = mem_alignment - 1;
ESP_RETURN_ON_FALSE_ISR((((uintptr_t)buffer & align_check_mask) == 0) && ((buffer_size & align_check_mask) == 0), ESP_ERR_INVALID_ARG,
ESP_RETURN_ON_FALSE_ISR(((((uintptr_t)buffer) & align_check_mask) == 0) && (((buffer_size) & align_check_mask) == 0), ESP_ERR_INVALID_ARG,
TAG, "buffer address or size are not %zu bytes aligned", mem_alignment);

rmt_group_t *group = channel->group;
Expand Down
27 changes: 14 additions & 13 deletions components/esp_driver_rmt/src/rmt_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ static esp_err_t rmt_tx_create_trans_queue(rmt_tx_channel_t *tx_channel, const r
static esp_err_t rmt_tx_destroy(rmt_tx_channel_t *tx_channel)
{
if (tx_channel->base.gpio_num >= 0) {
gpio_reset_pin(tx_channel->base.gpio_num);
gpio_output_disable(tx_channel->base.gpio_num);
esp_gpio_revoke(BIT64(tx_channel->base.gpio_num));
}
if (tx_channel->base.intr) {
Expand Down Expand Up @@ -266,7 +266,7 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_
#endif

#if !SOC_RMT_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(config->flags.backup_before_sleep == 0, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
ESP_RETURN_ON_FALSE(config->flags.allow_pd == 0, ESP_ERR_NOT_SUPPORTED, TAG, "not able to power down in light sleep");
#endif // SOC_RMT_SUPPORT_SLEEP_RETENTION

// malloc channel memory
Expand Down Expand Up @@ -302,7 +302,7 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_
int group_id = group->group_id;

#if RMT_USE_RETENTION_LINK
if (config->flags.backup_before_sleep != 0) {
if (config->flags.allow_pd != 0) {
rmt_create_retention_module(group);
}
#endif // RMT_USE_RETENTION_LINK
Expand Down Expand Up @@ -352,27 +352,28 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_
// always enable tx wrap, both DMA mode and ping-pong mode rely this feature
rmt_ll_tx_enable_wrap(hal->regs, channel_id, true);

// GPIO Matrix/MUX configuration
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
// also enable the input path if `io_loop_back` is on, this is useful for bi-directional buses
.mode = (config->flags.io_od_mode ? GPIO_MODE_OUTPUT_OD : GPIO_MODE_OUTPUT) | (config->flags.io_loop_back ? GPIO_MODE_INPUT : 0),
.pull_down_en = false,
.pull_up_en = true,
.pin_bit_mask = BIT64(config->gpio_num),
};
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed");
// reserve the GPIO output path, because we don't expect another peripheral to signal to the same GPIO
uint64_t old_gpio_rsv_mask = esp_gpio_reserve(BIT64(config->gpio_num));
// check if the GPIO is already used by others, RMT TX channel only uses the output path of the GPIO
if (old_gpio_rsv_mask & BIT64(config->gpio_num)) {
ESP_LOGW(TAG, "GPIO %d is not usable, maybe conflict with others", config->gpio_num);
}
// GPIO Matrix/MUX configuration
gpio_func_sel(config->gpio_num, PIN_FUNC_GPIO);
// connect the signal to the GPIO by matrix, it will also enable the output path properly
esp_rom_gpio_connect_out_signal(config->gpio_num,
rmt_periph_signals.groups[group_id].channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].tx_sig,
config->flags.invert_out, false);
tx_channel->base.gpio_num = config->gpio_num;

// deprecated, to be removed in in esp-idf v6.0
if (config->flags.io_loop_back) {
gpio_ll_input_enable(&GPIO, config->gpio_num);
}
if (config->flags.io_od_mode) {
gpio_ll_od_enable(&GPIO, config->gpio_num);
}

portMUX_INITIALIZE(&tx_channel->base.spinlock);
atomic_init(&tx_channel->base.fsm, RMT_FSM_INIT);
tx_channel->base.direction = RMT_CHANNEL_DIRECTION_TX;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ TEST_CASE("RMT interrupt priority", "[rmt]")
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
.gpio_num = 0,
.flags.with_dma = false, // Interrupt will only be allocated when dma disabled
.flags.io_loop_back = true, // the GPIO will act like a loopback
.intr_priority = 3,
};
// --- Check if specifying interrupt priority works
Expand Down
7 changes: 6 additions & 1 deletion components/esp_driver_rmt/test_apps/rmt/main/test_rmt_iram.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,18 @@ static void test_rmt_rx_iram_safe(size_t mem_block_symbols, bool with_dma, rmt_c
MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(remote_codes);

gpio_config_t sig_simulator_io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << TEST_RMT_GPIO_NUM_A,
};
TEST_ESP_OK(gpio_config(&sig_simulator_io_conf));

rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = clk_src,
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
.mem_block_symbols = mem_block_symbols,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.with_dma = with_dma,
.flags.io_loop_back = true, // the GPIO will act like a loopback
};
printf("install rx channel\r\n");
rmt_channel_handle_t rx_channel = NULL;
Expand Down
11 changes: 6 additions & 5 deletions components/esp_driver_rmt/test_apps/rmt/main/test_rmt_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ static void test_rmt_rx_nec_carrier(size_t mem_block_symbols, bool with_dma, rmt
.mem_block_symbols = mem_block_symbols,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.with_dma = with_dma,
.flags.io_loop_back = true, // the GPIO will act like a loopback
};
printf("install rx channel\r\n");
rmt_channel_handle_t rx_channel = NULL;
Expand All @@ -75,7 +74,6 @@ static void test_rmt_rx_nec_carrier(size_t mem_block_symbols, bool with_dma, rmt
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.trans_queue_depth = 4,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.io_loop_back = true, // TX channel and RX channel will bounded to the same GPIO
};
printf("install tx channel\r\n");
rmt_channel_handle_t tx_channel = NULL;
Expand Down Expand Up @@ -240,13 +238,18 @@ static void test_rmt_partial_receive(size_t mem_block_symbols, bool with_dma, rm
MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(receive_user_buf);

gpio_config_t sig_simulator_io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << TEST_RMT_GPIO_NUM_A,
};
TEST_ESP_OK(gpio_config(&sig_simulator_io_conf));

rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = clk_src,
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
.mem_block_symbols = mem_block_symbols,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.with_dma = with_dma,
.flags.io_loop_back = true, // the GPIO will act like a loopback
};
printf("install rx channel\r\n");
rmt_channel_handle_t rx_channel = NULL;
Expand Down Expand Up @@ -332,7 +335,6 @@ static void test_rmt_receive_filter(rmt_clock_source_t clk_src)
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.io_loop_back = true, // the GPIO will act like a loopback
};
printf("install rx channel\r\n");
rmt_channel_handle_t rx_channel = NULL;
Expand All @@ -355,7 +357,6 @@ static void test_rmt_receive_filter(rmt_clock_source_t clk_src)
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.trans_queue_depth = 4,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.io_loop_back = true, // TX channel and RX channel will bounded to the same GPIO
};
printf("install tx channel\r\n");
rmt_channel_handle_t tx_channel = NULL;
Expand Down
21 changes: 9 additions & 12 deletions components/esp_driver_rmt/test_apps/rmt/main/test_rmt_sleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include "test_util_rmt_encoders.h"
#include "test_board.h"

#if SOC_RMT_SUPPORT_SLEEP_RETENTION // TODO: IDF-10917
typedef struct {
TaskHandle_t task_to_notify;
size_t received_symbol_num;
Expand All @@ -43,9 +42,9 @@ static bool test_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx
/**
* @brief Test the RMT driver can still work after light sleep
*
* @param back_up_before_sleep Whether to back up RMT registers before sleep
* @param allow_pd Whether to allow power down the peripheral in light sleep
*/
static void test_rmt_tx_rx_sleep_retention(bool back_up_before_sleep)
static void test_rmt_tx_rx_sleep_retention(bool allow_pd)
{
uint32_t const test_rx_buffer_symbols = 128;
rmt_symbol_word_t *remote_codes = heap_caps_aligned_calloc(64, test_rx_buffer_symbols, sizeof(rmt_symbol_word_t),
Expand All @@ -57,8 +56,7 @@ static void test_rmt_tx_rx_sleep_retention(bool back_up_before_sleep)
.resolution_hz = 1000000,
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.io_loop_back = true,
.flags.backup_before_sleep = back_up_before_sleep,
.flags.allow_pd = allow_pd,
};
printf("install rx channel\r\n");
rmt_channel_handle_t rx_channel = NULL;
Expand All @@ -78,8 +76,7 @@ static void test_rmt_tx_rx_sleep_retention(bool back_up_before_sleep)
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.trans_queue_depth = 4,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.io_loop_back = true,
.flags.backup_before_sleep = back_up_before_sleep,
.flags.allow_pd = allow_pd,
};
printf("install tx channel\r\n");
rmt_channel_handle_t tx_channel = NULL;
Expand Down Expand Up @@ -111,13 +108,12 @@ static void test_rmt_tx_rx_sleep_retention(bool back_up_before_sleep)
printf("check if the sleep happened as expected\r\n");
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
#if SOC_RMT_SUPPORT_SLEEP_RETENTION
if (back_up_before_sleep) {
printf("sleep_ctx.sleep_flags=%lx\r\n", sleep_ctx.sleep_flags);
TEST_ASSERT_EQUAL(PMU_SLEEP_PD_TOP, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
}
// check if the power domain also is powered down
TEST_ASSERT_EQUAL(allow_pd ? PMU_SLEEP_PD_TOP : 0, (sleep_ctx.sleep_flags) & PMU_SLEEP_PD_TOP);
#endif
esp_sleep_set_sleep_context(NULL);

// enable both channels, pm lock will be acquired, so no light sleep will event happen during the transaction
TEST_ESP_OK(rmt_enable(tx_channel));
TEST_ESP_OK(rmt_enable(rx_channel));

Expand Down Expand Up @@ -147,6 +143,7 @@ static void test_rmt_tx_rx_sleep_retention(bool back_up_before_sleep)
TEST_CASE("rmt tx+rx after light sleep", "[rmt]")
{
test_rmt_tx_rx_sleep_retention(false);
#if SOC_RMT_SUPPORT_SLEEP_RETENTION
test_rmt_tx_rx_sleep_retention(true);
}
#endif
}
Loading

0 comments on commit f91ea4d

Please sign in to comment.