/* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "sdkconfig.h" #if CONFIG_DW_GDMA_ENABLE_DEBUG_LOG // The local log level must be defined before including esp_log.h // Set the maximum log level for this source file #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "soc/soc_caps.h" #include "soc/interrupts.h" #include "esp_log.h" #include "esp_check.h" #include "esp_intr_alloc.h" #include "esp_memory_utils.h" #include "esp_private/periph_ctrl.h" #include "esp_private/dw_gdma.h" #include "hal/dw_gdma_hal.h" #include "hal/dw_gdma_ll.h" #include "hal/cache_hal.h" #include "hal/cache_ll.h" #include "esp_cache.h" static const char *TAG = "dw-gdma"; #if !SOC_RCC_IS_INDEPENDENT // Reset and Clock Control registers are mixing with other peripherals, so we need to use a critical section #define DW_GDMA_RCC_ATOMIC() PERIPH_RCC_ATOMIC() #else #define DW_GDMA_RCC_ATOMIC() #endif #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE #define DW_GDMA_GET_NON_CACHE_ADDR(addr) ((addr) ? CACHE_LL_L2MEM_NON_CACHE_ADDR(addr) : 0) #else #define DW_GDMA_GET_NON_CACHE_ADDR(addr) (addr) #endif #if CONFIG_DW_GDMA_ISR_IRAM_SAFE || CONFIG_DW_GDMA_CTRL_FUNC_IN_IRAM || DW_GDMA_SETTER_FUNC_IN_IRAM #define DW_GDMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) #else #define DW_GDMA_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT #endif #if CONFIG_DW_GDMA_ISR_IRAM_SAFE #define DW_GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM) #else #define DW_GDMA_INTR_ALLOC_FLAGS 0 #endif #define DW_GDMA_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED #define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) typedef struct dw_gdma_group_t dw_gdma_group_t; typedef struct dw_gdma_channel_t dw_gdma_channel_t; typedef struct dw_gdma_link_list_t { uint32_t num_items; // number of items in the link list dw_gdma_link_list_item_t *items; // pointer to the link list items dw_gdma_link_list_item_t *items_nc; // pointer to the link list items, non-cached } dw_gdma_link_list_t; typedef struct { _lock_t mutex; // platform level mutex lock dw_gdma_group_t *groups[DW_GDMA_LL_GROUPS]; // array of DMA group instances int group_ref_counts[DW_GDMA_LL_GROUPS]; // reference count used to protect group install/uninstall } dw_gdma_platform_t; struct dw_gdma_group_t { int group_id; // Group ID, index from 0 dw_gdma_hal_context_t hal; // HAL instance is at group level int intr_priority; // all channels in the same group should share the same interrupt priority portMUX_TYPE spinlock; // group level spinlock, protect group level stuffs, e.g. hal object, pair handle slots and reference count of each pair dw_gdma_channel_t *channels[DW_GDMA_LL_CHANNELS_PER_GROUP]; // handles of DMA channels }; struct dw_gdma_channel_t { int chan_id; // channel ID, index from 0 intr_handle_t intr; // per-channel interrupt handle portMUX_TYPE spinlock; // channel level spinlock dw_gdma_group_t *group; // pointer to the group which the channel belongs to void *user_data; // user registered DMA event data dw_gdma_event_callbacks_t cbs; // Event callbacks dw_gdma_block_transfer_type_t src_transfer_type; // transfer type for source dw_gdma_block_transfer_type_t dst_transfer_type; // transfer type for destination }; // dw_gdma driver platform static dw_gdma_platform_t s_platform; static dw_gdma_group_t *dw_gdma_acquire_group_handle(int group_id) { bool new_group = false; dw_gdma_group_t *group = NULL; // prevent install dw_gdma group concurrently _lock_acquire(&s_platform.mutex); if (!s_platform.groups[group_id]) { // The group is handle is not created yet group = heap_caps_calloc(1, sizeof(dw_gdma_group_t), DW_GDMA_MEM_ALLOC_CAPS); if (group) { new_group = true; s_platform.groups[group_id] = group; // enable APB to access DMA registers DW_GDMA_RCC_ATOMIC() { dw_gdma_ll_enable_bus_clock(group_id, true); dw_gdma_ll_reset_register(group_id); } // initialize the HAL context dw_gdma_hal_config_t hal_config = {}; dw_gdma_hal_init(&group->hal, &hal_config); } } else { // the group is installed, we just retrieve it and increase the reference count group = s_platform.groups[group_id]; } if (group) { // someone acquired the group handle means we have a new object that refer to this group s_platform.group_ref_counts[group_id]++; } _lock_release(&s_platform.mutex); if (new_group) { portMUX_INITIALIZE(&group->spinlock); group->group_id = group_id; group->intr_priority = -1; // interrupt priority not assigned yet ESP_LOGD(TAG, "new group (%d) at %p", group_id, group); } return group; } static void dw_gdma_release_group_handle(dw_gdma_group_t *group) { int group_id = group->group_id; bool del_group = false; _lock_acquire(&s_platform.mutex); s_platform.group_ref_counts[group_id]--; if (s_platform.group_ref_counts[group_id] == 0) { del_group = true; // the group now is not used by any channel, unregister it from the platform s_platform.groups[group_id] = NULL; // deinitialize the HAL context dw_gdma_hal_deinit(&group->hal); DW_GDMA_RCC_ATOMIC() { dw_gdma_ll_enable_bus_clock(group_id, false); } } _lock_release(&s_platform.mutex); if (del_group) { free(group); ESP_LOGD(TAG, "delete group (%d)", group_id); } } static esp_err_t channel_register_to_group(dw_gdma_channel_t *chan) { dw_gdma_group_t *group = NULL; int chan_id = -1; for (int i = 0; i < DW_GDMA_LL_GROUPS; i++) { group = dw_gdma_acquire_group_handle(i); ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group(%d)", i); // loop to search free channel in the group portENTER_CRITICAL(&group->spinlock); for (int j = 0; j < DW_GDMA_LL_CHANNELS_PER_GROUP; j++) { if (group->channels[j] == NULL) { group->channels[j] = chan; chan_id = j; break; } } portEXIT_CRITICAL(&group->spinlock); if (chan_id < 0) { dw_gdma_release_group_handle(group); } else { chan->group = group; chan->chan_id = chan_id; break; } } ESP_RETURN_ON_FALSE(chan_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free channels"); return ESP_OK; } static void channel_unregister_from_group(dw_gdma_channel_t *chan) { dw_gdma_group_t *group = chan->group; int chan_id = chan->chan_id; portENTER_CRITICAL(&group->spinlock); group->channels[chan_id] = NULL; portEXIT_CRITICAL(&group->spinlock); // channel has a reference on group, release it now dw_gdma_release_group_handle(group); } static esp_err_t channel_destroy(dw_gdma_channel_t *chan) { if (chan->group) { channel_unregister_from_group(chan); } if (chan->intr) { esp_intr_free(chan->intr); } free(chan); return ESP_OK; } esp_err_t dw_gdma_new_channel(const dw_gdma_channel_alloc_config_t *config, dw_gdma_channel_handle_t *ret_chan) { #if CONFIG_DW_GDMA_ENABLE_DEBUG_LOG esp_log_level_set(TAG, ESP_LOG_DEBUG); #endif esp_err_t ret = ESP_OK; dw_gdma_channel_t *chan = NULL; ESP_RETURN_ON_FALSE(config && ret_chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(config->src.num_outstanding_requests >= 1 && config->src.num_outstanding_requests <= DW_GDMA_LL_MAX_OUTSTANDING_REQUESTS, ESP_ERR_INVALID_ARG, TAG, "invalid num_outstanding_requests"); ESP_RETURN_ON_FALSE(config->dst.num_outstanding_requests >= 1 && config->dst.num_outstanding_requests <= DW_GDMA_LL_MAX_OUTSTANDING_REQUESTS, ESP_ERR_INVALID_ARG, TAG, "invalid num_outstanding_request"); ESP_RETURN_ON_FALSE(config->chan_priority >= 0 && config->chan_priority < DW_GDMA_LL_CHANNELS_PER_GROUP, ESP_ERR_INVALID_ARG, TAG, "invalid channel priority"); if (config->intr_priority) { ESP_RETURN_ON_FALSE(1 << (config->intr_priority) & DW_GDMA_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, TAG, "invalid interrupt priority:%d", config->intr_priority); } chan = heap_caps_calloc(1, sizeof(dw_gdma_channel_t), DW_GDMA_MEM_ALLOC_CAPS); ESP_RETURN_ON_FALSE(chan, ESP_ERR_NO_MEM, TAG, "no mem for channel"); // register channel to the group ESP_GOTO_ON_ERROR(channel_register_to_group(chan), err, TAG, "register to group failed"); dw_gdma_group_t *group = chan->group; dw_gdma_hal_context_t *hal = &group->hal; int group_id = group->group_id; int chan_id = chan->chan_id; // all channels in the same group should use the same interrupt priority bool intr_priority_conflict = false; portENTER_CRITICAL(&group->spinlock); if (group->intr_priority == -1) { group->intr_priority = config->intr_priority; } else if (config->intr_priority != 0) { intr_priority_conflict = (group->intr_priority != config->intr_priority); } portEXIT_CRITICAL(&group->spinlock); ESP_GOTO_ON_FALSE(!intr_priority_conflict, ESP_ERR_INVALID_STATE, err, TAG, "intr_priority conflict, already is %d but attempt to %d", group->intr_priority, config->intr_priority); // basic initialization portMUX_INITIALIZE(&chan->spinlock); chan->src_transfer_type = config->src.block_transfer_type; chan->dst_transfer_type = config->dst.block_transfer_type; // set transfer flow type dw_gdma_ll_channel_set_trans_flow(hal->dev, chan_id, config->src.role, config->dst.role, config->flow_controller); // set the transfer type for source and destination dw_gdma_ll_channel_set_src_multi_block_type(hal->dev, chan_id, config->src.block_transfer_type); dw_gdma_ll_channel_set_dst_multi_block_type(hal->dev, chan_id, config->dst.block_transfer_type); // set handshake interface dw_gdma_ll_channel_set_src_handshake_interface(hal->dev, chan_id, config->src.handshake_type); dw_gdma_ll_channel_set_dst_handshake_interface(hal->dev, chan_id, config->dst.handshake_type); // set handshake peripheral if (config->src.role != DW_GDMA_ROLE_MEM) { dw_gdma_ll_channel_set_src_handshake_periph(hal->dev, chan_id, config->src.role); } if (config->dst.role != DW_GDMA_ROLE_MEM) { dw_gdma_ll_channel_set_dst_handshake_periph(hal->dev, chan_id, config->dst.role); } // set channel priority dw_gdma_ll_channel_set_priority(hal->dev, chan_id, config->chan_priority); // set the outstanding request number dw_gdma_ll_channel_set_src_outstanding_limit(hal->dev, chan_id, config->src.num_outstanding_requests); dw_gdma_ll_channel_set_dst_outstanding_limit(hal->dev, chan_id, config->dst.num_outstanding_requests); // set the status fetch address dw_gdma_ll_channel_set_src_periph_status_addr(hal->dev, chan_id, config->src.status_fetch_addr); dw_gdma_ll_channel_set_dst_periph_status_addr(hal->dev, chan_id, config->dst.status_fetch_addr); // enable all channel events (notes, they can't trigger an interrupt until `dw_gdma_ll_channel_enable_intr_propagation` is called) dw_gdma_ll_channel_enable_intr_generation(hal->dev, chan_id, UINT32_MAX, true); ESP_LOGD(TAG, "new channel (%d,%d) at %p", group_id, chan_id, chan); *ret_chan = chan; return ESP_OK; err: if (chan) { channel_destroy(chan); } return ret; } esp_err_t dw_gdma_del_channel(dw_gdma_channel_handle_t chan) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_group_t *group = chan->group; int group_id = group->group_id; int chan_id = chan->chan_id; ESP_LOGD(TAG, "del channel (%d,%d)", group_id, chan_id); // recycle memory resource ESP_RETURN_ON_ERROR(channel_destroy(chan), TAG, "destroy channel failed"); return ESP_OK; } esp_err_t dw_gdma_channel_enable_ctrl(dw_gdma_channel_handle_t chan, bool en_or_dis) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // the atomic is ensured by the hardware, so no lock is needed here dw_gdma_ll_channel_enable(hal->dev, chan_id, en_or_dis); return ESP_OK; } esp_err_t dw_gdma_channel_suspend_ctrl(dw_gdma_channel_handle_t chan, bool enter_or_exit) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // the atomic is ensured by the hardware, so no lock is needed here dw_gdma_ll_channel_suspend(hal->dev, chan_id, enter_or_exit); return ESP_OK; } esp_err_t dw_gdma_channel_abort(dw_gdma_channel_handle_t chan) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // the atomic is ensured by the hardware, so no lock is needed here dw_gdma_ll_channel_abort(hal->dev, chan_id); return ESP_OK; } esp_err_t dw_gdma_channel_lock(dw_gdma_channel_handle_t chan, dw_gdma_lock_level_t level) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // the lock control bit is located in a cfg register, with other configuration bits portENTER_CRITICAL(&chan->spinlock); dw_gdma_ll_channel_lock(hal->dev, chan_id, level); portEXIT_CRITICAL(&chan->spinlock); return ESP_OK; } esp_err_t dw_gdma_channel_unlock(dw_gdma_channel_handle_t chan) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // the lock control bit is located in a cfg register, with other configuration bits portENTER_CRITICAL(&chan->spinlock); dw_gdma_ll_channel_unlock(hal->dev, chan_id); portEXIT_CRITICAL(&chan->spinlock); return ESP_OK; } esp_err_t dw_gdma_channel_continue(dw_gdma_channel_handle_t chan) { ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // the atomic is ensured by the hardware, so no lock is needed here dw_gdma_ll_channel_resume_multi_block_transfer(hal->dev, chan_id); return ESP_OK; } esp_err_t dw_gdma_new_link_list(const dw_gdma_link_list_config_t *config, dw_gdma_link_list_handle_t *ret_list) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(ret_list, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_link_list_item_t *items = NULL; dw_gdma_link_list_t *list = NULL; uint32_t num_items = config->num_items; list = heap_caps_calloc(1, sizeof(dw_gdma_link_list_t), DW_GDMA_MEM_ALLOC_CAPS); ESP_GOTO_ON_FALSE(list, ESP_ERR_NO_MEM, err, TAG, "no mem for link list"); // allocate memory for link list items, from SRAM // the link list items has itw own alignment requirement // also we should respect the data cache line size uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA); uint32_t alignment = MAX(DW_GDMA_LL_LINK_LIST_ALIGNMENT, data_cache_line_size); // because we want to access the link list items via non-cache address, so the memory size should also align to the cache line size uint32_t lli_size = num_items * sizeof(dw_gdma_link_list_item_t); if (data_cache_line_size) { lli_size = ALIGN_UP(lli_size, data_cache_line_size); } items = heap_caps_aligned_calloc(alignment, 1, lli_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); ESP_GOTO_ON_FALSE(items, ESP_ERR_NO_MEM, err, TAG, "no mem for link list items"); if (data_cache_line_size) { // do memory sync only when the cache exists // write back and then invalidate the cache, we won't use the cache to operate the link list items afterwards // even the cache auto-write back happens, there's no risk the link list items will be overwritten ESP_GOTO_ON_ERROR(esp_cache_msync(items, lli_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE), err, TAG, "cache sync failed"); } list->num_items = num_items; list->items = items; // want to use non-cached address to operate the link list items list->items_nc = (dw_gdma_link_list_item_t *)DW_GDMA_GET_NON_CACHE_ADDR(items); // set up the link list for (size_t i = 0; i < num_items; i++) { dw_gdma_ll_lli_set_next_item_addr(list->items_nc + i, (uint32_t)(list->items + i + 1)); // set master port for the link list dw_gdma_ll_lli_set_link_list_master_port(list->items_nc + i, DW_GDMA_LL_MASTER_PORT_MEMORY); } switch (config->link_type) { case DW_GDMA_LINKED_LIST_TYPE_CIRCULAR: dw_gdma_ll_lli_set_next_item_addr(list->items_nc + num_items - 1, (uint32_t)(list->items)); break; case DW_GDMA_LINKED_LIST_TYPE_SINGLY: dw_gdma_ll_lli_set_next_item_addr(list->items_nc + num_items - 1, 0); break; } ESP_LOGD(TAG, "new link list @%p, items @%p", list, items); *ret_list = list; return ESP_OK; err: if (list) { free(list); } if (items) { free(items); } return ret; } esp_err_t dw_gdma_del_link_list(dw_gdma_link_list_handle_t list) { ESP_RETURN_ON_FALSE(list, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_LOGD(TAG, "del link list at %p", list); free(list->items); free(list); return ESP_OK; } esp_err_t dw_gdma_channel_use_link_list(dw_gdma_channel_handle_t chan, dw_gdma_link_list_handle_t list) { ESP_RETURN_ON_FALSE(chan && list, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(chan->src_transfer_type == DW_GDMA_BLOCK_TRANSFER_LIST || chan->dst_transfer_type == DW_GDMA_BLOCK_TRANSFER_LIST, ESP_ERR_INVALID_STATE, TAG, "invalid transfer type"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // set master port for the link list dw_gdma_ll_channel_set_link_list_master_port(hal->dev, chan_id, DW_GDMA_LL_MASTER_PORT_MEMORY); // set the link list head address dw_gdma_ll_channel_set_link_list_head_addr(hal->dev, chan_id, (uint32_t)(list->items)); return ESP_OK; } dw_gdma_lli_handle_t dw_gdma_link_list_get_item(dw_gdma_link_list_handle_t list, int item_index) { ESP_RETURN_ON_FALSE_ISR(list, NULL, TAG, "invalid argument"); ESP_RETURN_ON_FALSE_ISR(item_index < list->num_items, NULL, TAG, "invalid item index"); dw_gdma_link_list_item_t *lli = list->items_nc + item_index; return lli; } esp_err_t dw_gdma_lli_set_next(dw_gdma_lli_handle_t lli, dw_gdma_lli_handle_t next) { ESP_RETURN_ON_FALSE(lli && next, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); // the next field must use a cached address dw_gdma_ll_lli_set_next_item_addr(lli, CACHE_LL_L2MEM_CACHE_ADDR(next)); return ESP_OK; } esp_err_t dw_gdma_channel_config_transfer(dw_gdma_channel_handle_t chan, const dw_gdma_block_transfer_config_t *config) { ESP_RETURN_ON_FALSE(chan && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(chan->src_transfer_type != DW_GDMA_BLOCK_TRANSFER_LIST && chan->dst_transfer_type != DW_GDMA_BLOCK_TRANSFER_LIST, ESP_ERR_INVALID_STATE, TAG, "invalid transfer type"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // set memory address dw_gdma_ll_channel_set_src_addr(hal->dev, chan_id, config->src.addr); dw_gdma_ll_channel_set_dst_addr(hal->dev, chan_id, config->dst.addr); // transfer size dw_gdma_ll_channel_set_trans_block_size(hal->dev, chan_id, config->size); // [Ctrl0] register // set master port for the source and destination target dw_gdma_ll_channel_set_src_master_port(hal->dev, chan_id, config->src.addr); dw_gdma_ll_channel_set_dst_master_port(hal->dev, chan_id, config->dst.addr); // transfer width dw_gdma_ll_channel_set_src_trans_width(hal->dev, chan_id, config->src.width); dw_gdma_ll_channel_set_dst_trans_width(hal->dev, chan_id, config->dst.width); // set burst items dw_gdma_ll_channel_set_src_burst_items(hal->dev, chan_id, config->src.burst_items); dw_gdma_ll_channel_set_dst_burst_items(hal->dev, chan_id, config->dst.burst_items); // set burst mode dw_gdma_ll_channel_set_src_burst_mode(hal->dev, chan_id, config->src.burst_mode); dw_gdma_ll_channel_set_dst_burst_mode(hal->dev, chan_id, config->dst.burst_mode); // [Ctrl1] register // set burst length dw_gdma_ll_channel_set_src_burst_len(hal->dev, chan_id, config->src.burst_len); dw_gdma_ll_channel_set_dst_burst_len(hal->dev, chan_id, config->dst.burst_len); // whether to enable the peripheral status write back dw_gdma_ll_channel_enable_src_periph_status_write_back(hal->dev, chan_id, config->src.flags.en_status_write_back); dw_gdma_ll_channel_enable_dst_periph_status_write_back(hal->dev, chan_id, config->dst.flags.en_status_write_back); return ESP_OK; } esp_err_t dw_gdma_channel_set_block_markers(dw_gdma_channel_handle_t chan, dw_gdma_block_markers_t markers) { ESP_RETURN_ON_FALSE_ISR(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE_ISR(chan->src_transfer_type != DW_GDMA_BLOCK_TRANSFER_LIST && chan->dst_transfer_type != DW_GDMA_BLOCK_TRANSFER_LIST, ESP_ERR_INVALID_STATE, TAG, "invalid transfer type"); dw_gdma_hal_context_t *hal = &chan->group->hal; int chan_id = chan->chan_id; // [Ctrl1] register // set the block markers dw_gdma_ll_channel_set_block_markers(hal->dev, chan_id, markers.en_trans_done_intr, markers.is_last, markers.is_valid); return ESP_OK; } esp_err_t dw_gdma_lli_config_transfer(dw_gdma_lli_handle_t lli, dw_gdma_block_transfer_config_t *config) { ESP_RETURN_ON_FALSE(lli && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); // set memory address dw_gdma_ll_lli_set_src_addr(lli, config->src.addr); dw_gdma_ll_lli_set_dst_addr(lli, config->dst.addr); // transfer size dw_gdma_ll_lli_set_trans_block_size(lli, config->size); // [Ctrl0] register // set master port for the source and destination target dw_gdma_ll_lli_set_src_master_port(lli, config->src.addr); dw_gdma_ll_lli_set_dst_master_port(lli, config->dst.addr); // transfer width dw_gdma_ll_lli_set_src_trans_width(lli, config->src.width); dw_gdma_ll_lli_set_dst_trans_width(lli, config->dst.width); // set burst items dw_gdma_ll_lli_set_src_burst_items(lli, config->src.burst_items); dw_gdma_ll_lli_set_dst_burst_items(lli, config->dst.burst_items); // set burst mode dw_gdma_ll_lli_set_src_burst_mode(lli, config->src.burst_mode); dw_gdma_ll_lli_set_dst_burst_mode(lli, config->dst.burst_mode); // [Ctrl1] register // set burst length dw_gdma_ll_lli_set_src_burst_len(lli, config->src.burst_len); dw_gdma_ll_lli_set_dst_burst_len(lli, config->dst.burst_len); // whether to enable the peripheral status write back dw_gdma_ll_lli_enable_src_periph_status_write_back(lli, config->src.flags.en_status_write_back); dw_gdma_ll_lli_enable_dst_periph_status_write_back(lli, config->dst.flags.en_status_write_back); return ESP_OK; } esp_err_t dw_gdma_lli_set_block_markers(dw_gdma_lli_handle_t lli, dw_gdma_block_markers_t markers) { ESP_RETURN_ON_FALSE_ISR(lli, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); // [Ctrl1] register // set the block markers dw_gdma_ll_lli_set_block_markers(lli, markers.en_trans_done_intr, markers.is_last, markers.is_valid); return ESP_OK; } void dw_gdma_channel_default_isr(void *args) { dw_gdma_channel_t *chan = (dw_gdma_channel_t *)args; dw_gdma_group_t *group = chan->group; dw_gdma_hal_context_t *hal = &group->hal; int chan_id = chan->chan_id; bool need_yield = false; // clear pending interrupt event uint32_t intr_status = dw_gdma_ll_channel_get_intr_status(hal->dev, chan_id); dw_gdma_ll_channel_clear_intr(hal->dev, chan_id, intr_status); // call user callbacks if (intr_status & DW_GDMA_LL_CHANNEL_EVENT_SHADOWREG_OR_LLI_INVALID_ERR) { if (chan->cbs.on_invalid_block) { intptr_t invalid_lli_addr = dw_gdma_ll_channel_get_current_link_list_item_addr(hal->dev, chan_id); dw_gdma_break_event_data_t edata = { .invalid_lli = (dw_gdma_lli_handle_t)DW_GDMA_GET_NON_CACHE_ADDR(invalid_lli_addr), }; if (chan->cbs.on_invalid_block(chan, &edata, chan->user_data)) { need_yield = true; } } } if (intr_status & DW_GDMA_LL_CHANNEL_EVENT_BLOCK_TFR_DONE) { if (chan->cbs.on_block_trans_done) { dw_gdma_trans_done_event_data_t edata = {}; if (chan->cbs.on_block_trans_done(chan, &edata, chan->user_data)) { need_yield = true; } } } if (intr_status & DW_GDMA_LL_CHANNEL_EVENT_DMA_TFR_DONE) { if (chan->cbs.on_full_trans_done) { dw_gdma_trans_done_event_data_t edata = {}; if (chan->cbs.on_full_trans_done(chan, &edata, chan->user_data)) { need_yield = true; } } } if (need_yield) { portYIELD_FROM_ISR(); } } static esp_err_t dw_gdma_install_channel_interrupt(dw_gdma_channel_t *chan) { esp_err_t ret = ESP_OK; dw_gdma_group_t *group = chan->group; dw_gdma_hal_context_t *hal = &group->hal; int chan_id = chan->chan_id; // clear pending events dw_gdma_ll_channel_enable_intr_propagation(hal->dev, chan_id, UINT32_MAX, false); dw_gdma_ll_channel_clear_intr(hal->dev, chan_id, UINT32_MAX); // pre-alloc a interrupt handle, with handler disabled // DW_GDMA multiple channels share the same interrupt source, so we use a shared interrupt handle intr_handle_t intr = NULL; int isr_flags = DW_GDMA_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED; if (group->intr_priority) { isr_flags |= 1 << (group->intr_priority); } else { isr_flags |= DW_GDMA_ALLOW_INTR_PRIORITY_MASK; } ret = esp_intr_alloc_intrstatus(ETS_DW_GDMA_INTR_SOURCE, isr_flags, (uint32_t)dw_gdma_ll_get_intr_status_reg(hal->dev), DW_GDMA_LL_CHANNEL_EVENT_MASK(chan_id), dw_gdma_channel_default_isr, chan, &intr); ESP_RETURN_ON_ERROR(ret, TAG, "alloc interrupt failed"); ESP_LOGD(TAG, "install interrupt service for channel (%d,%d)", group->group_id, chan_id); chan->intr = intr; return ESP_OK; } esp_err_t dw_gdma_channel_register_event_callbacks(dw_gdma_channel_handle_t chan, dw_gdma_event_callbacks_t *cbs, void *user_data) { ESP_RETURN_ON_FALSE(chan && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); dw_gdma_group_t *group = chan->group; dw_gdma_hal_context_t *hal = &group->hal; int chan_id = chan->chan_id; #if CONFIG_DW_GDMA_ISR_IRAM_SAFE if (cbs->on_block_trans_done) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_block_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_block_trans_done not in IRAM"); } if (cbs->on_full_trans_done) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_full_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_full_trans_done not in IRAM"); } if (cbs->on_invalid_block) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_invalid_block), ESP_ERR_INVALID_ARG, TAG, "on_invalid_block not in IRAM"); } if (user_data) { ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); } #endif // CONFIG_DW_GDMA_ISR_IRAM_SAFE // lazy install interrupt service if (!chan->intr) { ESP_RETURN_ON_ERROR(dw_gdma_install_channel_interrupt(chan), TAG, "install interrupt service failed"); } // enable the event to be able to trigger an interrupt dw_gdma_ll_channel_enable_intr_propagation(hal->dev, chan_id, DW_GDMA_LL_CHANNEL_EVENT_BLOCK_TFR_DONE, cbs->on_block_trans_done != NULL); dw_gdma_ll_channel_enable_intr_propagation(hal->dev, chan_id, DW_GDMA_LL_CHANNEL_EVENT_DMA_TFR_DONE, cbs->on_full_trans_done != NULL); dw_gdma_ll_channel_enable_intr_propagation(hal->dev, chan_id, DW_GDMA_LL_CHANNEL_EVENT_SHADOWREG_OR_LLI_INVALID_ERR, cbs->on_invalid_block != NULL); chan->user_data = user_data; memcpy(&chan->cbs, cbs, sizeof(dw_gdma_event_callbacks_t)); return ESP_OK; } esp_err_t dw_gdma_channel_get_id(dw_gdma_channel_handle_t chan, int *channel_id) { ESP_RETURN_ON_FALSE(chan && channel_id, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); *channel_id = chan->chan_id; return ESP_OK; }