From 08eada73015d73c85209dbf6ebe6921400d8a88e Mon Sep 17 00:00:00 2001 From: Song Ruo Jing Date: Mon, 21 Aug 2023 17:04:55 +0800 Subject: [PATCH] feat(esp_hw_support/dma2d): Add 2D-DMA support on ESP32P4 --- components/esp_hw_support/CMakeLists.txt | 4 + components/esp_hw_support/Kconfig | 1 - components/esp_hw_support/dma/Kconfig.dma | 20 + components/esp_hw_support/dma/dma2d.c | 979 ++++++++++++++ components/esp_hw_support/dma/dma2d_priv.h | 94 ++ .../dma/include/esp_private/dma2d.h | 419 ++++++ components/esp_hw_support/dma/linker.lf | 34 + .../test_apps/.build-test-rules.yml | 6 + .../test_apps/dma2d/CMakeLists.txt | 10 + .../esp_hw_support/test_apps/dma2d/README.md | 2 + .../test_apps/dma2d/main/CMakeLists.txt | 10 + .../test_apps/dma2d/main/dma2d_test_utils.c | 162 +++ .../test_apps/dma2d/main/dma2d_test_utils.h | 63 + .../test_apps/dma2d/main/test_app_main.c | 36 + .../test_apps/dma2d/main/test_dma2d.c | 677 ++++++++++ .../test_apps/dma2d/pytest_dma2d.py | 18 + .../test_apps/dma2d/sdkconfig.ci.release | 6 + .../test_apps/dma2d/sdkconfig.defaults | 3 + .../dma2d/sdkconfig.defaults.esp32p4 | 3 + components/hal/CMakeLists.txt | 5 + components/hal/color_hal.c | 42 + components/hal/dma2d_hal.c | 29 + components/hal/esp32c2/include/hal/gdma_ll.h | 10 +- components/hal/esp32c3/include/hal/gdma_ll.h | 10 +- components/hal/esp32c6/include/hal/gdma_ll.h | 10 +- components/hal/esp32h2/include/hal/gdma_ll.h | 10 +- .../hal/esp32p4/include/hal/ahb_dma_ll.h | 10 +- .../hal/esp32p4/include/hal/axi_dma_ll.h | 10 +- .../hal/esp32p4/include/hal/clk_gate_ll.h | 3 - components/hal/esp32p4/include/hal/dma2d_ll.h | 1166 +++++++++++++++++ components/hal/esp32s3/include/hal/gdma_ll.h | 10 +- components/hal/include/hal/color_hal.h | 27 + components/hal/include/hal/dma2d_hal.h | 56 + components/hal/include/hal/dma2d_types.h | 252 ++++ components/soc/CMakeLists.txt | 4 + components/soc/esp32p4/dma2d_periph.c | 24 + .../esp32p4/include/soc/Kconfig.soc_caps.in | 16 + .../soc/esp32p4/include/soc/dma2d_channel.h | 19 + .../soc/esp32p4/include/soc/dma2d_struct.h | 287 ++-- components/soc/esp32p4/include/soc/soc_caps.h | 7 + .../soc/esp32p4/ld/esp32p4.peripherals.ld | 4 +- components/soc/include/soc/dma2d_periph.h | 29 + 42 files changed, 4333 insertions(+), 254 deletions(-) create mode 100644 components/esp_hw_support/dma/dma2d.c create mode 100644 components/esp_hw_support/dma/dma2d_priv.h create mode 100644 components/esp_hw_support/dma/include/esp_private/dma2d.h create mode 100644 components/esp_hw_support/test_apps/dma2d/CMakeLists.txt create mode 100644 components/esp_hw_support/test_apps/dma2d/README.md create mode 100644 components/esp_hw_support/test_apps/dma2d/main/CMakeLists.txt create mode 100644 components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.c create mode 100644 components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.h create mode 100644 components/esp_hw_support/test_apps/dma2d/main/test_app_main.c create mode 100644 components/esp_hw_support/test_apps/dma2d/main/test_dma2d.c create mode 100644 components/esp_hw_support/test_apps/dma2d/pytest_dma2d.py create mode 100644 components/esp_hw_support/test_apps/dma2d/sdkconfig.ci.release create mode 100644 components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults create mode 100644 components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults.esp32p4 create mode 100644 components/hal/color_hal.c create mode 100644 components/hal/dma2d_hal.c create mode 100644 components/hal/esp32p4/include/hal/dma2d_ll.h create mode 100644 components/hal/include/hal/color_hal.h create mode 100644 components/hal/include/hal/dma2d_hal.h create mode 100644 components/hal/include/hal/dma2d_types.h create mode 100644 components/soc/esp32p4/dma2d_periph.c create mode 100644 components/soc/esp32p4/include/soc/dma2d_channel.h create mode 100644 components/soc/include/soc/dma2d_periph.h diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index dec7467301..057ec69f8c 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -97,6 +97,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "dma/dw_gdma.c") endif() + if(CONFIG_SOC_DMA2D_SUPPORTED) + list(APPEND srcs "dma/dma2d.c") + endif() + if(CONFIG_SOC_SYSTIMER_SUPPORTED) list(APPEND srcs "port/${target}/systimer.c") endif() diff --git a/components/esp_hw_support/Kconfig b/components/esp_hw_support/Kconfig index cca9edb046..ba3b1cb9b3 100644 --- a/components/esp_hw_support/Kconfig +++ b/components/esp_hw_support/Kconfig @@ -348,7 +348,6 @@ menu "Hardware Settings" clock support isn't done yet. So with this option, we use xtal on FPGA as the clock source. - # Invisible bringup bypass options for esp_hw_support component config ESP_BRINGUP_BYPASS_RANDOM_SETTING bool default y if !SOC_RNG_SUPPORTED diff --git a/components/esp_hw_support/dma/Kconfig.dma b/components/esp_hw_support/dma/Kconfig.dma index 6ba2246ef3..48aef8846a 100644 --- a/components/esp_hw_support/dma/Kconfig.dma +++ b/components/esp_hw_support/dma/Kconfig.dma @@ -55,3 +55,23 @@ menu "DW_GDMA Configurations" Wether to enable the debug log message for DW_GDMA driver. Note that, this option only controls the DW_GDMA driver log, won't affect other drivers. endmenu # DW_GDMA Configurations + +menu "2D-DMA Configurations" + depends on SOC_DMA2D_SUPPORTED + + config DMA2D_OPERATION_FUNC_IN_IRAM + bool "Place 2D-DMA operation functions into IRAM" + default n + help + Place 2D-DMA all operation functions, including control functions (e.g. start/stop/append/reset) and setter + functions (e.g. connect/strategy/callback registration) into IRAM, so that these functions can be IRAM-safe + and able to be called in the other IRAM interrupt context. It also helps optimizing the performance. + + config DMA2D_ISR_IRAM_SAFE + bool "2D-DMA ISR IRAM-Safe" + default n + help + This will ensure the 2D-DMA interrupt handler is IRAM-Safe, allow to avoid flash + cache misses, and also be able to run whilst the cache is disabled. + (e.g. SPI Flash write). +endmenu # 2D-DMA Configurations diff --git a/components/esp_hw_support/dma/dma2d.c b/components/esp_hw_support/dma/dma2d.c new file mode 100644 index 0000000000..1e2c6ceca8 --- /dev/null +++ b/components/esp_hw_support/dma/dma2d.c @@ -0,0 +1,979 @@ +/* + * 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; + } else if (trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING) { + dma2d_group->tx_chans[channel_id]->base.status.has_sibling = 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; + } else if (trans_desc->channel_flags & DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING) { + dma2d_group->rx_chans[channel_id]->base.status.has_sibling = 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; + // 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; + 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; + 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); + group->tx_channel_free_mask |= bundled_tx_channel_mask; + group->rx_channel_free_mask |= (1 << channel_id); + int rx_periph_sel_id = group->rx_chans[channel_id]->base.status.periph_sel_id; + if (rx_periph_sel_id != -1 && ((1 << rx_periph_sel_id) & DMA2D_LL_CHANNEL_PERIPH_M2M_FREE_ID_MASK)) { + group->periph_m2m_free_id_mask |= (1 << rx_periph_sel_id); // release m2m periph_sel_id + } + + 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) || + (intr_status & DMA2D_LL_EVENT_RX_ERR_EOF) || + (intr_status & DMA2D_LL_EVENT_RX_DESC_ERROR)) { + 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->periph_m2m_free_id_mask = DMA2D_LL_CHANNEL_PERIPH_M2M_FREE_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); + // Configure 2D-DMA accessible memory range + dma2d_ll_set_accessible_mem_range(s_platform.groups[group_id]->hal.dev); + } 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; + portENTER_CRITICAL_SAFE(&group->spinlock); + if (trig_periph->periph == DMA2D_TRIG_PERIPH_M2M) { + // Assign peri_sel_id to one of {4, 5, 6, 7} + assert(dma2d_chan->status.has_sibling); + // First find out the peri_sel_id of its sibling channel + int sibling_periph_sel_id = -1; + if (dma2d_chan->direction == DMA2D_CHANNEL_DIRECTION_TX) { + sibling_periph_sel_id = group->rx_chans[channel_id]->base.status.periph_sel_id; + } else { + sibling_periph_sel_id = group->tx_chans[channel_id]->base.status.periph_sel_id; + } + + if (peri_sel_id == -1) { + // Unspecified periph_sel_id, decide by the driver + if (sibling_periph_sel_id != -1 && ((1 << sibling_periph_sel_id) & DMA2D_LL_CHANNEL_PERIPH_M2M_FREE_ID_MASK)) { + peri_sel_id = sibling_periph_sel_id; + } else { + peri_sel_id = __builtin_ctz(group->periph_m2m_free_id_mask); + } + } else { + // Check whether specified periph_sel_id is valid + if (sibling_periph_sel_id != -1) { + if (sibling_periph_sel_id != peri_sel_id) { + peri_sel_id = -1; // Conflict id with its sibling channel + } + } else { + if (!((1 << peri_sel_id) & group->periph_m2m_free_id_mask & DMA2D_LL_CHANNEL_PERIPH_M2M_FREE_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; + group->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_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_enable_page_bound_wrap(group->hal.dev, channel_id, ability->data_burst_length != 1); + 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_enable_page_bound_wrap(group->hal.dev, channel_id, ability->data_burst_length != 1); + 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, 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); +} diff --git a/components/esp_hw_support/dma/dma2d_priv.h b/components/esp_hw_support/dma/dma2d_priv.h new file mode 100644 index 0000000000..282b599eef --- /dev/null +++ b/components/esp_hw_support/dma/dma2d_priv.h @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "soc/soc_caps.h" +#include "hal/dma2d_hal.h" +#include "hal/dma2d_ll.h" +#include "esp_private/dma2d.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_DMA2D_OPERATION_FUNC_IN_IRAM || CONFIG_DMA2D_ISR_IRAM_SAFE +#define DMA2D_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define DMA2D_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#if CONFIG_DMA2D_ISR_IRAM_SAFE +#define DMA2D_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM) +#else +#define DMA2D_INTR_ALLOC_FLAGS ESP_INTR_FLAG_SHARED +#endif + +#define DMA2D_RX_DEFAULT_INTR_FLAG (DMA2D_LL_EVENT_RX_SUC_EOF | DMA2D_LL_EVENT_RX_ERR_EOF | DMA2D_LL_EVENT_RX_DESC_ERROR) + +typedef struct dma2d_channel_t dma2d_channel_t; +typedef struct dma2d_tx_channel_t dma2d_tx_channel_t; +typedef struct dma2d_rx_channel_t dma2d_rx_channel_t; +typedef struct dma2d_group_t dma2d_group_t; + +struct dma2d_trans_s { + TAILQ_ENTRY(dma2d_trans_s) entry; // Link entry + const dma2d_trans_config_t *desc; // Pointer to the structure containing all configuration items of a transaction + dma2d_channel_handle_t rx_chan; // Pointer to the RX channel handle that will be used to do the transaction +}; + +struct dma2d_group_t { + int group_id; // Group ID, index from 0 + dma2d_hal_context_t hal; // HAL instance is at group level + portMUX_TYPE spinlock; // Group level spinlock + TAILQ_HEAD(pending_trans, dma2d_trans_s) pending_trans_tailq; // Link head of pending 2D-DMA transactions + uint8_t tx_channel_free_mask; // Bit mask indicating the free TX channels at the moment + uint8_t rx_channel_free_mask; // Bit mask indicating the free RX channels at the moment + uint8_t tx_channel_reserved_mask; // Bit mask indicating the being reserved TX channels + uint8_t rx_channel_reserved_mask; // Bit mask indicating the being reserved RX channels + uint32_t periph_m2m_free_id_mask; // Bit mask indicating the available M2M peripheral selelction IDs at the moment + dma2d_tx_channel_t *tx_chans[SOC_DMA2D_TX_CHANNELS_PER_GROUP]; // Handles of 2D-DMA TX channels + dma2d_rx_channel_t *rx_chans[SOC_DMA2D_RX_CHANNELS_PER_GROUP]; // Handles of 2D-DMA RX channels + int intr_priority; // All channels in the same group should share the same interrupt priority +}; + +struct dma2d_channel_t { + dma2d_group_t *group; // Which group the channel belongs to + int channel_id; // Channel ID + dma2d_channel_direction_t direction; // Channel direction, TX or RX + intr_handle_t intr; // Per-channel interrupt handle + portMUX_TYPE spinlock; // Channel level spinlock + struct { + dma2d_trans_t *transaction; // Pointer to the 2D-DMA transaction context that is currently being processed on the channel + uint32_t reorder_en : 1; // This flag indicates the channel will enable reorder functionality + uint32_t has_sibling : 1; // This flag indicates its sibling channel is also in-use + int periph_sel_id : (DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH + 1); // This is used to record the periph_sel_id of each channel + } status; +}; + +struct dma2d_tx_channel_t { + dma2d_channel_t base; // 2D-DMA channel base class + void *user_data; // User registered DMA event data + dma2d_event_callback_t on_desc_done; // TX DONE event callback +}; + +struct dma2d_rx_channel_t { + dma2d_channel_t base; // 2D-DMA channel base class + void *user_data; // User registered DMA event data + dma2d_event_callback_t on_recv_eof; // RX EOF event callback + dma2d_event_callback_t on_desc_done; // RX DONE event callback + uint32_t bundled_tx_channel_mask; // Bit mask indicating the TX channels together with the RX channel to do the transaction +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/dma/include/esp_private/dma2d.h b/components/esp_hw_support/dma/include/esp_private/dma2d.h new file mode 100644 index 0000000000..33cd74ad56 --- /dev/null +++ b/components/esp_hw_support/dma/include/esp_private/dma2d.h @@ -0,0 +1,419 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// DO NOT USE THESE APIS IN ANY APPLICATIONS +// DMA2D driver is not public for end users, but for ESP-IDF developers. + +#pragma once + +#include +#include "esp_err.h" +#include "hal/dma2d_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of 2D-DMA pool handle + */ +typedef struct dma2d_group_t *dma2d_pool_handle_t; + +/** + * @brief Type of 2D-DMA transaction context (queue element) + */ +typedef struct dma2d_trans_s dma2d_trans_t; + +/** + * @brief Helper macro to get the size for struct `dma2d_trans_t` + */ +#define SIZEOF_DMA2D_TRANS_T dma2d_get_trans_elm_size() + +/** + * @brief Get the size for struct `dma2d_trans_t` + * + * @return size_t Size of struct `dma2d_trans_t` + */ +size_t dma2d_get_trans_elm_size(void); + +/** + * @brief A collection of configuration items that used for allocating a 2D-DMA pool + */ +typedef struct { + uint32_t pool_id; /*!< The ID number of the 2D-DMA pool to allocate */ + uint32_t intr_priority; /*!< 2D-DMA interrupt priority, + if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */ +} dma2d_pool_config_t; + +/** + * @brief Acquire a 2D-DMA pool + * + * @param[in] config Pointer to a collection of configurations for the 2D-DMA pool + * @param[out] ret_pool Returned pool handle + * @return + * - ESP_OK: Acquire the 2D-DMA pool successfully + * - ESP_ERR_INVALID_ARG: Acquire the 2D-DMA pool failed because of invalid argument + * - ESP_ERR_NO_MEM: Acquire the 2D-DMA pool failed because out of memory + * - ESP_FAIL: Acquire the 2D-DMA pool failed because of other error + */ +esp_err_t dma2d_acquire_pool(const dma2d_pool_config_t *config, dma2d_pool_handle_t *ret_pool); + +/** + * @brief Release a 2D-DMA pool + * + * @warning Upper driver should make sure there is no pending transaction (enqueued by the driver, but haven't be + * processed) before calling this function. + * + * @param[in] dma2d_pool 2D-DMA pool handle, allocated by `dma2d_acquire_pool` + * @return + * - ESP_OK: Release the 2D-DMA pool successfully + * - ESP_ERR_INVALID_ARG: Release the 2D-DMA pool failed because of invalid argument + * - ESP_ERR_NOT_ALLOWED: Release the 2D-DMA pool failed because there is pending transactions in the pool, + * pool can not be destroyed + */ +esp_err_t dma2d_release_pool(dma2d_pool_handle_t dma2d_pool); + +/** + * @brief Type of 2D-DMA channel handle + */ +typedef struct dma2d_channel_t *dma2d_channel_handle_t; + +/** + * @brief Struct to save the necessary information of a 2D-DMA channel for upper drivers to configure the channels + */ +typedef struct { + dma2d_channel_direction_t dir; /*!< Direction of the DMA channel */ + dma2d_channel_handle_t chan; /*!< Handle of the DMA channel */ +} dma2d_trans_channel_info_t; + +/** + * @brief Callback function to start a 2D-DMA transaction. This callback is being called when all necessary channels to + * do the transaction have been acquired. + * + * Inside this function, usually configure the DMA channels, and then call `dma2d_start` to start the transaction. + * + * @note This function could run in current thread, or in other threads, or in ISR context (needs to follow ISR rules!) + * + * @param[in] num_chans Number of DMA channels acquired for the transaction + * @param[in] dma2d_chans List of the channels acquired + * @param[in] user_config User registered data (usually dma channel configurations) from `dma2d_trans_config_t::user_config` which is passed into `dma2d_enqueue` + * + * @return Whether a task switch is needed after the callback function returns, + * this is usually due to the callback wakes up some high priority task. + */ +typedef bool (*dma2d_trans_callback_t)(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_config); + +/** + * @brief 2D-DMA channel special function flags + * + * These flags are supposed to be used to specify the 2D-DMA channel requirements for a transaction. + */ +#define DMA2D_CHANNEL_FUNCTION_FLAG_TX_REORDER (1 << 0) /*!< TX channel that has reorder functionality */ +#define DMA2D_CHANNEL_FUNCTION_FLAG_RX_REORDER (1 << 1) /*!< RX channel that has reorder functionality */ +#define DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC (1 << 2) /*!< TX channel that has color space conversion functionality */ +#define DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC (1 << 3) /*!< RX channel that has color space conversion functionality */ +#define DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING (1 << 4) /*!< TX and RX channel with same channel ID */ + +/** + * @brief A collection of configuration items for a 2D-DMA transaction + */ +typedef struct { + uint32_t tx_channel_num; /*!< Number of 2D-DMA TX channels required */ + uint32_t rx_channel_num; /*!< Number of 2D-DMA RX channels required */ + + // Special function requirements for channels + uint32_t channel_flags; /*!< Bitwise OR of `DMA2D_CHANNEL_FUNCTION_FLAG_*` flags indicating the required functions on the channels for the transaction */ + + // Specified to use reserved channels + uint32_t specified_tx_channel_mask; /*!< Bit mask of the specific TX channels to be used, the specified TX channels should have been reserved */ + uint32_t specified_rx_channel_mask; /*!< Bit mask of the specific RX channels to be used, the specified RX channels should have been reserved */ + + dma2d_trans_callback_t on_job_picked; /*!< Callback function to be called when all necessary channels to do the transaction have been acquired */ + void *user_config; /*!< User registered data to be passed into `on_job_picked` callback */ +} dma2d_trans_config_t; + +/** + * @brief Enqueue a 2D-DMA transaction to be picked up by a certain 2D-DMA pool + * + * @param[in] dma2d_pool 2D-DMA pool handle, allocated by `dma2d_acquire_pool` + * @param[in] trans_desc Pointer to a collection of configurations for a transaction + * The context must exist at least until `on_job_picked` callback function is called. + * @param[in] trans_placeholder Address to the memory for storing this transaction context + * Caller must malloc a placeholder for storing the 2D-DMA transaction, and pass it into the function. + * Size of the placeholder can be get from `SIZEOF_DMA2D_TRANS_T` macro. + * Freeing the 2D-DMA transaction placeholder should also be taken care by the upper driver. + * It can be freed when `on_job_picked` callback function is called or anytime later. + * @return + * - ESP_OK: Enqueue the 2D-DMA transaction successfully + * - ESP_ERR_INVALID_ARG: Enqueue the 2D-DMA transaction failed because of invalid argument + */ +esp_err_t dma2d_enqueue(dma2d_pool_handle_t dma2d_pool, const dma2d_trans_config_t *trans_desc, dma2d_trans_t *trans_placeholder); + +/** + * @brief Force end an in-flight 2D-DMA transaction + * + * This API is useful when the error was caused by the DMA consumer (such as JPEG). The error can only be detected + * by the consumer module, and the error info will only be propagated to the consumer driver. The 2D-DMA channels being + * involved to transfer the data has no way to be informed about the error at its upstream, it will keep waiting for + * the data. + * + * Therefore, when the consumer driver is doing the error handling, it is required to call this API to end the on-going + * transaction and release the taken TX and RX channels. It will stop and free the TX and RX channels that are bundled + * together to process the transaction. + * + * @param[in] trans Pointer to the 2D-DMA transaction context + * @param[out] need_yield Pointer to a status flag to record whether a task switch is needed if this API is being called in an ISR + * @return + * - ESP_OK: Force end an in-flight transaction successfully + * - ESP_ERR_INVALID_ARG: Force end failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Force end failed because the transaction is not yet in-flight + */ +esp_err_t dma2d_force_end(dma2d_trans_t *trans, bool *need_yield); + + +/*********************************************** DMA CHANNEL OPERATIONS ***********************************************/ + +/** + * @brief Type of 2D-DMA engine trigger + */ +typedef struct { + dma2d_trigger_peripheral_t periph; /*!< Target peripheral which will trigger DMA operations */ + int periph_sel_id; /*!< Peripheral selection ID. Supported IDs are listed in `soc/dma2d_channel.h` */ +} dma2d_trigger_t; + +/** + * @brief Connect 2D-DMA channel to trigger peripheral, and configure all other channel settings to a certain state (the channel will be reset first) + * + * Usually only to be called in `on_job_picked` callback, and is the first step to do inside the callback, since it resets other configurations to a default mode. + * + * @param[in] dma2d_chan 2D-DMA channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] trig_periph 2D-DMA trigger peripheral + * @return + * - ESP_OK: Connect 2D-DMA channel successfully + * - ESP_ERR_INVALID_ARG: Connect 2D-DMA channel failed because of invalid argument + */ +esp_err_t dma2d_connect(dma2d_channel_handle_t dma2d_chan, const dma2d_trigger_t *trig_periph); + +/** + * @brief A collection of strategy items that each 2D-DMA channel could apply + */ +typedef struct { + bool owner_check; /*!< If set / clear, DMA channel enables / disables checking owner validity */ + bool auto_update_desc; /*!< If set / clear, DMA channel enables / disables hardware to update descriptor automatically (TX channel only) */ + bool eof_till_data_popped; /*!< If set, EOF flag is generated until the data needs to read has been popped from the DMA FIFO (TX channel only) */ +} dma2d_strategy_config_t; + +/** + * @brief Apply channel strategy for 2D-DMA channel + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] config Configuration of 2D-DMA channel strategy + * @return + * - ESP_OK: Apply channel strategy successfully + * - ESP_ERR_INVALID_ARG: Apply channel strategy failed because of invalid argument + */ +esp_err_t dma2d_apply_strategy(dma2d_channel_handle_t dma2d_chan, const dma2d_strategy_config_t *config); + +/** + * @brief A collection of transfer ability items that each 2D-DMA channel could apply to improve transfer efficiency + * + * @note The 2D-DMA driver has no knowledge about the DMA buffer (address and size) used by upper layer. + * So it's the responsibility of the **upper layer** to take care of the buffer address and size. + * Usually RX buffer at least requires 4-byte alignment to avoid overwriting other data by DMA write PSRAM process + * or its data being overwritten. + */ +typedef struct { + bool desc_burst_en; /*!< If set / clear, DMA channel enables / disables burst reading descriptor link */ + dma2d_data_burst_length_t data_burst_length; /*!< Configure the DMA channel burst reading data length */ + dma2d_macro_block_size_t mb_size; /*!< Configure the DMA channel macro block size (only useful in DMA2D_DESCRIPTOR_BLOCK_RW_MODE_MULTIPLE mode) */ +} dma2d_transfer_ability_t; + +/** + * @brief Configure 2D-DMA channel transfer ability for transfer efficiency + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] ability Configuration of 2D-DMA channel transfer ability + * @return + * - ESP_OK: Set channel transfer ability successfully + * - ESP_ERR_INVALID_ARG: Set channel transfer ability failed because of invalid argument + */ +esp_err_t dma2d_set_transfer_ability(dma2d_channel_handle_t dma2d_chan, const dma2d_transfer_ability_t *ability); + +/** + * @brief A collection of color space conversion (CSC) items that each 2D-DMA channel could apply + */ +typedef struct { + union { + dma2d_csc_tx_option_t tx_csc_option; /*!< TX direction (into DMA) pixel format conversion option */ + dma2d_csc_rx_option_t rx_csc_option; /*!< RX direction (out from DMA) pixel format conversion option */ + }; + dma2d_scramble_order_t pre_scramble; /*!< DMA channel data scramble order before color conversion */ + dma2d_scramble_order_t post_scramble; /*!< DMA channel data scramble order after color conversion (only available for RX channels) */ +} dma2d_csc_config_t; + +/** + * @brief Configure color space conversion setting for 2D-DMA channel + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] config Configuration of 2D-DMA channel color space conversion + * @return + * - ESP_OK: Configure DMA color space conversion successfully + * - ESP_ERR_INVALID_ARG: Configure DMA color space conversion failed because of invalid argument + */ +esp_err_t dma2d_configure_color_space_conversion(dma2d_channel_handle_t dma2d_chan, const dma2d_csc_config_t *config); + +/** + * @brief Type of 2D-DMA event data + */ +typedef struct { + union { + intptr_t rx_eof_desc_addr; /*!< EOF descriptor address of RX channel */ + intptr_t tx_eof_desc_addr; /*!< EOF descriptor address of TX channel */ + }; + dma2d_trans_t *transaction; /*!< Pointer to the transaction context processed */ +} dma2d_event_data_t; + +/** + * @brief Type of 2D-DMA event callback + * + * @param dma2d_chan 2D-DMA channel handle. Depends on the callback events, sometimes will pass NULL to this parameter. + * @param event_data 2D-DMA event data + * @param user_data User registered data from `dma2d_register_tx/rx_event_callbacks` + * + * @return Whether a task switch is needed after the callback function returns, + * this is usually due to the callback wakes up some high priority task. + */ +typedef bool (*dma2d_event_callback_t)(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data); + +/** + * @brief Group of supported 2D-DMA TX callbacks + * @note The callbacks are all running under ISR environment + */ +typedef struct { + dma2d_event_callback_t on_desc_done; /*!< Invoked when TX engine completes processing all data in a descriptor. + When TX_DONE interrupt gets triggered but not EOF, it is considered + to be still in the middle of a complete transaction (partially done), + you are allowed to configure 2D-DMA channel hardware/descriptor in this + callback, and let the channels start running again */ +} dma2d_tx_event_callbacks_t; + +/** + * @brief Group of supported 2D-DMA RX callbacks + * @note The callbacks are all running under ISR environment + * + * Users should be clear on the unique responsibility of each callback when writing the callback functions, such as + * where to free the transaction memory. + */ +typedef struct { + dma2d_event_callback_t on_recv_eof; /*!< Invoked when RX engine meets EOF descriptor. + Note that in this callback, RX channel handle will not be given. + This is because at the moment of `on_recv_eof` callback is called, + the channels are returned to the pool and may have already been used to start another new transaction */ + dma2d_event_callback_t on_desc_done; /*!< Invoked when RX engine completes processing all data in a descriptor. + When RX_DONE interrupt gets triggered but not EOF, it is considered + to be still in the middle of a complete transaction (partially done), + you are allowed to configure 2D-DMA channel hardware/descriptor in this + callback, and let the channels start running again */ +} dma2d_rx_event_callbacks_t; + +/** + * @brief Set 2D-DMA event callbacks for TX channel + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA TX channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + */ +esp_err_t dma2d_register_tx_event_callbacks(dma2d_channel_handle_t dma2d_chan, dma2d_tx_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Set 2D-DMA event callbacks for RX channel + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA RX channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + */ +esp_err_t dma2d_register_rx_event_callbacks(dma2d_channel_handle_t dma2d_chan, dma2d_rx_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Set descriptor address for 2D-DMA channel + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @param[in] desc_base_addr Base address of descriptors + * @return + * - ESP_OK: Set 2D-DMA descriptor addr successfully + * - ESP_ERR_INVALID_ARG: Set 2D-DMA descriptor addr failed because of invalid argument + */ +esp_err_t dma2d_set_desc_addr(dma2d_channel_handle_t dma2d_chan, intptr_t desc_base_addr); + +/** + * @brief Start engine for 2D-DMA channel + * + * Usually only to be called in `on_job_picked` callback. + * + * @param[in] dma2d_chan 2D-DMA channel handle, get from the `on_job_picked` callback input argument `dma2d_chans` + * @return + * - ESP_OK: Start 2D-DMA engine successfully + * - ESP_ERR_INVALID_ARG: Start 2D-DMA engine failed because of invalid argument + */ +esp_err_t dma2d_start(dma2d_channel_handle_t dma2d_chan); + +/** + * @brief Stop engine for 2D-DMA channel + * + * Usually to be called in ISR context. + * + * @param[in] dma2d_chan 2D-DMA channel handle + * @return + * - ESP_OK: Stop 2D-DMA engine successfully + * - ESP_ERR_INVALID_ARG: Stop 2D-DMA engine failed because of invalid argument + */ +esp_err_t dma2d_stop(dma2d_channel_handle_t dma2d_chan); + +/** + * @brief Make the appended descriptors be aware to the 2D-DMA engine + * + * Usually to be called in ISR context. + * + * @param[in] dma2d_chan 2D-DMA channel handle + * @return + * - ESP_OK: Send append command to 2D-DMA engine successfully + * - ESP_ERR_INVALID_ARG: Send append command to 2D-DMA engine failed because of invalid argument + */ +esp_err_t dma2d_append(dma2d_channel_handle_t dma2d_chan); + +/** + * @brief Reset engine for 2D-DMA channel + * + * Usually to be called in ISR context. + * + * @param[in] dma2d_chan 2D-DMA channel handle + * @return + * - ESP_OK: Reset 2D-DMA engine successfully + * - ESP_ERR_INVALID_ARG: Reset 2D-DMA engine failed because of invalid argument + */ +esp_err_t dma2d_reset(dma2d_channel_handle_t dma2d_chan); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/dma/linker.lf b/components/esp_hw_support/dma/linker.lf index 7dd8eeb8a1..c4ace066f1 100644 --- a/components/esp_hw_support/dma/linker.lf +++ b/components/esp_hw_support/dma/linker.lf @@ -82,3 +82,37 @@ entries: if DW_GDMA_SETTER_FUNC_IN_IRAM = y: dw_gdma: dw_gdma_channel_set_block_markers (noflash) dw_gdma: dw_gdma_lli_set_block_markers (noflash) + +[mapping:dma2d_driver] +archive: libesp_hw_support.a +entries: + # performance optimization, always put the 2D-DMA default interrupt handler in IRAM + if SOC_DMA2D_SUPPORTED = y: + dma2d: acquire_free_channels_for_trans (noflash) + dma2d: free_up_channels (noflash) + dma2d: _dma2d_default_tx_isr (noflash) + dma2d: _dma2d_default_rx_isr (noflash) + dma2d: dma2d_default_isr (noflash) + + # put 2D-DMA operation functions in IRAM + if DMA2D_OPERATION_FUNC_IN_IRAM = y: + dma2d: dma2d_connect (noflash) + dma2d: dma2d_register_tx_event_callbacks (noflash) + dma2d: dma2d_register_rx_event_callbacks (noflash) + dma2d: dma2d_set_desc_addr (noflash) + dma2d: dma2d_start (noflash) + dma2d: dma2d_stop (noflash) + dma2d: dma2d_append (noflash) + dma2d: dma2d_reset (noflash) + dma2d: dma2d_force_end (noflash) + dma2d: dma2d_apply_strategy (noflash) + dma2d: dma2d_set_transfer_ability (noflash) + dma2d: dma2d_configure_color_space_conversion (noflash) + dma2d: dma2d_enqueue (noflash) + +[mapping:dma2d_hal] +archive: libhal.a +entries: + if DMA2D_ISR_IRAM_SAFE = y || DMA2D_OPERATION_FUNC_IN_IRAM = y: + dma2d_hal: dma2d_hal_tx_reset_channel (noflash) + dma2d_hal: dma2d_hal_rx_reset_channel (noflash) diff --git a/components/esp_hw_support/test_apps/.build-test-rules.yml b/components/esp_hw_support/test_apps/.build-test-rules.yml index 8a57eebe4a..388ffb17e2 100644 --- a/components/esp_hw_support/test_apps/.build-test-rules.yml +++ b/components/esp_hw_support/test_apps/.build-test-rules.yml @@ -8,6 +8,12 @@ components/esp_hw_support/test_apps/dma: depends_filepatterns: - components/esp_hw_support/dma/**/* +components/esp_hw_support/test_apps/dma2d: + disable: + - if: SOC_DMA2D_SUPPORTED != 1 + depends_filepatterns: + - components/esp_hw_support/dma/**/* + components/esp_hw_support/test_apps/esp_hw_support_unity_tests: disable: - if: SOC_GPSPI_SUPPORTED != 1 diff --git a/components/esp_hw_support/test_apps/dma2d/CMakeLists.txt b/components/esp_hw_support/test_apps/dma2d/CMakeLists.txt new file mode 100644 index 0000000000..e4fcac2b78 --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/CMakeLists.txt @@ -0,0 +1,10 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. We also depend on esp_psram +# as we set CONFIG_SPIRAM_... options. +set(COMPONENTS main esp_psram) + +project(dma2d_test) diff --git a/components/esp_hw_support/test_apps/dma2d/README.md b/components/esp_hw_support/test_apps/dma2d/README.md new file mode 100644 index 0000000000..909282018f --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | diff --git a/components/esp_hw_support/test_apps/dma2d/main/CMakeLists.txt b/components/esp_hw_support/test_apps/dma2d/main/CMakeLists.txt new file mode 100644 index 0000000000..1edd6de51c --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/main/CMakeLists.txt @@ -0,0 +1,10 @@ +set(srcs "test_app_main.c" + "test_dma2d.c" + "dma2d_test_utils.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS "." + PRIV_REQUIRES unity esp_mm + WHOLE_ARCHIVE) diff --git a/components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.c b/components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.c new file mode 100644 index 0000000000..0395f80280 --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.c @@ -0,0 +1,162 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "unity.h" +#include "dma2d_test_utils.h" +#include "esp_private/dma2d.h" +#include "soc/dma2d_channel.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_log.h" + +__attribute__((unused)) static const char *TAG = "dma2d_m2m"; + +#if CONFIG_DMA2D_OPERATION_FUNC_IN_IRAM || CONFIG_DMA2D_ISR_IRAM_SAFE +#define DMA2D_M2M_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define DMA2D_M2M_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#if CONFIG_DMA2D_ISR_IRAM_SAFE +#define DMA2D_M2M_ATTR IRAM_ATTR +#else +#define DMA2D_M2M_ATTR +#endif + +typedef dma2d_m2m_trans_config_t dma2d_m2m_trans_desc_t; + +typedef struct { + dma2d_m2m_trans_desc_t m2m_trans_desc; + dma2d_trans_config_t dma_chan_desc; + uint32_t dma_trans_placeholder_head; /* Head of the memory for storing the 2D-DMA transaction elm */ +} dma2d_m2m_transaction_t; + +static dma2d_pool_handle_t dma2d_pool_handle; + +esp_err_t dma2d_m2m_init(void) +{ + dma2d_pool_config_t dma2d_pool_config = { + .pool_id = 0, + }; + return dma2d_acquire_pool(&dma2d_pool_config, &dma2d_pool_handle); +} + +esp_err_t dma2d_m2m_deinit(void) +{ + return dma2d_release_pool(dma2d_pool_handle); +} + +static bool DMA2D_M2M_ATTR dma2d_m2m_transaction_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data) +{ + bool need_yield = false; + dma2d_m2m_transaction_t *trans_config = (dma2d_m2m_transaction_t *)user_data; + dma2d_m2m_trans_desc_t *m2m_trans_desc = &trans_config->m2m_trans_desc; + + if (m2m_trans_desc->trans_eof_cb) { + need_yield |= m2m_trans_desc->trans_eof_cb(m2m_trans_desc->user_data); + } + + free(trans_config); + + return need_yield; +} + +static bool DMA2D_M2M_ATTR dma2d_m2m_transaction_on_picked(uint32_t channel_num, const dma2d_trans_channel_info_t *dma2d_chans, void *user_config) +{ + assert(channel_num == 2 && dma2d_chans && user_config); + dma2d_m2m_transaction_t *trans_config = (dma2d_m2m_transaction_t *)user_config; + dma2d_m2m_trans_desc_t *m2m_trans_desc = &trans_config->m2m_trans_desc; + + // Get the required 2D-DMA channel handles + uint32_t dma_tx_chan_idx = 0; + uint32_t dma_rx_chan_idx = 1; + if (dma2d_chans[0].dir == DMA2D_CHANNEL_DIRECTION_RX) { + dma_tx_chan_idx = 1; + dma_rx_chan_idx = 0; + } + dma2d_channel_handle_t dma_tx_chan = dma2d_chans[dma_tx_chan_idx].chan; + dma2d_channel_handle_t dma_rx_chan = dma2d_chans[dma_rx_chan_idx].chan; + + dma2d_trigger_t trig_periph = { + .periph = DMA2D_TRIG_PERIPH_M2M, + .periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX, + }; + dma2d_connect(dma_tx_chan, &trig_periph); + trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX; + dma2d_connect(dma_rx_chan, &trig_periph); + + if (m2m_trans_desc->transfer_ability_config) { + dma2d_set_transfer_ability(dma_tx_chan, m2m_trans_desc->transfer_ability_config); + dma2d_set_transfer_ability(dma_rx_chan, m2m_trans_desc->transfer_ability_config); + } + + if (m2m_trans_desc->tx_strategy_config) { + dma2d_apply_strategy(dma_tx_chan, m2m_trans_desc->tx_strategy_config); + } + if (m2m_trans_desc->rx_strategy_config) { + dma2d_apply_strategy(dma_rx_chan, m2m_trans_desc->rx_strategy_config); + } + + if (m2m_trans_desc->tx_csc_config) { + dma2d_configure_color_space_conversion(dma_tx_chan, m2m_trans_desc->tx_csc_config); + } + if (m2m_trans_desc->rx_csc_config) { + dma2d_configure_color_space_conversion(dma_rx_chan, m2m_trans_desc->rx_csc_config); + } + + dma2d_rx_event_callbacks_t dma_cbs = { + .on_recv_eof = dma2d_m2m_transaction_done_cb, + }; + dma2d_register_rx_event_callbacks(dma_rx_chan, &dma_cbs, (void *)trans_config); + + dma2d_set_desc_addr(dma_tx_chan, m2m_trans_desc->tx_desc_base_addr); + dma2d_set_desc_addr(dma_rx_chan, m2m_trans_desc->rx_desc_base_addr); + + dma2d_start(dma_tx_chan); + dma2d_start(dma_rx_chan); + + // No need to yield + return false; +} + +esp_err_t dma2d_m2m(const dma2d_m2m_trans_config_t *trans_config) +{ +#if CONFIG_DMA2D_ISR_IRAM_SAFE + if (trans_config->trans_eof_cb) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(trans_config->trans_eof_cb), + ESP_ERR_INVALID_ARG, TAG, "trans_eof_cb not in IRAM"); + } + if (trans_config->user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(trans_config->user_data), + ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + dma2d_m2m_transaction_t *dma2d_m2m_trans = (dma2d_m2m_transaction_t *)heap_caps_calloc(1, sizeof(dma2d_m2m_transaction_t) + SIZEOF_DMA2D_TRANS_T, DMA2D_M2M_MEM_ALLOC_CAPS); + TEST_ASSERT_NOT_NULL(dma2d_m2m_trans); + + dma2d_m2m_trans->dma_chan_desc.tx_channel_num = 1; + dma2d_m2m_trans->dma_chan_desc.rx_channel_num = 1; + dma2d_m2m_trans->dma_chan_desc.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING; + dma2d_m2m_trans->dma_chan_desc.channel_flags |= (trans_config->tx_csc_config == NULL) ? 0 : DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC; + dma2d_m2m_trans->dma_chan_desc.channel_flags |= (trans_config->rx_csc_config == NULL) ? 0 : DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC; + dma2d_m2m_trans->dma_chan_desc.specified_tx_channel_mask = 0; + dma2d_m2m_trans->dma_chan_desc.specified_rx_channel_mask = 0; + + memcpy(&dma2d_m2m_trans->m2m_trans_desc, trans_config, sizeof(dma2d_m2m_trans_config_t)); + + dma2d_m2m_trans->dma_chan_desc.user_config = (void *)dma2d_m2m_trans; + dma2d_m2m_trans->dma_chan_desc.on_job_picked = dma2d_m2m_transaction_on_picked; + + esp_err_t ret = dma2d_enqueue(dma2d_pool_handle, &dma2d_m2m_trans->dma_chan_desc, (dma2d_trans_t *)&dma2d_m2m_trans->dma_trans_placeholder_head); + if (ret != ESP_OK) { + free(dma2d_m2m_trans); + } + return ret; +} diff --git a/components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.h b/components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.h new file mode 100644 index 0000000000..0447dfb7bc --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/main/dma2d_test_utils.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_private/dma2d.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the 2D-DMA module to perform memcopy operations + */ +esp_err_t dma2d_m2m_init(void); + +/** + * @brief De-initialize the 2D-DMA module + */ +esp_err_t dma2d_m2m_deinit(void); + +/** + * @brief Callback function when a memcopy operation is done + * + * @param user_data User registered data from `dma2d_m2m_trans_config_t` + * + * @return Whether a task switch is needed after the callback function returns, + * this is usually due to the callback wakes up some high priority task. + */ +typedef bool (*dma2d_m2m_trans_eof_callback_t)(void *user_data); + +/** + * @brief A collection of configuration items for perferming a memcopy operation with 2D-DMA + */ +typedef struct { + intptr_t tx_desc_base_addr; /*!< 2D-DMA TX descriptor address */ + intptr_t rx_desc_base_addr; /*!< 2D-DMA RX descriptor address */ + dma2d_m2m_trans_eof_callback_t trans_eof_cb; /*!< Callback function to be called when the memcopy operation is done */ + void *user_data; /*!< User registered data to be passed into `trans_eof_cb` callback */ + dma2d_transfer_ability_t *transfer_ability_config; /*!< Pointer to a collection of 2D-DMA transfer ability configuration */ + dma2d_strategy_config_t *tx_strategy_config; /*!< Pointer to a collection of 2D-DMA TX strategy configuration */ + dma2d_strategy_config_t *rx_strategy_config; /*!< Pointer to a collection of 2D-DMA RX strategy configuration */ + dma2d_csc_config_t *tx_csc_config; /*!< Pointer to a collection of 2D-DMA TX color space conversion configuration */ + dma2d_csc_config_t *rx_csc_config; /*!< Pointer to a collection of 2D-DMA RX color space conversion configuration */ +} dma2d_m2m_trans_config_t; + +/** + * @brief Do a memcopy operation with 2D-DMA module + * + * @param trans_config Pointer to a collection of configurations for the memcopy operation + * @return + * - ESP_OK: Enqueue the transaction to 2D-DMA pool successfully + * - ESP_ERR_INVALID_ARG: Enqueue the transaction to 2D-DMA pool failed because of invalid argument + */ +esp_err_t dma2d_m2m(const dma2d_m2m_trans_config_t *trans_config); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/test_apps/dma2d/main/test_app_main.c b/components/esp_hw_support/test_apps/dma2d/main/test_app_main.c new file mode 100644 index 0000000000..c4ff3a0c9e --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/main/test_app_main.c @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" +#include "unity_test_utils.h" + +// Some resources are lazy allocated in the driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (200) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + esp_reent_cleanup(); //clean up some of the newlib's lazy allocations + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + printf(" ______ ___ ___ ______ _____ ______ \n"); + printf("(______) (___)_(___) (______) _(_____) (______) \n"); + printf("(_) (_) (_) (_) (_) (_)____(_) (_) _(_) (_) (_) \n"); + printf("(_) (_)(_) (_) (_) (________) _(_) (_) (_)\n"); + printf("(_)___(_) (_) (_) (_) (_) (_)___ (_)___(_) \n"); + printf("(______) (_) (_) (_) (_) (_______) (______) \n"); + + unity_run_menu(); +} diff --git a/components/esp_hw_support/test_apps/dma2d/main/test_dma2d.c b/components/esp_hw_support/test_apps/dma2d/main/test_dma2d.c new file mode 100644 index 0000000000..03f01e6abf --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/main/test_dma2d.c @@ -0,0 +1,677 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_private/dma2d.h" +#include "dma2d_test_utils.h" +#include "hal/dma2d_types.h" +#include "hal/color_types.h" +#include "esp_heap_caps.h" +#include "esp_cache.h" + +// All test will perform `M2M_TRANS_TIMES` times memcpy transactions, utilizing all available 2D-DMA channels. +// This tests the hardware capability of multiple 2D-DMA transactions running together, and the driver capbility of +// transactions being send to a queue, and waiting for free channels becoming available, and being picked to start the +// real hardware operation. +#define M2M_TRANS_TIMES (8) + +// Descriptor and buffer address and size should aligned to 64 bytes (the cacheline size alignment restriction) to be used by CPU + +static dma2d_descriptor_t *tx_dsc[M2M_TRANS_TIMES]; +static dma2d_descriptor_t *rx_dsc[M2M_TRANS_TIMES]; + +static dma2d_m2m_trans_config_t m2m_trans_config[M2M_TRANS_TIMES]; + +static void dma2d_link_dscr_init(uint32_t *head, uint32_t *next, void *buf_ptr, + uint32_t ha, uint32_t va, uint32_t hb, uint32_t vb, + uint32_t eof, uint32_t en_2d, uint32_t pbyte, uint32_t mod, + uint32_t bias_x, uint32_t bias_y) +{ + dma2d_descriptor_t *dma2d = (dma2d_descriptor_t *)head; + memset(dma2d, 0, sizeof(dma2d_descriptor_t)); + dma2d->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA; + dma2d->suc_eof = eof; + dma2d->dma2d_en = en_2d; + dma2d->err_eof = 0; + dma2d->hb_length = hb; + dma2d->vb_size = vb; + dma2d->pbyte = pbyte; + dma2d->ha_length = ha; + dma2d->va_size = va; + dma2d->mode = mod; + dma2d->y = bias_y; + dma2d->x = bias_x; + dma2d->buffer = buf_ptr; + dma2d->next = (dma2d_descriptor_t *)next; +} + +static bool IRAM_ATTR dma2d_m2m_suc_eof_event_cb(void *user_data) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + SemaphoreHandle_t sem = (SemaphoreHandle_t)user_data; + xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken); + return (xHigherPriorityTaskWoken == pdTRUE); +} + +TEST_CASE("DMA2D_M2M_1D_basic", "[DMA2D]") +{ + // Test a 16KB data block pure memcopy + const uint32_t data_size = 16 * 1024; // unit: byte + + memset(m2m_trans_config, 0, M2M_TRANS_TIMES * sizeof(dma2d_m2m_trans_config_t)); + TEST_ESP_OK(dma2d_m2m_init()); + + dma2d_descriptor_t *tx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + dma2d_descriptor_t *rx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(tx_link_buffer); + TEST_ASSERT_NOT_NULL(rx_link_buffer); + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + tx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)tx_link_buffer + 64 * i); + rx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)rx_link_buffer + 64 * i); + } + + uint8_t *prtx; + uint8_t *prrx; + uint8_t *tx_buf = heap_caps_aligned_calloc(64, data_size * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + uint8_t *rx_buf = heap_caps_aligned_calloc(64, data_size * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(tx_buf); + TEST_ASSERT_NOT_NULL(rx_buf); + + dma2d_transfer_ability_t transfer_ability_config = { + .data_burst_length = DMA2D_DATA_BURST_LENGTH_64, + .desc_burst_en = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + + SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(M2M_TRANS_TIMES, 0); + + // Preparation + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + // Buffer data preparation + prtx = tx_buf + i * data_size; + prrx = rx_buf + i * data_size; + for (int idx = 0; idx < data_size; idx++) { + prtx[idx] = (i + idx + 0x45) & 0xFF; + prrx[idx] = 0; + } + // Writeback and invalidate the TX and RX buffers + esp_cache_msync((void *)prtx, data_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + esp_cache_msync((void *)prrx, data_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + + // DMA description preparation + dma2d_link_dscr_init((uint32_t *)tx_dsc[i], NULL, (void *)prtx, + data_size >> 14, data_size >> 14, + data_size & 0x3FFF, data_size & 0x3FFF, + 1, 0, DMA2D_DESCRIPTOR_PBYTE_1B0_PER_PIXEL, + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + dma2d_link_dscr_init((uint32_t *)rx_dsc[i], NULL, (void *)prrx, + 0, data_size >> 14, + 0, data_size & 0x3FFF, + 0, 0, DMA2D_DESCRIPTOR_PBYTE_1B0_PER_PIXEL, + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + // Writeback the DMA descriptors + esp_cache_msync((void *)tx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + esp_cache_msync((void *)rx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + + // Construct dma2d_m2m_trans_config_t structure + m2m_trans_config[i].tx_desc_base_addr = (intptr_t)tx_dsc[i]; + m2m_trans_config[i].rx_desc_base_addr = (intptr_t)rx_dsc[i]; + m2m_trans_config[i].trans_eof_cb = dma2d_m2m_suc_eof_event_cb; + m2m_trans_config[i].user_data = (void *)counting_sem; + m2m_trans_config[i].transfer_ability_config = &transfer_ability_config; + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + printf("trans %d\n", i); + TEST_ESP_OK(dma2d_m2m(&m2m_trans_config[i])); + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + printf("trans %d done\n", i); + } + printf("All transactions done!\n"); + + // Check result + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + prtx = tx_buf + i * data_size; + prrx = rx_buf + i * data_size; + for (int idx = 0; idx < data_size; idx++) { + TEST_ASSERT_EQUAL(prtx[idx], prrx[idx]); + TEST_ASSERT_EQUAL(prtx[idx], (i + idx + 0x45) & 0xFF); + } + } + + free(tx_link_buffer); + free(rx_link_buffer); + free(tx_buf); + free(rx_buf); + vSemaphoreDelete(counting_sem); + + TEST_ESP_OK(dma2d_m2m_deinit()); +} + +static void rgb565_to_rgb888(uint16_t rgb565, void *__r, void *__g, void *__b) +{ + uint8_t *r = (uint8_t *)__r; + uint8_t *g = (uint8_t *)__g; + uint8_t *b = (uint8_t *)__b; + + uint32_t _rgb565 = rgb565; + uint8_t _b = (_rgb565>>8) & 0xF8; + uint8_t _g = (_rgb565>>3) & 0xFC; + uint8_t _r = (_rgb565<<3) & 0xF8; + // *r = (_r & 0x08) ? (_r | 0x1) : (_r); + // *g = (_g & 0x04) ? (_g | 0x1) : (_g); + // *b = (_b & 0x08) ? (_b | 0x1) : (_b); + + *r = _r | ( (_r >>3) & 0x7); + *g = _g | ( (_g >>2) & 0x3); + *b = _b | ( (_b >>3) & 0x7); +} + +static int rgb565_to_rgb888_and_cmp(void *_rgb565, void *__rgb888, int pix) +{ + uint16_t *rgb565 = (uint16_t *)_rgb565; + uint8_t *_rgb888 = (uint8_t *)__rgb888; + uint8_t _r,_g,_b; + for (int i = 0; i < pix; i++) { + rgb565_to_rgb888(rgb565[i], &_r, &_g, &_b); + if (_r != _rgb888[0] || _g != _rgb888[1] || _b != _rgb888[2]) { + printf("idx %d - conv fail, %x:%x:%x, rgb565:%x, _rgb888:%x:%x:%x\r\n", + i, _r, _g, _b, rgb565[i], _rgb888[0], _rgb888[1] ,_rgb888[2]); + return -1; + } + _rgb888 += 3; + } + return 0; +} + +TEST_CASE("DMA2D_M2M_1D_RGB565_to_RGB888", "[DMA2D]") +{ + // Test a 4K pixel 1D buffer (original pixel in RGB565 format, convert to RGB888 format) + const uint32_t item_size = 1024 * 4; + + memset(m2m_trans_config, 0, M2M_TRANS_TIMES * sizeof(dma2d_m2m_trans_config_t)); + TEST_ESP_OK(dma2d_m2m_init()); + + dma2d_descriptor_t *tx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + dma2d_descriptor_t *rx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(tx_link_buffer); + TEST_ASSERT_NOT_NULL(rx_link_buffer); + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + tx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)tx_link_buffer + 64 * i); + rx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)rx_link_buffer + 64 * i); + } + + uint8_t *prtx; + uint8_t *prrx; + uint8_t *tx_buf = heap_caps_aligned_calloc(64, item_size * 2 * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + uint8_t *rx_buf = heap_caps_aligned_calloc(64, item_size * 3 * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(tx_buf); + TEST_ASSERT_NOT_NULL(rx_buf); + + SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(M2M_TRANS_TIMES, 0); + + dma2d_transfer_ability_t transfer_ability_config = { + .data_burst_length = DMA2D_DATA_BURST_LENGTH_128, + .desc_burst_en = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + + dma2d_csc_config_t m2m_dma2d_tx_csc = { + .tx_csc_option = DMA2D_CSC_TX_RGB565_TO_RGB888, + .pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, + }; + + // Preparation + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + // Buffer data preparation + prtx = tx_buf + i * item_size * 2; + prrx = rx_buf + i * item_size * 3; + for (int idx = 0; idx < item_size; idx++) { + uint32_t r = (idx * 32 / item_size) & 0x1F; + uint32_t g = (idx * 64 / item_size) & 0x3F; + uint32_t b = (idx * 32 / item_size) & 0x1F; + prtx[idx * 2] = (r << 3) || (g >> 3); + prtx[idx * 2 + 1] = (g << 5) || b; + prrx[idx * 3] = 0; + prrx[idx * 3 + 1] = 0; + prrx[idx * 3 + 2] = 0; + } + // Writeback and invalidate the TX and RX buffers + esp_cache_msync((void *)prtx, item_size * 2, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + esp_cache_msync((void *)prrx, item_size * 3, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + + // DMA description preparation + dma2d_link_dscr_init((uint32_t *)tx_dsc[i], NULL, (void *)prtx, + (item_size * 2) >> 14, (item_size * 2) >> 14, + (item_size * 2) & 0x3FFF, (item_size * 2) & 0x3FFF, + 1, 0, DMA2D_DESCRIPTOR_PBYTE_1B0_PER_PIXEL, + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + dma2d_link_dscr_init((uint32_t *)rx_dsc[i], NULL, (void *)prrx, + 0, (item_size * 3) >> 14, + 0, (item_size * 3) & 0x3FFF, + 0, 0, DMA2D_DESCRIPTOR_PBYTE_1B0_PER_PIXEL, + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + // Writeback the DMA descriptors + esp_cache_msync((void *)tx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + esp_cache_msync((void *)rx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + + // Construct dma2d_m2m_trans_config_t structure + m2m_trans_config[i].tx_desc_base_addr = (intptr_t)tx_dsc[i]; + m2m_trans_config[i].rx_desc_base_addr = (intptr_t)rx_dsc[i]; + m2m_trans_config[i].trans_eof_cb = dma2d_m2m_suc_eof_event_cb; + m2m_trans_config[i].user_data = (void *)counting_sem; + m2m_trans_config[i].transfer_ability_config = &transfer_ability_config; + m2m_trans_config[i].tx_csc_config = &m2m_dma2d_tx_csc; + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + printf("trans %d\n", i); + TEST_ESP_OK(dma2d_m2m(&m2m_trans_config[i])); + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + printf("trans %d done\n", i); + } + printf("All transactions done!\n"); + + // Check result + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + prtx = tx_buf + i * item_size * 2; + prrx = rx_buf + i * item_size * 3; + TEST_ASSERT_EQUAL(0, rgb565_to_rgb888_and_cmp(prtx, prrx, item_size)); + } + + free(tx_link_buffer); + free(rx_link_buffer); + free(tx_buf); + free(rx_buf); + vSemaphoreDelete(counting_sem); + + TEST_ESP_OK(dma2d_m2m_deinit()); +} + +TEST_CASE("DMA2D_M2M_2D_basic", "[DMA2D]") +{ + // Test a 128 x 128 pixel data block (one byte per pixel - assume A8) + const color_space_pixel_format_t pixel_format = { + .color_space = COLOR_SPACE_ALPHA, + .pixel_format = COLOR_PIXEL_A8, + }; + const uint32_t stripe_size = 128; // unit: bytes + + memset(m2m_trans_config, 0, M2M_TRANS_TIMES * sizeof(dma2d_m2m_trans_config_t)); + TEST_ESP_OK(dma2d_m2m_init()); + + dma2d_descriptor_t *tx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + dma2d_descriptor_t *rx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(tx_link_buffer); + TEST_ASSERT_NOT_NULL(rx_link_buffer); + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + tx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)tx_link_buffer + 64 * i); + rx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)rx_link_buffer + 64 * i); + } + + uint8_t *prtx; + uint8_t *prrx; + uint8_t *tx_buf = heap_caps_aligned_calloc(64, stripe_size * stripe_size * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + uint8_t *rx_buf = heap_caps_aligned_calloc(64, stripe_size * stripe_size * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(tx_buf); + TEST_ASSERT_NOT_NULL(rx_buf); + + SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(M2M_TRANS_TIMES, 0); + + dma2d_transfer_ability_t transfer_ability_config = { + .data_burst_length = DMA2D_DATA_BURST_LENGTH_128, + .desc_burst_en = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + + // Preparation + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + // Buffer data preparation + prtx = tx_buf + i * stripe_size * stripe_size; + prrx = rx_buf + i * stripe_size * stripe_size; + for (int idx = 0; idx < stripe_size * stripe_size; idx++) { + prtx[idx] = (i + idx + 0x45) & 0xFF; + prrx[idx] = 0; + } + // Writeback and invalidate the TX and RX buffers + esp_cache_msync((void *)prtx, stripe_size * stripe_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + esp_cache_msync((void *)prrx, stripe_size * stripe_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + + // DMA description preparation + dma2d_link_dscr_init((uint32_t *)tx_dsc[i], NULL, (void *)prtx, + stripe_size, stripe_size, + stripe_size, stripe_size, + 1, 1, dma2d_desc_pixel_format_to_pbyte_value(pixel_format), // 1 bytes/pixel + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + dma2d_link_dscr_init((uint32_t *)rx_dsc[i], NULL, (void *)prrx, + stripe_size, stripe_size, + stripe_size, stripe_size, + 0, 1, dma2d_desc_pixel_format_to_pbyte_value(pixel_format), // 1 bytes/pixel + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + // Writeback the DMA descriptors + esp_cache_msync((void *)tx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + esp_cache_msync((void *)rx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + + // Construct dma2d_m2m_trans_config_t structure + m2m_trans_config[i].tx_desc_base_addr = (intptr_t)tx_dsc[i]; + m2m_trans_config[i].rx_desc_base_addr = (intptr_t)rx_dsc[i]; + m2m_trans_config[i].trans_eof_cb = dma2d_m2m_suc_eof_event_cb; + m2m_trans_config[i].user_data = (void *)counting_sem; + m2m_trans_config[i].transfer_ability_config = &transfer_ability_config; + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + printf("trans %d\n", i); + TEST_ESP_OK(dma2d_m2m(&m2m_trans_config[i])); + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + printf("trans %d done\n", i); + } + printf("All transactions done!\n"); + + // Check result + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + prtx = tx_buf + i * stripe_size * stripe_size; + prrx = rx_buf + i * stripe_size * stripe_size; + for (int idx = 0; idx < stripe_size * stripe_size; idx++) { + TEST_ASSERT_EQUAL(prtx[idx], prrx[idx]); + TEST_ASSERT_EQUAL(prtx[idx], (i + idx + 0x45) & 0xFF); + } + } + + free(tx_link_buffer); + free(rx_link_buffer); + free(tx_buf); + free(rx_buf); + vSemaphoreDelete(counting_sem); + + TEST_ESP_OK(dma2d_m2m_deinit()); +} + +static void rgb888_to_rgb565(uint8_t r, uint8_t g, uint8_t b, uint16_t *rgb565) +{ + uint16_t _rgb565 = (b >> 3); + _rgb565 = (_rgb565 << 6) | (g >>2); + _rgb565 = (_rgb565 << 5) | (r >>3); + *rgb565 = _rgb565; +} + +static int rgb888_to_rgb565_and_cmp(void *__rgb888, void *__rgb565, int pix) +{ + uint8_t *_rgb888 = (uint8_t *)__rgb888; + uint16_t *rgb565 = (uint16_t *)__rgb565; + + uint16_t _rgb565 = 0; + uint8_t *rgb888 = _rgb888; + for (int i = 0; i < pix; i++) { + rgb888_to_rgb565(rgb888[0], rgb888[1], rgb888[2], &_rgb565); + if (_rgb565 != rgb565[0]) { + printf("conv fail, r:%x, g:%x, b:%x, rgb565:%x, _rgb565:%x\r\n", + rgb888[0], rgb888[1], rgb888[2], rgb565[0], _rgb565); + return -1; + } + rgb888 += 3; + rgb565++; + } + return 0; +} + +TEST_CASE("DMA2D_M2M_2D_RGB888_to_RGB565", "[DMA2D]") +{ + // Test a 64 x 64 pixel data block (original pixel in RGB888 format, convert to RGB565 format) + const color_space_pixel_format_t in_pixel_format = { + .color_space = COLOR_SPACE_RGB, + .pixel_format = COLOR_PIXEL_RGB888, + }; + const color_space_pixel_format_t out_pixel_format = { + .color_space = COLOR_SPACE_RGB, + .pixel_format = COLOR_PIXEL_RGB565, + }; + const uint32_t stripe_pixel_size = 64; // unit: pixel + + memset(m2m_trans_config, 0, M2M_TRANS_TIMES * sizeof(dma2d_m2m_trans_config_t)); + TEST_ESP_OK(dma2d_m2m_init()); + + dma2d_descriptor_t *tx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + dma2d_descriptor_t *rx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(tx_link_buffer); + TEST_ASSERT_NOT_NULL(rx_link_buffer); + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + tx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)tx_link_buffer + 64 * i); + rx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)rx_link_buffer + 64 * i); + } + + uint8_t *prtx; + uint8_t *prrx; + uint8_t *tx_buf = heap_caps_aligned_calloc(64, stripe_pixel_size * stripe_pixel_size * 3 * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + uint8_t *rx_buf = heap_caps_aligned_calloc(64, stripe_pixel_size * stripe_pixel_size * 2 * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(tx_buf); + TEST_ASSERT_NOT_NULL(rx_buf); + + SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(M2M_TRANS_TIMES, 0); + + dma2d_transfer_ability_t transfer_ability_config = { + .data_burst_length = DMA2D_DATA_BURST_LENGTH_16, + .desc_burst_en = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + + dma2d_csc_config_t m2m_dma2d_tx_csc = { + .tx_csc_option = DMA2D_CSC_TX_RGB888_TO_RGB565, + .pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, + }; + + // Preparation + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + // Buffer data preparation + prtx = tx_buf + i * stripe_pixel_size * stripe_pixel_size * 3; + prrx = rx_buf + i * stripe_pixel_size * stripe_pixel_size * 2; + for (int idx = 0; idx < stripe_pixel_size * stripe_pixel_size; idx++) { + uint32_t r = (i + idx + 0x5A) & 0xFF; + uint32_t g = (i + idx + 0x4C) & 0xFF; + uint32_t b = (i + idx + 0x9E) & 0xFF; + prtx[idx * 3] = r; + prtx[idx * 3 + 1] = g; + prtx[idx * 3 + 2] = b; + prrx[idx * 2] = 0; + prrx[idx * 2 + 1] = 0; + } + // Writeback and invalidate the TX and RX buffers + esp_cache_msync((void *)prtx, stripe_pixel_size * stripe_pixel_size * 3, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + esp_cache_msync((void *)prrx, stripe_pixel_size * stripe_pixel_size * 2, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + + // DMA description preparation + dma2d_link_dscr_init((uint32_t *)tx_dsc[i], NULL, (void *)prtx, + stripe_pixel_size, stripe_pixel_size, + stripe_pixel_size, stripe_pixel_size, + 1, 1, dma2d_desc_pixel_format_to_pbyte_value(in_pixel_format), + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + dma2d_link_dscr_init((uint32_t *)rx_dsc[i], NULL, (void *)prrx, + stripe_pixel_size, stripe_pixel_size, + stripe_pixel_size, stripe_pixel_size, + 0, 1, dma2d_desc_pixel_format_to_pbyte_value(out_pixel_format), + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + // Writeback the DMA descriptors + esp_cache_msync((void *)tx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + esp_cache_msync((void *)rx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + + // Construct dma2d_m2m_trans_config_t structure + m2m_trans_config[i].tx_desc_base_addr = (intptr_t)tx_dsc[i]; + m2m_trans_config[i].rx_desc_base_addr = (intptr_t)rx_dsc[i]; + m2m_trans_config[i].trans_eof_cb = dma2d_m2m_suc_eof_event_cb; + m2m_trans_config[i].user_data = (void *)counting_sem; + m2m_trans_config[i].transfer_ability_config = &transfer_ability_config; + m2m_trans_config[i].tx_csc_config = &m2m_dma2d_tx_csc; + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + printf("trans %d\n", i); + TEST_ESP_OK(dma2d_m2m(&m2m_trans_config[i])); + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + printf("trans %d done\n", i); + } + printf("All transactions done!\n"); + + // Check result + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + prtx = tx_buf + i * stripe_pixel_size * stripe_pixel_size * 3; + prrx = rx_buf + i * stripe_pixel_size * stripe_pixel_size * 2; + TEST_ASSERT_EQUAL(0, rgb888_to_rgb565_and_cmp(prtx, prrx, stripe_pixel_size * stripe_pixel_size)); + } + + free(tx_link_buffer); + free(rx_link_buffer); + free(tx_buf); + free(rx_buf); + vSemaphoreDelete(counting_sem); + + TEST_ESP_OK(dma2d_m2m_deinit()); +} + +TEST_CASE("DMA2D_M2M_2D_window", "[DMA2D]") +{ + // Test 2D memcpy to a 2 x 2 block at (2, 4) in a 8 x 8 picture (pixel in RGB565 format) + const color_space_pixel_format_t pixel_format = { + .color_space = COLOR_SPACE_RGB, + .pixel_format = COLOR_PIXEL_RGB565, + }; + const uint32_t va = 8, ha = 8; // Define picture height and width (unit: pixel) + const uint32_t vb = 2, hb = 2; // Define block height and width (unit: pixel) + const uint32_t x_offset = 2, y_offset = 4; // Define block location in the picture (unit: pixel) + + memset(m2m_trans_config, 0, M2M_TRANS_TIMES * sizeof(dma2d_m2m_trans_config_t)); + TEST_ESP_OK(dma2d_m2m_init()); + + dma2d_descriptor_t *tx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + dma2d_descriptor_t *rx_link_buffer = (dma2d_descriptor_t *)heap_caps_aligned_calloc(64, M2M_TRANS_TIMES, 64, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(tx_link_buffer); + TEST_ASSERT_NOT_NULL(rx_link_buffer); + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + tx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)tx_link_buffer + 64 * i); + rx_dsc[i] = (dma2d_descriptor_t *)((uint32_t)rx_link_buffer + 64 * i); + } + + uint8_t *prtx; + uint8_t *prrx; + uint8_t *tx_buf = heap_caps_aligned_calloc(64, 64 * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_INTERNAL); + uint8_t *rx_buf = heap_caps_aligned_calloc(64, va * ha * 2 * M2M_TRANS_TIMES, sizeof(uint8_t), MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(tx_buf); + TEST_ASSERT_NOT_NULL(rx_buf); + + SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(M2M_TRANS_TIMES, 0); + + dma2d_transfer_ability_t transfer_ability_config = { + .data_burst_length = DMA2D_DATA_BURST_LENGTH_128, + .desc_burst_en = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + + // Preparation + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + // Buffer data preparation + prtx = tx_buf + i * 64; + prrx = rx_buf + i * va * ha * 2; + for (int idx = 0; idx < vb * hb; idx++) { + prtx[idx * 2] = 0x55 + idx + i; + prtx[idx * 2 + 1] = 0xAA + idx + i; + } + for (int idx = 0; idx < va * ha; idx++) { + prrx[idx * 2] = 0xFF; + prrx[idx * 2 + 1] = 0xFF; + } + + // Writeback and invalidate the TX and RX buffers + esp_cache_msync((void *)prtx, 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + esp_cache_msync((void *)prrx, va * ha * 2, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + + // DMA description preparation + dma2d_link_dscr_init((uint32_t *)tx_dsc[i], NULL, (void *)prtx, + hb, vb, + hb, vb, + 1, 1, dma2d_desc_pixel_format_to_pbyte_value(pixel_format), + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, 0, 0); + dma2d_link_dscr_init((uint32_t *)rx_dsc[i], NULL, (void *)prrx, + ha, va, + hb, vb, + 0, 1, dma2d_desc_pixel_format_to_pbyte_value(pixel_format), + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, x_offset, y_offset); + // Writeback the DMA descriptors + esp_cache_msync((void *)tx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + esp_cache_msync((void *)rx_dsc[i], 64, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + + // Construct dma2d_m2m_trans_config_t structure + m2m_trans_config[i].tx_desc_base_addr = (intptr_t)tx_dsc[i]; + m2m_trans_config[i].rx_desc_base_addr = (intptr_t)rx_dsc[i]; + m2m_trans_config[i].trans_eof_cb = dma2d_m2m_suc_eof_event_cb; + m2m_trans_config[i].user_data = (void *)counting_sem; + m2m_trans_config[i].transfer_ability_config = &transfer_ability_config; + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + printf("trans %d\n", i); + TEST_ESP_OK(dma2d_m2m(&m2m_trans_config[i])); + } + + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + printf("trans %d done\n", i); + } + printf("All transactions done!\n"); + + // Print the picture and check result + for (int i = 0; i < M2M_TRANS_TIMES; i++) { + prtx = tx_buf + i * 64; + prrx = rx_buf + i * va * ha * 2; + printf("pic:\n"); + for (int idx = 0; idx < va * ha; idx++) { + printf("%02X%02X ", prrx[idx * 2], prrx[idx * 2 + 1]); + if (idx % ha == (ha - 1)) { + printf("\n"); + } + + bool pixel_in_window = false; + for (int window_y = 0; window_y < vb; window_y++) { + if (idx >= (ha * (y_offset + window_y) + x_offset) && idx < (ha * (y_offset + window_y) + x_offset + hb)) { + uint32_t window_x = idx - ha * (y_offset + window_y) - x_offset; + TEST_ASSERT_EQUAL(prtx[(window_y * hb + window_x) * 2], prrx[idx * 2]); + TEST_ASSERT_EQUAL(prtx[(window_y * hb + window_x) * 2 + 1], prrx[idx * 2 + 1]); + pixel_in_window = true; + break; + } + } + if (!pixel_in_window) { + TEST_ASSERT_EQUAL(0XFF, prrx[idx * 2]); + TEST_ASSERT_EQUAL(0XFF, prrx[idx * 2 + 1]); + } + } + } + + free(tx_link_buffer); + free(rx_link_buffer); + free(tx_buf); + free(rx_buf); + vSemaphoreDelete(counting_sem); + + TEST_ESP_OK(dma2d_m2m_deinit()); +} diff --git a/components/esp_hw_support/test_apps/dma2d/pytest_dma2d.py b/components/esp_hw_support/test_apps/dma2d/pytest_dma2d.py new file mode 100644 index 0000000000..1fbdd0e0dd --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/pytest_dma2d.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32p4 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_dma2d(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_hw_support/test_apps/dma2d/sdkconfig.ci.release b/components/esp_hw_support/test_apps/dma2d/sdkconfig.ci.release new file mode 100644 index 0000000000..8b0a31a9bc --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/sdkconfig.ci.release @@ -0,0 +1,6 @@ +# set compiler optimization level +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y + +# we can silent the assertion to save the binary footprint +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults b/components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults new file mode 100644 index 0000000000..1ee5d718bc --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_IDF_EXPERIMENTAL_FEATURES=y diff --git a/components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults.esp32p4 b/components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000000..702fac3342 --- /dev/null +++ b/components/esp_hw_support/test_apps/dma2d/sdkconfig.defaults.esp32p4 @@ -0,0 +1,3 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index a023f7cde0..0df99a5b13 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -41,6 +41,7 @@ if(NOT CONFIG_APP_BUILD_TYPE_PURE_RAM_APP) endif() if(NOT BOOTLOADER_BUILD) + list(APPEND srcs "color_hal.c") if(NOT CONFIG_APP_BUILD_TYPE_PURE_RAM_APP) if(CONFIG_SOC_SPI_FLASH_SUPPORTED) @@ -127,6 +128,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "dw_gdma_hal.c") endif() + if(CONFIG_SOC_DMA2D_SUPPORTED) + list(APPEND srcs "dma2d_hal.c") + endif() + if(CONFIG_SOC_I2S_SUPPORTED) list(APPEND srcs "i2s_hal.c") endif() diff --git a/components/hal/color_hal.c b/components/hal/color_hal.c new file mode 100644 index 0000000000..664d97490d --- /dev/null +++ b/components/hal/color_hal.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/color_hal.h" +#include "hal/assert.h" + +uint32_t color_hal_pixel_format_get_bit_depth(color_space_pixel_format_t format) +{ + switch (format.color_type_id) { + case COLOR_TYPE_ID(COLOR_SPACE_GRAY, COLOR_PIXEL_GRAY4): + case COLOR_TYPE_ID(COLOR_SPACE_ALPHA, COLOR_PIXEL_A4): + case COLOR_TYPE_ID(COLOR_SPACE_CLUT, COLOR_PIXEL_L4): + return 4; + case COLOR_TYPE_ID(COLOR_SPACE_RAW, COLOR_PIXEL_RAW8): + case COLOR_TYPE_ID(COLOR_SPACE_GRAY, COLOR_PIXEL_GRAY8): + case COLOR_TYPE_ID(COLOR_SPACE_ALPHA, COLOR_PIXEL_A8): + case COLOR_TYPE_ID(COLOR_SPACE_CLUT, COLOR_PIXEL_L8): + return 8; + case COLOR_TYPE_ID(COLOR_SPACE_RAW, COLOR_PIXEL_RAW10): + return 10; + case COLOR_TYPE_ID(COLOR_SPACE_RAW, COLOR_PIXEL_RAW12): + case COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV420): + case COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV411): + return 12; + case COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB565): + case COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV422): + return 16; + case COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB888): + case COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV444): + return 24; + case COLOR_TYPE_ID(COLOR_SPACE_ARGB, COLOR_PIXEL_ARGB8888): + return 32; + default: + // Unknown color space pixel format, unknown bit depth + HAL_ASSERT(false); + return 0; + } +} diff --git a/components/hal/dma2d_hal.c b/components/hal/dma2d_hal.c new file mode 100644 index 0000000000..ff533ff209 --- /dev/null +++ b/components/hal/dma2d_hal.c @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "hal/dma2d_hal.h" +#include "hal/dma2d_ll.h" + +void dma2d_hal_init(dma2d_hal_context_t *hal, int group_id) +{ + hal->dev = DMA2D_LL_GET_HW(group_id); +} + +void dma2d_hal_tx_reset_channel(dma2d_hal_context_t *hal, uint32_t channel) +{ + dma2d_ll_tx_disable_cmd(hal->dev, channel, true); + while (!dma2d_ll_tx_is_reset_avail(hal->dev, channel)); + dma2d_ll_tx_reset_channel(hal->dev, channel); + dma2d_ll_tx_disable_cmd(hal->dev, channel, false); +} + +void dma2d_hal_rx_reset_channel(dma2d_hal_context_t *hal, uint32_t channel) +{ + dma2d_ll_rx_disable_cmd(hal->dev, channel, true); + while (!dma2d_ll_rx_is_reset_avail(hal->dev, channel)); + dma2d_ll_rx_reset_channel(hal->dev, channel); + dma2d_ll_rx_disable_cmd(hal->dev, channel, false); +} diff --git a/components/hal/esp32c2/include/hal/gdma_ll.h b/components/hal/esp32c2/include/hal/gdma_ll.h index 6a9db5f76e..54a93d54cc 100644 --- a/components/hal/esp32c2/include/hal/gdma_ll.h +++ b/components/hal/esp32c2/include/hal/gdma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -242,9 +242,9 @@ static inline void gdma_ll_rx_enable_auto_return(gdma_dev_t *dev, uint32_t chann } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_rx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_rx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].in.in_link.park; } @@ -468,9 +468,9 @@ static inline void gdma_ll_tx_restart(gdma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_tx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_tx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].out.out_link.park; } diff --git a/components/hal/esp32c3/include/hal/gdma_ll.h b/components/hal/esp32c3/include/hal/gdma_ll.h index f51f47a90e..deaf734889 100644 --- a/components/hal/esp32c3/include/hal/gdma_ll.h +++ b/components/hal/esp32c3/include/hal/gdma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -242,9 +242,9 @@ static inline void gdma_ll_rx_enable_auto_return(gdma_dev_t *dev, uint32_t chann } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_rx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_rx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].in.in_link.park; } @@ -468,9 +468,9 @@ static inline void gdma_ll_tx_restart(gdma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_tx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_tx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].out.out_link.park; } diff --git a/components/hal/esp32c6/include/hal/gdma_ll.h b/components/hal/esp32c6/include/hal/gdma_ll.h index 054bd741a4..80d79dc426 100644 --- a/components/hal/esp32c6/include/hal/gdma_ll.h +++ b/components/hal/esp32c6/include/hal/gdma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -278,9 +278,9 @@ static inline void gdma_ll_rx_enable_auto_return(gdma_dev_t *dev, uint32_t chann } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_rx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_rx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].in.in_link.inlink_park; } @@ -514,9 +514,9 @@ static inline void gdma_ll_tx_restart(gdma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_tx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_tx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].out.out_link.outlink_park; } diff --git a/components/hal/esp32h2/include/hal/gdma_ll.h b/components/hal/esp32h2/include/hal/gdma_ll.h index 054bd741a4..80d79dc426 100644 --- a/components/hal/esp32h2/include/hal/gdma_ll.h +++ b/components/hal/esp32h2/include/hal/gdma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -278,9 +278,9 @@ static inline void gdma_ll_rx_enable_auto_return(gdma_dev_t *dev, uint32_t chann } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_rx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_rx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].in.in_link.inlink_park; } @@ -514,9 +514,9 @@ static inline void gdma_ll_tx_restart(gdma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_tx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_tx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].out.out_link.outlink_park; } diff --git a/components/hal/esp32p4/include/hal/ahb_dma_ll.h b/components/hal/esp32p4/include/hal/ahb_dma_ll.h index ffdb2d5190..b7a4667ed1 100644 --- a/components/hal/esp32p4/include/hal/ahb_dma_ll.h +++ b/components/hal/esp32p4/include/hal/ahb_dma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -215,9 +215,9 @@ static inline void ahb_dma_ll_rx_enable_auto_return(ahb_dma_dev_t *dev, uint32_t } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool ahb_dma_ll_rx_is_fsm_idle(ahb_dma_dev_t *dev, uint32_t channel) +static inline bool ahb_dma_ll_rx_is_desc_fsm_idle(ahb_dma_dev_t *dev, uint32_t channel) { return dev->channel[channel].in.in_link.inlink_park_chn; } @@ -451,9 +451,9 @@ static inline void ahb_dma_ll_tx_restart(ahb_dma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool ahb_dma_ll_tx_is_fsm_idle(ahb_dma_dev_t *dev, uint32_t channel) +static inline bool ahb_dma_ll_tx_is_desc_fsm_idle(ahb_dma_dev_t *dev, uint32_t channel) { return dev->channel[channel].out.out_link.outlink_park_chn; } diff --git a/components/hal/esp32p4/include/hal/axi_dma_ll.h b/components/hal/esp32p4/include/hal/axi_dma_ll.h index fe06f790d5..171b204527 100644 --- a/components/hal/esp32p4/include/hal/axi_dma_ll.h +++ b/components/hal/esp32p4/include/hal/axi_dma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -189,9 +189,9 @@ static inline void axi_dma_ll_rx_enable_auto_return(axi_dma_dev_t *dev, uint32_t } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool axi_dma_ll_rx_is_fsm_idle(axi_dma_dev_t *dev, uint32_t channel) +static inline bool axi_dma_ll_rx_is_desc_fsm_idle(axi_dma_dev_t *dev, uint32_t channel) { return dev->in[channel].conf.in_link1.inlink_park_chn; } @@ -397,9 +397,9 @@ static inline void axi_dma_ll_tx_restart(axi_dma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool axi_dma_ll_tx_is_fsm_idle(axi_dma_dev_t *dev, uint32_t channel) +static inline bool axi_dma_ll_tx_is_desc_fsm_idle(axi_dma_dev_t *dev, uint32_t channel) { return dev->out[channel].conf.out_link1.outlink_park_chn; } diff --git a/components/hal/esp32p4/include/hal/clk_gate_ll.h b/components/hal/esp32p4/include/hal/clk_gate_ll.h index 048d1488ea..012b45cd0c 100644 --- a/components/hal/esp32p4/include/hal/clk_gate_ll.h +++ b/components/hal/esp32p4/include/hal/clk_gate_ll.h @@ -60,8 +60,6 @@ static inline uint32_t periph_ll_get_rst_en_mask(periph_module_t periph, bool en return HP_SYS_CLKRST_REG_RST_EN_PVT_TOP; case PERIPH_ISP_MODULE: return HP_SYS_CLKRST_REG_RST_EN_ISP; - case PERIPH_DMA2D_MODULE: - return HP_SYS_CLKRST_REG_RST_EN_DMA2D; case PERIPH_UHCI_MODULE: return HP_SYS_CLKRST_REG_RST_EN_UHCI; case PERIPH_I3C_MODULE: @@ -141,7 +139,6 @@ static inline uint32_t periph_ll_get_rst_en_reg(periph_module_t periph) switch (periph) { case PERIPH_PVT_MODULE: case PERIPH_ISP_MODULE: - case PERIPH_DMA2D_MODULE: return HP_SYS_CLKRST_HP_RST_EN0_REG; case PERIPH_UHCI_MODULE: case PERIPH_I3C_MODULE: diff --git a/components/hal/esp32p4/include/hal/dma2d_ll.h b/components/hal/esp32p4/include/hal/dma2d_ll.h new file mode 100644 index 0000000000..558ef79671 --- /dev/null +++ b/components/hal/esp32p4/include/hal/dma2d_ll.h @@ -0,0 +1,1166 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include /* Required for NULL constant */ +#include +#include +#include "hal/dma2d_types.h" +#include "soc/dma2d_channel.h" +#include "soc/dma2d_struct.h" +#include "hal/misc.h" +#include "hal/assert.h" +#include "soc/soc.h" +#include "soc/hp_sys_clkrst_struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DMA2D_LL_GET_HW(id) (((id) == 0) ? (&DMA2D) : NULL) + +// 2D-DMA interrupts +#define DMA2D_LL_RX_EVENT_MASK (0x3FFF) +#define DMA2D_LL_TX_EVENT_MASK (0x1FFF) + +#define DMA2D_LL_EVENT_RX_DESC_TASK_OVF (1<<13) +#define DMA2D_LL_EVENT_RX_FIFO_REORDER_UDF (1<<12) +#define DMA2D_LL_EVENT_RX_FIFO_REORDER_OVF (1<<11) +#define DMA2D_LL_EVENT_RX_DESC_EMPTY (1<<10) +#define DMA2D_LL_EVENT_RX_FIFO_L3_UDF (1<<9) +#define DMA2D_LL_EVENT_RX_FIFO_L3_OVF (1<<8) +#define DMA2D_LL_EVENT_RX_FIFO_L2_UDF (1<<7) +#define DMA2D_LL_EVENT_RX_FIFO_L2_OVF (1<<6) +#define DMA2D_LL_EVENT_RX_FIFO_L1_UDF (1<<5) +#define DMA2D_LL_EVENT_RX_FIFO_L1_OVF (1<<4) +#define DMA2D_LL_EVENT_RX_DESC_ERROR (1<<3) +#define DMA2D_LL_EVENT_RX_ERR_EOF (1<<2) // Raised when the err_eof bit in the descriptor is set by auto writeback (Only JPEG) +#define DMA2D_LL_EVENT_RX_SUC_EOF (1<<1) // Raised when the suc_eof bit in the descriptor is set by auto writeback +#define DMA2D_LL_EVENT_RX_DONE (1<<0) // Raised when all data descripted in the descriptor are processed (every succeed descriptor will have one RX_DONE interrupt) + +#define DMA2D_LL_EVENT_TX_DESC_TASK_OVF (1<<12) +#define DMA2D_LL_EVENT_TX_FIFO_REORDER_UDF (1<<11) +#define DMA2D_LL_EVENT_TX_FIFO_REORDER_OVF (1<<10) +#define DMA2D_LL_EVENT_TX_FIFO_L3_UDF (1<<9) +#define DMA2D_LL_EVENT_TX_FIFO_L3_OVF (1<<8) +#define DMA2D_LL_EVENT_TX_FIFO_L2_UDF (1<<7) +#define DMA2D_LL_EVENT_TX_FIFO_L2_OVF (1<<6) +#define DMA2D_LL_EVENT_TX_FIFO_L1_UDF (1<<5) +#define DMA2D_LL_EVENT_TX_FIFO_L1_OVF (1<<4) +#define DMA2D_LL_EVENT_TX_TOTAL_EOF (1<<3) +#define DMA2D_LL_EVENT_TX_DESC_ERROR (1<<2) +#define DMA2D_LL_EVENT_TX_EOF (1<<1) +#define DMA2D_LL_EVENT_TX_DONE (1<<0) + +// Bit masks that are used to indicate availbility of some sub-features in the channels +#define DMA2D_LL_TX_CHANNEL_SUPPORT_RO_MASK (0U | BIT0) // TX channels that support reorder feature +#define DMA2D_LL_TX_CHANNEL_SUPPORT_CSC_MASK (0U | BIT0 | BIT1 | BIT2) // TX channels that support color space conversion feature + +#define DMA2D_LL_RX_CHANNEL_SUPPORT_RO_MASK (0U | BIT0) // RX channels that support reorder feature +#define DMA2D_LL_RX_CHANNEL_SUPPORT_CSC_MASK (0U | BIT0) // RX channels that support color space conversion feature + +// Any "dummy" peripheral selection ID can be used for M2M mode {4, 5, 6, 7} +#define DMA2D_LL_CHANNEL_PERIPH_M2M_FREE_ID_MASK (0xF0) +// Peripheral selection ID that disconnects 2D-DMA channel from any peripherals +#define DMA2D_LL_CHANNEL_PERIPH_NO_CHOICE (7) +// Peripheral selection ID register field width +#define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3) + +///////////////////////////////////// Common ///////////////////////////////////////// +/** + * @brief Enable the bus clock for 2D-DMA module + * + * @param group_id Group ID + * @param enable True to enable; false to disable + */ +static inline void dma2d_ll_enable_bus_clock(int group_id, bool enable) +{ + (void)group_id; + HP_SYS_CLKRST.soc_clk_ctrl1.reg_dma2d_sys_clk_en = enable; +} + +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define dma2d_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; dma2d_ll_enable_bus_clock(__VA_ARGS__) + +/** + * @brief Reset the 2D-DMA module + * + * @param group_id Group ID + */ +static inline void dma2d_ll_reset_register(int group_id) +{ + (void)group_id; + HP_SYS_CLKRST.hp_rst_en0.reg_rst_en_dma2d = 1; + HP_SYS_CLKRST.hp_rst_en0.reg_rst_en_dma2d = 0; +} + +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define dma2d_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; dma2d_ll_reset_register(__VA_ARGS__) + +/** + * @brief Configure 2D-DMA accessible internal and external memory start/end address (for both buffer and descriptor) + */ +static inline void dma2d_ll_set_accessible_mem_range(dma2d_dev_t *dev) +{ + dev->intr_mem_start_addr.access_intr_mem_start_addr = SOC_DIRAM_DRAM_LOW; // L2MEM region (2D-DMA indeed is able to access TCM/LP_MEM regions, but will be restricted in IDF) + dev->intr_mem_end_addr.access_intr_mem_end_addr = SOC_DIRAM_DRAM_HIGH; + dev->extr_mem_start_addr.access_extr_mem_start_addr = SOC_EXTRAM_LOW; + dev->extr_mem_end_addr.access_extr_mem_end_addr = SOC_EXTRAM_HIGH; +} + +/** + * @brief Enable 2D-DMA module + */ +static inline void dma2d_ll_hw_enable(dma2d_dev_t *dev, bool enable) +{ + dev->rst_conf.clk_en = enable; + // Reset AXI master read data FIFO + dev->rst_conf.axim_rd_rst = 1; + dev->rst_conf.axim_rd_rst = 0; + // Reset AXI master write data FIFO + dev->rst_conf.axim_wr_rst = 1; + dev->rst_conf.axim_wr_rst = 0; + // Disable TX/RX arbiter weight config + dev->out_arb_config.val = 0; + dev->in_arb_config.val = 0; +} + +/** + * @brief Helper function to get the register field value for different pixel bytes scramble order + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_get_scramble_order_sel(dma2d_scramble_order_t order) +{ + switch (order) { + case DMA2D_SCRAMBLE_ORDER_BYTE2_1_0: + return 0; + case DMA2D_SCRAMBLE_ORDER_BYTE2_0_1: + return 1; + case DMA2D_SCRAMBLE_ORDER_BYTE1_0_2: + return 2; + case DMA2D_SCRAMBLE_ORDER_BYTE1_2_0: + return 3; + case DMA2D_SCRAMBLE_ORDER_BYTE0_2_1: + return 4; + case DMA2D_SCRAMBLE_ORDER_BYTE0_1_2: + return 5; + default: + // Unsupported scramble order + abort(); + } +} + +/////////////////////////////////////// RX /////////////////////////////////////////// +#define DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, ch, reg) ((volatile void*[]){&dev->in_channel0.reg, &dev->in_channel1.reg}[(ch)]) + +/** + * @brief Get 2D-DMA RX channel interrupt status word + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_rx_get_interrupt_status(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_int_st_chn_reg_t *reg = (volatile dma2d_in_int_st_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_int_st); + return reg->val; +} + +/** + * @brief Enable 2D-DMA RX channel interrupt + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_interrupt(dma2d_dev_t *dev, uint32_t channel, uint32_t mask, bool enable) +{ + volatile dma2d_in_int_ena_chn_reg_t *reg = (volatile dma2d_in_int_ena_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_int_ena); + if (enable) { + reg->val = reg->val | (mask & DMA2D_LL_RX_EVENT_MASK); + } else { + reg->val = reg->val & ~(mask & DMA2D_LL_RX_EVENT_MASK); + } +} + +/** + * @brief Clear 2D-DMA RX channel interrupt + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_clear_interrupt_status(dma2d_dev_t *dev, uint32_t channel, uint32_t mask) +{ + volatile dma2d_in_int_clr_chn_reg_t *reg = (volatile dma2d_in_int_clr_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_int_clr); + reg->val = (mask & DMA2D_LL_RX_EVENT_MASK); +} + +/** + * @brief Get 2D-DMA RX channel interrupt status register address + */ +static inline volatile void *dma2d_ll_rx_get_interrupt_status_reg(dma2d_dev_t *dev, uint32_t channel) +{ + return (volatile void *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_int_st); +} + +/** + * @brief Enable 2D-DMA RX channel to check the owner bit in the descriptor, disabled by default + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_owner_check(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_check_owner_chn = enable; +} + +/** + * @brief Enable 2D-DMA RX channel page boundary wrap + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_page_bound_wrap(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_page_bound_en_chn = enable; +} + +/** + * @brief Set 2D-DMA RX channel maximum burst reading data length in bytes + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_data_burst_length(dma2d_dev_t *dev, uint32_t channel, dma2d_data_burst_length_t length) +{ + uint32_t sel; + switch (length) { + case DMA2D_DATA_BURST_LENGTH_1: + sel = 0; + break; + case DMA2D_DATA_BURST_LENGTH_16: + sel = 1; + break; + case DMA2D_DATA_BURST_LENGTH_32: + sel = 2; + break; + case DMA2D_DATA_BURST_LENGTH_64: + sel = 3; + break; + case DMA2D_DATA_BURST_LENGTH_128: + sel = 4; + break; + default: + // Unsupported data burst length + abort(); + } + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_mem_burst_length_chn = sel; +} + +/** + * @brief Enable 2D-DMA RX channel burst reading descriptor link, disabled by default + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_descriptor_burst(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->indscr_burst_en_chn = enable; +} + +/** + * @brief Reset 2D-DMA RX channel FSM and FIFO pointer + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_reset_channel(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_rst_chn = 1; + reg->in_rst_chn = 0; +} + +/** + * @brief Check if 2D-DMA RX channel is safe to reset + */ +__attribute__((always_inline)) +static inline bool dma2d_ll_rx_is_reset_avail(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_state_chn_reg_t *reg = (volatile dma2d_in_state_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_state); + return reg->in_reset_avail_chn; +} + +/** + * @brief Disable 2D-DMA RX channel via a command + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_disable_cmd(dma2d_dev_t *dev, uint32_t channel, bool disable) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_cmd_disable_chn = disable; +} + +/** + * @brief Enable 2D-DMA RX channel to obtain descriptor from IP port (here referring the PPA module) + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_dscr_port(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_dscr_port_en_chn = enable; +} + +/** + * @brief Select 2D-DMA RX channel macro block size (Only useful in mode 1 of the link descriptor) + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_macro_block_size(dma2d_dev_t *dev, uint32_t channel, dma2d_macro_block_size_t size) +{ + uint32_t sel; + switch (size) { + case DMA2D_MACRO_BLOCK_SIZE_NONE: + sel = 3; + break; + case DMA2D_MACRO_BLOCK_SIZE_8_8: + sel = 0; + break; + case DMA2D_MACRO_BLOCK_SIZE_8_16: + sel = 1; + break; + case DMA2D_MACRO_BLOCK_SIZE_16_16: + sel = 2; + break; + default: + // Unsupported macro block size + abort(); + } + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_macro_block_size_chn = sel; +} + +/** + * @brief Pop data from 2D-DMA RX FIFO + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_rx_pop_data(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_pop_chn_reg_t *reg = (volatile dma2d_in_pop_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_pop); + reg->infifo_pop_chn = 1; + return reg->infifo_rdata_chn; +} + +/** + * @brief Set the descriptor link base address for RX channel + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_desc_addr(dma2d_dev_t *dev, uint32_t channel, uint32_t addr) +{ + volatile dma2d_in_link_addr_chn_reg_t *reg = (volatile dma2d_in_link_addr_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_link_addr); + reg->inlink_addr_chn = addr; +} + +/** + * @brief Start dealing with RX descriptors + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_start(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_link_conf_chn_reg_t *reg = (volatile dma2d_in_link_conf_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_link_conf); + reg->inlink_start_chn = 1; +} + +/** + * @brief Stop dealing with RX descriptors + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_stop(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_link_conf_chn_reg_t *reg = (volatile dma2d_in_link_conf_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_link_conf); + reg->inlink_stop_chn = 1; +} + +/** + * @brief Restart a new inlink right after the last descriptor + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_restart(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_link_conf_chn_reg_t *reg = (volatile dma2d_in_link_conf_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_link_conf); + reg->inlink_restart_chn = 1; +} + +/** + * @brief Enable 2D-DMA RX to return the address of current descriptor when receives error + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_auto_return(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + volatile dma2d_in_link_conf_chn_reg_t *reg = (volatile dma2d_in_link_conf_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_link_conf); + reg->inlink_auto_ret_chn = enable; +} + +/** + * @brief Check if 2D-DMA RX descriptor FSM is in IDLE state + */ +__attribute__((always_inline)) +static inline bool dma2d_ll_rx_is_desc_fsm_idle(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_link_conf_chn_reg_t *reg = (volatile dma2d_in_link_conf_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_link_conf); + return reg->inlink_park_chn; +} + +/** + * @brief Check if 2D-DMA RX FSM is in IDLE state + */ +__attribute__((always_inline)) +static inline bool dma2d_ll_rx_is_fsm_idle(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_state_chn_reg_t *reg = (volatile dma2d_in_state_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_state); + return (reg->in_state_chn == 0); +} + +/** + * @brief Get RX success EOF descriptor's address + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_rx_get_success_eof_desc_addr(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_suc_eof_des_addr_chn_reg_t *reg = (volatile dma2d_in_suc_eof_des_addr_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_suc_eof_des_addr); + return reg->val; +} + +/** + * @brief Get RX error EOF descriptor's address + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_rx_get_error_eof_desc_addr(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_err_eof_des_addr_chn_reg_t *reg = (volatile dma2d_in_err_eof_des_addr_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_err_eof_des_addr); + return reg->val; +} + +/** + * @brief Get the pre-fetched RX descriptor's address + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_rx_get_prefetched_desc_addr(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_dscr_chn_reg_t *reg = (volatile dma2d_in_dscr_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_dscr); + return reg->val; +} + +/** + * @brief Set priority for 2D-DMA RX channel + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_priority(dma2d_dev_t *dev, uint32_t channel, uint32_t prio) +{ + volatile dma2d_in_arb_chn_reg_t *reg = (volatile dma2d_in_arb_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_arb); + reg->in_arb_priority_chn = prio; +} + +/** + * @brief Connect 2D-DMA RX channel to a given peripheral + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_connect_to_periph(dma2d_dev_t *dev, uint32_t channel, dma2d_trigger_peripheral_t periph, int periph_id) +{ + volatile dma2d_in_peri_sel_chn_reg_t *peri_sel_reg = (volatile dma2d_in_peri_sel_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_peri_sel); + peri_sel_reg->in_peri_sel_chn = periph_id; + volatile dma2d_in_conf0_chn_reg_t *conf0_reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + conf0_reg->in_mem_trans_en_chn = (periph == DMA2D_TRIG_PERIPH_M2M); +} + +/** + * @brief Disconnect 2D-DMA RX channel from peripheral + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_disconnect_from_periph(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_peri_sel_chn_reg_t *peri_sel_reg = (volatile dma2d_in_peri_sel_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_peri_sel); + peri_sel_reg->in_peri_sel_chn = DMA2D_LL_CHANNEL_PERIPH_NO_CHOICE; + volatile dma2d_in_conf0_chn_reg_t *conf0_reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + conf0_reg->in_mem_trans_en_chn = false; +} + +// REORDER FUNCTION (Only CH0 supports this feature) + +/** + * @brief Enable 2D-DMA RX channel macro block reorder function + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_enable_reorder(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + volatile dma2d_in_conf0_chn_reg_t *reg = (volatile dma2d_in_conf0_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_conf0); + reg->in_reorder_en_chn = enable; +} + +// COLOR SPACE CONVERTION FUNCTION + +/** + * @brief Configure 2D-DMA RX channel color space conversion parameters + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_configure_color_space_conv(dma2d_dev_t *dev, uint32_t channel, dma2d_csc_rx_option_t csc_sel) +{ + HAL_ASSERT(channel == 0); // Only channel 0 supports color space conversion + // L1 + uint32_t input_sel = 7; // Disable CSC + // L2 + bool proc_en = false; // Disable generic color convert module between color input & color output + int (*table)[4] = NULL; + // L3 + uint32_t output_sel = 1; // Output directly + + const int color_space_conv_param_yuv2rgb_bt601_table[3][4] = DMA2D_COLOR_SPACE_CONV_PARAM_YUV2RGB_BT601; + const int color_space_conv_param_yuv2rgb_bt709_table[3][4] = DMA2D_COLOR_SPACE_CONV_PARAM_YUV2RGB_BT709; + + switch (csc_sel) { + case DMA2D_CSC_RX_NONE: + input_sel = 7; + break; + case DMA2D_CSC_RX_SCRAMBLE: + input_sel = 1; // Or 2 + proc_en = false; + output_sel = 1; + break; + case DMA2D_CSC_RX_YUV422_TO_YUV444: + case DMA2D_CSC_RX_YUV420_TO_YUV444: + input_sel = 0; + proc_en = false; + output_sel = 1; + break; + case DMA2D_CSC_RX_YUV420_TO_RGB888_601: + case DMA2D_CSC_RX_YUV422_TO_RGB888_601: + input_sel = 0; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt601_table; + output_sel = 1; + break; + case DMA2D_CSC_RX_YUV420_TO_RGB565_601: + case DMA2D_CSC_RX_YUV422_TO_RGB565_601: + input_sel = 0; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt601_table; + output_sel = 0; + break; + case DMA2D_CSC_RX_YUV420_TO_RGB888_709: + case DMA2D_CSC_RX_YUV422_TO_RGB888_709: + input_sel = 0; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt709_table; + output_sel = 1; + break; + case DMA2D_CSC_RX_YUV420_TO_RGB565_709: + case DMA2D_CSC_RX_YUV422_TO_RGB565_709: + input_sel = 0; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt709_table; + output_sel = 0; + break; + case DMA2D_CSC_RX_YUV444_TO_RGB888_601: + input_sel = 2; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt601_table; + output_sel = 1; + break; + case DMA2D_CSC_RX_YUV444_TO_RGB565_601: + input_sel = 2; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt601_table; + output_sel = 0; + break; + case DMA2D_CSC_RX_YUV444_TO_RGB888_709: + input_sel = 2; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt709_table; + output_sel = 1; + break; + case DMA2D_CSC_RX_YUV444_TO_RGB565_709: + input_sel = 2; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt709_table; + output_sel = 0; + break; + default: + // Unsupported color space conversion type + abort(); + } + + dev->in_channel0.in_color_convert.in_color_input_sel_chn = input_sel; + dev->in_channel0.in_color_convert.in_color_3b_proc_en_chn = proc_en; + dev->in_channel0.in_color_convert.in_color_output_sel_chn = output_sel; + + if (proc_en) { + HAL_ASSERT(table); + typeof(dev->in_channel0.in_color_param_group) color_param_group; + + color_param_group.param_h.a = table[0][0]; + color_param_group.param_h.b = table[0][1]; + color_param_group.param_h.c = table[0][2]; + color_param_group.param_h.d = table[0][3]; + + color_param_group.param_m.a = table[1][0]; + color_param_group.param_m.b = table[1][1]; + color_param_group.param_m.c = table[1][2]; + color_param_group.param_m.d = table[1][3]; + + color_param_group.param_l.a = table[2][0]; + color_param_group.param_l.b = table[2][1]; + color_param_group.param_l.c = table[2][2]; + color_param_group.param_l.d = table[2][3]; + + dev->in_channel0.in_color_param_group.param_h.val[0] = color_param_group.param_h.val[0]; + dev->in_channel0.in_color_param_group.param_h.val[1] = color_param_group.param_h.val[1]; + dev->in_channel0.in_color_param_group.param_m.val[0] = color_param_group.param_m.val[0]; + dev->in_channel0.in_color_param_group.param_m.val[1] = color_param_group.param_m.val[1]; + dev->in_channel0.in_color_param_group.param_l.val[0] = color_param_group.param_l.val[0]; + dev->in_channel0.in_color_param_group.param_l.val[1] = color_param_group.param_l.val[1]; + } +} + +/** + * @brief Configure 2D-DMA RX channel data scramble before color conversion + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_csc_pre_scramble(dma2d_dev_t *dev, uint32_t channel, dma2d_scramble_order_t order) +{ + HAL_ASSERT(channel == 0); // Only channel 0 supports scramble + dev->in_channel0.in_scramble.in_scramble_sel_pre_chn = dma2d_ll_get_scramble_order_sel(order); +} + +/** + * @brief Configure 2D-DMA RX channel data scramble after color conversion + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_csc_post_scramble(dma2d_dev_t *dev, uint32_t channel, dma2d_scramble_order_t order) +{ + HAL_ASSERT(channel == 0); // Only channel 0 supports scramble + dev->in_channel0.in_scramble.in_scramble_sel_post_chn = dma2d_ll_get_scramble_order_sel(order); +} + +// Arbiter + +/** + * @brief Enable 2D-DMA RX side arbiter weight configuration + */ +static inline void dma2d_ll_rx_enable_arb_weight(dma2d_dev_t *dev, bool enable) +{ + dev->in_arb_config.in_weight_en = enable; +} + +/** + * @brief Set 2D-DMA RX side arbiter period + * + * @param timeout_num Number of AXI clock cycles (240MHz) in one arbiter period + */ +static inline void dma2d_ll_rx_set_arb_timeout(dma2d_dev_t *dev, uint32_t timeout_num) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->in_arb_config, in_arb_timeout_num, timeout_num); +} + +/** + * @brief Set 2D-DMA RX channel arbiter token number + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_arb_token_num(dma2d_dev_t *dev, uint32_t channel, uint32_t token_num) +{ + volatile dma2d_in_arb_chn_reg_t *reg = (volatile dma2d_in_arb_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_arb); + reg->in_arb_token_num_chn = token_num; +} + +/** + * @brief Get 2D-DMA RX channel arbiter token number + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_rx_get_arb_token_num(dma2d_dev_t *dev, uint32_t channel) +{ + volatile dma2d_in_arb_chn_reg_t *reg = (volatile dma2d_in_arb_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_arb); + return reg->in_arb_token_num_chn; +} + +/** + * @brief Set 2D-DMA RX channel arbiter priority + */ +__attribute__((always_inline)) +static inline void dma2d_ll_rx_set_arb_priority(dma2d_dev_t *dev, uint32_t channel, uint32_t priority) +{ + volatile dma2d_in_arb_chn_reg_t *reg = (volatile dma2d_in_arb_chn_reg_t *)DMA2D_LL_IN_CHANNEL_GET_REG_ADDR(dev, channel, in_arb); + reg->in_arb_priority_chn = priority; +} + +/////////////////////////////////////// TX /////////////////////////////////////////// +/** + * @brief Get 2D-DMA TX channel interrupt status word + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_tx_get_interrupt_status(dma2d_dev_t *dev, uint32_t channel) +{ + return dev->out_channel[channel].out_int_st.val & DMA2D_LL_TX_EVENT_MASK; +} + +/** + * @brief Enable 2D-DMA TX channel interrupt + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_interrupt(dma2d_dev_t *dev, uint32_t channel, uint32_t mask, bool enable) +{ + if (enable) { + dev->out_channel[channel].out_int_ena.val = dev->out_channel[channel].out_int_ena.val | (mask & DMA2D_LL_TX_EVENT_MASK); + } else { + dev->out_channel[channel].out_int_ena.val = dev->out_channel[channel].out_int_ena.val & ~(mask & DMA2D_LL_TX_EVENT_MASK); + } +} + +/** + * @brief Clear 2D-DMA TX channel interrupt + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_clear_interrupt_status(dma2d_dev_t *dev, uint32_t channel, uint32_t mask) +{ + dev->out_channel[channel].out_int_clr.val = (mask & DMA2D_LL_TX_EVENT_MASK); +} + +/** + * @brief Get 2D-DMA TX channel interrupt status register address + */ +static inline volatile void *dma2d_ll_tx_get_interrupt_status_reg(dma2d_dev_t *dev, uint32_t channel) +{ + return (volatile void *)(&dev->out_channel[channel].out_int_st); +} + +/** + * @brief Enable 2D-DMA TX channel to check the owner bit in the descriptor, disabled by default + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_owner_check(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.out_check_owner_chn = enable; +} + +/** + * @brief Enable 2D-DMA TX channel EOF flag generation mode to be EOF flag is generated when data has been popped from DMA FIFO + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_eof_mode(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.out_eof_mode_chn = enable; +} + +/** + * @brief Enable 2D-DMA TX channel automatic write results back to descriptor after all data has been sent out, disabled by default + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_auto_write_back(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.out_auto_wrback_chn = enable; +} + +/** + * @brief Enable 2D-DMA TX channel page boundary wrap + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_page_bound_wrap(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.out_page_bound_en_chn = enable; +} + +/** + * @brief Set 2D-DMA TX channel maximum burst reading data length in bytes + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_data_burst_length(dma2d_dev_t *dev, uint32_t channel, dma2d_data_burst_length_t length) +{ + uint32_t sel; + switch (length) { + case DMA2D_DATA_BURST_LENGTH_1: + sel = 0; + break; + case DMA2D_DATA_BURST_LENGTH_16: + sel = 1; + break; + case DMA2D_DATA_BURST_LENGTH_32: + sel = 2; + break; + case DMA2D_DATA_BURST_LENGTH_64: + sel = 3; + break; + case DMA2D_DATA_BURST_LENGTH_128: + sel = 4; + break; + default: + // Unsupported data burst length + abort(); + } + dev->out_channel[channel].out_conf0.out_mem_burst_length_chn = sel; +} + +/** + * @brief Enable 2D-DMA TX channel burst reading descriptor link, disabled by default + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_descriptor_burst(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.outdscr_burst_en_chn = enable; +} + +/** + * @brief Reset 2D-DMA TX channel FSM and FIFO pointer + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_reset_channel(dma2d_dev_t *dev, uint32_t channel) +{ + dev->out_channel[channel].out_conf0.out_rst_chn = 1; + dev->out_channel[channel].out_conf0.out_rst_chn = 0; +} + +/** + * @brief Check if 2D-DMA TX channel is safe to reset + */ +__attribute__((always_inline)) +static inline bool dma2d_ll_tx_is_reset_avail(dma2d_dev_t *dev, uint32_t channel) +{ + return dev->out_channel[channel].out_state.out_reset_avail_chn; +} + +/** + * @brief Disable 2D-DMA TX channel via a command + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_disable_cmd(dma2d_dev_t *dev, uint32_t channel, bool disable) +{ + dev->out_channel[channel].out_conf0.out_cmd_disable_chn = disable; +} + +/** + * @brief Enable 2D-DMA TX channel to obtain descriptor from IP port (here referring the PPA module) + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_dscr_port(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.out_dscr_port_en_chn = enable; +} + +/** + * @brief Select 2D-DMA TX channel macro block size + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_macro_block_size(dma2d_dev_t *dev, uint32_t channel, dma2d_macro_block_size_t size) +{ + uint32_t sel; + switch (size) { + case DMA2D_MACRO_BLOCK_SIZE_NONE: + sel = 3; + break; + case DMA2D_MACRO_BLOCK_SIZE_8_8: + sel = 0; + break; + case DMA2D_MACRO_BLOCK_SIZE_8_16: + sel = 1; + break; + case DMA2D_MACRO_BLOCK_SIZE_16_16: + sel = 2; + break; + default: + // Unsupported macro block size + abort(); + } + dev->out_channel[channel].out_conf0.out_macro_block_size_chn = sel; +} + +/** + * @brief Push data into 2D-DMA TX FIFO + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_push_data(dma2d_dev_t *dev, uint32_t channel, uint32_t data) +{ + dev->out_channel[channel].out_push.outfifo_wdata_chn = data; + dev->out_channel[channel].out_push.outfifo_push_chn = 1; +} + +/** + * @brief Set the descriptor link base address for TX channel + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_desc_addr(dma2d_dev_t *dev, uint32_t channel, uint32_t addr) +{ + dev->out_channel[channel].out_link_addr.outlink_addr_chn = addr; +} + +/** + * @brief Start dealing with TX descriptors + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_start(dma2d_dev_t *dev, uint32_t channel) +{ + dev->out_channel[channel].out_link_conf.outlink_start_chn = 1; +} + +/** + * @brief Stop dealing with TX descriptors + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_stop(dma2d_dev_t *dev, uint32_t channel) +{ + dev->out_channel[channel].out_link_conf.outlink_stop_chn = 1; +} + +/** + * @brief Restart a new outlink right after the last descriptor + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_restart(dma2d_dev_t *dev, uint32_t channel) +{ + dev->out_channel[channel].out_link_conf.outlink_restart_chn = 1; +} + +/** + * @brief Check if 2D-DMA TX descriptor FSM is in IDLE state + */ +__attribute__((always_inline)) +static inline bool dma2d_ll_tx_is_desc_fsm_idle(dma2d_dev_t *dev, uint32_t channel) +{ + return dev->out_channel[channel].out_link_conf.outlink_park_chn; +} + +/** + * @brief Check if 2D-DMA TX FSM is in IDLE state + */ +__attribute__((always_inline)) +static inline bool dma2d_ll_tx_is_fsm_idle(dma2d_dev_t *dev, uint32_t channel) +{ + return (dev->out_channel[channel].out_state.out_state_chn == 0); +} + +/** + * @brief Get TX EOF descriptor's address + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_tx_get_eof_desc_addr(dma2d_dev_t *dev, uint32_t channel) +{ + return dev->out_channel[channel].out_eof_des_addr.val; +} + +/** + * @brief Get the pre-fetched TX descriptor's address + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_tx_get_prefetched_desc_addr(dma2d_dev_t *dev, uint32_t channel) +{ + return dev->out_channel[channel].out_dscr.val; +} + +/** + * @brief Set priority for 2D-DMA TX channel + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_priority(dma2d_dev_t *dev, uint32_t channel, uint32_t prio) +{ + dev->out_channel[channel].out_arb.out_arb_priority_chn = prio; +} + +/** + * @brief Connect 2D-DMA TX channel to a given peripheral + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_connect_to_periph(dma2d_dev_t *dev, uint32_t channel, dma2d_trigger_peripheral_t periph, int periph_id) +{ + (void)periph; + dev->out_channel[channel].out_peri_sel.out_peri_sel_chn = periph_id; +} + +/** + * @brief Disconnect 2D-DMA TX channel from peripheral + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_disconnect_from_periph(dma2d_dev_t *dev, uint32_t channel) +{ + dev->out_channel[channel].out_peri_sel.out_peri_sel_chn = DMA2D_LL_CHANNEL_PERIPH_NO_CHOICE; +} + +// REORDER FUNCTION (Only CH0 supports this feature) + +/** + * @brief Enable 2D-DMA TX channel macro block reorder function + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_enable_reorder(dma2d_dev_t *dev, uint32_t channel, bool enable) +{ + dev->out_channel[channel].out_conf0.out_reorder_en_chn = enable; +} + +// COLOR SPACE CONVERTION FUNCTION + +/** + * @brief Configure 2D-DMA TX channel color space conversion parameters + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_configure_color_space_conv(dma2d_dev_t *dev, uint32_t channel, dma2d_csc_tx_option_t csc_sel) +{ + // L1 + uint32_t input_sel = 7; // Disable CSC + // L2 + bool proc_en = false; // Disable generic color convert module between color input & color output + int (*table)[4] = NULL; + // L3 + uint32_t output_sel = 2; // Output directly + + const int color_space_conv_param_rgb2yuv_bt601_table[3][4] = DMA2D_COLOR_SPACE_CONV_PARAM_RGB2YUV_BT601; + const int color_space_conv_param_rgb2yuv_bt709_table[3][4] = DMA2D_COLOR_SPACE_CONV_PARAM_RGB2YUV_BT709; + const int color_space_conv_param_yuv2rgb_bt601_table[3][4] = DMA2D_COLOR_SPACE_CONV_PARAM_YUV2RGB_BT601; + const int color_space_conv_param_yuv2rgb_bt709_table[3][4] = DMA2D_COLOR_SPACE_CONV_PARAM_YUV2RGB_BT709; + + switch (csc_sel) { + case DMA2D_CSC_TX_NONE: + input_sel = 7; + break; + case DMA2D_CSC_TX_SCRAMBLE: + input_sel = 2; // Or 3 + proc_en = false; + output_sel = 2; + break; + case DMA2D_CSC_TX_RGB888_TO_RGB565: + input_sel = 3; + proc_en = false; + output_sel = 0; + break; + case DMA2D_CSC_TX_RGB565_TO_RGB888: + input_sel = 0; + proc_en = false; + output_sel = 2; + break; + case DMA2D_CSC_TX_RGB888_TO_YUV444_601: + input_sel = 3; + proc_en = true; + table = (int (*)[4])color_space_conv_param_rgb2yuv_bt601_table; + output_sel = 2; + break; + case DMA2D_CSC_TX_RGB888_TO_YUV444_709: + input_sel = 3; + proc_en = true; + table = (int (*)[4])color_space_conv_param_rgb2yuv_bt709_table; + output_sel = 2; + break; + case DMA2D_CSC_TX_RGB888_TO_YUV422_601: + input_sel = 3; + proc_en = true; + table = (int (*)[4])color_space_conv_param_rgb2yuv_bt601_table; + output_sel = 1; + break; + case DMA2D_CSC_TX_RGB888_TO_YUV422_709: + input_sel = 3; + proc_en = true; + table = (int (*)[4])color_space_conv_param_rgb2yuv_bt709_table; + output_sel = 1; + break; + case DMA2D_CSC_TX_YUV444_TO_RGB888_601: + input_sel = 3; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt601_table; + output_sel = 2; + break; + case DMA2D_CSC_TX_YUV444_TO_RGB888_709: + input_sel = 3; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt709_table; + output_sel = 2; + break; + case DMA2D_CSC_TX_YUV422_TO_RGB888_601: + input_sel = 1; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt601_table; + output_sel = 2; + break; + case DMA2D_CSC_TX_YUV422_TO_RGB888_709: + input_sel = 1; + proc_en = true; + table = (int (*)[4])color_space_conv_param_yuv2rgb_bt709_table; + output_sel = 2; + break; + default: + // Unsupported color space conversion type + abort(); + } + + dev->out_channel[channel].out_color_convert.out_color_input_sel_chn = input_sel; + dev->out_channel[channel].out_color_convert.out_color_3b_proc_en_chn = proc_en; + dev->out_channel[channel].out_color_convert.out_color_output_sel_chn = output_sel; + if (proc_en) { + HAL_ASSERT(table); + typeof(dev->out_channel[channel].out_color_param_group) color_param_group; + + color_param_group.param_h.a = table[0][0]; + color_param_group.param_h.b = table[0][1]; + color_param_group.param_h.c = table[0][2]; + color_param_group.param_h.d = table[0][3]; + + color_param_group.param_m.a = table[1][0]; + color_param_group.param_m.b = table[1][1]; + color_param_group.param_m.c = table[1][2]; + color_param_group.param_m.d = table[1][3]; + + color_param_group.param_l.a = table[2][0]; + color_param_group.param_l.b = table[2][1]; + color_param_group.param_l.c = table[2][2]; + color_param_group.param_l.d = table[2][3]; + + dev->out_channel[channel].out_color_param_group.param_h.val[0] = color_param_group.param_h.val[0]; + dev->out_channel[channel].out_color_param_group.param_h.val[1] = color_param_group.param_h.val[1]; + dev->out_channel[channel].out_color_param_group.param_m.val[0] = color_param_group.param_m.val[0]; + dev->out_channel[channel].out_color_param_group.param_m.val[1] = color_param_group.param_m.val[1]; + dev->out_channel[channel].out_color_param_group.param_l.val[0] = color_param_group.param_l.val[0]; + dev->out_channel[channel].out_color_param_group.param_l.val[1] = color_param_group.param_l.val[1]; + } +} + +/** + * @brief Configure 2D-DMA TX channel data scramble before color conversion + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_csc_pre_scramble(dma2d_dev_t *dev, uint32_t channel, dma2d_scramble_order_t order) +{ + dev->out_channel[channel].out_scramble.out_scramble_sel_pre_chn = dma2d_ll_get_scramble_order_sel(order); +} + +// Arbiter + +/** + * @brief Enable 2D-DMA TX side arbiter weight configuration + */ +static inline void dma2d_ll_tx_enable_arb_weight(dma2d_dev_t *dev, bool enable) +{ + dev->out_arb_config.out_weight_en = enable; +} + +/** + * @brief Set 2D-DMA TX side arbiter period + * + * @param timeout_num Number of AXI clock cycles (240MHz) in one arbiter period + */ +static inline void dma2d_ll_tx_set_arb_timeout(dma2d_dev_t *dev, uint32_t timeout_num) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->out_arb_config, out_arb_timeout_num, timeout_num); +} + +/** + * @brief Set 2D-DMA TX channel arbiter token number + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_arb_token_num(dma2d_dev_t *dev, uint32_t channel, uint32_t token_num) +{ + dev->out_channel[channel].out_arb.out_arb_token_num_chn = token_num; +} + +/** + * @brief Get 2D-DMA TX channel arbiter token number + */ +__attribute__((always_inline)) +static inline uint32_t dma2d_ll_tx_get_arb_token_num(dma2d_dev_t *dev, uint32_t channel) +{ + return dev->out_channel[channel].out_arb.out_arb_token_num_chn; +} + +/** + * @brief Set 2D-DMA TX channel arbiter priority + */ +__attribute__((always_inline)) +static inline void dma2d_ll_tx_set_arb_priority(dma2d_dev_t *dev, uint32_t channel, uint32_t priority) +{ + dev->out_channel[channel].out_arb.out_arb_priority_chn = priority; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32s3/include/hal/gdma_ll.h b/components/hal/esp32s3/include/hal/gdma_ll.h index 558939e88b..c3f16bcbfb 100644 --- a/components/hal/esp32s3/include/hal/gdma_ll.h +++ b/components/hal/esp32s3/include/hal/gdma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -297,9 +297,9 @@ static inline void gdma_ll_rx_enable_auto_return(gdma_dev_t *dev, uint32_t chann } /** - * @brief Check if DMA RX FSM is in IDLE state + * @brief Check if DMA RX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_rx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_rx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].in.link.park; } @@ -565,9 +565,9 @@ static inline void gdma_ll_tx_restart(gdma_dev_t *dev, uint32_t channel) } /** - * @brief Check if DMA TX FSM is in IDLE state + * @brief Check if DMA TX descriptor FSM is in IDLE state */ -static inline bool gdma_ll_tx_is_fsm_idle(gdma_dev_t *dev, uint32_t channel) +static inline bool gdma_ll_tx_is_desc_fsm_idle(gdma_dev_t *dev, uint32_t channel) { return dev->channel[channel].out.link.park; } diff --git a/components/hal/include/hal/color_hal.h b/components/hal/include/hal/color_hal.h new file mode 100644 index 0000000000..3d8dd2ae9a --- /dev/null +++ b/components/hal/include/hal/color_hal.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "hal/color_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the bit depth (bits/pixel) of each color space pixel format + * + * @param color_type_id Value constructed in color_space_pixel_format_t struct + * + * @return Number of bits per pixel + */ +uint32_t color_hal_pixel_format_get_bit_depth(color_space_pixel_format_t color_type_id); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/include/hal/dma2d_hal.h b/components/hal/include/hal/dma2d_hal.h new file mode 100644 index 0000000000..f3c215636c --- /dev/null +++ b/components/hal/include/hal/dma2d_hal.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/******************************************************************************* + * NOTICE + * The HAL is not public api, don't use in application code. + * See readme.md in soc/README.md + ******************************************************************************/ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct dma2d_dev_t *dma2d_soc_handle_t; // 2D-DMA SOC layer handle + +/** + * Context that should be maintained by both the driver and the HAL + */ +typedef struct { + dma2d_soc_handle_t dev; // 2D-DMA SOC layer handle (i.e. register base address) +} dma2d_hal_context_t; + +/** + * @brief Init the 2D-DMA hal. This function should be called first before other hal layer function is called + * + * @param hal Context of the HAL layer + * @param group_id The 2D-DMA group number + */ +void dma2d_hal_init(dma2d_hal_context_t *hal, int group_id); + +/** + * @brief Reset 2D-DMA TX channel + * + * @param hal Context of the HAL layer + * @param channel TX channel ID + */ +void dma2d_hal_tx_reset_channel(dma2d_hal_context_t *hal, uint32_t channel); + +/** + * @brief Reset 2D-DMA RX channel + * + * @param hal Context of the HAL layer + * @param channel RX channel ID + */ +void dma2d_hal_rx_reset_channel(dma2d_hal_context_t *hal, uint32_t channel); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/include/hal/dma2d_types.h b/components/hal/include/hal/dma2d_types.h new file mode 100644 index 0000000000..9db23ba9c1 --- /dev/null +++ b/components/hal/include/hal/dma2d_types.h @@ -0,0 +1,252 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_assert.h" +#include "hal/assert.h" +#include "hal/color_hal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of 2D-DMA descriptor + */ +typedef struct dma2d_descriptor_align8_s dma2d_descriptor_align8_t; +struct dma2d_descriptor_align8_s { + struct { + uint32_t vb_size : 14; /*!< Vertical height of the block, unit: pixel + When dma2d_en field is 0, this field is the lower 14-bit of the buffer memory space size, unit: byte */ + uint32_t hb_length : 14; /*!< Horizontal width of the block, unit: pixel + When dma2d_en field is 0, this field is the lower 14-bit of the buffer length, unit: byte + For data receive, this field is filled by DMA after transfer finishes */ + uint32_t err_eof : 1; /*!< Whether the received buffer contains error + For data transfer, this bit is fixed to 0 */ + uint32_t dma2d_en : 1; /*!< Whether to enable 2D functionality */ + uint32_t suc_eof : 1; /*!< Whether the descriptor is the last one in the link */ + uint32_t owner : 1; /*!< Who is allowed to access the buffer that this descriptor points to, select DMA2D_DESCRIPTOR_BUFFER_OWNER_CPU or DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA + When owner is chosen to be DMA, after DMA finishs with the descriptor, it will clear this bit + For data transfer, the bit won't be cleared unless DMA2D_OUT_AUTO_WRBACK is enabled */ + }; /*!< Descriptor Word 0 */ + struct { + uint32_t va_size : 14; /*!< Vertical height of the picture, unit: pixel + When dma2d_en field is 0, this field is the higher 14-bit of the buffer memory space size, unit: byte */ + uint32_t ha_length : 14; /*!< Horizontal width of the picture, unit: pixel + When dma2d_en field is 0, this field is the higher 14-bit of the buffer length, unit: byte + For data receive, this field is filled by DMA after transfer finishes */ + uint32_t pbyte : 4; /*!< Number of bytes per pixel (make use of dma2d_desc_pixel_format_to_pbyte_value to get the value) + When dma2d_en field is 0, this field has no use */ + }; /*!< Descriptor Word 1 */ + struct { + uint32_t y : 14; /*!< The y-coordinate value of the hb * vb block, unit: pixel + When dma2d_en field is 0, this field has no use */ + uint32_t x : 14; /*!< The x-coordinate value of the hb * vb block, unit: pixel + When dma2d_en field is 0, this field has no use */ + uint32_t mode : 1; /*!< Data block read/write mode, select DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE or DMA2D_DESCRIPTOR_BLOCK_RW_MODE_MULTIPLE + When dma2d_en field is 0, this field must be 0 (i.e. DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE mode) */ + uint32_t reserved29 : 3; + }; /*!< Descriptor Word 2 */ + void *buffer; /*!< Pointer to the buffer (of ha * va picture) + For RX buffer, there is 4-byte alignment addr and size requirement (spiram and axi bus restriction, otherwises, could overwrite other data or be overwritten) */ + dma2d_descriptor_align8_t *next; /*!< Pointer to the next descriptor (set to NULL if the descriptor is the last one, e.g. suc_eof=1) */ +} __attribute__((aligned(8))); + +ESP_STATIC_ASSERT(sizeof(dma2d_descriptor_align8_t) == 24, "dma2d_descriptor_align8_t should occupy 24 bytes in memory"); + +// 2D-DMA descriptor requires 8-byte alignment +typedef dma2d_descriptor_align8_t dma2d_descriptor_t; + +#define DMA2D_DESCRIPTOR_BUFFER_OWNER_CPU (0) /*!< 2D-DMA buffer is allowed to be accessed by CPU */ +#define DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA (1) /*!< 2D-DMA buffer is allowed to be accessed by 2D-DMA engine */ + +#define DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE (0) /*!< 2D-DMA data block single R/W mode, only r/w one hb * vb block */ +#define DMA2D_DESCRIPTOR_BLOCK_RW_MODE_MULTIPLE (1) /*!< 2D-DMA data block multiple R/W mode, continues r/w hb * vb blocks until the ha * va picture is done */ + +#define DMA2D_DESCRIPTOR_PBYTE_0B5_PER_PIXEL (0) /*!< 2D-DMA descriptor pbyte value when 0.5 bytes/pixel */ +#define DMA2D_DESCRIPTOR_PBYTE_1B0_PER_PIXEL (1) /*!< 2D-DMA descriptor pbyte value when 1 bytes/pixel */ +#define DMA2D_DESCRIPTOR_PBYTE_1B5_PER_PIXEL (2) /*!< 2D-DMA descriptor pbyte value when 1.5 bytes/pixel */ +#define DMA2D_DESCRIPTOR_PBYTE_2B0_PER_PIXEL (3) /*!< 2D-DMA descriptor pbyte value when 2 bytes/pixel */ +#define DMA2D_DESCRIPTOR_PBYTE_3B0_PER_PIXEL (4) /*!< 2D-DMA descriptor pbyte value when 3 bytes/pixel */ +#define DMA2D_DESCRIPTOR_PBYTE_4B0_PER_PIXEL (5) /*!< 2D-DMA descriptor pbyte value when 4 bytes/pixel */ + +// Helper function to convert pixel format to 2D-DMA descriptor pbyte value +static inline uint32_t dma2d_desc_pixel_format_to_pbyte_value(color_space_pixel_format_t pixel_format) +{ + switch (color_hal_pixel_format_get_bit_depth(pixel_format)) { + case 4: + return DMA2D_DESCRIPTOR_PBYTE_0B5_PER_PIXEL; + case 8: + return DMA2D_DESCRIPTOR_PBYTE_1B0_PER_PIXEL; + case 12: + return DMA2D_DESCRIPTOR_PBYTE_1B5_PER_PIXEL; + case 16: + return DMA2D_DESCRIPTOR_PBYTE_2B0_PER_PIXEL; + case 24: + return DMA2D_DESCRIPTOR_PBYTE_3B0_PER_PIXEL; + case 32: + return DMA2D_DESCRIPTOR_PBYTE_4B0_PER_PIXEL; + default: + // Unsupported bit depth + abort(); + } +} + +/** + * @brief Enumeration of peripherals which have the 2D-DMA capability + */ +typedef enum { + DMA2D_TRIG_PERIPH_M2M, /*!< 2D-DMA trigger peripheral: M2M */ + DMA2D_TRIG_PERIPH_JPEG_ENCODER, /*!< 2D-DMA trigger peripheral: JPEG Encoder */ + DMA2D_TRIG_PERIPH_JPEG_DECODER, /*!< 2D-DMA trigger peripheral: JPEG Decoder */ + DMA2D_TRIG_PERIPH_PPA_SR, /*!< 2D-DMA trigger peripheral: PPA SR engine */ + DMA2D_TRIG_PERIPH_PPA_BLEND, /*!< 2D-DMA trigger peripheral: PPA Blending engine */ +} dma2d_trigger_peripheral_t; + +/** + * @brief Enumeration of 2D-DMA channel direction + */ +typedef enum { + DMA2D_CHANNEL_DIRECTION_TX, /*!< 2D-DMA channel direction: TX */ + DMA2D_CHANNEL_DIRECTION_RX, /*!< 2D-DMA channel direction: RX */ +} dma2d_channel_direction_t; + +/** + * @brief Enumeration of 2D-DMA data burst length options + */ +typedef enum { + DMA2D_DATA_BURST_LENGTH_1, /*!< 2D-DMA block size: single byte */ + DMA2D_DATA_BURST_LENGTH_16, /*!< 2D-DMA block size: 16 bytes */ + DMA2D_DATA_BURST_LENGTH_32, /*!< 2D-DMA block size: 32 bytes */ + DMA2D_DATA_BURST_LENGTH_64, /*!< 2D-DMA block size: 64 bytes */ + DMA2D_DATA_BURST_LENGTH_128, /*!< 2D-DMA block size: 128 bytes */ + DMA2D_DATA_BURST_LENGTH_INVALID, /*!< Invalid 2D-DMA block size */ +} dma2d_data_burst_length_t; + +/** + * @brief Enumeration of 2D-DMA macro block size options + * Only useful in DMA2D_DESCRIPTOR_BLOCK_RW_MODE_MULTIPLE mode (dma2d_en = 1, mode = 1) + * Descriptor vb and hb fields has to be multiples of macro block sizes + */ +typedef enum { + DMA2D_MACRO_BLOCK_SIZE_NONE, /*!< 2D-DMA no macro block */ + DMA2D_MACRO_BLOCK_SIZE_8_8, /*!< 2D-DMA 8 pixel x 8 pixel macro block */ + DMA2D_MACRO_BLOCK_SIZE_8_16, /*!< 2D-DMA 8 pixel x 16 pixel macro block */ + DMA2D_MACRO_BLOCK_SIZE_16_16, /*!< 2D-DMA 16 pixel x 16 pixel macro block */ + DMA2D_MACRO_BLOCK_SIZE_INVALID, /*!< Invalid 2D-DMA macro block size */ +} dma2d_macro_block_size_t; + +/** + * @brief Enumeration of 2D-DMA pixel bytes scamble order in color space conversion + * + * Assuming the original pixel byte order (from MSB to LSB) is 2-1-0 . + */ +typedef enum { + DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, /*!< 2D-DMA pixel data scrambled as BYTE2-1-0 (no scramble) */ + DMA2D_SCRAMBLE_ORDER_BYTE2_0_1, /*!< 2D-DMA pixel data scrambled as BYTE2-0-1 */ + DMA2D_SCRAMBLE_ORDER_BYTE1_0_2, /*!< 2D-DMA pixel data scrambled as BYTE1-0-2 */ + DMA2D_SCRAMBLE_ORDER_BYTE1_2_0, /*!< 2D-DMA pixel data scrambled as BYTE1-2-0 */ + DMA2D_SCRAMBLE_ORDER_BYTE0_2_1, /*!< 2D-DMA pixel data scrambled as BYTE0-2-1 */ + DMA2D_SCRAMBLE_ORDER_BYTE0_1_2, /*!< 2D-DMA pixel data scrambled as BYTE0-1-2 */ + DMA2D_SCRAMBLE_ORDER_INVALID, /*!< Invalid 2D-DMA pixel data scramble order */ +} dma2d_scramble_order_t; + +//*********************BT601***********************************// +// Y = 16 + 0.257 * R + 0.504 * g + 0.098 * b // +// Cb = 128 - 0.148 * R - 0.291 * g + 0.439 * b // +// Cr = 128 + 0.439 * R - 0.368 * g - 0.071 * b // +// R = 1.164 *(Y - 16) + 1.596 *(Cr - 128) // +// G = 1.164 *(Y - 16) - 0.392 *(Cb - 128) - 0.812 *(Cr - 128)// +// B = 1.164 *(Y - 16) + 2.016 *(Cb - 128) // +//*********************BT601***********************************// + +//*********************BT709***********************************// +// Y = 16 + 0.183 * R + 0.614 * g + 0.062 * b // +// Cb = 128 - 0.101 * R - 0.339 * g + 0.439 * b // +// Cr = 128 + 0.439 * R - 0.399 * g - 0.040 * b // +// R = 1.164 *(Y - 16) + 1.792 *(Cr - 128) // +// G = 1.164 *(Y - 16) - 0.213 *(Cb - 128) - 0.534 *(Cr - 128)// +// B = 1.164 *(Y - 16) + 2.114 *(Cb - 128) // +//*********************BT709***********************************// + +// 256 * Q = A[9:0] * x + B[10:0] * y + C[9:0] * z + D[17:0] + +#define DMA2D_COLOR_SPACE_CONV_PARAM_RGB2YUV_BT601 \ +{ \ + { 66, 129, 25, 4096}, \ + { -38, -74, 112, 32768}, \ + { 112, -94, -18, 32768}, \ +} + +#define DMA2D_COLOR_SPACE_CONV_PARAM_RGB2YUV_BT709 \ +{ \ + { 47, 157, 16, 4096}, \ + { -26, -86, 112, 32768}, \ + { 112, -102, -10, 32768}, \ +} + +#define DMA2D_COLOR_SPACE_CONV_PARAM_YUV2RGB_BT601 \ +{ \ + { 298, 0, 409, -56906}, \ + { 298, -100, -208, 34707}, \ + { 298, 516, 0, -70836}, \ +} + +#define DMA2D_COLOR_SPACE_CONV_PARAM_YUV2RGB_BT709 \ +{ \ + { 298, 0, 459, -63367}, \ + { 298, -55, -136, 19681}, \ + { 298, 541, 0, -73918}, \ +} + +/** + * @brief Enumeration of 2D-DMA TX color space conversion (CSC) option + */ +typedef enum { + DMA2D_CSC_TX_NONE, /*!< 2D-DMA TX perform no CSC */ + DMA2D_CSC_TX_SCRAMBLE, /*!< 2D-DMA TX perform only data scramble */ + DMA2D_CSC_TX_RGB888_TO_RGB565, /*!< 2D-DMA TX perform RGB888 to RGB565 conversion */ + DMA2D_CSC_TX_RGB565_TO_RGB888, /*!< 2D-DMA TX perform RGB565 to RGB888 conversion */ + DMA2D_CSC_TX_RGB888_TO_YUV444_601, /*!< 2D-DMA TX perform RGB888 to YUV444 conversion (follow BT601 standard) */ + DMA2D_CSC_TX_RGB888_TO_YUV444_709, /*!< 2D-DMA TX perform RGB888 to YUV444 conversion (follow BT709 standard) */ + DMA2D_CSC_TX_RGB888_TO_YUV422_601, /*!< 2D-DMA TX perform RGB888 to YUV422-MIPI conversion (follow BT601 standard) */ + DMA2D_CSC_TX_RGB888_TO_YUV422_709, /*!< 2D-DMA TX perform RGB888 to YUV422-MIPI conversion (follow BT709 standard) */ + DMA2D_CSC_TX_YUV444_TO_RGB888_601, /*!< 2D-DMA TX perform YUV444 to RGB888 conversion (follow BT601 standard) */ + DMA2D_CSC_TX_YUV444_TO_RGB888_709, /*!< 2D-DMA TX perform YUV444 to RGB888 conversion (follow BT709 standard) */ + DMA2D_CSC_TX_YUV422_TO_RGB888_601, /*!< 2D-DMA TX perform YUV422-MIPI to RGB888 conversion (follow BT601 standard) */ + DMA2D_CSC_TX_YUV422_TO_RGB888_709, /*!< 2D-DMA TX perform YUV422-MIPI to RGB888 conversion (follow BT709 standard) */ + DMA2D_CSC_TX_INVALID, /*!< Invalid 2D-DMA TX color space conversion */ +} dma2d_csc_tx_option_t; + +/** + * @brief Enumeration of 2D-DMA RX color space conversion (CSC) option + * RX side only JPEG requires CSC + */ +typedef enum { + DMA2D_CSC_RX_NONE, /*!< 2D-DMA RX perform no CSC */ + DMA2D_CSC_RX_SCRAMBLE, /*!< 2D-DMA RX perform only data scramble */ + DMA2D_CSC_RX_YUV422_TO_YUV444, /*!< 2D-DMA RX perform YUV422 to YUV444 conversion */ + DMA2D_CSC_RX_YUV420_TO_YUV444, /*!< 2D-DMA RX perform YUV420 to YUV444 conversion */ + DMA2D_CSC_RX_YUV420_TO_RGB888_601, /*!< 2D-DMA RX perform YUV420 to RGB888 conversion (follow BT601 standard) */ + DMA2D_CSC_RX_YUV420_TO_RGB565_601, /*!< 2D-DMA RX perform YUV420 to RGB565 conversion (follow BT601 standard) */ + DMA2D_CSC_RX_YUV420_TO_RGB888_709, /*!< 2D-DMA RX perform YUV420 to RGB888 conversion (follow BT709 standard) */ + DMA2D_CSC_RX_YUV420_TO_RGB565_709, /*!< 2D-DMA RX perform YUV420 to RGB565 conversion (follow BT709 standard) */ + DMA2D_CSC_RX_YUV422_TO_RGB888_601, /*!< 2D-DMA RX perform YUV422 to RGB888 conversion (follow BT601 standard) */ + DMA2D_CSC_RX_YUV422_TO_RGB565_601, /*!< 2D-DMA RX perform YUV422 to RGB565 conversion (follow BT601 standard) */ + DMA2D_CSC_RX_YUV422_TO_RGB888_709, /*!< 2D-DMA RX perform YUV422 to RGB888 conversion (follow BT709 standard) */ + DMA2D_CSC_RX_YUV422_TO_RGB565_709, /*!< 2D-DMA RX perform YUV422 to RGB565 conversion (follow BT709 standard) */ + DMA2D_CSC_RX_YUV444_TO_RGB888_601, /*!< 2D-DMA RX perform YUV444 to RGB888 conversion (follow BT601 standard) */ + DMA2D_CSC_RX_YUV444_TO_RGB565_601, /*!< 2D-DMA RX perform YUV444 to RGB565 conversion (follow BT601 standard) */ + DMA2D_CSC_RX_YUV444_TO_RGB888_709, /*!< 2D-DMA RX perform YUV444 to RGB888 conversion (follow BT709 standard) */ + DMA2D_CSC_RX_YUV444_TO_RGB565_709, /*!< 2D-DMA RX perform YUV444 to RGB565 conversion (follow BT709 standard) */ + DMA2D_CSC_RX_INVALID, /*!< Invalid 2D-DMA RX color space conversion */ +} dma2d_csc_rx_option_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/soc/CMakeLists.txt b/components/soc/CMakeLists.txt index ea4f978498..fe7943a2e0 100644 --- a/components/soc/CMakeLists.txt +++ b/components/soc/CMakeLists.txt @@ -35,6 +35,10 @@ if(CONFIG_SOC_GDMA_SUPPORTED) list(APPEND srcs "${target}/gdma_periph.c") endif() +if(CONFIG_SOC_DMA2D_SUPPORTED) + list(APPEND srcs "${target}/dma2d_periph.c") +endif() + if(CONFIG_SOC_GPSPI_SUPPORTED) list(APPEND srcs "${target}/spi_periph.c") endif() diff --git a/components/soc/esp32p4/dma2d_periph.c b/components/soc/esp32p4/dma2d_periph.c new file mode 100644 index 0000000000..e7043df942 --- /dev/null +++ b/components/soc/esp32p4/dma2d_periph.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/dma2d_periph.h" +#include "soc/interrupts.h" + +const dma2d_signal_conn_t dma2d_periph_signals = { + .groups = { + [0] = { + .tx_irq_id = { + [0] = ETS_DMA2D_OUT_CH0_INTR_SOURCE, + [1] = ETS_DMA2D_OUT_CH1_INTR_SOURCE, + [2] = ETS_DMA2D_OUT_CH2_INTR_SOURCE, + }, + .rx_irq_id = { + [0] = ETS_DMA2D_IN_CH0_INTR_SOURCE, + [1] = ETS_DMA2D_IN_CH1_INTR_SOURCE, + } + } + } +}; diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index b2fb0c346f..86001c90bc 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -27,6 +27,10 @@ config SOC_DW_GDMA_SUPPORTED bool default y +config SOC_DMA2D_SUPPORTED + bool + default y + config SOC_GPTIMER_SUPPORTED bool default y @@ -403,6 +407,18 @@ config SOC_GDMA_SUPPORT_ETM bool default y +config SOC_DMA2D_GROUPS + int + default 1 + +config SOC_DMA2D_TX_CHANNELS_PER_GROUP + int + default 3 + +config SOC_DMA2D_RX_CHANNELS_PER_GROUP + int + default 2 + config SOC_ETM_GROUPS int default 1 diff --git a/components/soc/esp32p4/include/soc/dma2d_channel.h b/components/soc/esp32p4/include/soc/dma2d_channel.h new file mode 100644 index 0000000000..ababc5e2c5 --- /dev/null +++ b/components/soc/esp32p4/include/soc/dma2d_channel.h @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +// The following macros are matched with the 2D-DMA peri_sel field peripheral selection ID +#define SOC_DMA2D_TRIG_PERIPH_JPEG_RX (0) +#define SOC_DMA2D_TRIG_PERIPH_PPA_SR_RX (1) +#define SOC_DMA2D_TRIG_PERIPH_PPA_BLEND_RX (2) +#define SOC_DMA2D_TRIG_PERIPH_M2M_RX (-1) // Any value of 3 ~ 7, but TX and RX needs the same ID for M2M + +#define SOC_DMA2D_TRIG_PERIPH_JPEG_TX (0) +#define SOC_DMA2D_TRIG_PERIPH_PPA_SR_TX (1) +#define SOC_DMA2D_TRIG_PERIPH_PPA_BLEND_FG_TX (2) +#define SOC_DMA2D_TRIG_PERIPH_PPA_BLEND_BG_TX (3) +#define SOC_DMA2D_TRIG_PERIPH_M2M_TX (-1) // Any value of 4 ~ 7, but TX and RX needs the same ID for M2M diff --git a/components/soc/esp32p4/include/soc/dma2d_struct.h b/components/soc/esp32p4/include/soc/dma2d_struct.h index 1e65a5c48f..bd765b5c88 100644 --- a/components/soc/esp32p4/include/soc/dma2d_struct.h +++ b/components/soc/esp32p4/include/soc/dma2d_struct.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -690,90 +690,6 @@ typedef union { uint32_t val; } dma2d_out_scramble_chn_reg_t; -/** Type of out_color_param0_chn register - * Configures the tx color convert parameter of channel 0 - */ -typedef union { - struct { - /** out_color_param_h0_chn : R/W; bitpos: [20:0]; default: 298; - * Set first 2 parameter of most significant byte of pending 3 bytes - */ - uint32_t out_color_param_h0_chn:21; - uint32_t reserved_21:11; - }; - uint32_t val; -} dma2d_out_color_param0_chn_reg_t; - -/** Type of out_color_param1_chn register - * Configures the tx color convert parameter of channel 0 - */ -typedef union { - struct { - /** out_color_param_h1_chn : R/W; bitpos: [27:0]; default: 210164121; - * Set last 2 parameter of most significant byte of pending 3 bytes - */ - uint32_t out_color_param_h1_chn:28; - uint32_t reserved_28:4; - }; - uint32_t val; -} dma2d_out_color_param1_chn_reg_t; - -/** Type of out_color_param2_chn register - * Configures the tx color convert parameter of channel 0 - */ -typedef union { - struct { - /** out_color_param_m0_chn : R/W; bitpos: [20:0]; default: 1995050; - * Set first 2 parameter of midium significant byte of pending 3 bytes - */ - uint32_t out_color_param_m0_chn:21; - uint32_t reserved_21:11; - }; - uint32_t val; -} dma2d_out_color_param2_chn_reg_t; - -/** Type of out_color_param3_chn register - * Configures the tx color convert parameter of channel 0 - */ -typedef union { - struct { - /** out_color_param_m1_chn : R/W; bitpos: [27:0]; default: 35540784; - * Set last 2 parameter of midium significant byte of pending 3 bytes - */ - uint32_t out_color_param_m1_chn:28; - uint32_t reserved_28:4; - }; - uint32_t val; -} dma2d_out_color_param3_chn_reg_t; - -/** Type of out_color_param4_chn register - * Configures the tx color convert parameter of channel 0 - */ -typedef union { - struct { - /** out_color_param_l0_chn : R/W; bitpos: [20:0]; default: 528682; - * Set first 2 parameter of least significant byte of pending 3 bytes - */ - uint32_t out_color_param_l0_chn:21; - uint32_t reserved_21:11; - }; - uint32_t val; -} dma2d_out_color_param4_chn_reg_t; - -/** Type of out_color_param5_chn register - * Configures the tx color convert parameter of channel 0 - */ -typedef union { - struct { - /** out_color_param_l1_chn : R/W; bitpos: [27:0]; default: 195899392; - * Set last 2 parameter of least significant byte of pending 3 bytes - */ - uint32_t out_color_param_l1_chn:28; - uint32_t reserved_28:4; - }; - uint32_t val; -} dma2d_out_color_param5_chn_reg_t; - /** Type of out_etm_conf_chn register * Configures the tx etm of channel 0 */ @@ -1529,90 +1445,6 @@ typedef union { uint32_t val; } dma2d_in_scramble_chn_reg_t; -/** Type of in_color_param0_chn register - * Configures the rx color convert parameter of channel 0 - */ -typedef union { - struct { - /** in_color_param_h0_chn : R/W; bitpos: [20:0]; default: 298; - * Set first 2 parameter of most significant byte of pending 3 bytes - */ - uint32_t in_color_param_h0_chn:21; - uint32_t reserved_21:11; - }; - uint32_t val; -} dma2d_in_color_param0_chn_reg_t; - -/** Type of in_color_param1_chn register - * Configures the rx color convert parameter of channel 0 - */ -typedef union { - struct { - /** in_color_param_h1_chn : R/W; bitpos: [27:0]; default: 210164121; - * Set last 2 parameter of most significant byte of pending 3 bytes - */ - uint32_t in_color_param_h1_chn:28; - uint32_t reserved_28:4; - }; - uint32_t val; -} dma2d_in_color_param1_chn_reg_t; - -/** Type of in_color_param2_chn register - * Configures the rx color convert parameter of channel 0 - */ -typedef union { - struct { - /** in_color_param_m0_chn : R/W; bitpos: [20:0]; default: 1995050; - * Set first 2 parameter of midium significant byte of pending 3 bytes - */ - uint32_t in_color_param_m0_chn:21; - uint32_t reserved_21:11; - }; - uint32_t val; -} dma2d_in_color_param2_chn_reg_t; - -/** Type of in_color_param3_chn register - * Configures the rx color convert parameter of channel 0 - */ -typedef union { - struct { - /** in_color_param_m1_chn : R/W; bitpos: [27:0]; default: 35540784; - * Set last 2 parameter of midium significant byte of pending 3 bytes - */ - uint32_t in_color_param_m1_chn:28; - uint32_t reserved_28:4; - }; - uint32_t val; -} dma2d_in_color_param3_chn_reg_t; - -/** Type of in_color_param4_chn register - * Configures the rx color convert parameter of channel 0 - */ -typedef union { - struct { - /** in_color_param_l0_chn : R/W; bitpos: [20:0]; default: 528682; - * Set first 2 parameter of least significant byte of pending 3 bytes - */ - uint32_t in_color_param_l0_chn:21; - uint32_t reserved_21:11; - }; - uint32_t val; -} dma2d_in_color_param4_chn_reg_t; - -/** Type of in_color_param5_chn register - * Configures the rx color convert parameter of channel 0 - */ -typedef union { - struct { - /** in_color_param_l1_chn : R/W; bitpos: [27:0]; default: 195899392; - * Set last 2 parameter of least significant byte of pending 3 bytes - */ - uint32_t in_color_param_l1_chn:28; - uint32_t reserved_28:4; - }; - uint32_t val; -} dma2d_in_color_param5_chn_reg_t; - /** Type of in_etm_conf_chn register * Configures the rx etm of channel 0 */ @@ -1718,7 +1550,7 @@ typedef union { */ typedef union { struct { - /** access_intr_mem_start_addr : R/W; bitpos: [31:0]; default: 806354944; + /** access_intr_mem_start_addr : R/W; bitpos: [31:0]; default: 806354944 (0x30100000); * The start address of accessible address space. */ uint32_t access_intr_mem_start_addr:32; @@ -1731,7 +1563,7 @@ typedef union { */ typedef union { struct { - /** access_intr_mem_end_addr : R/W; bitpos: [31:0]; default: 2415919103; + /** access_intr_mem_end_addr : R/W; bitpos: [31:0]; default: 2415919103 (0x8FFFFFFF); * The end address of accessible address space. The access address beyond this range * would lead to descriptor error. */ @@ -1745,7 +1577,7 @@ typedef union { */ typedef union { struct { - /** access_extr_mem_start_addr : R/W; bitpos: [31:0]; default: 806354944; + /** access_extr_mem_start_addr : R/W; bitpos: [31:0]; default: 806354944 (0x30100000); * The start address of accessible address space. */ uint32_t access_extr_mem_start_addr:32; @@ -1758,7 +1590,7 @@ typedef union { */ typedef union { struct { - /** access_extr_mem_end_addr : R/W; bitpos: [31:0]; default: 2415919103; + /** access_extr_mem_end_addr : R/W; bitpos: [31:0]; default: 2415919103 (0x8FFFFFFF); * The end address of accessible address space. The access address beyond this range * would lead to descriptor error. */ @@ -1847,36 +1679,69 @@ typedef union { uint32_t val; } dma2d_rdn_eco_low_reg_t; + +/** Type of in/out_color_param_h/m/l_chn register + * Configures the rx/tx color convert parameter of channel n + */ +typedef union { + struct { + struct { + /** a: R/W; bitpos: [9:0]; default: h:298, m:298, l:298 + * Set the first parameter of the most/medium/least significant byte of pending 3 bytes + */ + uint32_t a : 10; + /** b: R/W; bitpos: [20:10]; default: h:0, in_m:1948, l:516 + * Set the second parameter of the most/medium/least significant byte of pending 3 bytes + */ + uint32_t b : 11; + uint32_t reserved21 : 11; + }; + struct { + /** c: R/W; bitpos: [41:32]; default: h:409, m:816, l:0 + * Set the third parameter of the most/medium/least significant byte of pending 3 bytes + */ + uint32_t c : 10; + /** d: R/W; bitpos: [59:42]; default: h:205238, m:34707, l:191308 + * Set the fourth parameter of the most/medium/least significant byte of pending 3 bytes + */ + uint32_t d : 18; + uint32_t reserved60 : 4; + }; + }; + uint32_t val[2]; +} dma2d_color_param_reg_t; + typedef struct { - volatile dma2d_out_conf0_chn_reg_t out_conf0_ch0; - volatile dma2d_out_int_raw_chn_reg_t out_int_raw_ch0; - volatile dma2d_out_int_ena_chn_reg_t out_int_ena_ch0; - volatile dma2d_out_int_st_chn_reg_t out_int_st_ch0; - volatile dma2d_out_int_clr_chn_reg_t out_int_clr_ch0; - volatile dma2d_outfifo_status_chn_reg_t outfifo_status_ch0; - volatile dma2d_out_push_chn_reg_t out_push_ch0; - volatile dma2d_out_link_conf_chn_reg_t out_link_conf_ch0; - volatile dma2d_out_link_addr_chn_reg_t out_link_addr_ch0; - volatile dma2d_out_state_chn_reg_t out_state_ch0; - volatile dma2d_out_eof_des_addr_chn_reg_t out_eof_des_addr_ch0; - volatile dma2d_out_dscr_chn_reg_t out_dscr_ch0; - volatile dma2d_out_dscr_bf0_chn_reg_t out_dscr_bf0_ch0; - volatile dma2d_out_dscr_bf1_chn_reg_t out_dscr_bf1_ch0; - volatile dma2d_out_peri_sel_chn_reg_t out_peri_sel_ch0; - volatile dma2d_out_arb_chn_reg_t out_arb_ch0; - volatile dma2d_out_ro_status_chn_reg_t out_ro_status_ch0; - volatile dma2d_out_ro_pd_conf_chn_reg_t out_ro_pd_conf_ch0; //only chn0 - volatile dma2d_out_color_convert_chn_reg_t out_color_convert_ch0; - volatile dma2d_out_scramble_chn_reg_t out_scramble_ch0; - volatile dma2d_out_color_param0_chn_reg_t out_color_param0_ch0; - volatile dma2d_out_color_param1_chn_reg_t out_color_param1_ch0; - volatile dma2d_out_color_param2_chn_reg_t out_color_param2_ch0; - volatile dma2d_out_color_param3_chn_reg_t out_color_param3_ch0; - volatile dma2d_out_color_param4_chn_reg_t out_color_param4_ch0; - volatile dma2d_out_color_param5_chn_reg_t out_color_param5_ch0; - volatile dma2d_out_etm_conf_chn_reg_t out_etm_conf_ch0; - volatile dma2d_out_dscr_port_blk_chn_reg_t out_dscr_port_blk_ch0; - uint32_t reserved[36]; + volatile dma2d_color_param_reg_t param_h; + volatile dma2d_color_param_reg_t param_m; + volatile dma2d_color_param_reg_t param_l; +} dma2d_color_param_group_chn_reg_t; + +typedef struct { + volatile dma2d_out_conf0_chn_reg_t out_conf0; + volatile dma2d_out_int_raw_chn_reg_t out_int_raw; + volatile dma2d_out_int_ena_chn_reg_t out_int_ena; + volatile dma2d_out_int_st_chn_reg_t out_int_st; + volatile dma2d_out_int_clr_chn_reg_t out_int_clr; + volatile dma2d_outfifo_status_chn_reg_t outfifo_status; + volatile dma2d_out_push_chn_reg_t out_push; + volatile dma2d_out_link_conf_chn_reg_t out_link_conf; + volatile dma2d_out_link_addr_chn_reg_t out_link_addr; + volatile dma2d_out_state_chn_reg_t out_state; + volatile dma2d_out_eof_des_addr_chn_reg_t out_eof_des_addr; + volatile dma2d_out_dscr_chn_reg_t out_dscr; + volatile dma2d_out_dscr_bf0_chn_reg_t out_dscr_bf0; + volatile dma2d_out_dscr_bf1_chn_reg_t out_dscr_bf1; + volatile dma2d_out_peri_sel_chn_reg_t out_peri_sel; + volatile dma2d_out_arb_chn_reg_t out_arb; + volatile dma2d_out_ro_status_chn_reg_t out_ro_status; + volatile dma2d_out_ro_pd_conf_chn_reg_t out_ro_pd_conf; /* only exist on channel0 */ + volatile dma2d_out_color_convert_chn_reg_t out_color_convert; + volatile dma2d_out_scramble_chn_reg_t out_scramble; + volatile dma2d_color_param_group_chn_reg_t out_color_param_group; + volatile dma2d_out_etm_conf_chn_reg_t out_etm_conf; + volatile dma2d_out_dscr_port_blk_chn_reg_t out_dscr_port_blk; + uint32_t reserved_out[36]; } dma2d_out_chn_reg_t; typedef struct { @@ -1898,15 +1763,10 @@ typedef struct { volatile dma2d_in_peri_sel_chn_reg_t in_peri_sel; volatile dma2d_in_arb_chn_reg_t in_arb; volatile dma2d_in_ro_status_chn_reg_t in_ro_status; - volatile dma2d_in_ro_pd_conf_chn_reg_t in_ro_pd_conf; //only ch0 - volatile dma2d_in_color_convert_chn_reg_t in_color_convert; //only ch0 - volatile dma2d_in_scramble_chn_reg_t in_scramble; //only ch0 - volatile dma2d_in_color_param0_chn_reg_t in_color_param0; //only ch0 - volatile dma2d_in_color_param1_chn_reg_t in_color_param1; //only ch0 - volatile dma2d_in_color_param2_chn_reg_t in_color_param2; //only ch0 - volatile dma2d_in_color_param3_chn_reg_t in_color_param3; //only ch0 - volatile dma2d_in_color_param4_chn_reg_t in_color_param4; //only ch0 - volatile dma2d_in_color_param5_chn_reg_t in_color_param5; //only ch0 + volatile dma2d_in_ro_pd_conf_chn_reg_t in_ro_pd_conf; + volatile dma2d_in_color_convert_chn_reg_t in_color_convert; + volatile dma2d_in_scramble_chn_reg_t in_scramble; + volatile dma2d_color_param_group_chn_reg_t in_color_param_group; volatile dma2d_in_etm_conf_chn_reg_t in_etm_conf; uint32_t reserved_570[36]; } dma2d_in_ch0_reg_t; @@ -1931,14 +1791,15 @@ typedef struct { volatile dma2d_in_arb_chn_reg_t in_arb; volatile dma2d_in_ro_status_chn_reg_t in_ro_status; volatile dma2d_in_etm_conf_chn_reg_t in_etm_conf; + uint32_t reserved_64c[45]; } dma2d_in_ch1_reg_t; -typedef struct { +typedef struct dma2d_dev_t { volatile dma2d_out_chn_reg_t out_channel[3]; uint32_t reserved_300[128]; volatile dma2d_in_ch0_reg_t in_channel0; volatile dma2d_in_ch1_reg_t in_channel1; - uint32_t reserved_6dc[237]; + uint32_t reserved_700[192]; volatile dma2d_axi_err_reg_t axi_err; volatile dma2d_rst_conf_reg_t rst_conf; volatile dma2d_intr_mem_start_addr_reg_t intr_mem_start_addr; diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 0358f666a3..2d77c90182 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -25,6 +25,7 @@ #define SOC_AHB_GDMA_SUPPORTED 1 #define SOC_AXI_GDMA_SUPPORTED 1 #define SOC_DW_GDMA_SUPPORTED 1 +#define SOC_DMA2D_SUPPORTED 1 #define SOC_GPTIMER_SUPPORTED 1 #define SOC_PCNT_SUPPORTED 1 // #define SOC_LCDCAM_SUPPORTED 1 // TODO: IDF-7465 @@ -180,6 +181,12 @@ #define SOC_AXI_GDMA_SUPPORT_PSRAM 1 #define SOC_GDMA_SUPPORT_ETM 1 +/*-------------------------- 2D-DMA CAPS -------------------------------------*/ +#define SOC_DMA2D_GROUPS (1U) // Number of 2D-DMA groups +#define SOC_DMA2D_TX_CHANNELS_PER_GROUP (3) // Number of 2D-DMA TX (OUT) channels in each group +#define SOC_DMA2D_RX_CHANNELS_PER_GROUP (2) // Number of 2D-DMA RX (IN) channels in each group +// #define SOC_DMA2D_SUPPORT_ETM (1) // Support ETM submodule + /*-------------------------- ETM CAPS --------------------------------------*/ #define SOC_ETM_GROUPS 1U // Number of ETM groups #define SOC_ETM_CHANNELS_PER_GROUP 50 // Number of ETM channels in the group diff --git a/components/soc/esp32p4/ld/esp32p4.peripherals.ld b/components/soc/esp32p4/ld/esp32p4.peripherals.ld index e7b3e7c3ac..62636b9dff 100644 --- a/components/soc/esp32p4/ld/esp32p4.peripherals.ld +++ b/components/soc/esp32p4/ld/esp32p4.peripherals.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -100,9 +100,9 @@ PROVIDE ( I3C_MST = 0x500DA000 ); PROVIDE ( I3C_MST_MEM = 0x500DA000 ); PROVIDE ( I3C_SLV = 0x500DB000 ); +PROVIDE ( JPEG = 0x50086000 ); PROVIDE ( PPA = 0x50087000 ); PROVIDE ( DMA2D = 0x50088000 ); -PROVIDE ( JPEG = 0x50086000 ); PROVIDE ( USB_WRAP = 0x50080000 ); PROVIDE ( USB_DWC_HS = 0x50000000 ); diff --git a/components/soc/include/soc/dma2d_periph.h b/components/soc/include/soc/dma2d_periph.h new file mode 100644 index 0000000000..36ca68272f --- /dev/null +++ b/components/soc/include/soc/dma2d_periph.h @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_DMA2D_SUPPORTED +typedef struct { + struct { + const int tx_irq_id[SOC_DMA2D_TX_CHANNELS_PER_GROUP]; + const int rx_irq_id[SOC_DMA2D_RX_CHANNELS_PER_GROUP]; + } groups[SOC_DMA2D_GROUPS]; +} dma2d_signal_conn_t; + +extern const dma2d_signal_conn_t dma2d_periph_signals; +#endif + +#ifdef __cplusplus +} +#endif