diff --git a/components/esp_driver_dma/CMakeLists.txt b/components/esp_driver_dma/CMakeLists.txt index ec28f99c5c0..6e9b57e330a 100644 --- a/components/esp_driver_dma/CMakeLists.txt +++ b/components/esp_driver_dma/CMakeLists.txt @@ -36,7 +36,7 @@ if(CONFIG_SOC_DW_GDMA_SUPPORTED) endif() if(CONFIG_SOC_DMA2D_SUPPORTED) - list(APPEND srcs "src/dma2d.c") + list(APPEND srcs "src/dma2d.c" "src/esp_async_color_convert.c" "src/async_color_convert_dma2d.c") endif() idf_component_register(SRCS ${srcs} diff --git a/components/esp_driver_dma/include/esp_async_color_convert.h b/components/esp_driver_dma/include/esp_async_color_convert.h new file mode 100644 index 00000000000..18399c2f15c --- /dev/null +++ b/components/esp_driver_dma/include/esp_async_color_convert.h @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "hal/color_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Opaque handle of async color conversion driver instance + */ +typedef struct async_color_convert_context_t *async_color_convert_handle_t; + +/** + * @brief Async color conversion event data + */ +typedef struct { +} async_color_convert_event_data_t; + +/** + * @brief Async color conversion callback type + * + * @note This callback runs in ISR context. + * + * @param[in] conv_hdl Driver handle that produced this event + * @param[in] edata Event data for the completed request + * @param[in] cb_args User context passed to :cpp:func:`esp_async_color_convert` + * + * @return + * - true: a higher-priority task was woken and a yield is requested + * - false: no yield request + */ +typedef bool (*async_color_convert_isr_cb_t)(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args); + +/** + * @brief Async color conversion driver configuration + */ +typedef struct { + uint32_t backlog; /*!< Number of in-flight/pending requests. 0 means driver default. */ + size_t dma_burst_size; /*!< DMA burst length in bytes. 0 means driver default. */ + uint32_t intr_priority; /*!< Interrupt priority. 0 means default low/medium priority. */ +} async_color_convert_config_t; + +/** + * @brief Install async color conversion driver with the DMA2D backend + * + * This API allocates internal resources and creates a conversion context. + * + * @param[in] config Driver configuration + * @param[out] ret_hdl Returned driver handle + * + * @return + * - ESP_OK: Driver installed successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NO_MEM: Out of memory + * - ESP_ERR_NOT_FOUND: Required DMA2D resource is unavailable + * - others: Error from lower-level DMA2D driver + */ +esp_err_t esp_async_color_convert_install_dma2d(const async_color_convert_config_t *config, + async_color_convert_handle_t *ret_hdl); + +/** + * @brief Uninstall async color conversion driver + * + * @param[in] conv_hdl Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d` + * + * @return + * - ESP_OK: Driver uninstalled successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: There are pending requests in the queue + */ +esp_err_t esp_async_color_convert_uninstall(async_color_convert_handle_t conv_hdl); + +/** + * @brief Async color conversion request + * + * Coordinates and size are in pixels. + * + * The source and destination windows are: + * - source: [src_x, src_x + copy_width) x [src_y, src_y + copy_height) + * - destination: [dst_x, dst_x + copy_width) x [dst_y, dst_y + copy_height) + * + * Both windows must be fully inside their corresponding image bounds. + * + * Conversion rule is inferred from source and destination formats: + * - If source and destination are the same format, it performs 2D copy only. + */ +typedef struct { + const void *src_buffer; /*!< Source picture base address */ + uint32_t src_stride; /*!< Source picture row stride in pixels */ + uint32_t src_height; /*!< Source picture height in pixels */ + uint32_t src_x; /*!< Source window x offset in pixels */ + uint32_t src_y; /*!< Source window y offset in pixels */ + + void *dst_buffer; /*!< Destination picture base address */ + uint32_t dst_stride; /*!< Destination picture row stride in pixels */ + uint32_t dst_height; /*!< Destination picture height in pixels */ + uint32_t dst_x; /*!< Destination window x offset in pixels */ + uint32_t dst_y; /*!< Destination window y offset in pixels */ + + uint32_t copy_width; /*!< Conversion window width in pixels */ + uint32_t copy_height; /*!< Conversion window height in pixels */ + + esp_color_fourcc_t src_color_format; /*!< Source pixel format */ + esp_color_fourcc_t dst_color_format; /*!< Destination pixel format */ + color_conv_std_rgb_yuv_t color_conv_std; /*!< RGB/YUV conversion standard for RGB888<->UYVY422 */ +} async_color_convert_request_t; + +/** + * @brief Submit an asynchronous 2D color conversion request + * + * The request is enqueued and completed later in DMA2D interrupt context. + * The callback can be NULL if no completion notification is needed. + * + * @param[in] conv_hdl Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d` + * @param[in] request Color conversion request + * @param[in] cb_isr ISR callback invoked on conversion completion, can be NULL + * @param[in] cb_args User context passed to @p cb_isr + * + * @return + * - ESP_OK: Request accepted + * - ESP_ERR_INVALID_ARG: Invalid argument or invalid request fields + * - ESP_ERR_INVALID_STATE: No free internal transaction slot (queue full) + * - others: Error from lower-level DMA2D driver + */ +esp_err_t esp_async_color_convert(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args); + +/** + * @brief Blocking 2D color conversion API built on async request path + * + * @note This API must not be called from ISR context. + * + * @param[in] conv_hdl Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d` + * @param[in] request Color conversion request + * @param[in] timeout_ms Timeout in milliseconds. Currently only ``-1`` is supported, which waits forever. + * + * @return + * - ESP_OK: Conversion completed successfully + * - ESP_ERR_INVALID_ARG: Invalid argument, unsupported timeout, or invalid request fields + * - ESP_ERR_INVALID_STATE: Called from ISR context, or queue unavailable + * - others: Error from lower-level DMA2D driver + */ +esp_err_t esp_color_convert_blocking(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + int32_t timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/src/async_color_convert_dma2d.c b/components/esp_driver_dma/src/async_color_convert_dma2d.c new file mode 100644 index 00000000000..720ffa7656d --- /dev/null +++ b/components/esp_driver_dma/src/async_color_convert_dma2d.c @@ -0,0 +1,470 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_cache.h" +#include "esp_private/esp_cache_private.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "esp_async_color_convert_priv.h" +#include "soc/dma2d_channel.h" +#include "hal/dma2d_types.h" +#include "hal/dma2d_ll.h" +#include "hal/color_hal.h" + +ESP_LOG_ATTR_TAG(TAG, "async_color_dma2d"); + +typedef struct async_color_convert_dma2d_context_t async_color_convert_dma2d_context_t; +typedef struct async_color_convert_transaction async_color_convert_transaction_t; + +struct async_color_convert_transaction { + // Keep descriptors in dedicated cache-line-sized allocations so cache sync only + // touches descriptor state, not the surrounding transaction metadata. + dma2d_descriptor_t *tx_desc; // TX descriptor used by DMA2D source channel + dma2d_descriptor_t *rx_desc; // RX descriptor used by DMA2D destination channel + dma2d_trans_t *dma2d_trans_placeholder; // Opaque DMA2D transaction object storage + dma2d_trans_config_t dma2d_trans_config; // Per-request DMA2D transaction configuration + + async_color_convert_request_t request; // Cached user request used to build DMA2D transaction + dma2d_csc_config_t tx_csc; // Cached DMA2D CSC configuration resolved in task context + async_color_convert_isr_cb_t cb_isr; // User ISR callback for this request + void *cb_args; // User callback argument + async_color_convert_dma2d_context_t *ctx; // Back pointer to parent context + + STAILQ_ENTRY(async_color_convert_transaction) queue_entry; // Node in idle transaction queue +}; + +struct async_color_convert_dma2d_context_t { + async_color_convert_context_t parent; // Base interface used by common API wrappers + dma2d_pool_handle_t pool; // DMA2D pool handle for enqueue/dequeue scheduling + size_t dma_burst_size; // DMA burst size applied on job picked + size_t desc_alloc_size; // Cache-line-sized DMA2D descriptor allocation size + portMUX_TYPE spinlock; // Protects idle_queue + uint32_t num_trans_objs; // Total number of transaction objects in trans_pool + _Atomic uint32_t idle_num; // Number of currently available transaction objects + _Atomic bool deleting; // Whether uninstall is in progress + async_color_convert_transaction_t *trans_pool; // Pre-allocated transaction object pool + STAILQ_HEAD(, async_color_convert_transaction) idle_queue; // Queue of available transaction objects +}; + +static esp_err_t async_color_convert_dma2d_del(async_color_convert_context_t *ctx); +static esp_err_t async_color_convert_dma2d_convert(async_color_convert_context_t *ctx, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args); + +static bool try_convert_request_to_dma2d_csc(const async_color_convert_request_t *request, + dma2d_csc_config_t *out_tx_csc) +{ + esp_color_fourcc_t src_fourcc = request->src_color_format; + esp_color_fourcc_t dst_fourcc = request->dst_color_format; + + if (src_fourcc == dst_fourcc) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_NONE; + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + out_tx_csc->post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + return true; + } + + if (src_fourcc == ESP_COLOR_FOURCC_RGB16 && dst_fourcc == ESP_COLOR_FOURCC_BGR24) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB565_TO_RGB888; + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + out_tx_csc->post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + return true; + } + + if (src_fourcc == ESP_COLOR_FOURCC_BGR24 && dst_fourcc == ESP_COLOR_FOURCC_RGB16) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_RGB565; + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + out_tx_csc->post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + return true; + } + + if (src_fourcc == ESP_COLOR_FOURCC_BGR24 && dst_fourcc == ESP_COLOR_FOURCC_UYVY) { + if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT601) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_YUV422_601; + } else if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT709) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_YUV422_709; + } else { + return false; + } + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + out_tx_csc->post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + return true; + } + + if (src_fourcc == ESP_COLOR_FOURCC_UYVY && dst_fourcc == ESP_COLOR_FOURCC_BGR24) { + if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT601) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_YUV422_TO_RGB888_601; + } else if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT709) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_YUV422_TO_RGB888_709; + } else { + return false; + } + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + out_tx_csc->post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0; + return true; + } + + return false; +} + +static inline bool needs_tx_csc(const dma2d_csc_config_t *tx_csc) +{ + return tx_csc->tx_csc_option != DMA2D_CSC_TX_NONE; +} + +static esp_err_t sync_if_cacheable(void *addr, size_t size, int flags) +{ + return esp_cache_get_line_size_by_addr(addr) > 0 ? esp_cache_msync(addr, size, flags) : ESP_OK; +} + +static size_t get_picture_size_bytes(uint32_t stride, uint32_t height, uint32_t bit_depth) +{ + return (((size_t)stride * height * bit_depth) + 7) / 8; +} + +static esp_err_t validate_request(const async_color_convert_request_t *request) +{ + ESP_RETURN_ON_FALSE(request->src_color_format != 0 && request->dst_color_format != 0, + ESP_ERR_INVALID_ARG, TAG, "invalid color format"); + ESP_RETURN_ON_FALSE(request->src_buffer && request->dst_buffer, ESP_ERR_INVALID_ARG, TAG, "invalid buffer"); + ESP_RETURN_ON_FALSE(request->copy_width > 0 && request->copy_height > 0, ESP_ERR_INVALID_ARG, TAG, "invalid copy window"); + + uint64_t src_x_end = (uint64_t)request->src_x + request->copy_width; + uint64_t src_y_end = (uint64_t)request->src_y + request->copy_height; + uint64_t dst_x_end = (uint64_t)request->dst_x + request->copy_width; + uint64_t dst_y_end = (uint64_t)request->dst_y + request->copy_height; + + ESP_RETURN_ON_FALSE(src_x_end <= request->src_stride, ESP_ERR_INVALID_ARG, TAG, "source window out of width"); + ESP_RETURN_ON_FALSE(src_y_end <= request->src_height, ESP_ERR_INVALID_ARG, TAG, "source window out of height"); + ESP_RETURN_ON_FALSE(dst_x_end <= request->dst_stride, ESP_ERR_INVALID_ARG, TAG, "destination window out of width"); + ESP_RETURN_ON_FALSE(dst_y_end <= request->dst_height, ESP_ERR_INVALID_ARG, TAG, "destination window out of height"); + + ESP_RETURN_ON_FALSE(request->src_stride <= DMA2D_LL_DESC_2D_FIELD_MAX && + request->src_height <= DMA2D_LL_DESC_2D_FIELD_MAX && + request->dst_stride <= DMA2D_LL_DESC_2D_FIELD_MAX && + request->dst_height <= DMA2D_LL_DESC_2D_FIELD_MAX, + ESP_ERR_INVALID_ARG, TAG, "dimension exceeds DMA2D descriptor field limit"); + + return ESP_OK; +} + +static async_color_convert_transaction_t *try_acquire_trans(async_color_convert_dma2d_context_t *ctx) +{ + async_color_convert_transaction_t *trans = NULL; + portENTER_CRITICAL(&ctx->spinlock); + if (!atomic_load(&ctx->deleting)) { + trans = STAILQ_FIRST(&ctx->idle_queue); + if (trans) { + STAILQ_REMOVE_HEAD(&ctx->idle_queue, queue_entry); + atomic_fetch_sub(&ctx->idle_num, 1); + } + } + portEXIT_CRITICAL(&ctx->spinlock); + return trans; +} + +static void recycle_trans(async_color_convert_dma2d_context_t *ctx, + async_color_convert_transaction_t *trans) +{ + portENTER_CRITICAL_SAFE(&ctx->spinlock); + STAILQ_INSERT_TAIL(&ctx->idle_queue, trans, queue_entry); + atomic_fetch_add(&ctx->idle_num, 1); + portEXIT_CRITICAL_SAFE(&ctx->spinlock); +} + +static bool async_color_convert_done_cb(dma2d_channel_handle_t dma2d_chan, + dma2d_event_data_t *event_data, + void *user_data) +{ + bool need_yield = false; + async_color_convert_transaction_t *trans = (async_color_convert_transaction_t *)user_data; + async_color_convert_dma2d_context_t *ctx = trans->ctx; + (void)dma2d_chan; + (void)event_data; + + if (trans->cb_isr) { + async_color_convert_event_data_t edata = {}; + need_yield = trans->cb_isr(&ctx->parent, &edata, trans->cb_args); + } + trans->cb_isr = NULL; + trans->cb_args = NULL; + + recycle_trans(ctx, trans); + return need_yield; +} + +static bool async_color_convert_on_job_picked(uint32_t channel_num, + const dma2d_trans_channel_info_t *dma2d_chans, + void *user_config) +{ + async_color_convert_transaction_t *trans = (async_color_convert_transaction_t *)user_config; + + dma2d_channel_handle_t tx_chan = NULL; + dma2d_channel_handle_t rx_chan = NULL; + + for (uint32_t i = 0; i < channel_num; i++) { + if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_TX) { + tx_chan = dma2d_chans[i].chan; + } else { + rx_chan = dma2d_chans[i].chan; + } + } + + dma2d_trigger_t trig_periph = { + .periph = DMA2D_TRIG_PERIPH_M2M, + .periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX, + }; + dma2d_connect(tx_chan, &trig_periph); + trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX; + dma2d_connect(rx_chan, &trig_periph); + + async_color_convert_dma2d_context_t *ctx = trans->ctx; + dma2d_transfer_ability_t transfer_ability = { + .desc_burst_en = true, + .data_burst_length = ctx->dma_burst_size, + .access_ext_mem = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + dma2d_set_transfer_ability(tx_chan, &transfer_ability); + dma2d_set_transfer_ability(rx_chan, &transfer_ability); + + if (needs_tx_csc(&trans->tx_csc)) { + dma2d_configure_color_space_conversion(tx_chan, &trans->tx_csc); + } + + dma2d_rx_event_callbacks_t cbs = { + .on_recv_eof = async_color_convert_done_cb, + }; + dma2d_register_rx_event_callbacks(rx_chan, &cbs, trans); + + dma2d_set_desc_addr(rx_chan, (intptr_t)trans->rx_desc); + dma2d_set_desc_addr(tx_chan, (intptr_t)trans->tx_desc); + + dma2d_start(rx_chan); + dma2d_start(tx_chan); + return false; +} + +static void setup_desc(dma2d_descriptor_t *desc, + void *buffer, + uint32_t pic_w, + uint32_t pic_h, + uint32_t win_w, + uint32_t win_h, + uint32_t x, + uint32_t y, + uint32_t pbyte) +{ + memset(desc, 0, sizeof(*desc)); + desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->suc_eof = 1; + desc->dma2d_en = 1; + desc->ha_length = pic_w; + desc->va_size = pic_h; + desc->hb_length = win_w; + desc->vb_size = win_h; + desc->x = x; + desc->y = y; + desc->pbyte = pbyte; + desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE; + desc->buffer = buffer; + desc->next = NULL; +} + +static esp_err_t async_color_convert_dma2d_convert(async_color_convert_context_t *ctx, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(ctx && request, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_ERROR(validate_request(request), TAG, "invalid request"); + + async_color_convert_dma2d_context_t *color_ctx = __containerof(ctx, async_color_convert_dma2d_context_t, parent); + async_color_convert_transaction_t *trans = try_acquire_trans(color_ctx); + ESP_RETURN_ON_FALSE(trans, ESP_ERR_INVALID_STATE, TAG, "no free transaction in pool"); + + trans->request = *request; // copy request to transaction object + trans->cb_isr = cb_isr; + trans->cb_args = cb_args; + + esp_color_fourcc_t src_fourcc = request->src_color_format; + esp_color_fourcc_t dst_fourcc = request->dst_color_format; + trans->tx_csc = (dma2d_csc_config_t) {}; + ESP_GOTO_ON_FALSE(try_convert_request_to_dma2d_csc(request, &trans->tx_csc), + ESP_ERR_INVALID_ARG, recycle_and_out, TAG, "unsupported color conversion mode"); + + if (needs_tx_csc(&trans->tx_csc)) { + trans->dma2d_trans_config.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING | DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC; + } else { + trans->dma2d_trans_config.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING; + } + + setup_desc(trans->tx_desc, + (void *)request->src_buffer, + request->src_stride, + request->src_height, + request->copy_width, + request->copy_height, + request->src_x, + request->src_y, + dma2d_desc_pixel_format_to_pbyte_value(src_fourcc)); + + setup_desc(trans->rx_desc, + request->dst_buffer, + request->dst_stride, + request->dst_height, + request->copy_width, + request->copy_height, + request->dst_x, + request->dst_y, + dma2d_desc_pixel_format_to_pbyte_value(dst_fourcc)); + + uint32_t src_bpp = color_hal_pixel_format_fourcc_get_bit_depth(src_fourcc); + uint32_t dst_bpp = color_hal_pixel_format_fourcc_get_bit_depth(dst_fourcc); + + size_t src_total_size = get_picture_size_bytes(request->src_stride, request->src_height, src_bpp); + size_t dst_total_size = get_picture_size_bytes(request->dst_stride, request->dst_height, dst_bpp); + + ESP_GOTO_ON_ERROR(sync_if_cacheable((void *)request->src_buffer, src_total_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), + recycle_and_out, TAG, "source cache sync failed"); + + ESP_GOTO_ON_ERROR(sync_if_cacheable(request->dst_buffer, dst_total_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE), + recycle_and_out, TAG, "destination cache sync failed"); + + ESP_GOTO_ON_ERROR(sync_if_cacheable(trans->tx_desc, color_ctx->desc_alloc_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE), + recycle_and_out, TAG, "tx descriptor cache sync failed"); + + ESP_GOTO_ON_ERROR(sync_if_cacheable(trans->rx_desc, color_ctx->desc_alloc_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE), + recycle_and_out, TAG, "rx descriptor cache sync failed"); + + ESP_GOTO_ON_ERROR(dma2d_enqueue(color_ctx->pool, + &trans->dma2d_trans_config, + trans->dma2d_trans_placeholder), + recycle_and_out, TAG, "enqueue dma2d transaction failed"); + + return ESP_OK; + +recycle_and_out: + recycle_trans(color_ctx, trans); + return ret; +} + +static esp_err_t async_color_convert_dma2d_destroy(async_color_convert_dma2d_context_t *ctx) +{ + if (ctx->pool) { + dma2d_release_pool(ctx->pool); + } + if (ctx->trans_pool) { + for (uint32_t i = 0; i < ctx->num_trans_objs; i++) { + free(ctx->trans_pool[i].tx_desc); + free(ctx->trans_pool[i].rx_desc); + free(ctx->trans_pool[i].dma2d_trans_placeholder); + } + free(ctx->trans_pool); + } + free(ctx); + return ESP_OK; +} + +static esp_err_t async_color_convert_dma2d_del(async_color_convert_context_t *ctx) +{ + async_color_convert_dma2d_context_t *color_ctx = __containerof(ctx, async_color_convert_dma2d_context_t, parent); + bool can_destroy = false; + portENTER_CRITICAL(&color_ctx->spinlock); + atomic_store(&color_ctx->deleting, true); + can_destroy = (atomic_load(&color_ctx->idle_num) == color_ctx->num_trans_objs); + if (!can_destroy) { + atomic_store(&color_ctx->deleting, false); + } + portEXIT_CRITICAL(&color_ctx->spinlock); + ESP_RETURN_ON_FALSE(can_destroy, + ESP_ERR_INVALID_STATE, TAG, "pending transactions exist"); + return async_color_convert_dma2d_destroy(color_ctx); +} + +esp_err_t esp_async_color_convert_install_dma2d(const async_color_convert_config_t *config, + async_color_convert_handle_t *ret_hdl) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(config && ret_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + uint32_t trans_queue_len = config->backlog ? config->backlog : DEFAULT_COLOR_CONVERT_BACKLOG; + + async_color_convert_dma2d_context_t *ctx = heap_caps_calloc(1, sizeof(async_color_convert_dma2d_context_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_RETURN_ON_FALSE(ctx, ESP_ERR_NO_MEM, TAG, "no mem for color convert context"); + + ctx->trans_pool = heap_caps_calloc(trans_queue_len, sizeof(async_color_convert_transaction_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(ctx->trans_pool, ESP_ERR_NO_MEM, err, TAG, "no mem for transaction pool"); + + esp_cache_get_alignment(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA, &ctx->desc_alloc_size); + if (ctx->desc_alloc_size < sizeof(dma2d_descriptor_t)) { + ctx->desc_alloc_size = sizeof(dma2d_descriptor_t); + } + + ctx->num_trans_objs = trans_queue_len; + ctx->dma_burst_size = config->dma_burst_size ? config->dma_burst_size : 32; + portMUX_INITIALIZE(&ctx->spinlock); + STAILQ_INIT(&ctx->idle_queue); + atomic_init(&ctx->idle_num, trans_queue_len); + atomic_init(&ctx->deleting, false); + + dma2d_pool_config_t pool_cfg = { + .pool_id = 0, + .intr_priority = config->intr_priority, + }; + ESP_GOTO_ON_ERROR(dma2d_acquire_pool(&pool_cfg, &ctx->pool), err, TAG, "acquire dma2d pool failed"); + + for (uint32_t i = 0; i < trans_queue_len; i++) { + async_color_convert_transaction_t *trans = &ctx->trans_pool[i]; + trans->ctx = ctx; + // one DMA descriptor is enough for one color-conversion job since + // the driver will configure the descriptors in single-block mode and won't split the block into multiple tiles + trans->tx_desc = heap_caps_aligned_calloc(DMA2D_LL_DESC_ALIGNMENT, 1, ctx->desc_alloc_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(trans->tx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for tx descriptor"); + trans->rx_desc = heap_caps_aligned_calloc(DMA2D_LL_DESC_ALIGNMENT, 1, ctx->desc_alloc_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(trans->rx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for rx descriptor"); + trans->dma2d_trans_placeholder = heap_caps_calloc(1, dma2d_get_trans_elm_size(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(trans->dma2d_trans_placeholder, ESP_ERR_NO_MEM, err, TAG, "no memory for dma2d transaction placeholder"); + // dma2d_enqueue requires a long-lived transaction config, so we prepare it here with the common part configured. + // The per-request specific part will be filled in on job picked. + trans->dma2d_trans_config = (dma2d_trans_config_t) { + .tx_channel_num = 1, + .rx_channel_num = 1, + .channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING, + .specified_tx_channel_mask = 0, + .specified_rx_channel_mask = 0, + .on_job_picked = async_color_convert_on_job_picked, + .user_config = trans, + }; + STAILQ_INSERT_TAIL(&ctx->idle_queue, trans, queue_entry); + } + + ctx->parent.convert = async_color_convert_dma2d_convert; + ctx->parent.del = async_color_convert_dma2d_del; + + *ret_hdl = &ctx->parent; + return ESP_OK; + +err: + async_color_convert_dma2d_destroy(ctx); + return ret; +} diff --git a/components/esp_driver_dma/src/esp_async_color_convert.c b/components/esp_driver_dma/src/esp_async_color_convert.c new file mode 100644 index 00000000000..383e5fb5ad6 --- /dev/null +++ b/components/esp_driver_dma/src/esp_async_color_convert.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_async_color_convert.h" +#include "esp_async_color_convert_priv.h" + +ESP_LOG_ATTR_TAG(TAG, "async_color_conv"); + +esp_err_t esp_async_color_convert_uninstall(async_color_convert_handle_t conv_hdl) +{ + ESP_RETURN_ON_FALSE(conv_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return conv_hdl->del(conv_hdl); +} + +esp_err_t esp_async_color_convert(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args) +{ + ESP_RETURN_ON_FALSE(conv_hdl && request, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return conv_hdl->convert(conv_hdl, request, cb_isr, cb_args); +} + +typedef struct { + SemaphoreHandle_t done_sem; + StaticSemaphore_t done_sem_buffer; +} color_convert_blocking_context_t; + +static bool color_convert_blocking_cb(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args) +{ + BaseType_t high_task_woken = pdFALSE; + color_convert_blocking_context_t *ctx = (color_convert_blocking_context_t *)cb_args; + (void)conv_hdl; + (void)edata; + xSemaphoreGiveFromISR(ctx->done_sem, &high_task_woken); + return (high_task_woken == pdTRUE); +} + +esp_err_t esp_color_convert_blocking(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + int32_t timeout_ms) +{ + ESP_RETURN_ON_FALSE(conv_hdl && request, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(!xPortInIsrContext(), ESP_ERR_INVALID_STATE, TAG, "called from ISR context is not allowed"); + ESP_RETURN_ON_FALSE(timeout_ms == -1, ESP_ERR_INVALID_ARG, TAG, "only timeout -1 is supported"); + + color_convert_blocking_context_t ctx = {}; + ctx.done_sem = xSemaphoreCreateBinaryStatic(&ctx.done_sem_buffer); + + ESP_RETURN_ON_ERROR(esp_async_color_convert(conv_hdl, request, color_convert_blocking_cb, &ctx), TAG, "fail to start async color conversion"); + // Wait for the conversion to complete + xSemaphoreTake(ctx.done_sem, portMAX_DELAY); + return ESP_OK; +} diff --git a/components/esp_driver_dma/src/esp_async_color_convert_priv.h b/components/esp_driver_dma/src/esp_async_color_convert_priv.h new file mode 100644 index 00000000000..9fccdf930a1 --- /dev/null +++ b/components/esp_driver_dma/src/esp_async_color_convert_priv.h @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_private/dma2d.h" +#include "esp_async_color_convert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEFAULT_COLOR_CONVERT_BACKLOG 8 + +typedef struct async_color_convert_context_t async_color_convert_context_t; + +struct async_color_convert_context_t { + esp_err_t (*convert)(async_color_convert_context_t *ctx, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args); + esp_err_t (*del)(async_color_convert_context_t *ctx); +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt b/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt index bd6d24b9d8f..45b05834453 100644 --- a/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt +++ b/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt @@ -1,5 +1,6 @@ set(srcs "test_app_main.c" "test_dma2d.c" + "test_async_color_convert.c" "dma2d_test_utils.c") # In order for the cases defined by `TEST_CASE` to be linked into the final elf, diff --git a/components/esp_driver_dma/test_apps/dma2d/main/test_async_color_convert.c b/components/esp_driver_dma/test_apps/dma2d/main/test_async_color_convert.c new file mode 100644 index 00000000000..ee2c500aa6f --- /dev/null +++ b/components/esp_driver_dma/test_apps/dma2d/main/test_async_color_convert.c @@ -0,0 +1,379 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "soc/soc_caps.h" +#include "esp_heap_caps.h" +#include "hal/color_types.h" +#include "hal/color_hal.h" +#include "esp_async_color_convert.h" + +typedef struct { + SemaphoreHandle_t sem; + int cb_called; +} async_color_convert_user_ctx_t; + +static void fill_pattern(uint8_t *buf, size_t len, uint8_t seed) +{ + for (size_t i = 0; i < len; i++) { + buf[i] = (uint8_t)(seed + i * 13); + } +} + +static bool test_async_color_convert_cb(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args) +{ + (void)conv_hdl; + (void)edata; + async_color_convert_user_ctx_t *user_ctx = (async_color_convert_user_ctx_t *)cb_args; + user_ctx->cb_called++; + BaseType_t high_task_wakeup = pdFALSE; + xSemaphoreGiveFromISR(user_ctx->sem, &high_task_wakeup); + return (high_task_wakeup == pdTRUE); +} + +TEST_CASE("async color convert basic callback", "[async_color_convert]") +{ + const uint32_t width = 32; + const uint32_t height = 24; + const uint32_t pixel_num = width * height; + + uint16_t *src565 = heap_caps_aligned_calloc(64, pixel_num, sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst888 = heap_caps_aligned_calloc(64, pixel_num, 3, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src565); + TEST_ASSERT_NOT_NULL(dst888); + + for (uint32_t i = 0; i < pixel_num; i++) { + src565[i] = (uint16_t)((i * 13) ^ 0x5AA5); + } + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req = { + .src_buffer = src565, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst888, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_RGB16, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + + async_color_convert_user_ctx_t user_ctx = { + .sem = xSemaphoreCreateBinary(), + .cb_called = 0, + }; + TEST_ASSERT_NOT_NULL(user_ctx.sem); + + TEST_ESP_OK(esp_async_color_convert(conv_hdl, &req, test_async_color_convert_cb, &user_ctx)); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(user_ctx.sem, pdMS_TO_TICKS(200))); + TEST_ASSERT_EQUAL(1, user_ctx.cb_called); + + vSemaphoreDelete(user_ctx.sem); + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + + free(src565); + free(dst888); +} + +TEST_CASE("async color convert roundtrip: RGB565<->RGB888", "[async_color_convert]") +{ + const uint32_t width = 32; + const uint32_t height = 20; + const uint32_t pixel_num = width * height; + + uint16_t *src565 = heap_caps_aligned_calloc(64, pixel_num, sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *mid888 = heap_caps_aligned_calloc(64, pixel_num, 3, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint16_t *dst565 = heap_caps_aligned_calloc(64, pixel_num, sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src565); + TEST_ASSERT_NOT_NULL(mid888); + TEST_ASSERT_NOT_NULL(dst565); + + for (uint32_t i = 0; i < pixel_num; i++) { + src565[i] = (uint16_t)((i * 37) ^ 0xA55A); + } + + async_color_convert_config_t config = { + .backlog = 4, + .intr_priority = 0, + .dma_burst_size = 32, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req_565_to_888 = { + .src_buffer = src565, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = mid888, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_RGB16, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + + async_color_convert_request_t req_888_to_565 = { + .src_buffer = mid888, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst565, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_BGR24, + .dst_color_format = ESP_COLOR_FOURCC_RGB16, + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_565_to_888, -1)); + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_888_to_565, -1)); + + // The final dst565 should be the same as the original src565 after round-trip conversion + TEST_ASSERT_EQUAL_MEMORY(src565, dst565, pixel_num * sizeof(uint16_t)); + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + + free(src565); + free(mid888); + free(dst565); +} + +TEST_CASE("async color convert bypasses color convert for 2D copy", "[async_color_convert]") +{ + const uint32_t src_stride = 48; + const uint32_t src_height = 28; + const uint32_t dst_stride = 64; + const uint32_t dst_height = 30; + const uint32_t copy_width = 32; + const uint32_t copy_height = 18; + const uint32_t src_x = 5; + const uint32_t src_y = 4; + const uint32_t dst_x = 7; + const uint32_t dst_y = 6; + const esp_color_fourcc_t fourcc = ESP_COLOR_FOURCC_RGB16; + const size_t bytes_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(fourcc) / 8; + const size_t src_size = (size_t)src_stride * src_height * bytes_per_pixel; + const size_t dst_size = (size_t)dst_stride * dst_height * bytes_per_pixel; + const size_t row_size = (size_t)copy_width * bytes_per_pixel; + + uint8_t *src = heap_caps_aligned_calloc(64, 1, src_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *expected = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src); + TEST_ASSERT_NOT_NULL(dst); + TEST_ASSERT_NOT_NULL(expected); + + fill_pattern(src, src_size, 0x3C); + memset(dst, 0xA5, dst_size); + memset(expected, 0xA5, dst_size); + + for (uint32_t row = 0; row < copy_height; row++) { + const size_t src_offset = ((size_t)(src_y + row) * src_stride + src_x) * bytes_per_pixel; + const size_t dst_offset = ((size_t)(dst_y + row) * dst_stride + dst_x) * bytes_per_pixel; + memcpy(expected + dst_offset, src + src_offset, row_size); + } + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req = { + .src_buffer = src, + .src_stride = src_stride, + .src_height = src_height, + .src_x = src_x, + .src_y = src_y, + .dst_buffer = dst, + .dst_stride = dst_stride, + .dst_height = dst_height, + .dst_x = dst_x, + .dst_y = dst_y, + .copy_width = copy_width, + .copy_height = copy_height, + .src_color_format = fourcc, + .dst_color_format = fourcc, + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req, -1)); + TEST_ASSERT_EQUAL_MEMORY(expected, dst, dst_size); + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + + free(src); + free(dst); + free(expected); +} + +static uint8_t clamp_to_u8(int value) +{ + if (value < 0) { + return 0; + } + if (value > 255) { + return 255; + } + return (uint8_t)value; +} + +static void uyvy_to_bgr24_reference_pixel(uint8_t y, uint8_t u, uint8_t v, + color_conv_std_rgb_yuv_t color_conv_std, + uint8_t *out_bgr) +{ + static const int bt601[3][4] = { + { 298, 0, 409, -56906 }, + { 298, -100, -208, 34707 }, + { 298, 516, 0, -70836 }, + }; + static const int bt709[3][4] = { + { 298, 0, 459, -63367 }, + { 298, -55, -136, 19681 }, + { 298, 541, 0, -73918 }, + }; + + const int (*coeff)[4] = (color_conv_std == COLOR_CONV_STD_RGB_YUV_BT709) ? bt709 : bt601; + int r = (coeff[0][0] * y + coeff[0][1] * u + coeff[0][2] * v + coeff[0][3] + 128) >> 8; + int g = (coeff[1][0] * y + coeff[1][1] * u + coeff[1][2] * v + coeff[1][3] + 128) >> 8; + int b = (coeff[2][0] * y + coeff[2][1] * u + coeff[2][2] * v + coeff[2][3] + 128) >> 8; + out_bgr[0] = clamp_to_u8(b); + out_bgr[1] = clamp_to_u8(g); + out_bgr[2] = clamp_to_u8(r); +} + +static void uyvy_to_bgr24_reference_image(const uint8_t *src_uyvy, uint8_t *dst_bgr24, + uint32_t src_stride, uint32_t dst_stride, + uint32_t copy_width, uint32_t copy_height, + color_conv_std_rgb_yuv_t color_conv_std) +{ + TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, copy_width % 2, "UYVY width must be even"); + + for (uint32_t y = 0; y < copy_height; y++) { + for (uint32_t x = 0; x < copy_width; x += 2) { + size_t src_idx = ((size_t)y * src_stride + x) * 2; + size_t dst_idx0 = ((size_t)y * dst_stride + x) * 3; + size_t dst_idx1 = ((size_t)y * dst_stride + x + 1) * 3; + uint8_t u = src_uyvy[src_idx + 0]; + uint8_t y0 = src_uyvy[src_idx + 1]; + uint8_t v = src_uyvy[src_idx + 2]; + uint8_t y1 = src_uyvy[src_idx + 3]; + + uyvy_to_bgr24_reference_pixel(y0, u, v, color_conv_std, &dst_bgr24[dst_idx0]); + uyvy_to_bgr24_reference_pixel(y1, u, v, color_conv_std, &dst_bgr24[dst_idx1]); + } + } +} + +TEST_CASE("async color convert UYVY->RGB888 matches reference", "[async_color_convert]") +{ + const uint32_t src_stride = 32; + const uint32_t dst_stride = 64; + const uint32_t height = 2; + const uint32_t copy_width = 6; + const uint32_t copy_height = 2; + const size_t src_size = src_stride * height * 2; + const size_t dst_size = dst_stride * height * 3; + static const uint8_t sample_uyvy[] = { + 128, 16, 128, 235, 90, 81, 240, 145, 240, 200, 16, 54, + 54, 32, 200, 210, 180, 100, 90, 180, 16, 235, 240, 16, + }; + TEST_ASSERT_EQUAL(sizeof(sample_uyvy), copy_width * copy_height * 2); + + uint8_t *src = heap_caps_aligned_calloc(64, 1, src_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *expected = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src); + TEST_ASSERT_NOT_NULL(dst); + TEST_ASSERT_NOT_NULL(expected); + for (uint32_t row = 0; row < copy_height; row++) { + memcpy(src + row * src_stride * 2, sample_uyvy + row * copy_width * 2, copy_width * 2); + } + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + const color_conv_std_rgb_yuv_t conv_stds[] = { + COLOR_CONV_STD_RGB_YUV_BT601, + COLOR_CONV_STD_RGB_YUV_BT709, + }; + for (size_t i = 0; i < sizeof(conv_stds) / sizeof(conv_stds[0]); i++) { + memset(dst, 0xA5, dst_size); + memset(expected, 0xA5, dst_size); + uyvy_to_bgr24_reference_image(src, expected, src_stride, dst_stride, copy_width, copy_height, conv_stds[i]); + + async_color_convert_request_t req = { + .src_buffer = src, + .src_stride = src_stride, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst, + .dst_stride = dst_stride, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = copy_width, + .copy_height = copy_height, + .src_color_format = ESP_COLOR_FOURCC_UYVY, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + .color_conv_std = conv_stds[i], + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req, -1)); + TEST_ASSERT_EQUAL_MEMORY(expected, dst, dst_size); + } + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + free(src); + free(dst); + free(expected); +} diff --git a/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h b/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h index a524173ff42..f96263f8e61 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h @@ -88,6 +88,7 @@ #define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3) #define DMA2D_LL_DESC_ALIGNMENT 8 // Descriptor must be aligned to 8 bytes +#define DMA2D_LL_DESC_2D_FIELD_MAX 0x3FFFU // 2D descriptor width/height/coordinate fields are 14-bit #ifdef __cplusplus extern "C" { diff --git a/components/esp_hal_dma/esp32s31/include/hal/dma2d_ll.h b/components/esp_hal_dma/esp32s31/include/hal/dma2d_ll.h index 04acb948bb2..27970ecf257 100644 --- a/components/esp_hal_dma/esp32s31/include/hal/dma2d_ll.h +++ b/components/esp_hal_dma/esp32s31/include/hal/dma2d_ll.h @@ -78,6 +78,7 @@ #define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3) #define DMA2D_LL_DESC_ALIGNMENT 8 // Descriptor must be aligned to 8 bytes +#define DMA2D_LL_DESC_2D_FIELD_MAX 0x3FFFU // 2D descriptor width/height/coordinate fields are 14-bit #ifdef __cplusplus extern "C" {