Skip to content

Commit

Permalink
Merge branch 'feat/rmt_dma_burst_size' into 'master'
Browse files Browse the repository at this point in the history
feat(rmt): enable DMA burst transfer

See merge request espressif/esp-idf!31127
  • Loading branch information
suda-morris committed Jul 20, 2024
2 parents 4178315 + 6960c74 commit 0f6004e
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 85 deletions.
4 changes: 2 additions & 2 deletions components/driver/deprecated/rmt_legacy.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ static void rmt_module_enable(void)
{
RMT_ENTER_CRITICAL();
if (rmt_contex.rmt_module_enabled == false) {
rmt_ll_mem_power_by_pmu(rmt_contex.hal.regs);
RMT_RCC_ATOMIC() {
rmt_ll_enable_bus_clock(0, true);
rmt_ll_reset_register(0);
}
rmt_ll_mem_power_by_pmu(rmt_contex.hal.regs);
rmt_contex.rmt_module_enabled = true;
}
RMT_EXIT_CRITICAL();
Expand All @@ -154,10 +154,10 @@ static void rmt_module_disable(void)
{
RMT_ENTER_CRITICAL();
if (rmt_contex.rmt_module_enabled == true) {
rmt_ll_mem_force_power_off(rmt_contex.hal.regs);
RMT_RCC_ATOMIC() {
rmt_ll_enable_bus_clock(0, false);
}
rmt_ll_mem_force_power_off(rmt_contex.hal.regs);
rmt_contex.rmt_module_enabled = false;
}
RMT_EXIT_CRITICAL();
Expand Down
4 changes: 1 addition & 3 deletions components/esp_driver_rmt/src/rmt_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ extern "C" {

#define RMT_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED

// DMA buffer size must align to `rmt_symbol_word_t`
#define RMT_DMA_DESC_BUF_MAX_SIZE (DMA_DESCRIPTOR_BUFFER_MAX_SIZE & ~(sizeof(rmt_symbol_word_t) - 1))

#define RMT_DMA_NODES_PING_PONG 2 // two nodes ping-pong
#define RMT_PM_LOCK_NAME_LEN_MAX 16
#define RMT_GROUP_INTR_PRIORITY_UNINITIALIZED (-1)
Expand Down Expand Up @@ -205,6 +202,7 @@ struct rmt_rx_channel_t {
void *user_data; // user context
rmt_rx_trans_desc_t trans_desc; // transaction description
size_t num_dma_nodes; // number of DMA nodes, determined by how big the memory block that user configures
size_t dma_int_mem_alignment; // DMA buffer alignment (both in size and address) for internal RX memory
rmt_dma_descriptor_t *dma_nodes; // DMA link nodes
rmt_dma_descriptor_t *dma_nodes_nc; // DMA descriptor nodes accessed in non-cached way
};
Expand Down
84 changes: 48 additions & 36 deletions components/esp_driver_rmt/src/rmt_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,26 @@ static esp_err_t rmt_rx_init_dma_link(rmt_rx_channel_t *rx_channel, const rmt_rx
.direction = GDMA_CHANNEL_DIRECTION_RX,
};
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_chan_config, &rx_channel->base.dma_chan), TAG, "allocate RX DMA channel failed");

// circular DMA descriptor
for (int i = 0; i < rx_channel->num_dma_nodes; i++) {
rx_channel->dma_nodes_nc[i].next = &rx_channel->dma_nodes[i + 1];
}
rx_channel->dma_nodes_nc[rx_channel->num_dma_nodes - 1].next = &rx_channel->dma_nodes[0];
gdma_transfer_config_t transfer_cfg = {
.access_ext_mem = false, // [IDF-8997]: PSRAM is not supported yet
.max_data_burst_size = 32,
};
ESP_RETURN_ON_ERROR(gdma_config_transfer(rx_channel->base.dma_chan, &transfer_cfg), TAG, "config DMA transfer failed");
// get the alignment requirement from DMA
gdma_get_alignment_constraints(rx_channel->base.dma_chan, &rx_channel->dma_int_mem_alignment, NULL);

// register event callbacks
gdma_rx_event_callbacks_t cbs = {
.on_recv_done = rmt_dma_rx_one_block_cb,
};
// register the DMA callbacks may fail if the interrupt service can not be installed successfully
ESP_RETURN_ON_ERROR(gdma_register_rx_event_callbacks(rx_channel->base.dma_chan, &cbs, rx_channel), TAG, "register DMA callbacks failed");

// circular DMA descriptor
for (int i = 0; i < rx_channel->num_dma_nodes - 1; i++) {
rx_channel->dma_nodes_nc[i].next = &rx_channel->dma_nodes[i + 1];
}
rx_channel->dma_nodes_nc[rx_channel->num_dma_nodes - 1].next = &rx_channel->dma_nodes[0];
return ESP_OK;
}
#endif // SOC_RMT_SUPPORT_DMA
Expand Down Expand Up @@ -199,32 +206,32 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
ESP_GOTO_ON_FALSE(rx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for rx channel");
// gpio is not configured yet
rx_channel->base.gpio_num = -1;

#if SOC_RMT_SUPPORT_DMA
// create DMA descriptor
size_t num_dma_nodes = 0;
if (config->flags.with_dma) {
// DMA descriptors must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
num_dma_nodes = config->mem_block_symbols * sizeof(rmt_symbol_word_t) / RMT_DMA_DESC_BUF_MAX_SIZE + 1;
num_dma_nodes = config->mem_block_symbols * sizeof(rmt_symbol_word_t) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1;
num_dma_nodes = MAX(2, num_dma_nodes); // at least 2 DMA nodes for ping-pong
// DMA descriptors must be placed in internal SRAM
uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
// the alignment should meet both the DMA and cache requirement
size_t alignment = MAX(data_cache_line_size, RMT_DMA_DESC_ALIGN);
size_t dma_nodes_size = ALIGN_UP(num_dma_nodes * sizeof(rmt_dma_descriptor_t), alignment);
rmt_dma_descriptor_t *dma_nodes = heap_caps_aligned_calloc(alignment, 1, dma_nodes_size, mem_caps);
rmt_dma_descriptor_t *dma_nodes = heap_caps_aligned_calloc(RMT_DMA_DESC_ALIGN, num_dma_nodes, sizeof(rmt_dma_descriptor_t), mem_caps);
ESP_GOTO_ON_FALSE(dma_nodes, ESP_ERR_NO_MEM, err, TAG, "no mem for rx channel DMA nodes");
rx_channel->dma_nodes = dma_nodes;
// do memory sync only when the data cache exists
uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
if (data_cache_line_size) {
// write back and then invalidate the cached dma_nodes, we will skip the cache (by non-cacheable address) when access the dma_nodes
// even the cache auto-write back happens, there's no risk the dma_nodes will be overwritten
ESP_GOTO_ON_ERROR(esp_cache_msync(dma_nodes, dma_nodes_size,
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE),
// write back and then invalidate the cached dma_nodes, because later the DMA nodes are accessed by non-cacheable address
ESP_GOTO_ON_ERROR(esp_cache_msync(dma_nodes, num_dma_nodes * sizeof(rmt_dma_descriptor_t),
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE | ESP_CACHE_MSYNC_FLAG_UNALIGNED),
err, TAG, "cache sync failed");
}
// we will use the non-cached address to manipulate the DMA descriptor, for simplicity
rx_channel->dma_nodes_nc = (rmt_dma_descriptor_t *)RMT_GET_NON_CACHE_ADDR(dma_nodes);
}
rx_channel->num_dma_nodes = num_dma_nodes;
#endif // SOC_RMT_SUPPORT_DMA

// register the channel to group
ESP_GOTO_ON_ERROR(rmt_rx_register_to_group(rx_channel, config), err, TAG, "register channel failed");
rmt_group_t *group = rx_channel->base.group;
Expand Down Expand Up @@ -377,25 +384,24 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_
ESP_RETURN_ON_FALSE_ISR(!config->flags.en_partial_rx, ESP_ERR_NOT_SUPPORTED, TAG, "partial receive not supported");
#endif
rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base);
size_t per_dma_block_size = 0;
size_t last_dma_block_size = 0;
uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
size_t mem_alignment = sizeof(rmt_symbol_word_t);

#if SOC_RMT_SUPPORT_DMA
if (channel->dma_chan) {
// Currently we assume the user buffer is allocated from internal RAM, PSRAM is not supported yet.
ESP_RETURN_ON_FALSE_ISR(esp_ptr_internal(buffer), ESP_ERR_INVALID_ARG, TAG, "user buffer not allocated from internal RAM");
// DMA doesn't have alignment requirement for SRAM buffer if the burst mode is not enabled,
// but we need to make sure the buffer is aligned to cache line size
uint32_t align_mask = data_cache_line_size ? (data_cache_line_size - 1) : 0;
ESP_RETURN_ON_FALSE_ISR(((uintptr_t)buffer & align_mask) == 0, ESP_ERR_INVALID_ARG, TAG, "buffer address not aligned");
ESP_RETURN_ON_FALSE_ISR((buffer_size & align_mask) == 0, ESP_ERR_INVALID_ARG, TAG, "buffer size not aligned");
ESP_RETURN_ON_FALSE_ISR(buffer_size <= rx_chan->num_dma_nodes * RMT_DMA_DESC_BUF_MAX_SIZE,
ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity");
per_dma_block_size = buffer_size / rx_chan->num_dma_nodes;
per_dma_block_size = ALIGN_DOWN(per_dma_block_size, sizeof(rmt_symbol_word_t));
last_dma_block_size = buffer_size - per_dma_block_size * (rx_chan->num_dma_nodes - 1);
ESP_RETURN_ON_FALSE_ISR(last_dma_block_size <= RMT_DMA_DESC_BUF_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity");
// append the alignment requirement from the DMA
mem_alignment = MAX(mem_alignment, rx_chan->dma_int_mem_alignment);
// [IDF-8997]: Currently we assume the user buffer is allocated from internal RAM, PSRAM is not supported yet.
ESP_RETURN_ON_FALSE_ISR(esp_ptr_internal(buffer), ESP_ERR_INVALID_ARG, TAG, "user buffer not in the internal RAM");
size_t max_buf_sz_per_dma_node = ALIGN_DOWN(DMA_DESCRIPTOR_BUFFER_MAX_SIZE, mem_alignment);
ESP_RETURN_ON_FALSE_ISR(buffer_size <= rx_chan->num_dma_nodes * max_buf_sz_per_dma_node,
ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity: %zu", rx_chan->num_dma_nodes * max_buf_sz_per_dma_node);
}
#endif // SOC_RMT_SUPPORT_DMA

// 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,
TAG, "buffer address or size are not %zu bytes aligned", mem_alignment);

rmt_group_t *group = channel->group;
rmt_hal_context_t *hal = &group->hal;
Expand All @@ -421,17 +427,23 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_
t->dma_desc_index = 0;
t->flags.en_partial_rx = config->flags.en_partial_rx;

if (channel->dma_chan) {
#if SOC_RMT_SUPPORT_DMA
if (channel->dma_chan) {
// invalidate the user buffer, in case cache auto-write back happens and breaks the data just written by the DMA
if (data_cache_line_size) {
uint32_t int_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
if (int_mem_cache_line_size) {
// this function will also check the alignment of the buffer and size, against the cache line size
ESP_RETURN_ON_ERROR_ISR(esp_cache_msync(buffer, buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C), TAG, "cache sync failed");
}
// we will mount the buffer to multiple DMA nodes, in a balanced way
size_t per_dma_block_size = buffer_size / rx_chan->num_dma_nodes;
per_dma_block_size = ALIGN_DOWN(per_dma_block_size, mem_alignment);
size_t last_dma_block_size = buffer_size - per_dma_block_size * (rx_chan->num_dma_nodes - 1);
rmt_rx_mount_dma_buffer(rx_chan, buffer, buffer_size, per_dma_block_size, last_dma_block_size);
gdma_reset(channel->dma_chan);
gdma_start(channel->dma_chan, (intptr_t)rx_chan->dma_nodes); // note, we must use the cached descriptor address to start the DMA
#endif
}
#endif

rx_chan->mem_off = 0;
portENTER_CRITICAL_SAFE(&channel->spinlock);
Expand Down
Loading

0 comments on commit 0f6004e

Please sign in to comment.