Merge branch 'feat/async_color_converter_v6.0' into 'release/v6.0'

feat(esp_driver_dma): add async color convert driver (v6.0)

See merge request espressif/esp-idf!49748
This commit is contained in:
morris
2026-07-01 10:25:10 +08:00
27 changed files with 2379 additions and 361 deletions
+1 -1
View File
@@ -35,7 +35,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}
@@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#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
@@ -0,0 +1,507 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include <sys/queue.h>
#include <inttypes.h>
#include <assert.h>
#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 TX CSC configuration resolved in task context
dma2d_csc_config_t rx_csc; // Cached DMA2D RX 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 is_rgb24_or_bgr24_fourcc(esp_color_fourcc_t fourcc)
{
return fourcc == ESP_COLOR_FOURCC_BGR24 || fourcc == ESP_COLOR_FOURCC_RGB24;
}
static dma2d_csc_config_t default_tx_csc_config(void)
{
return (dma2d_csc_config_t) {
.tx_csc_option = DMA2D_CSC_TX_NONE,
.pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0,
.post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0,
};
}
static dma2d_csc_config_t default_rx_csc_config(void)
{
return (dma2d_csc_config_t) {
.rx_csc_option = DMA2D_CSC_RX_NONE,
.pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0,
.post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0,
};
}
static bool resolve_dma2d_csc_configs(const async_color_convert_request_t *request,
dma2d_csc_config_t *out_tx_csc,
dma2d_csc_config_t *out_rx_csc)
{
esp_color_fourcc_t src_fourcc = request->src_color_format;
esp_color_fourcc_t dst_fourcc = request->dst_color_format;
bool src_is_rgb24_or_bgr24 = is_rgb24_or_bgr24_fourcc(src_fourcc);
bool dst_is_rgb24_or_bgr24 = is_rgb24_or_bgr24_fourcc(dst_fourcc);
*out_tx_csc = default_tx_csc_config();
*out_rx_csc = default_rx_csc_config();
if (src_fourcc == dst_fourcc) {
return true;
}
if (src_is_rgb24_or_bgr24 && dst_is_rgb24_or_bgr24) {
out_tx_csc->tx_csc_option = DMA2D_CSC_TX_SCRAMBLE; // RGB<->BGR conversion is just a scramble operation
out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE0_1_2;
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;
return true;
}
if (src_is_rgb24_or_bgr24 && dst_fourcc == ESP_COLOR_FOURCC_RGB16) {
out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_RGB565;
if (src_fourcc == ESP_COLOR_FOURCC_RGB24) {
out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE0_1_2;
}
return true;
}
if (src_is_rgb24_or_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;
}
if (src_fourcc == ESP_COLOR_FOURCC_RGB24) {
out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE0_1_2;
}
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;
}
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 inline bool needs_rx_csc(const dma2d_csc_config_t *rx_csc)
{
return rx_csc->rx_csc_option != DMA2D_CSC_RX_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);
dma2d_configure_color_space_conversion(tx_chan, &trans->tx_csc);
dma2d_configure_color_space_conversion(rx_chan, &trans->rx_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 = default_tx_csc_config();
trans->rx_csc = default_rx_csc_config();
ESP_GOTO_ON_FALSE(resolve_dma2d_csc_configs(request, &trans->tx_csc, &trans->rx_csc),
ESP_ERR_INVALID_ARG, recycle_and_out, TAG, "unsupported color conversion mode");
trans->dma2d_trans_config.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING;
if (needs_tx_csc(&trans->tx_csc)) {
trans->dma2d_trans_config.channel_flags |= DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC;
}
if (needs_rx_csc(&trans->rx_csc)) {
trans->dma2d_trans_config.channel_flags |= DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC;
}
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 | ESP_CACHE_MSYNC_FLAG_UNALIGNED),
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;
}
@@ -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;
}
@@ -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
@@ -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,
@@ -0,0 +1,561 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdint.h>
#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 *dst_bgr24 = 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(dst_bgr24);
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 = dst_bgr24,
.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(dst_bgr24);
}
TEST_CASE("async color convert roundtrip: RGB16<->BGR24", "[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 *mid_bgr24 = 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(mid_bgr24);
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_bgr24 = {
.src_buffer = src565,
.src_stride = width,
.src_height = height,
.src_x = 0,
.src_y = 0,
.dst_buffer = mid_bgr24,
.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_bgr24_to_565 = {
.src_buffer = mid_bgr24,
.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_bgr24, -1));
TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_bgr24_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(mid_bgr24);
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 swaps RGB24 and BGR24 byte order", "[async_color_convert]")
{
const uint32_t width = 4;
const uint32_t height = 2;
const size_t pixel_count = width * height;
const size_t buf_size = pixel_count * 3;
static const uint8_t src_rgb24[] = {
0x10, 0x20, 0x30, 0x7F, 0x80, 0x81, 0xAA, 0x55, 0xFE, 0x01, 0xC0, 0x99,
0xDE, 0xAD, 0xBE, 0x00, 0x11, 0x22, 0x44, 0x88, 0xCC, 0xF0, 0x0D, 0x42,
};
static const uint8_t src_bgr24[] = {
0x30, 0x20, 0x10, 0x81, 0x80, 0x7F, 0xFE, 0x55, 0xAA, 0x99, 0xC0, 0x01,
0xBE, 0xAD, 0xDE, 0x22, 0x11, 0x00, 0xCC, 0x88, 0x44, 0x42, 0x0D, 0xF0,
};
TEST_ASSERT_EQUAL(sizeof(src_rgb24), buf_size);
TEST_ASSERT_EQUAL(sizeof(src_bgr24), buf_size);
uint8_t *rgb24 = heap_caps_aligned_calloc(64, 1, buf_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
uint8_t *bgr24 = heap_caps_aligned_calloc(64, 1, buf_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
uint8_t *dst_bgr24 = heap_caps_aligned_calloc(64, 1, buf_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
uint8_t *dst_rgb24 = heap_caps_aligned_calloc(64, 1, buf_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(rgb24);
TEST_ASSERT_NOT_NULL(bgr24);
TEST_ASSERT_NOT_NULL(dst_bgr24);
TEST_ASSERT_NOT_NULL(dst_rgb24);
memcpy(rgb24, src_rgb24, buf_size);
memcpy(bgr24, src_bgr24, buf_size);
memset(dst_bgr24, 0xA5, buf_size);
memset(dst_rgb24, 0x5A, buf_size);
async_color_convert_config_t config = {
.backlog = 1,
.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_rgb_to_bgr = {
.src_buffer = rgb24,
.src_stride = width,
.src_height = height,
.src_x = 0,
.src_y = 0,
.dst_buffer = dst_bgr24,
.dst_stride = width,
.dst_height = height,
.dst_x = 0,
.dst_y = 0,
.copy_width = width,
.copy_height = height,
.src_color_format = ESP_COLOR_FOURCC_RGB24,
.dst_color_format = ESP_COLOR_FOURCC_BGR24,
};
async_color_convert_request_t req_bgr_to_rgb = {
.src_buffer = bgr24,
.src_stride = width,
.src_height = height,
.src_x = 0,
.src_y = 0,
.dst_buffer = dst_rgb24,
.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_RGB24,
};
TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_rgb_to_bgr, -1));
TEST_ASSERT_EQUAL_MEMORY(src_bgr24, dst_bgr24, buf_size);
TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_bgr_to_rgb, -1));
TEST_ASSERT_EQUAL_MEMORY(src_rgb24, dst_rgb24, buf_size);
TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl));
free(rgb24);
free(bgr24);
free(dst_bgr24);
free(dst_rgb24);
}
// Verifies the scramble route and BGR24/RGB24->UYVY conversion compose correctly.
TEST_CASE("async color convert RGB24 and BGR24 inputs produce identical UYVY output", "[async_color_convert]")
{
const uint32_t width = 4;
const uint32_t height = 2;
const size_t pixel_count = width * height;
const size_t rgb_size = pixel_count * 3;
const size_t uyvy_size = pixel_count * 2;
static const uint8_t src_rgb24[] = {
0x10, 0x20, 0x30, 0x7F, 0x80, 0x81, 0xAA, 0x55, 0xFE, 0x01, 0xC0, 0x99,
0xDE, 0xAD, 0xBE, 0x00, 0x11, 0x22, 0x44, 0x88, 0xCC, 0xF0, 0x0D, 0x42,
};
static const uint8_t src_bgr24[] = {
0x30, 0x20, 0x10, 0x81, 0x80, 0x7F, 0xFE, 0x55, 0xAA, 0x99, 0xC0, 0x01,
0xBE, 0xAD, 0xDE, 0x22, 0x11, 0x00, 0xCC, 0x88, 0x44, 0x42, 0x0D, 0xF0,
};
const color_conv_std_rgb_yuv_t conv_std = COLOR_CONV_STD_RGB_YUV_BT601;
TEST_ASSERT_EQUAL(sizeof(src_rgb24), rgb_size);
TEST_ASSERT_EQUAL(sizeof(src_bgr24), rgb_size);
uint8_t *rgb24 = heap_caps_aligned_calloc(64, 1, rgb_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
uint8_t *bgr24 = heap_caps_aligned_calloc(64, 1, rgb_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
uint8_t *dst_from_rgb24 = heap_caps_aligned_calloc(64, 1, uyvy_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
uint8_t *dst_from_bgr24 = heap_caps_aligned_calloc(64, 1, uyvy_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(rgb24);
TEST_ASSERT_NOT_NULL(bgr24);
TEST_ASSERT_NOT_NULL(dst_from_rgb24);
TEST_ASSERT_NOT_NULL(dst_from_bgr24);
memcpy(rgb24, src_rgb24, rgb_size);
memcpy(bgr24, src_bgr24, rgb_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));
memset(dst_from_rgb24, 0xA5, uyvy_size);
memset(dst_from_bgr24, 0x5A, uyvy_size);
async_color_convert_request_t req_rgb24_to_uyvy = {
.src_buffer = rgb24,
.src_stride = width,
.src_height = height,
.src_x = 0,
.src_y = 0,
.dst_buffer = dst_from_rgb24,
.dst_stride = width,
.dst_height = height,
.dst_x = 0,
.dst_y = 0,
.copy_width = width,
.copy_height = height,
.src_color_format = ESP_COLOR_FOURCC_RGB24,
.dst_color_format = ESP_COLOR_FOURCC_UYVY,
.color_conv_std = conv_std,
};
async_color_convert_request_t req_bgr24_to_uyvy = {
.src_buffer = bgr24,
.src_stride = width,
.src_height = height,
.src_x = 0,
.src_y = 0,
.dst_buffer = dst_from_bgr24,
.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_UYVY,
.color_conv_std = conv_std,
};
TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_rgb24_to_uyvy, -1));
TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_bgr24_to_uyvy, -1));
TEST_ASSERT_EQUAL_MEMORY(dst_from_bgr24, dst_from_rgb24, uyvy_size);
TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl));
free(rgb24);
free(bgr24);
free(dst_from_rgb24);
free(dst_from_bgr24);
}
TEST_CASE("async color convert UYVY->BGR24 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);
}
@@ -91,6 +91,7 @@ extern "C" {
#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
///////////////////////////////////// Common /////////////////////////////////////////
/**
@@ -1008,7 +1009,7 @@ static inline void dma2d_ll_tx_configure_color_space_conv(dma2d_dev_t *dev, uint
input_sel = 7;
break;
case DMA2D_CSC_TX_SCRAMBLE:
input_sel = 2; // Or 3
input_sel = 3; // Other 3-byte/pixel input path
proc_en = false;
output_sel = 2;
break;
-4
View File
@@ -13,10 +13,6 @@ set(includes "include" "interface")
set(priv_requires "esp_mm" "esp_psram" "esp_pm" "esp_driver_i2s" "esp_driver_dma")
set(public_requires "esp_driver_gpio" "esp_driver_i2c" "esp_driver_spi" "esp_driver_parlio" "esp_hal_lcd")
if(CONFIG_SOC_DMA2D_SUPPORTED)
list(APPEND srcs "src/esp_async_fbcpy.c")
endif()
if(CONFIG_SOC_I2C_SUPPORTED)
list(APPEND srcs "i2c/esp_lcd_panel_io_i2c.c")
endif()
+38 -46
View File
@@ -6,11 +6,11 @@
#include <sys/param.h>
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_async_color_convert.h"
#include "esp_intr_alloc.h"
#include "esp_clk_tree.h"
#include "esp_cache.h"
#include "mipi_dsi_priv.h"
#include "esp_async_fbcpy.h"
#include "esp_memory_utils.h"
#include "esp_private/dw_gdma.h"
#include "hal/color_hal.h"
@@ -44,8 +44,7 @@ struct esp_lcd_dpi_panel_t {
esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function
void* hook_ctx; // Hook context
bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation
esp_async_fbcpy_handle_t fbcpy_handle; // Use DMA2D to do frame buffer copy (only when using DMA2D draw bitmap hook)
SemaphoreHandle_t draw_sem; // A semaphore used to synchronize the draw operations when DMA2D is used
async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy
#if CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock; // Power management lock
@@ -65,24 +64,18 @@ static bool dpi_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel)
return false;
}
static bool async_fbcpy_done_cb(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event, void *cb_args)
static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args)
{
bool need_yield = false;
esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)cb_args;
// release the draw semaphore first
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(dpi_panel->draw_sem, &task_woken);
if (task_woken == pdTRUE) {
need_yield = true;
}
(void)conv_hdl;
(void)event;
if (dpi_panel->on_hook_end) {
if (dpi_panel->on_hook_end(&dpi_panel->base)) {
need_yield = true;
}
}
return need_yield;
}
@@ -475,28 +468,31 @@ static esp_err_t dpi_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const
{
ESP_LOGV(TAG, "copy draw buffer by DMA2D");
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
// ensure the previous draw operation is finished
ESP_RETURN_ON_FALSE(xSemaphoreTake(dpi_panel->draw_sem, 0) == pdTRUE, ESP_ERR_INVALID_STATE,
TAG, "previous draw operation is not finished");
(void)hook_ctx;
esp_async_fbcpy_trans_desc_t fbcpy_trans_config = {
async_color_convert_request_t fbcpy_trans_config = {
.src_buffer = hook_data->src_data,
.dst_buffer = hook_data->dst_data,
.src_buffer_size_x = hook_data->src_x_size,
.src_buffer_size_y = hook_data->src_y_size,
.dst_buffer_size_x = hook_data->dst_x_size,
.dst_buffer_size_y = hook_data->dst_y_size,
.src_offset_x = hook_data->src_x_start,
.src_offset_y = hook_data->src_y_start,
.dst_offset_x = hook_data->dst_x_start,
.dst_offset_y = hook_data->dst_y_start,
.copy_size_x = hook_data->src_x_end - hook_data->src_x_start,
.copy_size_y = hook_data->src_y_end - hook_data->src_y_start,
.pixel_format_fourcc_id = dpi_panel->in_color_format,
.src_stride = hook_data->src_x_size,
.src_height = hook_data->src_y_size,
.dst_stride = hook_data->dst_x_size,
.dst_height = hook_data->dst_y_size,
.src_x = hook_data->src_x_start,
.src_y = hook_data->src_y_start,
.dst_x = hook_data->dst_x_start,
.dst_y = hook_data->dst_y_start,
.copy_width = hook_data->src_x_end - hook_data->src_x_start,
.copy_height = hook_data->src_y_end - hook_data->src_y_start,
// For this DMA2D hook we only do window copy from draw buffer to frame buffer.
// Source and destination color formats are intentionally set to the same value to disable CSC.
.src_color_format = dpi_panel->in_color_format,
.dst_color_format = dpi_panel->in_color_format,
};
// save the on_hook_end callback, and invoke it when the async memcpy is done
// The async color convert backend owns source/destination cache sync for the
// DMA2D copy path, so the LCD driver should not perform extra cache sync here.
// Save the completion callback and invoke it when the async frame buffer copy finishes.
dpi_panel->on_hook_end = hook_data->on_hook_end;
ESP_RETURN_ON_ERROR(esp_async_fbcpy(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async memcpy failed");
ESP_RETURN_ON_ERROR(esp_async_color_convert(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async frame buffer copy failed");
return ESP_OK;
}
@@ -521,13 +517,13 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel)
// Check if built-in DMA2D draw bitmap hook is registered
ESP_RETURN_ON_FALSE(!dpi_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook is already registered");
// Initialize DMA2D resources
esp_async_fbcpy_config_t fbcpy_config = {};
ESP_RETURN_ON_ERROR(esp_async_fbcpy_install(&fbcpy_config, &dpi_panel->fbcpy_handle), TAG, "install async memcpy 2d failed");
// Initialize the async color convert backend used by the built-in DMA2D copy hook.
// Use its default backlog to queue multiple frame buffer copy requests.
async_color_convert_config_t fbcpy_config = {
.dma_burst_size = 128, // for better performance
};
ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &dpi_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed");
dpi_panel->draw_sem = xSemaphoreCreateBinaryWithCaps(DSI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dpi_panel->draw_sem, ESP_ERR_NO_MEM, err, TAG, "no memory for draw semaphore");
xSemaphoreGive(dpi_panel->draw_sem);
// Register the DMA2D draw bitmap hook
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook,
@@ -538,13 +534,10 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel)
err:
if (dpi_panel->fbcpy_handle) {
esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle);
esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle);
dpi_panel->fbcpy_handle = NULL;
}
if (dpi_panel->draw_sem) {
vSemaphoreDeleteWithCaps(dpi_panel->draw_sem);
dpi_panel->draw_sem = NULL;
}
dpi_panel->on_hook_end = NULL;
return ret;
}
@@ -561,13 +554,10 @@ esp_err_t esp_lcd_dpi_panel_disable_dma2d(esp_lcd_panel_handle_t panel)
};
ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed");
if (dpi_panel->fbcpy_handle) {
ESP_RETURN_ON_ERROR(esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed");
ESP_RETURN_ON_ERROR(esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed");
dpi_panel->fbcpy_handle = NULL;
}
if (dpi_panel->draw_sem) {
vSemaphoreDeleteWithCaps(dpi_panel->draw_sem);
dpi_panel->draw_sem = NULL;
}
dpi_panel->on_hook_end = NULL;
return ESP_OK;
}
@@ -633,7 +623,9 @@ static esp_err_t dpi_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, i
}
} else if (dpi_panel->draw_bitmap_hook) { // copy using draw bitmap hook
ESP_LOGV(TAG, "copy draw buffer by draw bitmap hook");
// Note, whether the previous draw operation is finished should be ensured by the hook
// Note, whether the previous draw operation is finished should be ensured by the hook.
// For the built-in DMA2D hook, cache maintenance of the source and destination
// buffers is handled inside the async color convert driver.
esp_lcd_draw_bitmap_hook_data_t hook_data = {
.dst_data = frame_buffer,
@@ -1,90 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "hal/color_types.h"
/**
* @brief Async FrameBuffer copy context
*/
typedef struct esp_async_fbcpy_context_t *esp_async_fbcpy_handle_t;
/**
* @brief Async FrameBuffer copy configuration
*/
typedef struct {
} esp_async_fbcpy_config_t;
/**
* @brief Install Async FrameBuffer copy driver
*
* @param config Async FrameBuffer copy configuration
* @param mcp Returned Async FrameBuffer copy handle
* @return
* - ESP_OK: Install Async FrameBuffer copy driver successfully
* - ESP_ERR_INVALID_ARG: Install Async FrameBuffer copy driver failed because of invalid argument
* - ESP_ERR_NO_MEM: Install Async FrameBuffer copy driver failed because of out of memory
* - ESP_FAIL: Install Async FrameBuffer copy driver failed because of other error
*/
esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp);
/**
* @brief Uninstall Async FrameBuffer copy driver
*
* @param mcp Async FrameBuffer copy handle
* @return
* - ESP_OK: Uninstall Async FrameBuffer copy driver successfully
* - ESP_ERR_INVALID_ARG: Uninstall Async FrameBuffer copy driver failed because of invalid argument
* - ESP_FAIL: Uninstall Async FrameBuffer copy driver failed because of other error
*/
esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp);
/**
* @brief Async FrameBuffer copy transaction descriptor
*/
typedef struct {
const void *src_buffer; /*!< Source buffer */
void *dst_buffer; /*!< Destination buffer */
size_t src_buffer_size_x; /*!< Source buffer size in x direction, size count in the number of pixels */
size_t src_buffer_size_y; /*!< Source buffer size in y direction, size count in the number of pixels */
size_t dst_buffer_size_x; /*!< Destination buffer size in x direction, size count in the number of pixels */
size_t dst_buffer_size_y; /*!< Destination buffer size in y direction, size count in the number of pixels */
size_t src_offset_x; /*!< Copy action will start from this offset in source buffer in the x direction, offset count in the number of pixels */
size_t src_offset_y; /*!< Copy action will start from this offset in source buffer in the y direction, offset count in the number of pixels */
size_t dst_offset_x; /*!< Copy action will start from this offset in destination buffer in the x direction, offset count in the number of pixels */
size_t dst_offset_y; /*!< Copy action will start from this offset in destination buffer in the y direction, offset count in the number of pixels */
size_t copy_size_x; /*!< Copy size in the x direction, size count in the number of pixels */
size_t copy_size_y; /*!< Copy size in the y direction, size count in the number of pixels */
esp_color_fourcc_t pixel_format_fourcc_id; /*!< Pixel format unique ID */
} esp_async_fbcpy_trans_desc_t;
/**
* @brief Async FrameBuffer copy event data
*/
typedef struct {
} esp_async_fbcpy_event_data_t;
/**
* @brief Async FrameBuffer copy event callback prototype
*/
typedef bool (*esp_async_fbcpy_event_callback_t)(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event_data, void *cb_args);
/**
* @brief Start Async FrameBuffer copy transaction
*
* @param mcp Async FrameBuffer copy handle
* @param transaction Async FrameBuffer copy transaction descriptor
* @param memcpy_done_cb Callback function that will be invoked when Async FrameBuffer copy transaction finishes
* @param cb_args User data
* @return
* - ESP_OK: Start Async FrameBuffer copy transaction successfully
* - ESP_ERR_INVALID_ARG: Start Async FrameBuffer copy transaction failed because of invalid argument
* - ESP_FAIL: Start Async FrameBuffer copy transaction failed because of other error
*/
esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction,
esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args);
-219
View File
@@ -1,219 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "soc/dma2d_channel.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#include "hal/dma2d_ll.h"
#include "esp_private/dma2d.h"
#include "esp_async_fbcpy.h"
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
static const char *TAG = "async_fbcpy";
typedef struct esp_async_fbcpy_context_t {
dma2d_pool_handle_t client; // DMA2D client
dma2d_descriptor_t* tx_desc; // DMA2D TX descriptor
dma2d_descriptor_t* rx_desc; // DMA2D RX descriptor
dma2d_trans_t* trans_desc; // DMA2D transaction descriptor
size_t dma_desc_size; // DMA2D descriptor size
esp_async_fbcpy_event_callback_t memcpy_done_cb; // memory copy done callback
void *cb_args; // callback arguments
} esp_async_fbcpy_context_t;
static esp_err_t async_fbcpy_del_context(esp_async_fbcpy_context_t* ctx)
{
if (ctx->tx_desc) {
free(ctx->tx_desc);
}
if (ctx->rx_desc) {
free(ctx->rx_desc);
}
if (ctx->trans_desc) {
free(ctx->trans_desc);
}
if (ctx->client) {
dma2d_release_pool(ctx->client);
}
free(ctx);
return ESP_OK;
}
esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp)
{
esp_err_t ret = ESP_OK;
esp_async_fbcpy_context_t *ctx = NULL;
dma2d_trans_t* trans_desc = NULL;
dma2d_descriptor_t* dma_tx_desc = NULL;
dma2d_descriptor_t* dma_rx_desc = NULL;
dma2d_pool_handle_t dma2d_client = NULL;
ESP_RETURN_ON_FALSE(config && mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// allocate context memory
ctx = heap_caps_calloc(1, sizeof(esp_async_fbcpy_context_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, err, TAG, "no mem for esp_async_fbcpy_context_t");
// according to the dma2d design, the transaction descriptor is also saved by the user
trans_desc = heap_caps_calloc(1, dma2d_get_trans_elm_size(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(trans_desc, ESP_ERR_NO_MEM, err, TAG, "no mem for trans_desc");
ctx->trans_desc = trans_desc;
// allocate memory for DMA descriptor, the descriptor must be allocated from the internal memory, and alignment to the cache line size
uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
size_t alignment = MAX(DMA2D_LL_DESC_ALIGNMENT, data_cache_line_size);
size_t dma_desc_mem_size = ALIGN_UP(sizeof(dma2d_descriptor_align8_t), alignment);
dma_tx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
dma_rx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(dma_tx_desc && dma_rx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for DMA2D descriptors");
ctx->tx_desc = dma_tx_desc;
ctx->rx_desc = dma_rx_desc;
ctx->dma_desc_size = dma_desc_mem_size;
// initialize DMA2D client
dma2d_pool_config_t dma2d_client_config = {}; // all follow default configurations
ESP_GOTO_ON_ERROR(dma2d_acquire_pool(&dma2d_client_config, &dma2d_client), err, TAG, "create DMA2D client failed");
ctx->client = dma2d_client;
*mcp = ctx;
return ESP_OK;
err:
if (ctx) {
async_fbcpy_del_context(ctx);
}
return ret;
}
esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp)
{
ESP_RETURN_ON_FALSE(mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return async_fbcpy_del_context(mcp);
}
static void async_memcpy_setup_dma2d_descriptor(esp_async_fbcpy_context_t* mcp_ctx, esp_async_fbcpy_trans_desc_t* transaction)
{
dma2d_descriptor_t* tx_desc = mcp_ctx->tx_desc;
dma2d_descriptor_t* rx_desc = mcp_ctx->rx_desc;
size_t dma_desc_size = mcp_ctx->dma_desc_size;
uint8_t dma2d_pbyte = dma2d_desc_pixel_format_to_pbyte_value(transaction->pixel_format_fourcc_id);
tx_desc->buffer = (void*)transaction->src_buffer;
tx_desc->next = NULL;
tx_desc->dma2d_en = 1;
tx_desc->suc_eof = 1;
tx_desc->ha_length = transaction->src_buffer_size_x;
tx_desc->va_size = transaction->src_buffer_size_y;
tx_desc->hb_length = transaction->copy_size_x;
tx_desc->vb_size = transaction->copy_size_y;
tx_desc->x = transaction->src_offset_x;
tx_desc->y = transaction->src_offset_y;
tx_desc->pbyte = dma2d_pbyte;
tx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE;
tx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA;
rx_desc->buffer = transaction->dst_buffer;
rx_desc->next = NULL;
rx_desc->dma2d_en = 1;
rx_desc->suc_eof = 1;
rx_desc->ha_length = transaction->dst_buffer_size_x;
rx_desc->va_size = transaction->dst_buffer_size_y;
rx_desc->hb_length = transaction->copy_size_x;
rx_desc->vb_size = transaction->copy_size_y;
rx_desc->x = transaction->dst_offset_x;
rx_desc->y = transaction->dst_offset_y;
rx_desc->pbyte = dma2d_pbyte;
rx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE;
rx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA;
esp_cache_msync(tx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
esp_cache_msync(rx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
}
static bool dma2d_memcpy_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data)
{
bool need_yield = false;
esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data;
if (mcp->memcpy_done_cb) {
need_yield = mcp->memcpy_done_cb(mcp, NULL, mcp->cb_args);
}
return need_yield;
}
static bool dma2d_job_picked_cb(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_data)
{
esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data;
dma2d_channel_handle_t tx_chan = NULL;
dma2d_channel_handle_t rx_chan = NULL;
for (uint32_t i = 0; i < num_chans; i++) {
if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_TX) {
tx_chan = dma2d_chans[i].chan;
}
if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_RX) {
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);
dma2d_rx_event_callbacks_t dma_cbs = {
.on_recv_eof = dma2d_memcpy_done_cb,
};
dma2d_register_rx_event_callbacks(rx_chan, &dma_cbs, mcp);
// 2D-DMA channel data burst length is set to the maximum burst length by default, which meets the encryption alignment restriction
// so even if flash encryption is enabled, it can work properly
dma2d_set_desc_addr(tx_chan, (intptr_t)(mcp->tx_desc));
dma2d_set_desc_addr(rx_chan, (intptr_t)(mcp->rx_desc));
dma2d_start(tx_chan);
dma2d_start(rx_chan);
return false;
}
esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction, esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args)
{
ESP_RETURN_ON_FALSE(mcp && transaction, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(transaction->copy_size_x > 0 && transaction->copy_size_y > 0, ESP_ERR_INVALID_ARG, TAG, "invalid copy size");
mcp->memcpy_done_cb = memcpy_done_cb;
mcp->cb_args = cb_args;
// Write back the user's draw buffer only when it is behind cache, so that the DMA can see the correct data.
// Note, the user src buffer may not be contiguous, write back from the head to the tail anyways.
size_t bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(transaction->pixel_format_fourcc_id);
size_t copy_head = (transaction->src_offset_x + transaction->src_offset_y * transaction->src_buffer_size_x) * bits_per_pixel / 8;
size_t copy_size = (transaction->copy_size_x + (transaction->copy_size_y - 1) * transaction->src_buffer_size_x) * bits_per_pixel / 8;
void *copy_start = (void *)((const uint8_t *)transaction->src_buffer + copy_head);
size_t cache_line_size = esp_cache_get_line_size_by_addr(copy_start);
if (cache_line_size > 0) {
ESP_RETURN_ON_ERROR(esp_cache_msync(copy_start, copy_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), TAG, "writeback draw buffer failed");
}
// mount the data to the DMA descriptor
async_memcpy_setup_dma2d_descriptor(mcp, transaction);
// submit the DMA2D request
static dma2d_trans_config_t dma2d_trans_conf = {
.tx_channel_num = 1,
.rx_channel_num = 1,
.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING,
.on_job_picked = dma2d_job_picked_cb,
};
dma2d_trans_conf.user_config = mcp;
ESP_RETURN_ON_ERROR(dma2d_enqueue(mcp->client, &dma2d_trans_conf, mcp->trans_desc), TAG, "DMA2D enqueue failed");
return ESP_OK;
}