mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
12325ff47e
1. mem_burst_length register field correction 2. remove dma buffer addr range restriction 3. M2M periph sel ID no need to be identical for TX and RX channels 4. correct rx descriptor owner field auto clear 5. remove fsm idle assertion for ERR_EOF
971 lines
47 KiB
C
971 lines
47 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/lock.h>
|
|
#include "esp_check.h"
|
|
#include "freertos/portmacro.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "esp_intr_alloc.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_memory_utils.h"
|
|
#include "esp_attr.h"
|
|
#include "esp_log.h"
|
|
#include "esp_private/periph_ctrl.h"
|
|
#include "dma2d_priv.h"
|
|
#include "esp_private/dma2d.h"
|
|
#include "hal/dma2d_hal.h"
|
|
#include "hal/dma2d_ll.h"
|
|
#include "soc/dma2d_channel.h"
|
|
#include "soc/dma2d_periph.h"
|
|
#include "soc/soc_caps.h"
|
|
#include "esp_bit_defs.h"
|
|
|
|
/**
|
|
* The 2D-DMA driver is designed with a pool & client model + queue design pattern.
|
|
*
|
|
* Pools represents the groups of 2D-DMA module, which contain the limited resource, channels.
|
|
* Clients represents the upper modules which are the consumers of the 2D-DMA channels, such as JPEG and PPA.
|
|
*
|
|
* Each pool has a queue to store the 2D-DMA transactions that are waiting to be processed.
|
|
*
|
|
* The upper modules should register themselves as the clients to a 2D-DMA pool. And then they should push the
|
|
* 2D-DMA transactions into the pool queue. The driver will continuously look for the desired resources from the pool to
|
|
* complete the transactions.
|
|
*/
|
|
|
|
static const char *TAG = "dma2d";
|
|
|
|
typedef struct dma2d_platform_t {
|
|
_lock_t mutex; // platform level mutex lock to protect the dma2d_acquire_pool/dma2d_release_pool process
|
|
dma2d_group_t *groups[SOC_DMA2D_GROUPS]; // array of 2D-DMA group instances
|
|
int group_ref_counts[SOC_DMA2D_GROUPS]; // reference count used to protect group install/uninstall
|
|
} dma2d_platform_t;
|
|
|
|
// 2D-DMA driver platform
|
|
static dma2d_platform_t s_platform = {
|
|
.groups = {},
|
|
};
|
|
|
|
// extern 2D-DMA channel reserved mask variables to be ORed in the constructors
|
|
uint32_t dma2d_tx_channel_reserved_mask[SOC_DMA2D_GROUPS] = { [0 ... SOC_DMA2D_GROUPS - 1] = 0 };
|
|
uint32_t dma2d_rx_channel_reserved_mask[SOC_DMA2D_GROUPS] = { [0 ... SOC_DMA2D_GROUPS - 1] = 0 };
|
|
|
|
// The most number of channels required for a 2D-DMA transaction (a PPA Blend operation requires 2 TX + 1 RX)
|
|
#define DMA2D_MAX_CHANNEL_NUM_PER_TRANSACTION 3
|
|
|
|
|
|
/* This static function is not thread-safe, group's spinlock protection should be added in its caller */
|
|
static bool acquire_free_channels_for_trans(dma2d_group_t *dma2d_group, const dma2d_trans_config_t *trans_desc, dma2d_trans_channel_info_t *channel_handle_array)
|
|
{
|
|
bool found = true;
|
|
uint32_t idx = 0;
|
|
uint32_t bundled_tx_channel_mask = 0;
|
|
|
|
if (trans_desc->tx_channel_num > 0) {
|
|
uint32_t tx_free_channel_mask;
|
|
if (!trans_desc->specified_tx_channel_mask) {
|
|
tx_free_channel_mask = dma2d_group->tx_channel_free_mask;
|
|
tx_free_channel_mask &= (((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_TX_REORDER) ? DMA2D_LL_TX_CHANNEL_SUPPORT_RO_MASK : UINT32_MAX) &
|
|
((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC) ? DMA2D_LL_TX_CHANNEL_SUPPORT_CSC_MASK : UINT32_MAX));
|
|
tx_free_channel_mask &= ~dma2d_group->tx_channel_reserved_mask;
|
|
if (trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING) {
|
|
uint32_t rx_channel_candidate = dma2d_group->rx_channel_free_mask &
|
|
((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_REORDER) ? DMA2D_LL_RX_CHANNEL_SUPPORT_RO_MASK : UINT32_MAX) &
|
|
((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC) ? DMA2D_LL_RX_CHANNEL_SUPPORT_CSC_MASK : UINT32_MAX) &
|
|
~dma2d_group->rx_channel_reserved_mask;
|
|
tx_free_channel_mask &= rx_channel_candidate;
|
|
}
|
|
// As long as __builtin_popcount(tx_free_channel_mask) >= trans_desc->tx_channel_num, it can meet the criteria of "found"
|
|
} else {
|
|
tx_free_channel_mask = trans_desc->specified_tx_channel_mask & dma2d_group->tx_channel_free_mask;
|
|
// tx_free_channel_mask need to be exactly equal to trans_desc->specified_tx_channel_mask to meet the criteria of "found"
|
|
}
|
|
|
|
for (int i = 0; i < trans_desc->tx_channel_num; i++) {
|
|
if (tx_free_channel_mask) {
|
|
int channel_id = 31 - __builtin_clz(tx_free_channel_mask); // channel 0 has the most features, acquire other channels first if possible
|
|
tx_free_channel_mask &= ~(1 << channel_id);
|
|
dma2d_group->tx_channel_free_mask &= ~(1 << channel_id);
|
|
bundled_tx_channel_mask |= (1 << channel_id);
|
|
// Record channel status
|
|
memset(&dma2d_group->tx_chans[channel_id]->base.status, 0, sizeof(dma2d_group->tx_chans[channel_id]->base.status));
|
|
dma2d_group->tx_chans[channel_id]->base.status.periph_sel_id = -1;
|
|
if (trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_TX_REORDER) {
|
|
dma2d_group->tx_chans[channel_id]->base.status.reorder_en = true;
|
|
}
|
|
channel_handle_array[idx].chan = &dma2d_group->tx_chans[channel_id]->base;
|
|
channel_handle_array[idx].dir = DMA2D_CHANNEL_DIRECTION_TX;
|
|
idx++;
|
|
} else {
|
|
found = false;
|
|
goto revert;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (trans_desc->rx_channel_num > 0) {
|
|
uint32_t rx_free_channel_mask;
|
|
if (trans_desc->specified_rx_channel_mask) {
|
|
rx_free_channel_mask = trans_desc->specified_rx_channel_mask & dma2d_group->rx_channel_free_mask;
|
|
// rx_free_channel_mask need to be exactly equal to trans_desc->specified_rx_channel_mask to meet the criteria of "found"
|
|
} else if (trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING) {
|
|
// rx channel has already been determined
|
|
rx_free_channel_mask = bundled_tx_channel_mask;
|
|
} else {
|
|
rx_free_channel_mask = dma2d_group->rx_channel_free_mask;
|
|
rx_free_channel_mask &= (((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_REORDER) ? DMA2D_LL_RX_CHANNEL_SUPPORT_RO_MASK : UINT32_MAX) &
|
|
((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC) ? DMA2D_LL_RX_CHANNEL_SUPPORT_CSC_MASK : UINT32_MAX));
|
|
rx_free_channel_mask &= ~dma2d_group->rx_channel_reserved_mask;
|
|
// As long as __builtin_popcount(rx_free_channel_mask) >= trans_desc->rx_channel_num, it can meet the criteria of "found"
|
|
}
|
|
|
|
// Requires one RX channel at most, no need a for loop
|
|
if (rx_free_channel_mask) {
|
|
int channel_id = 31 - __builtin_clz(rx_free_channel_mask); // channel 0 has full features, acquire other channels first if possible
|
|
rx_free_channel_mask &= ~(1 << channel_id);
|
|
dma2d_group->rx_channel_free_mask &= ~(1 << channel_id);
|
|
// Record channel status
|
|
memset(&dma2d_group->rx_chans[channel_id]->base.status, 0, sizeof(dma2d_group->rx_chans[channel_id]->base.status));
|
|
dma2d_group->rx_chans[channel_id]->base.status.periph_sel_id = -1;
|
|
if (trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_REORDER) {
|
|
dma2d_group->rx_chans[channel_id]->base.status.reorder_en = true;
|
|
}
|
|
channel_handle_array[idx].chan = &dma2d_group->rx_chans[channel_id]->base;
|
|
channel_handle_array[idx].dir = DMA2D_CHANNEL_DIRECTION_RX;
|
|
idx++;
|
|
|
|
// Record its bundled TX channels, to be freed in the isr
|
|
dma2d_rx_channel_t *rx_chan = dma2d_group->rx_chans[channel_id];
|
|
portENTER_CRITICAL_SAFE(&rx_chan->base.spinlock);
|
|
rx_chan->bundled_tx_channel_mask = bundled_tx_channel_mask;
|
|
portEXIT_CRITICAL_SAFE(&rx_chan->base.spinlock);
|
|
} else {
|
|
found = false;
|
|
goto revert;
|
|
}
|
|
}
|
|
|
|
revert:
|
|
if (!found) {
|
|
for (int i = 0; i < idx; i++) {
|
|
int free_channel_mask = (1 << channel_handle_array[i].chan->channel_id);
|
|
if (channel_handle_array[i].dir == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_group->tx_channel_free_mask |= free_channel_mask;
|
|
} else {
|
|
dma2d_group->rx_channel_free_mask |= free_channel_mask;
|
|
}
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/* This function will free up the RX channel and its bundled TX channels, then check for whether there is next transaction to be picked up */
|
|
static bool free_up_channels(dma2d_group_t *group, dma2d_rx_channel_t *rx_chan)
|
|
{
|
|
bool need_yield = false;
|
|
uint32_t channel_id = rx_chan->base.channel_id;
|
|
// 1. Clean up channels
|
|
uint32_t bundled_tx_channel_mask = rx_chan->bundled_tx_channel_mask;
|
|
uint32_t tx_periph_sel_id_mask = 0;
|
|
uint32_t rx_periph_sel_id_mask = 0;
|
|
// Disable RX channel interrupt
|
|
portENTER_CRITICAL_SAFE(&rx_chan->base.spinlock);
|
|
dma2d_ll_rx_enable_interrupt(group->hal.dev, channel_id, UINT32_MAX, false);
|
|
// Reset RX channel event related pointers and flags
|
|
rx_chan->on_recv_eof = NULL;
|
|
rx_chan->on_desc_done = NULL;
|
|
// Disconnect RX channel from the peripheral
|
|
dma2d_ll_rx_disconnect_from_periph(group->hal.dev, channel_id);
|
|
// Clear the pointer that points to the finished transaction
|
|
rx_chan->base.status.transaction = NULL;
|
|
// Record its periph_sel_id
|
|
assert(rx_chan->base.status.periph_sel_id != -1);
|
|
rx_periph_sel_id_mask |= (1 << rx_chan->base.status.periph_sel_id);
|
|
portEXIT_CRITICAL_SAFE(&rx_chan->base.spinlock);
|
|
// For every bundled TX channels:
|
|
while (rx_chan->bundled_tx_channel_mask) {
|
|
uint32_t nbit = __builtin_ffs(rx_chan->bundled_tx_channel_mask) - 1;
|
|
rx_chan->bundled_tx_channel_mask &= ~(1 << nbit);
|
|
dma2d_tx_channel_t *tx_chan = group->tx_chans[nbit];
|
|
// Disable TX channel interrupt
|
|
portENTER_CRITICAL_SAFE(&tx_chan->base.spinlock);
|
|
dma2d_ll_tx_enable_interrupt(group->hal.dev, nbit, UINT32_MAX, false);
|
|
// Reset TX channel event related pointers
|
|
tx_chan->on_desc_done = NULL;
|
|
// Disconnect TX channel from the peripheral
|
|
dma2d_ll_tx_disconnect_from_periph(group->hal.dev, nbit);
|
|
// Clear the pointer that points to the finished transaction
|
|
tx_chan->base.status.transaction = NULL;
|
|
// Record its periph_sel_id
|
|
assert(tx_chan->base.status.periph_sel_id != -1);
|
|
tx_periph_sel_id_mask |= (1 << tx_chan->base.status.periph_sel_id);
|
|
portEXIT_CRITICAL_SAFE(&tx_chan->base.spinlock);
|
|
}
|
|
// Channel functionality flags will be reset and assigned new values inside `acquire_free_channels_for_trans`
|
|
// Channel reset will always be done at `dma2d_connect` (i.e. when the channel is selected for a new transaction)
|
|
|
|
// 2. Check if next pending transaction in the tailq can start
|
|
bool channels_found = false;
|
|
const dma2d_trans_config_t *next_trans = NULL;
|
|
dma2d_trans_channel_info_t channel_handle_array[DMA2D_MAX_CHANNEL_NUM_PER_TRANSACTION];
|
|
|
|
portENTER_CRITICAL_SAFE(&group->spinlock);
|
|
// Release channels
|
|
group->tx_channel_free_mask |= bundled_tx_channel_mask;
|
|
group->rx_channel_free_mask |= (1 << channel_id);
|
|
// Release M2M periph_sel_id
|
|
group->tx_periph_m2m_free_id_mask |= (tx_periph_sel_id_mask & DMA2D_LL_TX_CHANNEL_PERIPH_M2M_AVAILABLE_ID_MASK);
|
|
group->rx_periph_m2m_free_id_mask |= (rx_periph_sel_id_mask & DMA2D_LL_RX_CHANNEL_PERIPH_M2M_AVAILABLE_ID_MASK);
|
|
|
|
dma2d_trans_t *next_trans_elm = TAILQ_FIRST(&group->pending_trans_tailq);
|
|
if (next_trans_elm) {
|
|
// There is a pending transaction
|
|
next_trans = next_trans_elm->desc;
|
|
channels_found = acquire_free_channels_for_trans(group, next_trans, channel_handle_array);
|
|
}
|
|
|
|
if (channels_found) {
|
|
TAILQ_REMOVE(&group->pending_trans_tailq, next_trans_elm, entry);
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
|
|
|
if (channels_found) {
|
|
// If the transaction can be processed, let consumer handle the transaction
|
|
uint32_t total_channel_num = next_trans->tx_channel_num + next_trans->rx_channel_num;
|
|
// Store the acquired rx_chan into trans_elm (dma2d_trans_t) in case upper driver later need it to call `dma2d_force_end`
|
|
// Upper driver controls the life cycle of trans_elm
|
|
for (int i = 0; i < total_channel_num; i++) {
|
|
if (channel_handle_array[i].dir == DMA2D_CHANNEL_DIRECTION_RX) {
|
|
next_trans_elm->rx_chan = channel_handle_array[i].chan;
|
|
}
|
|
// Also save the transaction pointer
|
|
channel_handle_array[i].chan->status.transaction = next_trans_elm;
|
|
}
|
|
need_yield |= next_trans->on_job_picked(total_channel_num, channel_handle_array, next_trans->user_config);
|
|
}
|
|
return need_yield;
|
|
}
|
|
|
|
static NOINLINE_ATTR bool _dma2d_default_tx_isr(dma2d_group_t *group, int channel_id)
|
|
{
|
|
bool need_yield = false;
|
|
dma2d_tx_channel_t *tx_chan = group->tx_chans[channel_id];
|
|
dma2d_event_data_t edata = {
|
|
.transaction = tx_chan->base.status.transaction,
|
|
};
|
|
// Clear pending interrupt event
|
|
uint32_t intr_status = dma2d_ll_tx_get_interrupt_status(group->hal.dev, channel_id);
|
|
dma2d_ll_tx_clear_interrupt_status(group->hal.dev, channel_id, intr_status);
|
|
|
|
// Handle callback
|
|
if (intr_status & DMA2D_LL_EVENT_TX_DONE) {
|
|
if (tx_chan->on_desc_done) {
|
|
need_yield |= tx_chan->on_desc_done(&tx_chan->base, &edata, tx_chan->user_data);
|
|
}
|
|
}
|
|
return need_yield;
|
|
}
|
|
|
|
static NOINLINE_ATTR bool _dma2d_default_rx_isr(dma2d_group_t *group, int channel_id)
|
|
{
|
|
bool need_yield = false;
|
|
dma2d_rx_channel_t *rx_chan = group->rx_chans[channel_id];
|
|
dma2d_event_data_t edata = {
|
|
.transaction = rx_chan->base.status.transaction,
|
|
};
|
|
// Clear pending interrupt event
|
|
uint32_t intr_status = dma2d_ll_rx_get_interrupt_status(group->hal.dev, channel_id);
|
|
dma2d_ll_rx_clear_interrupt_status(group->hal.dev, channel_id, intr_status);
|
|
// Save RX channel EOF callback pointers temporarily, could be overwritten by new ones
|
|
dma2d_event_callback_t on_recv_eof = rx_chan->on_recv_eof;
|
|
void *user_data = rx_chan->user_data;
|
|
uint32_t suc_eof_desc_addr = dma2d_ll_rx_get_success_eof_desc_addr(group->hal.dev, channel_id);
|
|
|
|
// It is guaranteed in hardware that if SUC_EOF/ERR_EOF interrupt is raised, it will always be raised together with
|
|
// RX_DONE interrupt at the same time.
|
|
|
|
// On RX_DONE triggered, it may be an indication of partially done, call `on_desc_done` callback, allowing 2D-DMA
|
|
// channel operations on the currently acquired channels. Channel may continue running again.
|
|
if (intr_status & DMA2D_LL_EVENT_RX_DONE) {
|
|
if (rx_chan->on_desc_done) {
|
|
need_yield |= rx_chan->on_desc_done(&rx_chan->base, &edata, user_data);
|
|
}
|
|
}
|
|
|
|
// If last transcation completes (regardless success or not), free the channels
|
|
if (intr_status & (DMA2D_LL_EVENT_RX_SUC_EOF | DMA2D_LL_EVENT_RX_ERR_EOF | DMA2D_LL_EVENT_RX_DESC_ERROR)) {
|
|
if (!(intr_status & DMA2D_LL_EVENT_RX_ERR_EOF)) {
|
|
assert(dma2d_ll_rx_is_fsm_idle(group->hal.dev, channel_id));
|
|
}
|
|
need_yield |= free_up_channels(group, rx_chan);
|
|
}
|
|
|
|
// Handle last transaction's end callbacks (at this point, last transaction's channels are completely freed,
|
|
// therefore, we don't pass in channel handle to the callbacks anymore)
|
|
if (intr_status & DMA2D_LL_EVENT_RX_SUC_EOF) {
|
|
if (on_recv_eof) {
|
|
edata.rx_eof_desc_addr = suc_eof_desc_addr;
|
|
need_yield |= on_recv_eof(NULL, &edata, user_data);
|
|
}
|
|
}
|
|
return need_yield;
|
|
}
|
|
|
|
static void dma2d_default_isr(void *args)
|
|
{
|
|
dma2d_channel_t *chan = (dma2d_channel_t *)args;
|
|
dma2d_group_t *group = chan->group;
|
|
|
|
bool need_yield = false;
|
|
|
|
if (chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
need_yield |= _dma2d_default_tx_isr(group, chan->channel_id);
|
|
} else {
|
|
// For RX channel interrupt triggered, we need to check whether there is any interrupt triggered for the
|
|
// bundled TX channels but hasn't been processed. If so, handle TX interrupt first.
|
|
uint32_t bundled_tx_channel_mask = group->rx_chans[chan->channel_id]->bundled_tx_channel_mask;
|
|
while (bundled_tx_channel_mask) {
|
|
uint32_t chan_id = __builtin_ffs(bundled_tx_channel_mask) - 1;
|
|
bundled_tx_channel_mask &= ~(1 << chan_id);
|
|
need_yield |= _dma2d_default_tx_isr(group, chan_id);
|
|
}
|
|
|
|
need_yield |= _dma2d_default_rx_isr(group, chan->channel_id);
|
|
}
|
|
|
|
if (need_yield) {
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
}
|
|
|
|
esp_err_t dma2d_acquire_pool(const dma2d_pool_config_t *config, dma2d_pool_handle_t *ret_pool)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_RETURN_ON_FALSE(config && ret_pool, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(config->pool_id < SOC_DMA2D_GROUPS, ESP_ERR_INVALID_ARG, TAG, "invalid pool_id");
|
|
if (config->intr_priority) {
|
|
ESP_RETURN_ON_FALSE(1 << (config->intr_priority) & ESP_INTR_FLAG_LOWMED, ESP_ERR_INVALID_ARG, TAG,
|
|
"invalid interrupt priority: %" PRIu32, config->intr_priority);
|
|
}
|
|
|
|
int group_id = config->pool_id; // A pool is referring to a module group in hardware
|
|
|
|
_lock_acquire(&s_platform.mutex);
|
|
if (!s_platform.groups[group_id]) {
|
|
dma2d_group_t *pre_alloc_group = heap_caps_calloc(1, sizeof(dma2d_group_t), DMA2D_MEM_ALLOC_CAPS);
|
|
dma2d_tx_channel_t *pre_alloc_tx_channels = heap_caps_calloc(SOC_DMA2D_TX_CHANNELS_PER_GROUP, sizeof(dma2d_tx_channel_t), DMA2D_MEM_ALLOC_CAPS);
|
|
dma2d_rx_channel_t *pre_alloc_rx_channels = heap_caps_calloc(SOC_DMA2D_RX_CHANNELS_PER_GROUP, sizeof(dma2d_rx_channel_t), DMA2D_MEM_ALLOC_CAPS);
|
|
if (pre_alloc_group && pre_alloc_tx_channels && pre_alloc_rx_channels) {
|
|
pre_alloc_group->group_id = group_id;
|
|
pre_alloc_group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
|
TAILQ_INIT(&pre_alloc_group->pending_trans_tailq);
|
|
pre_alloc_group->tx_channel_free_mask = (1 << SOC_DMA2D_TX_CHANNELS_PER_GROUP) - 1;
|
|
pre_alloc_group->rx_channel_free_mask = (1 << SOC_DMA2D_RX_CHANNELS_PER_GROUP) - 1;
|
|
pre_alloc_group->tx_channel_reserved_mask = dma2d_tx_channel_reserved_mask[group_id];
|
|
pre_alloc_group->rx_channel_reserved_mask = dma2d_rx_channel_reserved_mask[group_id];
|
|
pre_alloc_group->tx_periph_m2m_free_id_mask = DMA2D_LL_TX_CHANNEL_PERIPH_M2M_AVAILABLE_ID_MASK;
|
|
pre_alloc_group->rx_periph_m2m_free_id_mask = DMA2D_LL_RX_CHANNEL_PERIPH_M2M_AVAILABLE_ID_MASK;
|
|
pre_alloc_group->intr_priority = -1;
|
|
for (int i = 0; i < SOC_DMA2D_TX_CHANNELS_PER_GROUP; i++) {
|
|
pre_alloc_group->tx_chans[i] = &pre_alloc_tx_channels[i];
|
|
dma2d_tx_channel_t *tx_chan = pre_alloc_group->tx_chans[i];
|
|
tx_chan->base.group = pre_alloc_group;
|
|
tx_chan->base.channel_id = i;
|
|
tx_chan->base.direction = DMA2D_CHANNEL_DIRECTION_TX;
|
|
tx_chan->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
|
}
|
|
for (int i = 0; i < SOC_DMA2D_RX_CHANNELS_PER_GROUP; i++) {
|
|
pre_alloc_group->rx_chans[i] = &pre_alloc_rx_channels[i];
|
|
dma2d_rx_channel_t *rx_chan = pre_alloc_group->rx_chans[i];
|
|
rx_chan->base.group = pre_alloc_group;
|
|
rx_chan->base.channel_id = i;
|
|
rx_chan->base.direction = DMA2D_CHANNEL_DIRECTION_RX;
|
|
rx_chan->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
|
}
|
|
s_platform.groups[group_id] = pre_alloc_group; // register to platform
|
|
|
|
// Enable bus clock for the 2D-DMA registers
|
|
PERIPH_RCC_ATOMIC() {
|
|
dma2d_ll_enable_bus_clock(group_id, true);
|
|
dma2d_ll_reset_register(group_id);
|
|
}
|
|
dma2d_hal_init(&pre_alloc_group->hal, group_id); // initialize HAL context
|
|
// Enable 2D-DMA module clock
|
|
dma2d_ll_hw_enable(s_platform.groups[group_id]->hal.dev, true);
|
|
} else {
|
|
ret = ESP_ERR_NO_MEM;
|
|
free(pre_alloc_tx_channels);
|
|
free(pre_alloc_rx_channels);
|
|
free(pre_alloc_group);
|
|
}
|
|
}
|
|
|
|
// Tracks the number of consumers of 2D-DMA module (clients of the pool)
|
|
if (s_platform.groups[group_id]) {
|
|
s_platform.group_ref_counts[group_id]++;
|
|
}
|
|
|
|
// Allocate interrupts
|
|
// First figure out the interrupt priority
|
|
bool intr_priority_conflict = false;
|
|
if (s_platform.groups[group_id]->intr_priority == -1) {
|
|
s_platform.groups[group_id]->intr_priority = config->intr_priority;
|
|
} else if (config->intr_priority != 0) {
|
|
intr_priority_conflict = (s_platform.groups[group_id]->intr_priority != config->intr_priority);
|
|
}
|
|
ESP_GOTO_ON_FALSE(!intr_priority_conflict, ESP_ERR_INVALID_ARG, wrap_up, TAG, "intr_priority conflict, already is %d but attempt to %" PRIu32, s_platform.groups[group_id]->intr_priority, config->intr_priority);
|
|
uint32_t intr_flags = DMA2D_INTR_ALLOC_FLAGS;
|
|
if (s_platform.groups[group_id]->intr_priority) {
|
|
intr_flags |= (1 << s_platform.groups[group_id]->intr_priority);
|
|
} else {
|
|
intr_flags |= ESP_INTR_FLAG_LOWMED;
|
|
}
|
|
|
|
// Allocate TX and RX interrupts
|
|
if (s_platform.groups[group_id]) {
|
|
for (int i = 0; i < SOC_DMA2D_RX_CHANNELS_PER_GROUP; i++) {
|
|
dma2d_rx_channel_t *rx_chan = s_platform.groups[group_id]->rx_chans[i];
|
|
if (rx_chan->base.intr == NULL) {
|
|
ret = esp_intr_alloc_intrstatus(dma2d_periph_signals.groups[group_id].rx_irq_id[i],
|
|
intr_flags,
|
|
(uint32_t)dma2d_ll_rx_get_interrupt_status_reg(s_platform.groups[group_id]->hal.dev, i),
|
|
DMA2D_LL_RX_EVENT_MASK, dma2d_default_isr, &rx_chan->base, &rx_chan->base.intr);
|
|
if (ret != ESP_OK) {
|
|
ret = ESP_FAIL;
|
|
ESP_LOGE(TAG, "alloc interrupt failed on rx channel (%d, %d)", group_id, i);
|
|
goto wrap_up;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < SOC_DMA2D_TX_CHANNELS_PER_GROUP; i++) {
|
|
dma2d_tx_channel_t *tx_chan = s_platform.groups[group_id]->tx_chans[i];
|
|
if (tx_chan->base.intr == NULL) {
|
|
ret = esp_intr_alloc_intrstatus(dma2d_periph_signals.groups[group_id].tx_irq_id[i],
|
|
intr_flags,
|
|
(uint32_t)dma2d_ll_tx_get_interrupt_status_reg(s_platform.groups[group_id]->hal.dev, i),
|
|
DMA2D_LL_TX_EVENT_MASK, dma2d_default_isr, &tx_chan->base, &tx_chan->base.intr);
|
|
if (ret != ESP_OK) {
|
|
ret = ESP_FAIL;
|
|
ESP_LOGE(TAG, "alloc interrupt failed on tx channel (%d, %d)", group_id, i);
|
|
goto wrap_up;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
wrap_up:
|
|
_lock_release(&s_platform.mutex);
|
|
|
|
if (ret != ESP_OK && s_platform.groups[group_id]) {
|
|
dma2d_release_pool(s_platform.groups[group_id]);
|
|
}
|
|
|
|
*ret_pool = s_platform.groups[group_id];
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_release_pool(dma2d_pool_handle_t dma2d_pool)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_RETURN_ON_FALSE(dma2d_pool, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
dma2d_group_t *dma2d_group = dma2d_pool;
|
|
|
|
bool do_deinitialize = false;
|
|
int group_id = dma2d_group->group_id;
|
|
|
|
_lock_acquire(&s_platform.mutex);
|
|
// Remove a client from the 2D-DMA pool
|
|
s_platform.group_ref_counts[group_id]--;
|
|
// If the pool has no client, then release pool resources
|
|
if (s_platform.group_ref_counts[group_id] == 0) {
|
|
assert(s_platform.groups[group_id]);
|
|
do_deinitialize = true;
|
|
// There must be no transaction pending (this should be handled by upper (consumer) driver)
|
|
// Transaction tailq should be empty at this moment
|
|
if (!TAILQ_EMPTY(&dma2d_group->pending_trans_tailq)) {
|
|
ret = ESP_ERR_NOT_ALLOWED;
|
|
ESP_LOGE(TAG, "Still pending transaction in the pool");
|
|
s_platform.group_ref_counts[group_id]++;
|
|
goto err;
|
|
}
|
|
s_platform.groups[group_id] = NULL; // deregister from platform
|
|
// Disable 2D-DMA module clock
|
|
dma2d_ll_hw_enable(dma2d_group->hal.dev, false);
|
|
// Disable the bus clock for the 2D-DMA registers
|
|
PERIPH_RCC_ATOMIC() {
|
|
dma2d_ll_enable_bus_clock(group_id, false);
|
|
}
|
|
}
|
|
|
|
if (do_deinitialize) {
|
|
for (int i = 0; i < SOC_DMA2D_RX_CHANNELS_PER_GROUP; i++) {
|
|
if (dma2d_group->rx_chans[i]->base.intr) {
|
|
esp_intr_free(dma2d_group->rx_chans[i]->base.intr);
|
|
}
|
|
}
|
|
for (int i = 0; i < SOC_DMA2D_TX_CHANNELS_PER_GROUP; i++) {
|
|
if (dma2d_group->tx_chans[i]->base.intr) {
|
|
esp_intr_free(dma2d_group->tx_chans[i]->base.intr);
|
|
}
|
|
}
|
|
free(*(dma2d_group->tx_chans));
|
|
free(*(dma2d_group->rx_chans));
|
|
free(dma2d_group);
|
|
s_platform.groups[group_id] = NULL;
|
|
}
|
|
err:
|
|
_lock_release(&s_platform.mutex);
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_connect(dma2d_channel_handle_t dma2d_chan, const dma2d_trigger_t *trig_periph)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && trig_periph, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
// Find periph_sel_id for the channel
|
|
int peri_sel_id = trig_periph->periph_sel_id;
|
|
uint32_t *periph_m2m_free_id_mask = NULL;
|
|
uint32_t periph_m2m_available_id_mask = 0;
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
periph_m2m_free_id_mask = &group->tx_periph_m2m_free_id_mask;
|
|
periph_m2m_available_id_mask = DMA2D_LL_TX_CHANNEL_PERIPH_M2M_AVAILABLE_ID_MASK;
|
|
} else {
|
|
periph_m2m_free_id_mask = &group->rx_periph_m2m_free_id_mask;
|
|
periph_m2m_available_id_mask = DMA2D_LL_RX_CHANNEL_PERIPH_M2M_AVAILABLE_ID_MASK;
|
|
}
|
|
portENTER_CRITICAL_SAFE(&group->spinlock);
|
|
if (trig_periph->periph == DMA2D_TRIG_PERIPH_M2M) {
|
|
if (peri_sel_id == -1) {
|
|
// Unspecified periph_sel_id, decide by the driver
|
|
peri_sel_id = __builtin_ctz(*periph_m2m_free_id_mask);
|
|
} else {
|
|
// Check whether specified periph_sel_id is valid
|
|
if (!((1 << peri_sel_id) & *periph_m2m_free_id_mask & periph_m2m_available_id_mask)) {
|
|
peri_sel_id = -1; // Occupied or invalid m2m peri_sel_id
|
|
}
|
|
}
|
|
}
|
|
if (peri_sel_id >= 0) {
|
|
dma2d_chan->status.periph_sel_id = peri_sel_id;
|
|
*periph_m2m_free_id_mask &= ~(1 << peri_sel_id); // acquire m2m periph_sel_id
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
|
ESP_GOTO_ON_FALSE_ISR(peri_sel_id >= 0, ESP_ERR_INVALID_ARG, err, TAG, "invalid periph_sel_id");
|
|
|
|
portENTER_CRITICAL_SAFE(&dma2d_chan->spinlock);
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_ll_tx_stop(group->hal.dev, channel_id);
|
|
dma2d_hal_tx_reset_channel(&group->hal, channel_id);
|
|
dma2d_ll_tx_connect_to_periph(group->hal.dev, channel_id, trig_periph->periph, peri_sel_id);
|
|
|
|
// Configure reorder functionality
|
|
dma2d_ll_tx_enable_reorder(group->hal.dev, channel_id, dma2d_chan->status.reorder_en);
|
|
// Assume dscr_port enable or not can be directly derived from trig_periph
|
|
dma2d_ll_tx_enable_dscr_port(group->hal.dev, channel_id, trig_periph->periph == DMA2D_TRIG_PERIPH_PPA_SR);
|
|
|
|
// Reset to certain settings
|
|
dma2d_ll_tx_enable_owner_check(group->hal.dev, channel_id, false);
|
|
dma2d_ll_tx_enable_auto_write_back(group->hal.dev, channel_id, false);
|
|
dma2d_ll_tx_enable_eof_mode(group->hal.dev, channel_id, true);
|
|
dma2d_ll_tx_enable_descriptor_burst(group->hal.dev, channel_id, false);
|
|
dma2d_ll_tx_set_data_burst_length(group->hal.dev, channel_id, DMA2D_DATA_BURST_LENGTH_128);
|
|
dma2d_ll_tx_enable_page_bound_wrap(group->hal.dev, channel_id, true);
|
|
dma2d_ll_tx_set_macro_block_size(group->hal.dev, channel_id, DMA2D_MACRO_BLOCK_SIZE_NONE);
|
|
if ((1 << channel_id) & DMA2D_LL_TX_CHANNEL_SUPPORT_CSC_MASK) {
|
|
dma2d_ll_tx_configure_color_space_conv(group->hal.dev, channel_id, DMA2D_CSC_TX_NONE);
|
|
}
|
|
|
|
// Disable and clear all interrupt events
|
|
dma2d_ll_tx_enable_interrupt(group->hal.dev, channel_id, UINT32_MAX, false); // disable all interrupt events
|
|
dma2d_ll_tx_clear_interrupt_status(group->hal.dev, channel_id, UINT32_MAX); // clear all pending events
|
|
} else {
|
|
dma2d_ll_rx_stop(group->hal.dev, channel_id);
|
|
dma2d_hal_rx_reset_channel(&group->hal, channel_id);
|
|
dma2d_ll_rx_connect_to_periph(group->hal.dev, channel_id, trig_periph->periph, peri_sel_id);
|
|
|
|
// Configure reorder functionality
|
|
dma2d_ll_rx_enable_reorder(group->hal.dev, channel_id, dma2d_chan->status.reorder_en);
|
|
// Assume dscr_port enable or not can be directly derived from trig_periph
|
|
dma2d_ll_rx_enable_dscr_port(group->hal.dev, channel_id, trig_periph->periph == DMA2D_TRIG_PERIPH_PPA_SR);
|
|
|
|
// Reset to certain settings
|
|
dma2d_ll_rx_enable_owner_check(group->hal.dev, channel_id, false);
|
|
dma2d_ll_rx_set_auto_return_owner(group->hal.dev, channel_id, DMA2D_DESCRIPTOR_BUFFER_OWNER_CPU); // After auto write back, the owner field will be cleared
|
|
dma2d_ll_rx_enable_descriptor_burst(group->hal.dev, channel_id, false);
|
|
dma2d_ll_rx_set_data_burst_length(group->hal.dev, channel_id, DMA2D_DATA_BURST_LENGTH_128);
|
|
dma2d_ll_rx_enable_page_bound_wrap(group->hal.dev, channel_id, true);
|
|
dma2d_ll_rx_set_macro_block_size(group->hal.dev, channel_id, DMA2D_MACRO_BLOCK_SIZE_NONE);
|
|
if ((1 << channel_id) & DMA2D_LL_RX_CHANNEL_SUPPORT_CSC_MASK) {
|
|
dma2d_ll_rx_configure_color_space_conv(group->hal.dev, channel_id, DMA2D_CSC_RX_NONE);
|
|
}
|
|
|
|
// Disable and clear all interrupt events
|
|
dma2d_ll_rx_enable_interrupt(group->hal.dev, channel_id, UINT32_MAX, false); // disable all interrupt events
|
|
dma2d_ll_rx_clear_interrupt_status(group->hal.dev, channel_id, UINT32_MAX); // clear all pending events
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&dma2d_chan->spinlock);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_register_tx_event_callbacks(dma2d_channel_handle_t dma2d_chan, dma2d_tx_event_callbacks_t *cbs, void *user_data)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX && cbs, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan->intr, ESP_ERR_INVALID_STATE, err, TAG, "tx channel intr not allocated");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
dma2d_tx_channel_t *tx_chan = __containerof(dma2d_chan, dma2d_tx_channel_t, base);
|
|
|
|
#if CONFIG_DMA2D_ISR_IRAM_SAFE
|
|
if (cbs->on_desc_done) {
|
|
ESP_GOTO_ON_FALSE_ISR(esp_ptr_in_iram(cbs->on_desc_done),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "on_desc_done not in IRAM");
|
|
}
|
|
if (user_data) {
|
|
ESP_GOTO_ON_FALSE_ISR(esp_ptr_internal(user_data),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM");
|
|
}
|
|
#endif
|
|
|
|
// Enable/Disable 2D-DMA interrupt events for the TX channel
|
|
uint32_t mask = 0;
|
|
portENTER_CRITICAL_SAFE(&tx_chan->base.spinlock);
|
|
if (cbs->on_desc_done) {
|
|
tx_chan->on_desc_done = cbs->on_desc_done;
|
|
mask |= DMA2D_LL_EVENT_TX_DONE;
|
|
}
|
|
tx_chan->user_data = user_data;
|
|
dma2d_ll_tx_enable_interrupt(group->hal.dev, tx_chan->base.channel_id, mask, true);
|
|
portEXIT_CRITICAL_SAFE(&tx_chan->base.spinlock);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_register_rx_event_callbacks(dma2d_channel_handle_t dma2d_chan, dma2d_rx_event_callbacks_t *cbs, void *user_data)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_RX && cbs, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
dma2d_rx_channel_t *rx_chan = __containerof(dma2d_chan, dma2d_rx_channel_t, base);
|
|
|
|
#if CONFIG_DMA2D_ISR_IRAM_SAFE
|
|
if (cbs->on_recv_eof) {
|
|
ESP_GOTO_ON_FALSE_ISR(esp_ptr_in_iram(cbs->on_recv_eof),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "on_recv_eof not in IRAM");
|
|
}
|
|
if (cbs->on_desc_done) {
|
|
ESP_GOTO_ON_FALSE_ISR(esp_ptr_in_iram(cbs->on_desc_done),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "on_desc_done not in IRAM");
|
|
}
|
|
if (user_data) {
|
|
ESP_GOTO_ON_FALSE_ISR(esp_ptr_internal(user_data),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM");
|
|
}
|
|
#endif
|
|
|
|
// Enable/Disable 2D-DMA interrupt events for the RX channel
|
|
uint32_t mask = 0;
|
|
portENTER_CRITICAL_SAFE(&rx_chan->base.spinlock);
|
|
if (cbs->on_recv_eof) {
|
|
rx_chan->on_recv_eof = cbs->on_recv_eof;
|
|
mask |= DMA2D_LL_EVENT_RX_SUC_EOF;
|
|
}
|
|
if (cbs->on_desc_done) {
|
|
rx_chan->on_desc_done = cbs->on_desc_done;
|
|
mask |= DMA2D_LL_EVENT_RX_DONE;
|
|
}
|
|
rx_chan->user_data = user_data;
|
|
dma2d_ll_rx_enable_interrupt(group->hal.dev, rx_chan->base.channel_id, mask, true);
|
|
|
|
portEXIT_CRITICAL_SAFE(&rx_chan->base.spinlock);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_set_desc_addr(dma2d_channel_handle_t dma2d_chan, intptr_t desc_base_addr)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && desc_base_addr, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
// 2D-DMA descriptor addr needs 8-byte alignment and not in TCM (addr not in TCM is IDF restriction)
|
|
ESP_GOTO_ON_FALSE_ISR((desc_base_addr & 0x7) == 0 && !esp_ptr_in_tcm((void *)desc_base_addr), ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_ll_tx_set_desc_addr(group->hal.dev, channel_id, desc_base_addr);
|
|
} else {
|
|
dma2d_ll_rx_set_desc_addr(group->hal.dev, channel_id, desc_base_addr);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_start(dma2d_channel_handle_t dma2d_chan)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_RX) {
|
|
// dma2d driver relies on going into ISR to free the channels,
|
|
// so even if callbacks are not necessary in some cases, minimum interrupt events should be enabled to trigger ISR
|
|
dma2d_ll_rx_enable_interrupt(group->hal.dev, channel_id, DMA2D_RX_DEFAULT_INTR_FLAG, true);
|
|
}
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
assert(dma2d_ll_tx_is_fsm_idle(group->hal.dev, channel_id));
|
|
dma2d_ll_tx_start(group->hal.dev, channel_id);
|
|
} else {
|
|
assert(dma2d_ll_rx_is_fsm_idle(group->hal.dev, channel_id));
|
|
dma2d_ll_rx_start(group->hal.dev, channel_id);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_stop(dma2d_channel_handle_t dma2d_chan)
|
|
{
|
|
ESP_RETURN_ON_FALSE_ISR(dma2d_chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_ll_tx_stop(group->hal.dev, channel_id);
|
|
} else {
|
|
dma2d_ll_rx_stop(group->hal.dev, channel_id);
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t dma2d_append(dma2d_channel_handle_t dma2d_chan)
|
|
{
|
|
ESP_RETURN_ON_FALSE_ISR(dma2d_chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_ll_tx_restart(group->hal.dev, channel_id);
|
|
} else {
|
|
dma2d_ll_rx_restart(group->hal.dev, channel_id);
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t dma2d_reset(dma2d_channel_handle_t dma2d_chan)
|
|
{
|
|
ESP_RETURN_ON_FALSE_ISR(dma2d_chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
portENTER_CRITICAL_SAFE(&dma2d_chan->spinlock);
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_hal_tx_reset_channel(&group->hal, channel_id);
|
|
} else {
|
|
dma2d_hal_rx_reset_channel(&group->hal, channel_id);
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&dma2d_chan->spinlock);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t dma2d_apply_strategy(dma2d_channel_handle_t dma2d_chan, const dma2d_strategy_config_t *config)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && config, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_ll_tx_enable_owner_check(group->hal.dev, channel_id, config->owner_check);
|
|
dma2d_ll_tx_enable_auto_write_back(group->hal.dev, channel_id, config->auto_update_desc);
|
|
dma2d_ll_tx_enable_eof_mode(group->hal.dev, channel_id, config->eof_till_data_popped);
|
|
} else {
|
|
dma2d_ll_rx_enable_owner_check(group->hal.dev, channel_id, config->owner_check);
|
|
// RX channels do not have control over auto_write_back (always auto_write_back) and eof_mode
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_set_transfer_ability(dma2d_channel_handle_t dma2d_chan, const dma2d_transfer_ability_t *ability)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && ability, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(ability->data_burst_length < DMA2D_DATA_BURST_LENGTH_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(ability->mb_size < DMA2D_MACRO_BLOCK_SIZE_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
dma2d_ll_tx_enable_descriptor_burst(group->hal.dev, channel_id, ability->desc_burst_en);
|
|
dma2d_ll_tx_set_data_burst_length(group->hal.dev, channel_id, ability->data_burst_length);
|
|
dma2d_ll_tx_set_macro_block_size(group->hal.dev, channel_id, ability->mb_size);
|
|
} else {
|
|
dma2d_ll_rx_enable_descriptor_burst(group->hal.dev, channel_id, ability->desc_burst_en);
|
|
dma2d_ll_rx_set_data_burst_length(group->hal.dev, channel_id, ability->data_burst_length);
|
|
dma2d_ll_rx_set_macro_block_size(group->hal.dev, channel_id, ability->mb_size);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_configure_color_space_conversion(dma2d_channel_handle_t dma2d_chan, const dma2d_csc_config_t *config)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_chan && config, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_group_t *group = dma2d_chan->group;
|
|
int channel_id = dma2d_chan->channel_id;
|
|
|
|
if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) {
|
|
ESP_GOTO_ON_FALSE_ISR((1 << channel_id) & DMA2D_LL_TX_CHANNEL_SUPPORT_CSC_MASK, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(config->tx_csc_option < DMA2D_CSC_TX_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(config->post_scramble == 0, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(config->pre_scramble == DMA2D_SCRAMBLE_ORDER_BYTE2_1_0 || (config->pre_scramble != DMA2D_SCRAMBLE_ORDER_BYTE2_1_0 && config->tx_csc_option != DMA2D_CSC_TX_NONE),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_ll_tx_configure_color_space_conv(group->hal.dev, channel_id, config->tx_csc_option);
|
|
dma2d_ll_tx_set_csc_pre_scramble(group->hal.dev, channel_id, config->pre_scramble);
|
|
} else {
|
|
ESP_GOTO_ON_FALSE_ISR((1 << channel_id) & DMA2D_LL_RX_CHANNEL_SUPPORT_CSC_MASK, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(config->rx_csc_option < DMA2D_CSC_RX_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR((config->pre_scramble == DMA2D_SCRAMBLE_ORDER_BYTE2_1_0 && config->post_scramble == DMA2D_SCRAMBLE_ORDER_BYTE2_1_0) ||
|
|
((config->pre_scramble != DMA2D_SCRAMBLE_ORDER_BYTE2_1_0 || config->post_scramble != DMA2D_SCRAMBLE_ORDER_BYTE2_1_0) && config->rx_csc_option != DMA2D_CSC_RX_NONE),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
dma2d_ll_rx_configure_color_space_conv(group->hal.dev, channel_id, config->rx_csc_option);
|
|
dma2d_ll_rx_set_csc_pre_scramble(group->hal.dev, channel_id, config->pre_scramble);
|
|
dma2d_ll_rx_set_csc_post_scramble(group->hal.dev, channel_id, config->post_scramble);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_enqueue(dma2d_pool_handle_t dma2d_pool, const dma2d_trans_config_t *trans_desc, dma2d_trans_t *trans_placeholder)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE_ISR(dma2d_pool && trans_desc && trans_placeholder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE_ISR(trans_desc->rx_channel_num <= 1, ESP_ERR_INVALID_ARG, err, TAG, "one trans at most has one rx channel");
|
|
uint32_t total_channel_num = trans_desc->tx_channel_num + trans_desc->rx_channel_num;
|
|
ESP_GOTO_ON_FALSE_ISR(total_channel_num <= DMA2D_MAX_CHANNEL_NUM_PER_TRANSACTION, ESP_ERR_INVALID_ARG, err, TAG, "too many channels acquiring for a trans");
|
|
dma2d_group_t *dma2d_group = dma2d_pool;
|
|
if (trans_desc->specified_tx_channel_mask || trans_desc->specified_rx_channel_mask) {
|
|
ESP_GOTO_ON_FALSE_ISR(
|
|
(trans_desc->specified_tx_channel_mask ? (trans_desc->specified_tx_channel_mask & dma2d_group->tx_channel_reserved_mask) : 1 ) &&
|
|
(trans_desc->specified_rx_channel_mask ? (trans_desc->specified_rx_channel_mask & dma2d_group->rx_channel_reserved_mask) : 1 ),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "specified channel(s) not reserved");
|
|
ESP_GOTO_ON_FALSE_ISR(
|
|
(__builtin_popcount(trans_desc->specified_tx_channel_mask) == trans_desc->tx_channel_num) &&
|
|
(__builtin_popcount(trans_desc->specified_rx_channel_mask) == trans_desc->rx_channel_num) &&
|
|
(!trans_desc->tx_channel_num ? 1 : (trans_desc->specified_tx_channel_mask & ((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_TX_REORDER) ? DMA2D_LL_TX_CHANNEL_SUPPORT_RO_MASK : UINT32_MAX) & ((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC) ? DMA2D_LL_TX_CHANNEL_SUPPORT_CSC_MASK : UINT32_MAX))) &&
|
|
(!trans_desc->rx_channel_num ? 1 : (trans_desc->specified_rx_channel_mask & ((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_REORDER) ? DMA2D_LL_RX_CHANNEL_SUPPORT_RO_MASK : UINT32_MAX) & ((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC) ? DMA2D_LL_RX_CHANNEL_SUPPORT_CSC_MASK : UINT32_MAX))) &&
|
|
((trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING) ? (trans_desc->specified_tx_channel_mask == trans_desc->specified_rx_channel_mask) : 1),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "specified channels cannot meet function requirements");
|
|
}
|
|
|
|
#if CONFIG_DMA2D_ISR_IRAM_SAFE
|
|
ESP_GOTO_ON_FALSE_ISR(trans_desc->on_job_picked && esp_ptr_in_iram(trans_desc->on_job_picked),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "on_job_picked not in IRAM");
|
|
ESP_GOTO_ON_FALSE_ISR(trans_desc->user_config && esp_ptr_internal(trans_desc->user_config),
|
|
ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM");
|
|
#endif
|
|
|
|
trans_placeholder->desc = trans_desc;
|
|
dma2d_trans_channel_info_t channel_handle_array[DMA2D_MAX_CHANNEL_NUM_PER_TRANSACTION];
|
|
|
|
portENTER_CRITICAL_SAFE(&dma2d_group->spinlock);
|
|
bool enqueue = !acquire_free_channels_for_trans(dma2d_group, trans_desc, channel_handle_array);
|
|
if (enqueue) {
|
|
if (!trans_desc->specified_tx_channel_mask && !trans_desc->specified_rx_channel_mask) {
|
|
TAILQ_INSERT_TAIL(&dma2d_group->pending_trans_tailq, trans_placeholder, entry);
|
|
} else {
|
|
TAILQ_INSERT_HEAD(&dma2d_group->pending_trans_tailq, trans_placeholder, entry);
|
|
}
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&dma2d_group->spinlock);
|
|
if (!enqueue) {
|
|
// Free channels available, start transaction immediately
|
|
// Store the acquired rx_chan into trans_placeholder (dma2d_trans_t) in case upper driver later need it to call `dma2d_force_end`
|
|
// Upper driver controls the life cycle of trans_placeholder
|
|
for (int i = 0; i < total_channel_num; i++) {
|
|
if (channel_handle_array[i].dir == DMA2D_CHANNEL_DIRECTION_RX) {
|
|
trans_placeholder->rx_chan = channel_handle_array[i].chan;
|
|
}
|
|
// Also save the transaction pointer
|
|
channel_handle_array[i].chan->status.transaction = trans_placeholder;
|
|
}
|
|
trans_desc->on_job_picked(total_channel_num, channel_handle_array, trans_desc->user_config);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dma2d_force_end(dma2d_trans_t *trans, bool *need_yield)
|
|
{
|
|
ESP_RETURN_ON_FALSE_ISR(trans && trans->rx_chan && need_yield, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
assert(trans->rx_chan->direction == DMA2D_CHANNEL_DIRECTION_RX);
|
|
|
|
dma2d_group_t *group = trans->rx_chan->group;
|
|
|
|
bool in_flight = false;
|
|
// We judge whether the transaction is in-flight by checking the RX channel it uses is in-use or free
|
|
portENTER_CRITICAL_SAFE(&group->spinlock);
|
|
if (!(group->rx_channel_free_mask & (1 << trans->rx_chan->channel_id))) {
|
|
in_flight = true;
|
|
dma2d_ll_rx_enable_interrupt(group->hal.dev, trans->rx_chan->channel_id, UINT32_MAX, false);
|
|
assert(!dma2d_ll_rx_is_fsm_idle(group->hal.dev, trans->rx_chan->channel_id));
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
|
ESP_RETURN_ON_FALSE_ISR(in_flight, ESP_ERR_INVALID_STATE, TAG, "transaction not in-flight");
|
|
|
|
dma2d_rx_channel_t *rx_chan = group->rx_chans[trans->rx_chan->channel_id];
|
|
// Stop the RX channel and its bundled TX channels first
|
|
dma2d_stop(&rx_chan->base);
|
|
uint32_t tx_chans = rx_chan->bundled_tx_channel_mask;
|
|
for (int i = 0; i < SOC_DMA2D_TX_CHANNELS_PER_GROUP; i++) {
|
|
if (tx_chans & (1 << i)) {
|
|
dma2d_stop(&group->tx_chans[i]->base);
|
|
}
|
|
}
|
|
// Then release channels
|
|
*need_yield = free_up_channels(group, rx_chan);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
size_t dma2d_get_trans_elm_size(void)
|
|
{
|
|
return sizeof(dma2d_trans_t);
|
|
}
|