/* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #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); }