mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
702 lines
29 KiB
C
702 lines
29 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdatomic.h>
|
|
#include <sys/cdefs.h>
|
|
#include <sys/lock.h>
|
|
#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)
|
|
#define DW_GDMA_GET_CACHE_ADDRESS(nc_addr) ((nc_addr) ? CACHE_LL_L2MEM_CACHE_ADDR(nc_addr) : 0)
|
|
#else
|
|
#define DW_GDMA_GET_NON_CACHE_ADDR(addr) (addr)
|
|
#define DW_GDMA_GET_CACHE_ADDRESS(nc_addr) (nc_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 internal memory
|
|
// the link list items has its own alignment requirement, the heap allocator can help handle the cache alignment as well
|
|
items = heap_caps_aligned_calloc(DW_GDMA_LL_LINK_LIST_ALIGNMENT, num_items, sizeof(dw_gdma_link_list_item_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
|
|
ESP_GOTO_ON_FALSE(items, ESP_ERR_NO_MEM, err, TAG, "no mem for link list items");
|
|
// do memory sync when the link list items are cached
|
|
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 cache, because later we will read/write the link list items by non-cacheable address
|
|
ESP_GOTO_ON_ERROR(esp_cache_msync(items, num_items * sizeof(dw_gdma_link_list_item_t),
|
|
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE | ESP_CACHE_MSYNC_FLAG_UNALIGNED),
|
|
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");
|
|
// Note: the returned address is non-cached
|
|
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, so convert it to a cached address
|
|
dw_gdma_ll_lli_set_next_item_addr(lli, DW_GDMA_GET_CACHE_ADDRESS(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;
|
|
}
|