From 8464fac84575752f3465b0450a5f1ff139276b9d Mon Sep 17 00:00:00 2001 From: Song Ruo Jing Date: Thu, 25 Apr 2024 22:18:38 +0800 Subject: [PATCH] feat(ppa): add PPA driver support for ESP32P4 Remove the check for in_accepting_trans_state Add color_pixel_xxxx_data_t structures to color_types.h Fix PM lock protection (Tested, now works well) * CPU_MAX, PM lock and semaphore order * Remove ppa_driver PM lock Modify concurrency (queue, trans recycle, semaphore, ...) Add programming guide Add test apps --- .../esp_driver_ppa/include/driver/ppa.h | 27 +- components/esp_driver_ppa/src/ppa_blend.c | 13 +- components/esp_driver_ppa/src/ppa_core.c | 128 ++--- components/esp_driver_ppa/src/ppa_fill.c | 9 +- components/esp_driver_ppa/src/ppa_priv.h | 6 +- components/esp_driver_ppa/src/ppa_srm.c | 13 +- .../test_apps/.build-test-rules.yml | 7 + .../esp_driver_ppa/test_apps/CMakeLists.txt | 9 + components/esp_driver_ppa/test_apps/README.md | 2 + .../test_apps/main/CMakeLists.txt | 9 + .../test_apps/main/idf_component.yml | 2 + .../test_apps/main/test_app_main.c | 42 ++ .../esp_driver_ppa/test_apps/main/test_ppa.c | 489 ++++++++++++++++++ .../esp_driver_ppa/test_apps/pytest_ppa.py | 17 + .../test_apps/sdkconfig.ci.release | 6 + .../test_apps/sdkconfig.defaults | 3 + .../test_apps/sdkconfig.defaults.esp32p4 | 3 + components/hal/esp32p4/include/hal/ppa_ll.h | 42 +- components/hal/include/hal/color_types.h | 38 ++ components/hal/include/hal/ppa_types.h | 16 + docs/_static/diagrams/ppa/pic_blk_concept.png | Bin 0 -> 26960 bytes docs/conf_common.py | 3 + docs/doxygen/Doxyfile_esp32p4 | 2 + docs/en/api-reference/peripherals/index.rst | 1 + docs/en/api-reference/peripherals/ppa.rst | 149 ++++++ .../zh_CN/api-reference/peripherals/index.rst | 1 + docs/zh_CN/api-reference/peripherals/ppa.rst | 1 + 27 files changed, 906 insertions(+), 132 deletions(-) create mode 100644 components/esp_driver_ppa/test_apps/.build-test-rules.yml create mode 100644 components/esp_driver_ppa/test_apps/CMakeLists.txt create mode 100644 components/esp_driver_ppa/test_apps/README.md create mode 100644 components/esp_driver_ppa/test_apps/main/CMakeLists.txt create mode 100644 components/esp_driver_ppa/test_apps/main/idf_component.yml create mode 100644 components/esp_driver_ppa/test_apps/main/test_app_main.c create mode 100644 components/esp_driver_ppa/test_apps/main/test_ppa.c create mode 100644 components/esp_driver_ppa/test_apps/pytest_ppa.py create mode 100644 components/esp_driver_ppa/test_apps/sdkconfig.ci.release create mode 100644 components/esp_driver_ppa/test_apps/sdkconfig.defaults create mode 100644 components/esp_driver_ppa/test_apps/sdkconfig.defaults.esp32p4 create mode 100644 docs/_static/diagrams/ppa/pic_blk_concept.png create mode 100644 docs/en/api-reference/peripherals/ppa.rst create mode 100644 docs/zh_CN/api-reference/peripherals/ppa.rst diff --git a/components/esp_driver_ppa/include/driver/ppa.h b/components/esp_driver_ppa/include/driver/ppa.h index f903dc49b4..543a6664ba 100644 --- a/components/esp_driver_ppa/include/driver/ppa.h +++ b/components/esp_driver_ppa/include/driver/ppa.h @@ -21,7 +21,7 @@ typedef enum { PPA_OPERATION_SRM, /*!< Do scale-rotate-mirror operation */ PPA_OPERATION_BLEND, /*!< Do blend operation */ PPA_OPERATION_FILL, /*!< Do fill operation, use one constant pixel to fill a target window */ - PPA_OPERATION_NUM, /*!< Quantity of PPA operations */ + PPA_OPERATION_INVALID, /*!< Invalid PPA operations, indicates the quantity of available PPA operations */ } ppa_operation_t; /** @@ -89,7 +89,7 @@ typedef bool (*ppa_event_callback_t)(ppa_client_handle_t ppa_client, ppa_event_d * @brief Group of supported PPA callbacks */ typedef struct { - ppa_event_callback_t on_trans_done; /*! Invoked when a PPA transaction finishes */ + ppa_event_callback_t on_trans_done; /*!< Invoked when a PPA transaction finishes */ } ppa_event_callbacks_t; /** @@ -111,7 +111,7 @@ esp_err_t ppa_client_register_event_callbacks(ppa_client_handle_t ppa_client, co * @brief A collection of configuration items for an input picture and the target block inside the picture */ typedef struct { - void *buffer; /*!< Pointer to the input picture buffer */ + const void *buffer; /*!< Pointer to the input picture buffer */ uint32_t pic_w; /*!< Input picture width (unit: pixel) */ uint32_t pic_h; /*!< Input picture height (unit: pixel) */ uint32_t block_w; /*!< Target block width (unit: pixel) */ @@ -123,8 +123,8 @@ typedef struct { ppa_blend_color_mode_t blend_cm; /*!< Color mode of the picture in a PPA blend operation. Supported color mode in `ppa_blend_color_mode_t` */ ppa_fill_color_mode_t fill_cm; /*!< Color mode of the picture in a PPA fill operation. Supported color mode in `ppa_fill_color_mode_t` */ }; - color_range_t yuv_range; /*!< When the color mode is any YUV color space, this field is to describe its color range */ - color_conv_std_rgb_yuv_t yuv_std; /*!< When the color mode is any YUV color space, this field is to describe its YUV<->RGB conversion standard */ + ppa_color_range_t yuv_range; /*!< When the color mode is any YUV color space, this field is to describe its color range */ + ppa_color_conv_std_rgb_yuv_t yuv_std; /*!< When the color mode is any YUV color space, this field is to describe its YUV<->RGB conversion standard */ } ppa_in_pic_blk_config_t; /** @@ -142,8 +142,8 @@ typedef struct { ppa_blend_color_mode_t blend_cm; /*!< Color mode of the picture in a PPA blend operation. Supported color mode in `ppa_blend_color_mode_t` */ ppa_fill_color_mode_t fill_cm; /*!< Color mode of the picture in a PPA fill operation. Supported color mode in `ppa_fill_color_mode_t` */ }; - color_range_t yuv_range; /*!< When the color mode is any YUV color space, this field is to describe its color range */ - color_conv_std_rgb_yuv_t yuv_std; /*!< When the color mode is any YUV color space, this field is to describe its YUV<->RGB conversion standard */ + ppa_color_range_t yuv_range; /*!< When the color mode is any YUV color space, this field is to describe its color range */ + ppa_color_conv_std_rgb_yuv_t yuv_std; /*!< When the color mode is any YUV color space, this field is to describe its YUV<->RGB conversion standard */ } ppa_out_pic_blk_config_t; /** @@ -193,8 +193,7 @@ typedef struct { * @return * - ESP_OK: Perform a SRM operation successfully * - ESP_ERR_INVALID_ARG: Perform a SRM operation failed because of invalid argument - * - ESP_ERR_NO_MEM: Perform a SRM operation failed because out of memory - * - ESP_FAIL: Perform a SRM operation failed because the client cannot accept transaction now + * - ESP_FAIL: Perform a SRM operation failed because the client's pending transactions has reached its maximum capacity */ esp_err_t ppa_do_scale_rotate_mirror(ppa_client_handle_t ppa_client, const ppa_srm_oper_config_t *config); @@ -227,7 +226,7 @@ typedef struct { When PPA_ALPHA_SCALE mode is selected, alpha_scale_ratio is the multiplier to the input alpha value (output_alpha = alpha_scale_ratio * input_alpha) Ratio resolution is 1/256 */ }; - uint32_t fg_fix_rgb_val; /*!< When in_fg.blend_cm is PPA_BLEND_COLOR_MODE_A8/4, this field can be used to set a fixed color for the foreground, in RGB888 format (R[23:16], G[15: 8], B[7:0]) */ + color_pixel_rgb888_data_t fg_fix_rgb_val; /*!< When in_fg.blend_cm is PPA_BLEND_COLOR_MODE_A8/4, this field can be used to set a fixed color for the foreground, in RGB888 format */ // color-keying // A pixel, where its background element and foreground element are both out of their color-keying ranges, will follow Alpha Blending @@ -255,8 +254,7 @@ typedef struct { * @return * - ESP_OK: Perform a blend operation successfully * - ESP_ERR_INVALID_ARG: Perform a blend operation failed because of invalid argument - * - ESP_ERR_NO_MEM: Perform a blend operation failed because out of memory - * - ESP_FAIL: Perform a blend operation failed because the client cannot accept transaction now + * - ESP_FAIL: Perform a blend operation failed because the client's pending transactions has reached its maximum capacity */ esp_err_t ppa_do_blend(ppa_client_handle_t ppa_client, const ppa_blend_oper_config_t *config); @@ -268,7 +266,7 @@ typedef struct { uint32_t fill_block_w; /*!< The width of the block to be filled (unit: pixel) */ uint32_t fill_block_h; /*!< The height of the block to be filled (unit: pixel) */ - uint32_t fill_argb_color; /*!< The color to be filled, in ARGB8888 format ((A[31:24], R[23:16], G[15: 8], B[7:0])) */ + color_pixel_argb8888_data_t fill_argb_color; /*!< The color to be filled, in ARGB8888 format */ ppa_trans_mode_t mode; /*!< Determines whether to block inside the operation functions, see `ppa_trans_mode_t` */ void *user_data; /*!< User registered data to be passed into `done_cb` callback function */ @@ -283,8 +281,7 @@ typedef struct { * @return * - ESP_OK: Perform a fill operation successfully * - ESP_ERR_INVALID_ARG: Perform a fill operation failed because of invalid argument - * - ESP_ERR_NO_MEM: Perform a fill operation failed because out of memory - * - ESP_FAIL: Perform a fill operation failed because the client cannot accept transaction now + * - ESP_FAIL: Perform a fill operation failed because the client's pending transactions has reached its maximum capacity */ esp_err_t ppa_do_fill(ppa_client_handle_t ppa_client, const ppa_fill_oper_config_t *config); diff --git a/components/esp_driver_ppa/src/ppa_blend.c b/components/esp_driver_ppa/src/ppa_blend.c index 420a091160..fc4c6c5a3c 100644 --- a/components/esp_driver_ppa/src/ppa_blend.c +++ b/components/esp_driver_ppa/src/ppa_blend.c @@ -146,7 +146,7 @@ bool ppa_blend_transaction_on_picked(uint32_t num_chans, const dma2d_trans_chann ppa_ll_blend_set_rx_fg_color_mode(platform->hal.dev, blend_trans_desc->in_fg.blend_cm); if (COLOR_SPACE_TYPE((uint32_t)blend_trans_desc->in_fg.blend_cm) == COLOR_SPACE_ALPHA) { - ppa_ll_blend_set_rx_fg_fix_rgb(platform->hal.dev, blend_trans_desc->fg_fix_rgb_val); + ppa_ll_blend_set_rx_fg_fix_rgb(platform->hal.dev, &blend_trans_desc->fg_fix_rgb_val); } ppa_ll_blend_enable_rx_fg_byte_swap(platform->hal.dev, blend_trans_desc->fg_byte_swap); ppa_ll_blend_enable_rx_fg_rgb_swap(platform->hal.dev, blend_trans_desc->fg_rgb_swap); @@ -231,18 +231,21 @@ esp_err_t ppa_do_blend(ppa_client_handle_t ppa_client, const ppa_blend_oper_conf .color_type_id = config->in_bg.blend_cm, }; uint32_t in_bg_pic_len = config->in_bg.pic_w * config->in_bg.pic_h * color_hal_pixel_format_get_bit_depth(in_bg_pixel_format) / 8; - esp_cache_msync(config->in_bg.buffer, in_bg_pic_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + esp_cache_msync((void *)config->in_bg.buffer, in_bg_pic_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); color_space_pixel_format_t in_fg_pixel_format = { .color_type_id = config->in_fg.blend_cm, }; uint32_t in_fg_pic_len = config->in_fg.pic_w * config->in_fg.pic_h * color_hal_pixel_format_get_bit_depth(in_fg_pixel_format) / 8; - esp_cache_msync(config->in_fg.buffer, in_fg_pic_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + esp_cache_msync((void *)config->in_fg.buffer, in_fg_pic_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); // Invalidate out_buffer - esp_cache_msync(config->out.buffer, config->out.buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); + esp_cache_msync((void *)config->out.buffer, config->out.buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); esp_err_t ret = ESP_OK; ppa_trans_t *trans_elm = NULL; - if (xQueueReceive(ppa_client->trans_elm_ptr_queue, (void *)&trans_elm, 0)) { + portENTER_CRITICAL(&ppa_client->spinlock); + bool trans_elm_acquired = xQueueReceive(ppa_client->trans_elm_ptr_queue, (void *)&trans_elm, 0); + portEXIT_CRITICAL(&ppa_client->spinlock); + if (trans_elm_acquired) { dma2d_trans_config_t *dma_trans_desc = trans_elm->trans_desc; ppa_dma2d_trans_on_picked_config_t *trans_on_picked_desc = dma_trans_desc->user_config; diff --git a/components/esp_driver_ppa/src/ppa_core.c b/components/esp_driver_ppa/src/ppa_core.c index dc08fdced7..c1f3bdf2a7 100644 --- a/components/esp_driver_ppa/src/ppa_core.c +++ b/components/esp_driver_ppa/src/ppa_core.c @@ -46,10 +46,10 @@ static esp_err_t ppa_engine_release(ppa_engine_t *ppa_engine); static bool ppa_malloc_transaction(QueueHandle_t trans_elm_ptr_queue, uint32_t trans_elm_num, ppa_operation_t oper_type); static void ppa_free_transaction(ppa_trans_t *trans_elm); -const dma2d_trans_on_picked_callback_t ppa_oper_trans_on_picked_func[PPA_OPERATION_NUM] = { - ppa_srm_transaction_on_picked, - ppa_blend_transaction_on_picked, - ppa_fill_transaction_on_picked, +const dma2d_trans_on_picked_callback_t ppa_oper_trans_on_picked_func[PPA_OPERATION_INVALID] = { + [PPA_OPERATION_SRM] = ppa_srm_transaction_on_picked, + [PPA_OPERATION_BLEND] = ppa_blend_transaction_on_picked, + [PPA_OPERATION_FILL] = ppa_fill_transaction_on_picked, }; static esp_err_t ppa_engine_acquire(const ppa_engine_config_t *config, ppa_engine_t **ret_engine) @@ -104,9 +104,11 @@ static esp_err_t ppa_engine_acquire(const ppa_engine_config_t *config, ppa_engin } #if CONFIG_PM_ENABLE - ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "ppa_srm", &srm_engine->base.pm_lock); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "create pm lock failed"); + if (ret == ESP_OK) { + ret = esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "ppa_srm", &srm_engine->base.pm_lock); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "create pm lock failed"); + } } #endif } else { @@ -147,9 +149,11 @@ static esp_err_t ppa_engine_acquire(const ppa_engine_config_t *config, ppa_engin } #if CONFIG_PM_ENABLE - ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "ppa_blending", &blending_engine->base.pm_lock); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "create pm lock failed"); + if (ret == ESP_OK) { + ret = esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "ppa_blending", &blending_engine->base.pm_lock); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "create pm lock failed"); + } } #endif } else { @@ -180,16 +184,6 @@ static esp_err_t ppa_engine_acquire(const ppa_engine_config_t *config, ppa_engin ESP_LOGE(TAG, "install 2D-DMA failed"); goto wrap_up; } - -#if CONFIG_PM_ENABLE - assert(!s_platform.pm_lock); - // Create and acquire the PM lock - ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "ppa", &s_platform.pm_lock); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "create pm lock failed"); - goto wrap_up; - } -#endif } } wrap_up: @@ -250,14 +244,6 @@ static esp_err_t ppa_engine_release(ppa_engine_t *ppa_engine) if (!s_platform.srm && !s_platform.blending) { assert(s_platform.srm_engine_ref_count == 0 && s_platform.blend_engine_ref_count == 0); -#if CONFIG_PM_ENABLE - if (s_platform.pm_lock) { - ret = esp_pm_lock_delete(s_platform.pm_lock); - assert(ret == ESP_OK); - s_platform.pm_lock = NULL; - } -#endif - if (s_platform.dma2d_pool_handle) { dma2d_release_pool(s_platform.dma2d_pool_handle); // TODO: check return value. If not ESP_OK, then must be error on other 2D-DMA clients :( Give a warning log? s_platform.dma2d_pool_handle = NULL; @@ -278,19 +264,19 @@ esp_err_t ppa_register_client(const ppa_client_config_t *config, ppa_client_hand { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(config && ret_client, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - ESP_RETURN_ON_FALSE(config->oper_type < PPA_OPERATION_NUM, ESP_ERR_INVALID_ARG, TAG, "unknown operation"); + ESP_RETURN_ON_FALSE(config->oper_type < PPA_OPERATION_INVALID, ESP_ERR_INVALID_ARG, TAG, "unknown operation"); ppa_client_t *client = (ppa_client_t *)heap_caps_calloc(1, sizeof(ppa_client_t), PPA_MEM_ALLOC_CAPS); ESP_RETURN_ON_FALSE(client, ESP_ERR_NO_MEM, TAG, "no mem to register client"); - uint32_t ring_buf_size = MAX(1, config->max_pending_trans_num); - client->trans_elm_ptr_queue = xQueueCreateWithCaps(ring_buf_size, sizeof(uint32_t), PPA_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(client->trans_elm_ptr_queue && ppa_malloc_transaction(client->trans_elm_ptr_queue, ring_buf_size, config->oper_type), + // Allocate memory for storing transaction contexts and create a queue to save these trans_elm_ptr + uint32_t queue_size = MAX(1, config->max_pending_trans_num); + client->trans_elm_ptr_queue = xQueueCreateWithCaps(queue_size, sizeof(uint32_t), PPA_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(client->trans_elm_ptr_queue && ppa_malloc_transaction(client->trans_elm_ptr_queue, queue_size, config->oper_type), ESP_ERR_NO_MEM, err, TAG, "no mem for transaction storage"); client->oper_type = config->oper_type; client->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; - client->in_accepting_trans_state = true; if (config->oper_type == PPA_OPERATION_SRM) { ppa_engine_config_t engine_config = { .engine = PPA_ENGINE_TYPE_SRM, @@ -318,7 +304,6 @@ esp_err_t ppa_unregister_client(ppa_client_handle_t ppa_client) bool do_unregister = false; portENTER_CRITICAL(&ppa_client->spinlock); if (ppa_client->trans_cnt == 0) { - ppa_client->in_accepting_trans_state = false; do_unregister = true; } portEXIT_CRITICAL(&ppa_client->spinlock); @@ -387,12 +372,12 @@ static bool ppa_malloc_transaction(QueueHandle_t trans_elm_ptr_queue, uint32_t t trans_on_picked_desc->op_desc = ppa_trans_desc; trans_on_picked_desc->trans_elm = new_trans_elm; dma_trans_desc->user_config = (void *)trans_on_picked_desc; - dma_trans_desc->on_job_picked = ppa_oper_trans_on_picked_func[oper_type]; // TODO: This maybe better to be in the ppa_do_xxx function + dma_trans_desc->on_job_picked = ppa_oper_trans_on_picked_func[oper_type]; new_trans_elm->trans_desc = dma_trans_desc; new_trans_elm->dma_trans_placeholder = dma_trans_elm; new_trans_elm->sem = ppa_trans_sem; - // Fill the ring buffer with allocated transaction element pointer + // Fill the queue with allocated transaction element pointer BaseType_t sent = xQueueSend(trans_elm_ptr_queue, &new_trans_elm, 0); assert(sent); } @@ -424,27 +409,14 @@ esp_err_t ppa_do_operation(ppa_client_handle_t ppa_client, ppa_engine_t *ppa_eng esp_err_t ret = ESP_OK; esp_err_t pm_lock_ret __attribute__((unused)); -#if CONFIG_PM_ENABLE - pm_lock_ret = esp_pm_lock_acquire(s_platform.pm_lock); - assert((pm_lock_ret == ESP_OK) && "acquire pm_lock failed"); -#endif - portENTER_CRITICAL(&ppa_client->spinlock); - // TODO: Check whether trans_cnt and trans_elm_ptr_queue need in one spinlock!!! - if (ppa_client->in_accepting_trans_state) { - // Send transaction into PPA engine queue - STAILQ_INSERT_TAIL(&ppa_engine_base->trans_stailq, trans_elm, entry); - ppa_client->trans_cnt++; - } else { - ret = ESP_FAIL; - } + // Send transaction into PPA engine queue + portENTER_CRITICAL(&ppa_engine_base->spinlock); + STAILQ_INSERT_TAIL(&ppa_engine_base->trans_stailq, trans_elm, entry); + portEXIT_CRITICAL(&ppa_engine_base->spinlock); + ppa_client->trans_cnt++; portEXIT_CRITICAL(&ppa_client->spinlock); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "The client cannot accept transaction now"); - goto err; - } - TickType_t ticks_to_wait = (mode == PPA_TRANS_MODE_NON_BLOCKING) ? 0 : portMAX_DELAY; if (xSemaphoreTake(ppa_engine_base->sem, ticks_to_wait) == pdTRUE) { // Check if the transaction has already been started from the ISR @@ -469,11 +441,11 @@ esp_err_t ppa_do_operation(ppa_client_handle_t ppa_client, ppa_engine_t *ppa_eng portENTER_CRITICAL(&ppa_engine_base->spinlock); STAILQ_REMOVE(&ppa_engine_base->trans_stailq, trans_elm, ppa_trans_s, entry); portEXIT_CRITICAL(&ppa_engine_base->spinlock); + xSemaphoreGive(ppa_engine_base->sem); #if CONFIG_PM_ENABLE pm_lock_ret = esp_pm_lock_release(ppa_engine_base->pm_lock); assert((pm_lock_ret == ESP_OK) && "release pm_lock failed"); #endif - xSemaphoreGive(ppa_engine_base->sem); portENTER_CRITICAL(&ppa_client->spinlock); ppa_client->trans_cnt--; portEXIT_CRITICAL(&ppa_client->spinlock); @@ -489,15 +461,9 @@ esp_err_t ppa_do_operation(ppa_client_handle_t ppa_client, ppa_engine_t *ppa_eng // printf("ppa intr: %ld\n", PPA.int_raw.val); // } xSemaphoreTake(trans_elm->sem, portMAX_DELAY); // Given in the ISR - // Sanity check new_trans_elm not in trans_stailq anymore? (loop takes time tho) - // ppa_recycle_transaction(ppa_client, trans_elm); // TODO: Do we need it to be here or can be at the end of done_cb? + // TODO: Sanity check new_trans_elm not in trans_stailq anymore? (loop takes time tho) } -#if CONFIG_PM_ENABLE - pm_lock_ret = esp_pm_lock_release(s_platform.pm_lock); - assert((pm_lock_ret == ESP_OK) && "release pm_lock failed"); -#endif - err: return ret; } @@ -510,11 +476,9 @@ bool ppa_transaction_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data ppa_client_t *client = trans_elm->client; ppa_dma2d_trans_on_picked_config_t *trans_on_picked_desc = (ppa_dma2d_trans_on_picked_config_t *)trans_elm->trans_desc->user_config; ppa_engine_t *engine_base = trans_on_picked_desc->ppa_engine; - - if (client->done_cb) { - ppa_event_data_t edata = {}; - need_yield |= client->done_cb(client, &edata, trans_elm->user_data); - } + // Save callback contexts + ppa_event_callback_t done_cb = client->done_cb; + void *trans_elm_user_data = trans_elm->user_data; ppa_trans_t *next_start_trans = NULL; portENTER_CRITICAL_ISR(&engine_base->spinlock); @@ -523,28 +487,34 @@ bool ppa_transaction_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data next_start_trans = STAILQ_FIRST(&engine_base->trans_stailq); portEXIT_CRITICAL_ISR(&engine_base->spinlock); + portENTER_CRITICAL_ISR(&client->spinlock); + // Release transaction semaphore to unblock ppa_do_operation + xSemaphoreGiveFromISR(trans_elm->sem, &HPTaskAwoken); + need_yield |= (HPTaskAwoken == pdTRUE); + + // Then recycle transaction elm + need_yield |= ppa_recycle_transaction(client, trans_elm); + + client->trans_cnt--; + portEXIT_CRITICAL_ISR(&client->spinlock); + // If there is next trans in PPA engine queue, send it to DMA queue; otherwise, release the engine semaphore if (next_start_trans) { ppa_dma2d_enqueue(next_start_trans); } else { + xSemaphoreGiveFromISR(engine_base->sem, &HPTaskAwoken); + need_yield |= (HPTaskAwoken == pdTRUE); #if CONFIG_PM_ENABLE esp_err_t pm_lock_ret = esp_pm_lock_release(engine_base->pm_lock); assert(pm_lock_ret == ESP_OK); #endif - xSemaphoreGiveFromISR(engine_base->sem, &HPTaskAwoken); - need_yield |= (HPTaskAwoken == pdTRUE); } - // Recycle transaction and release transaction semaphore - // if (trans_elm->sem != NULL) { - xSemaphoreGiveFromISR(trans_elm->sem, &HPTaskAwoken); - need_yield |= (HPTaskAwoken == pdTRUE); - // } - // TODO: Check whether trans_cnt and trans_elm_ptr_queue need in one spinlock!!! - portENTER_CRITICAL_ISR(&client->spinlock); - need_yield |= ppa_recycle_transaction(client, trans_elm); - client->trans_cnt--; - portEXIT_CRITICAL_ISR(&client->spinlock); + // Process last transaction's callback + if (done_cb) { + ppa_event_data_t edata = {}; + need_yield |= done_cb(client, &edata, trans_elm_user_data); + } return need_yield; } diff --git a/components/esp_driver_ppa/src/ppa_fill.c b/components/esp_driver_ppa/src/ppa_fill.c index 476b4f36cf..8582e9d3a5 100644 --- a/components/esp_driver_ppa/src/ppa_fill.c +++ b/components/esp_driver_ppa/src/ppa_fill.c @@ -77,7 +77,7 @@ bool ppa_fill_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channe dma2d_start(dma2d_rx_chan); // Configure PPA Blending engine - ppa_ll_blend_configure_filling_block(platform->hal.dev, fill_trans_desc->fill_argb_color, fill_trans_desc->fill_block_w, fill_trans_desc->fill_block_h); + ppa_ll_blend_configure_filling_block(platform->hal.dev, &fill_trans_desc->fill_argb_color, fill_trans_desc->fill_block_w, fill_trans_desc->fill_block_h); ppa_ll_blend_set_tx_color_mode(platform->hal.dev, fill_trans_desc->out.fill_cm); ppa_ll_blend_start(platform->hal.dev, PPA_LL_BLEND_TRANS_MODE_FILL); @@ -104,11 +104,14 @@ esp_err_t ppa_do_fill(ppa_client_handle_t ppa_client, const ppa_fill_oper_config // To reduce complexity, color_mode, fill_block_w/h correctness are checked in their corresponding LL functions // Write back and invalidate are performed on the entire picture (the window content is not continuous in the buffer) - esp_cache_msync(config->out.buffer, config->out.buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + esp_cache_msync((void *)config->out.buffer, config->out.buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); esp_err_t ret = ESP_OK; ppa_trans_t *trans_elm = NULL; - if (xQueueReceive(ppa_client->trans_elm_ptr_queue, (void *)&trans_elm, 0)) { + portENTER_CRITICAL(&ppa_client->spinlock); + bool trans_elm_acquired = xQueueReceive(ppa_client->trans_elm_ptr_queue, (void *)&trans_elm, 0); + portEXIT_CRITICAL(&ppa_client->spinlock); + if (trans_elm_acquired) { dma2d_trans_config_t *dma_trans_desc = trans_elm->trans_desc; ppa_dma2d_trans_on_picked_config_t *trans_on_picked_desc = dma_trans_desc->user_config; diff --git a/components/esp_driver_ppa/src/ppa_priv.h b/components/esp_driver_ppa/src/ppa_priv.h index 827276b238..fdcf7f6bd8 100644 --- a/components/esp_driver_ppa/src/ppa_priv.h +++ b/components/esp_driver_ppa/src/ppa_priv.h @@ -78,7 +78,6 @@ struct ppa_client_t { ppa_engine_t *engine; // Pointer to the PPA engine that in charge of performing the PPA operation uint32_t trans_cnt; // Number of pending PPA transactions portMUX_TYPE spinlock; // Client level spinlock - bool in_accepting_trans_state; // Indicates whether the client can accept new PPA transaction requests now ppa_event_callback_t done_cb; // Transaction done callback QueueHandle_t trans_elm_ptr_queue; // Queue that contains the pointers to the allocated memory to save the transaction contexts }; @@ -141,7 +140,7 @@ typedef struct { uint32_t fg_alpha_fix_val; float fg_alpha_scale_ratio; }; - uint32_t fg_fix_rgb_val; + color_pixel_rgb888_data_t fg_fix_rgb_val; // color-keying bool bg_ck_en; @@ -209,9 +208,6 @@ struct ppa_platform_t { uint32_t blend_engine_ref_count; // Reference count used to protect PPA blending engine acquire and release size_t buf_alignment_size; // Alignment requirement for the outgoing buffer addr and size to satisfy cache line size uint32_t dma_desc_mem_size; // Alignment requirement for the 2D-DMA descriptor to satisfy cache line size -#if CONFIG_PM_ENABLE - esp_pm_lock_handle_t pm_lock; // Power management lock -#endif }; #ifdef __cplusplus diff --git a/components/esp_driver_ppa/src/ppa_srm.c b/components/esp_driver_ppa/src/ppa_srm.c index bbde15789a..331faea3d6 100644 --- a/components/esp_driver_ppa/src/ppa_srm.c +++ b/components/esp_driver_ppa/src/ppa_srm.c @@ -108,7 +108,7 @@ bool ppa_srm_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channel if (ppa_in_color_mode == PPA_SRM_COLOR_MODE_YUV444) { ppa_in_color_mode = PPA_SRM_COLOR_MODE_RGB888; dma2d_csc_config_t dma_tx_csc = {0}; - if (srm_trans_desc->in.yuv_std == COLOR_CONV_STD_RGB_YUV_BT601) { + if (srm_trans_desc->in.yuv_std == PPA_COLOR_CONV_STD_RGB_YUV_BT601) { dma_tx_csc.tx_csc_option = DMA2D_CSC_TX_YUV444_TO_RGB888_601; } else { dma_tx_csc.tx_csc_option = DMA2D_CSC_TX_YUV444_TO_RGB888_709; @@ -117,7 +117,7 @@ bool ppa_srm_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channel } else if (ppa_in_color_mode == PPA_SRM_COLOR_MODE_YUV422) { ppa_in_color_mode = PPA_SRM_COLOR_MODE_RGB888; dma2d_csc_config_t dma_tx_csc = {0}; - if (srm_trans_desc->in.yuv_std == COLOR_CONV_STD_RGB_YUV_BT601) { + if (srm_trans_desc->in.yuv_std == PPA_COLOR_CONV_STD_RGB_YUV_BT601) { dma_tx_csc.tx_csc_option = DMA2D_CSC_TX_YUV422_TO_RGB888_601; } else { dma_tx_csc.tx_csc_option = DMA2D_CSC_TX_YUV422_TO_RGB888_709; @@ -227,13 +227,16 @@ esp_err_t ppa_do_scale_rotate_mirror(ppa_client_handle_t ppa_client, const ppa_s .color_type_id = config->in.srm_cm, }; uint32_t in_pic_len = config->in.pic_w * config->in.pic_h * color_hal_pixel_format_get_bit_depth(in_pixel_format) / 8; - esp_cache_msync(config->in.buffer, in_pic_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + esp_cache_msync((void *)config->in.buffer, in_pic_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); // Invalidate out_buffer - esp_cache_msync(config->out.buffer, config->out.buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); + esp_cache_msync((void *)config->out.buffer, config->out.buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); esp_err_t ret = ESP_OK; ppa_trans_t *trans_elm = NULL; - if (xQueueReceive(ppa_client->trans_elm_ptr_queue, (void *)&trans_elm, 0)) { + portENTER_CRITICAL(&ppa_client->spinlock); + bool trans_elm_acquired = xQueueReceive(ppa_client->trans_elm_ptr_queue, (void *)&trans_elm, 0); + portEXIT_CRITICAL(&ppa_client->spinlock); + if (trans_elm_acquired) { dma2d_trans_config_t *dma_trans_desc = trans_elm->trans_desc; ppa_dma2d_trans_on_picked_config_t *trans_on_picked_desc = dma_trans_desc->user_config; diff --git a/components/esp_driver_ppa/test_apps/.build-test-rules.yml b/components/esp_driver_ppa/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..7767104a7f --- /dev/null +++ b/components/esp_driver_ppa/test_apps/.build-test-rules.yml @@ -0,0 +1,7 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/esp_driver_ppa/test_apps: + disable: + - if: SOC_PPA_SUPPORTED != 1 + depends_components: + - esp_driver_ppa diff --git a/components/esp_driver_ppa/test_apps/CMakeLists.txt b/components/esp_driver_ppa/test_apps/CMakeLists.txt new file mode 100644 index 0000000000..dde4141943 --- /dev/null +++ b/components/esp_driver_ppa/test_apps/CMakeLists.txt @@ -0,0 +1,9 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(ppa_test) diff --git a/components/esp_driver_ppa/test_apps/README.md b/components/esp_driver_ppa/test_apps/README.md new file mode 100644 index 0000000000..909282018f --- /dev/null +++ b/components/esp_driver_ppa/test_apps/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | diff --git a/components/esp_driver_ppa/test_apps/main/CMakeLists.txt b/components/esp_driver_ppa/test_apps/main/CMakeLists.txt new file mode 100644 index 0000000000..25e3680685 --- /dev/null +++ b/components/esp_driver_ppa/test_apps/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(srcs "test_app_main.c" + "test_ppa.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS "." + PRIV_REQUIRES esp_driver_ppa esp_psram unity + WHOLE_ARCHIVE) diff --git a/components/esp_driver_ppa/test_apps/main/idf_component.yml b/components/esp_driver_ppa/test_apps/main/idf_component.yml new file mode 100644 index 0000000000..2ae836a935 --- /dev/null +++ b/components/esp_driver_ppa/test_apps/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + ccomp_timer: "^1.0.0" diff --git a/components/esp_driver_ppa/test_apps/main/test_app_main.c b/components/esp_driver_ppa/test_apps/main/test_app_main.c new file mode 100644 index 0000000000..bdc0d7ecb3 --- /dev/null +++ b/components/esp_driver_ppa/test_apps/main/test_app_main.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" +#include "unity_test_utils.h" + +// Some resources are lazy allocated in the driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (300) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + esp_reent_cleanup(); //clean up some of the newlib's lazy allocations + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + printf(" ________ \n"); + printf("|___ ___ \\ \n"); + printf(" | \\_/ | \n"); + printf(" '.___.' \n"); + printf(" ________ \n"); + printf("|___ ___ \\ \n"); + printf(" | \\_/ | \n"); + printf(" '.___.' \n"); + printf(" _______ \n"); + printf("|__. _ '. \n"); + printf(" __||_/ / \n"); + printf("|______.' \n"); + + unity_run_menu(); +} diff --git a/components/esp_driver_ppa/test_apps/main/test_ppa.c b/components/esp_driver_ppa/test_apps/main/test_ppa.c new file mode 100644 index 0000000000..2d92e4afba --- /dev/null +++ b/components/esp_driver_ppa/test_apps/main/test_ppa.c @@ -0,0 +1,489 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "driver/ppa.h" +#include "esp_heap_caps.h" +#include "esp_err.h" +#include "ccomp_timer.h" +#include "hal/color_hal.h" + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +TEST_CASE("ppa_client_do_ppa_operation", "[PPA]") +{ + const uint32_t w = 480; + const uint32_t h = 480; + const uint32_t buf_1_color_type_id = COLOR_TYPE_ID(COLOR_SPACE_ARGB, COLOR_PIXEL_ARGB8888); + const uint32_t buf_2_color_type_id = COLOR_TYPE_ID(COLOR_SPACE_ARGB, COLOR_PIXEL_ARGB8888); + + color_space_pixel_format_t buf_1_cm = { + .color_type_id = buf_1_color_type_id, + }; + color_space_pixel_format_t buf_2_cm = { + .color_type_id = buf_2_color_type_id, + }; + + uint32_t buf_1_size = ALIGN_UP(w * h * color_hal_pixel_format_get_bit_depth(buf_1_cm) / 8, 64); + uint32_t buf_2_size = ALIGN_UP(w * h * color_hal_pixel_format_get_bit_depth(buf_2_cm) / 8, 64); + uint8_t *buf_1 = heap_caps_aligned_calloc(64, buf_1_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buf_1); + uint8_t *buf_2 = heap_caps_aligned_calloc(64, buf_2_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buf_2); + + // Register different types of PPA clients + ppa_client_handle_t ppa_client_a_handle; + ppa_client_handle_t ppa_client_b_handle; + ppa_client_handle_t ppa_client_c_handle; + ppa_client_handle_t ppa_client_d_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_SRM, + }; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_a_handle)); + ppa_client_config.oper_type = PPA_OPERATION_BLEND; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_b_handle)); + ppa_client_config.oper_type = PPA_OPERATION_FILL; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_c_handle)); + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_d_handle)); + + ppa_srm_oper_config_t srm_oper_config = { + .in.buffer = buf_1, + .in.pic_w = w, + .in.pic_h = h, + .in.block_w = w, + .in.block_h = h, + .in.block_offset_x = 0, + .in.block_offset_y = 0, + .in.srm_cm = buf_1_color_type_id, + + .out.buffer = buf_2, + .out.buffer_size = buf_2_size, + .out.pic_w = w, + .out.pic_h = h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.srm_cm = buf_2_color_type_id, + + .rotation_angle = PPA_SRM_ROTATION_ANGLE_0, + .scale_x = 1.0, + .scale_y = 1.0, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + // A SRM client can request to do a SRM operation + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_a_handle, &srm_oper_config)); + // A non-SRM client can not request to do a SRM operation + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, ppa_do_scale_rotate_mirror(ppa_client_b_handle, &srm_oper_config)); + + ppa_blend_oper_config_t blend_oper_config = { + .in_bg.buffer = buf_1, + .in_bg.pic_w = w, + .in_bg.pic_h = h, + .in_bg.block_w = w, + .in_bg.block_h = h, + .in_bg.block_offset_x = 0, + .in_bg.block_offset_y = 0, + .in_bg.blend_cm = buf_1_color_type_id, + + .in_fg.buffer = buf_2, + .in_fg.pic_w = w, + .in_fg.pic_h = h, + .in_fg.block_w = w, + .in_fg.block_h = h, + .in_fg.block_offset_x = 0, + .in_fg.block_offset_y = 0, + .in_fg.blend_cm = buf_2_color_type_id, + + .out.buffer = buf_1, + .out.buffer_size = buf_1_size, + .out.pic_w = w, + .out.pic_h = h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.blend_cm = buf_1_color_type_id, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + // A blend client can request to do a blend operation + TEST_ESP_OK(ppa_do_blend(ppa_client_b_handle, &blend_oper_config)); + // A non-blend client can not request to do a blend operation + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, ppa_do_blend(ppa_client_d_handle, &blend_oper_config)); + + ppa_fill_oper_config_t fill_oper_config = { + .out.buffer = buf_1, + .out.buffer_size = buf_1_size, + .out.pic_w = w, + .out.pic_h = h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.fill_cm = buf_1_color_type_id, + + .fill_block_w = w, + .fill_block_h = h, + .fill_argb_color = { + .val = 0xFF00FF00, + }, + + .mode = PPA_TRANS_MODE_NON_BLOCKING, + }; + // A fill client can request to do a fill operation + TEST_ESP_OK(ppa_do_fill(ppa_client_c_handle, &fill_oper_config)); + // Another fill client can also request another fill operation at the same time + TEST_ESP_OK(ppa_do_fill(ppa_client_d_handle, &fill_oper_config)); + + vTaskDelay(pdMS_TO_TICKS(500)); + + // Unregister all PPA clients + TEST_ESP_OK(ppa_unregister_client(ppa_client_a_handle)); + TEST_ESP_OK(ppa_unregister_client(ppa_client_b_handle)); + TEST_ESP_OK(ppa_unregister_client(ppa_client_c_handle)); + TEST_ESP_OK(ppa_unregister_client(ppa_client_d_handle)); + + free(buf_1); + free(buf_2); +} + +static bool ppa_trans_done_cb(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + SemaphoreHandle_t sem = (SemaphoreHandle_t)user_data; + xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken); + return (xHigherPriorityTaskWoken == pdTRUE); +} + +TEST_CASE("ppa_pending_transactions_in_queue", "[PPA]") +{ + // A big picture block takes longer time to process, desired for this test case + const uint32_t w = 1920; + const uint32_t h = 1080; + const uint32_t buf_1_color_type_id = COLOR_TYPE_ID(COLOR_SPACE_ARGB, COLOR_PIXEL_ARGB8888); + const uint32_t buf_2_color_type_id = COLOR_TYPE_ID(COLOR_SPACE_ARGB, COLOR_PIXEL_ARGB8888); + + color_space_pixel_format_t buf_1_cm = { + .color_type_id = buf_1_color_type_id, + }; + color_space_pixel_format_t buf_2_cm = { + .color_type_id = buf_2_color_type_id, + }; + + uint32_t buf_1_size = w * h * color_hal_pixel_format_get_bit_depth(buf_1_cm) / 8; + uint32_t buf_2_size = ALIGN_UP(w * h * color_hal_pixel_format_get_bit_depth(buf_2_cm) / 8, 64); + uint8_t *buf_1 = heap_caps_aligned_calloc(64, buf_1_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buf_1); + uint8_t *buf_2 = heap_caps_aligned_calloc(64, buf_2_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buf_2); + + // Register two PPA SRM clients with different max_pending_trans_num + ppa_client_handle_t ppa_client_a_handle; + ppa_client_handle_t ppa_client_b_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_SRM, + }; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_a_handle)); + ppa_client_config.max_pending_trans_num = 3; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_b_handle)); + + ppa_event_callbacks_t cbs = { + .on_trans_done = ppa_trans_done_cb, + }; + ppa_client_register_event_callbacks(ppa_client_a_handle, &cbs); + + SemaphoreHandle_t sem = xSemaphoreCreateBinary(); + + ppa_srm_oper_config_t oper_config = { + .in.buffer = buf_1, + .in.pic_w = w, + .in.pic_h = h, + .in.block_w = w, + .in.block_h = h, + .in.block_offset_x = 0, + .in.block_offset_y = 0, + .in.srm_cm = buf_1_color_type_id, + + .out.buffer = buf_2, + .out.buffer_size = buf_2_size, + .out.pic_w = w, + .out.pic_h = h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.srm_cm = buf_2_color_type_id, + + .rotation_angle = PPA_SRM_ROTATION_ANGLE_0, + .scale_x = 1.0, + .scale_y = 1.0, + + .user_data = (void *)sem, + .mode = PPA_TRANS_MODE_NON_BLOCKING, + }; + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_a_handle, &oper_config)); + + // Another transaction cannot be accept since client_a can only hold one transaction + TEST_ESP_ERR(ESP_FAIL, ppa_do_scale_rotate_mirror(ppa_client_a_handle, &oper_config)); + + // Wait for the last transaction finishes + xSemaphoreTake(sem, portMAX_DELAY); + // Then a new transaction can be accepted again + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_a_handle, &oper_config)); + + // Client can not be unregistered when there are unfinished transactions + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, ppa_unregister_client(ppa_client_a_handle)); + + oper_config.mode = PPA_TRANS_MODE_BLOCKING; + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_b_handle, &oper_config)); + // Every PPA engine can only process one operation at a time + // Transactions are being processed with First-In-First-Out + // So, at the moment, the new transaction requested by client_b has finished, the last transaction requested by client_a for sure has finished + TEST_ASSERT(xSemaphoreTake(sem, 0) == pdTRUE); + // client_b can accept more than one transactions + oper_config.mode = PPA_TRANS_MODE_NON_BLOCKING; + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_b_handle, &oper_config)); + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_b_handle, &oper_config)); + oper_config.mode = PPA_TRANS_MODE_BLOCKING; + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_b_handle, &oper_config)); + // The last transaction requested is with BLOCKING mode, so the last call to ppa_do_scale_rotate_mirror returned means all transactions finished + + // Unregister all PPA clients + TEST_ESP_OK(ppa_unregister_client(ppa_client_a_handle)); + TEST_ESP_OK(ppa_unregister_client(ppa_client_b_handle)); + + vSemaphoreDelete(sem); + free(buf_1); + free(buf_2); +} + +TEST_CASE("ppa_srm_performance", "[PPA][ignore]") +{ + const uint32_t w = 1920; // 1920 / 1280 / 800 / 640 + const uint32_t h = 1080; // 1080 / 720 / 480 + const uint32_t block_w = w; + const uint32_t block_h = h; + const ppa_srm_color_mode_t in_cm = PPA_SRM_COLOR_MODE_ARGB8888; + const ppa_srm_color_mode_t out_cm = PPA_SRM_COLOR_MODE_YUV420; + const ppa_srm_rotation_angle_t rotation = PPA_SRM_ROTATION_ANGLE_0; + const float scale_x = 1.0; + const float scale_y = 1.0; + + color_space_pixel_format_t in_pixel_format = { + .color_type_id = in_cm, + }; + color_space_pixel_format_t out_pixel_format = { + .color_type_id = out_cm, + }; + + uint32_t in_buf_size = w * h * color_hal_pixel_format_get_bit_depth(in_pixel_format) / 8; + uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_get_bit_depth(out_pixel_format) / 8, 64); + uint8_t *out_buf = heap_caps_aligned_calloc(64, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(out_buf); + uint8_t *in_buf = heap_caps_aligned_calloc(64, in_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(in_buf); + + uint8_t *ptr = in_buf; + for (int x = 0; x < in_buf_size; x++) { + ptr[x] = x; + } + + ppa_client_handle_t ppa_client_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_SRM, + .max_pending_trans_num = 1, + }; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); + + uint32_t out_pic_w = (rotation == PPA_SRM_ROTATION_ANGLE_0 || rotation == PPA_SRM_ROTATION_ANGLE_180) ? w : h; + uint32_t out_pic_h = (rotation == PPA_SRM_ROTATION_ANGLE_0 || rotation == PPA_SRM_ROTATION_ANGLE_180) ? h : w; + ppa_srm_oper_config_t oper_config = { + .in.buffer = in_buf, + .in.pic_w = w, + .in.pic_h = h, + .in.block_w = block_w, + .in.block_h = block_h, + .in.block_offset_x = 0, + .in.block_offset_y = 0, + .in.srm_cm = in_cm, + + .out.buffer = out_buf, + .out.buffer_size = out_buf_size, + .out.pic_w = out_pic_w, + .out.pic_h = out_pic_h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.srm_cm = out_cm, + + .rotation_angle = rotation, + .scale_x = scale_x, + .scale_y = scale_y, + + .rgb_swap = 0, + .byte_swap = 0, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + + ccomp_timer_start(); + + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_handle, &oper_config)); + + int64_t oper_time = ccomp_timer_stop(); + printf("Time passed: %lld us\n", oper_time); + + TEST_ESP_OK(ppa_unregister_client(ppa_client_handle)); + + free(in_buf); + free(out_buf); +} + +TEST_CASE("ppa_blend_performance", "[PPA][ignore]") +{ + const uint32_t w = 1280; + const uint32_t h = 720; + const uint32_t block_w = w; + const uint32_t block_h = h; + const ppa_blend_color_mode_t in_bg_cm = PPA_BLEND_COLOR_MODE_ARGB8888; + const ppa_blend_color_mode_t in_fg_cm = PPA_BLEND_COLOR_MODE_ARGB8888; + const ppa_blend_color_mode_t out_cm = PPA_BLEND_COLOR_MODE_ARGB8888; + + color_space_pixel_format_t in_bg_pixel_format = { + .color_type_id = in_bg_cm, + }; + color_space_pixel_format_t in_fg_pixel_format = { + .color_type_id = in_fg_cm, + }; + color_space_pixel_format_t out_pixel_format = { + .color_type_id = out_cm, + }; + + uint32_t in_bg_buf_size = w * h * color_hal_pixel_format_get_bit_depth(in_bg_pixel_format) / 8; + uint32_t in_fg_buf_size = w * h * color_hal_pixel_format_get_bit_depth(in_fg_pixel_format) / 8; + uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_get_bit_depth(out_pixel_format) / 8, 64); + uint8_t *out_buf = heap_caps_aligned_calloc(64, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(out_buf); + uint8_t *in_bg_buf = heap_caps_aligned_calloc(64, in_bg_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(in_bg_buf); + uint8_t *in_fg_buf = heap_caps_aligned_calloc(64, in_fg_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(in_fg_buf); + + uint8_t *ptr = in_bg_buf; + for (int x = 0; x < in_bg_buf_size; x++) { + ptr[x] = x & 0x55; + } + ptr = in_fg_buf; + for (int x = 0; x < in_fg_buf_size; x++) { + ptr[x] = x & 0xAA; + } + + ppa_client_handle_t ppa_client_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_BLEND, + .max_pending_trans_num = 1, + }; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); + + ppa_blend_oper_config_t oper_config = { + .in_bg.buffer = in_bg_buf, + .in_bg.pic_w = w, + .in_bg.pic_h = h, + .in_bg.block_w = block_w, + .in_bg.block_h = block_h, + .in_bg.block_offset_x = 0, + .in_bg.block_offset_y = 0, + .in_bg.blend_cm = in_bg_cm, + + .in_fg.buffer = in_fg_buf, + .in_fg.pic_w = w, + .in_fg.pic_h = h, + .in_fg.block_w = block_w, + .in_fg.block_h = block_h, + .in_fg.block_offset_x = 0, + .in_fg.block_offset_y = 0, + .in_fg.blend_cm = in_fg_cm, + + .out.buffer = out_buf, + .out.buffer_size = out_buf_size, + .out.pic_w = w, + .out.pic_h = h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.blend_cm = out_cm, + + .bg_ck_en = false, + .fg_ck_en = false, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + + ccomp_timer_start(); + + TEST_ESP_OK(ppa_do_blend(ppa_client_handle, &oper_config)); + + int64_t oper_time = ccomp_timer_stop(); + printf("Time passed: %lld us\n", oper_time); + + TEST_ESP_OK(ppa_unregister_client(ppa_client_handle)); + + free(in_bg_buf); + free(in_fg_buf); + free(out_buf); +} + +TEST_CASE("ppa_fill_performance", "[PPA][ignore]") +{ + const uint32_t w = 1280; + const uint32_t h = 720; + const uint32_t block_w = 800; + const uint32_t block_h = 480; + const ppa_fill_color_mode_t out_cm = PPA_FILL_COLOR_MODE_RGB565; + + color_space_pixel_format_t out_pixel_format = { + .color_type_id = out_cm, + }; + + uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_get_bit_depth(out_pixel_format) / 8, 64); + uint8_t *out_buf = heap_caps_aligned_calloc(64, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(out_buf); + + ppa_client_handle_t ppa_client_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_FILL, + .max_pending_trans_num = 1, + }; + TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); + + ppa_fill_oper_config_t oper_config = { + .out.buffer = out_buf, + .out.buffer_size = out_buf_size, + .out.pic_w = w, + .out.pic_h = h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.fill_cm = out_cm, + + .fill_block_w = block_w, + .fill_block_h = block_h, + .fill_argb_color = { + .val = 0xFF00FFFF, + }, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + + ccomp_timer_start(); + + TEST_ESP_OK(ppa_do_fill(ppa_client_handle, &oper_config)); + + int64_t oper_time = ccomp_timer_stop(); + printf("Time passed: %lld us\n", oper_time); + + TEST_ESP_OK(ppa_unregister_client(ppa_client_handle)); + + free(out_buf); +} diff --git a/components/esp_driver_ppa/test_apps/pytest_ppa.py b/components/esp_driver_ppa/test_apps/pytest_ppa.py new file mode 100644 index 0000000000..534d32cee1 --- /dev/null +++ b/components/esp_driver_ppa/test_apps/pytest_ppa.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32p4 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_ppa(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_driver_ppa/test_apps/sdkconfig.ci.release b/components/esp_driver_ppa/test_apps/sdkconfig.ci.release new file mode 100644 index 0000000000..199b0cf97c --- /dev/null +++ b/components/esp_driver_ppa/test_apps/sdkconfig.ci.release @@ -0,0 +1,6 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_PM_DFS_INIT_AUTO=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/esp_driver_ppa/test_apps/sdkconfig.defaults b/components/esp_driver_ppa/test_apps/sdkconfig.defaults new file mode 100644 index 0000000000..1ee5d718bc --- /dev/null +++ b/components/esp_driver_ppa/test_apps/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_IDF_EXPERIMENTAL_FEATURES=y diff --git a/components/esp_driver_ppa/test_apps/sdkconfig.defaults.esp32p4 b/components/esp_driver_ppa/test_apps/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000000..702fac3342 --- /dev/null +++ b/components/esp_driver_ppa/test_apps/sdkconfig.defaults.esp32p4 @@ -0,0 +1,3 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y diff --git a/components/hal/esp32p4/include/hal/ppa_ll.h b/components/hal/esp32p4/include/hal/ppa_ll.h index 60ab9766b1..e5ccd126e8 100644 --- a/components/hal/esp32p4/include/hal/ppa_ll.h +++ b/components/hal/esp32p4/include/hal/ppa_ll.h @@ -230,15 +230,15 @@ static inline void ppa_ll_srm_set_tx_color_mode(ppa_dev_t *dev, ppa_srm_color_mo * @brief Set YUV to RGB protocol when PPA SRM RX pixel color space is YUV * * @param dev Peripheral instance address - * @param std One of the RGB-YUV conversion standards in color_conv_std_rgb_yuv_t + * @param std One of the RGB-YUV conversion standards in ppa_color_conv_std_rgb_yuv_t */ -static inline void ppa_ll_srm_set_rx_yuv2rgb_std(ppa_dev_t *dev, color_conv_std_rgb_yuv_t std) +static inline void ppa_ll_srm_set_rx_yuv2rgb_std(ppa_dev_t *dev, ppa_color_conv_std_rgb_yuv_t std) { switch (std) { - case COLOR_CONV_STD_RGB_YUV_BT601: + case PPA_COLOR_CONV_STD_RGB_YUV_BT601: dev->sr_color_mode.yuv2rgb_protocol = 0; break; - case COLOR_CONV_STD_RGB_YUV_BT709: + case PPA_COLOR_CONV_STD_RGB_YUV_BT709: dev->sr_color_mode.yuv2rgb_protocol = 1; break; default: @@ -251,15 +251,15 @@ static inline void ppa_ll_srm_set_rx_yuv2rgb_std(ppa_dev_t *dev, color_conv_std_ * @brief Set RGB to YUV protocol when PPA SRM TX pixel color space is YUV * * @param dev Peripheral instance address - * @param std One of the RGB-YUV conversion standards in color_conv_std_rgb_yuv_t + * @param std One of the RGB-YUV conversion standards in ppa_color_conv_std_rgb_yuv_t */ -static inline void ppa_ll_srm_set_tx_rgb2yuv_std(ppa_dev_t *dev, color_conv_std_rgb_yuv_t std) +static inline void ppa_ll_srm_set_tx_rgb2yuv_std(ppa_dev_t *dev, ppa_color_conv_std_rgb_yuv_t std) { switch (std) { - case COLOR_CONV_STD_RGB_YUV_BT601: + case PPA_COLOR_CONV_STD_RGB_YUV_BT601: dev->sr_color_mode.rgb2yuv_protocol = 0; break; - case COLOR_CONV_STD_RGB_YUV_BT709: + case PPA_COLOR_CONV_STD_RGB_YUV_BT709: dev->sr_color_mode.rgb2yuv_protocol = 1; break; default: @@ -272,15 +272,15 @@ static inline void ppa_ll_srm_set_tx_rgb2yuv_std(ppa_dev_t *dev, color_conv_std_ * @brief Set PPA SRM YUV input range * * @param dev Peripheral instance address - * @param range One of color range options in color_range_t + * @param range One of color range options in ppa_color_range_t */ -static inline void ppa_ll_srm_set_rx_yuv_range(ppa_dev_t *dev, color_range_t range) +static inline void ppa_ll_srm_set_rx_yuv_range(ppa_dev_t *dev, ppa_color_range_t range) { switch (range) { - case COLOR_RANGE_LIMIT: + case PPA_COLOR_RANGE_LIMIT: dev->sr_color_mode.yuv_rx_range = 0; break; - case COLOR_RANGE_FULL: + case PPA_COLOR_RANGE_FULL: dev->sr_color_mode.yuv_rx_range = 1; break; default: @@ -293,15 +293,15 @@ static inline void ppa_ll_srm_set_rx_yuv_range(ppa_dev_t *dev, color_range_t ran * @brief Set PPA SRM YUV output range * * @param dev Peripheral instance address - * @param range One of color range options in color_range_t + * @param range One of color range options in ppa_color_range_t */ -static inline void ppa_ll_srm_set_tx_yuv_range(ppa_dev_t *dev, color_range_t range) +static inline void ppa_ll_srm_set_tx_yuv_range(ppa_dev_t *dev, ppa_color_range_t range) { switch (range) { - case COLOR_RANGE_LIMIT: + case PPA_COLOR_RANGE_LIMIT: dev->sr_color_mode.yuv_tx_range = 0; break; - case COLOR_RANGE_FULL: + case PPA_COLOR_RANGE_FULL: dev->sr_color_mode.yuv_tx_range = 1; break; default: @@ -643,10 +643,10 @@ static inline void ppa_ll_blend_configure_rx_fg_alpha(ppa_dev_t *dev, ppa_alpha_ * @param hb The horizontal width of image block that would be filled in fix pixel filling mode. The unit is pixel. * @param vb The vertical height of image block that would be filled in fix pixel filling mode. The unit is pixel. */ -static inline void ppa_ll_blend_configure_filling_block(ppa_dev_t *dev, uint32_t data, uint32_t hb, uint32_t vb) +static inline void ppa_ll_blend_configure_filling_block(ppa_dev_t *dev, color_pixel_argb8888_data_t *data, uint32_t hb, uint32_t vb) { HAL_ASSERT(hb <= PPA_BLEND_HB_V && vb <= PPA_BLEND_VB_V); - dev->blend_fix_pixel.blend_tx_fix_pixel = data; + dev->blend_fix_pixel.blend_tx_fix_pixel = data->val; dev->blend_tx_size.blend_hb = hb; dev->blend_tx_size.blend_vb = vb; } @@ -657,9 +657,11 @@ static inline void ppa_ll_blend_configure_filling_block(ppa_dev_t *dev, uint32_t * @param dev Peripheral instance address * @param rgb RGB color for A4/A8 mode in RGB888 format */ -static inline void ppa_ll_blend_set_rx_fg_fix_rgb(ppa_dev_t *dev, uint32_t rgb) +static inline void ppa_ll_blend_set_rx_fg_fix_rgb(ppa_dev_t *dev, color_pixel_rgb888_data_t *rgb) { - dev->blend_rgb.val = rgb; + dev->blend_rgb.blend1_rx_b = rgb->b; + dev->blend_rgb.blend1_rx_g = rgb->g; + dev->blend_rgb.blend1_rx_r = rgb->r; } /* diff --git a/components/hal/include/hal/color_types.h b/components/hal/include/hal/color_types.h index e129bd09db..32c2d2b96f 100644 --- a/components/hal/include/hal/color_types.h +++ b/components/hal/include/hal/color_types.h @@ -150,6 +150,44 @@ typedef enum { COLOR_RGB_ELEMENT_ORDER_BGR, /*!< RGB element order: BGR */ } color_rgb_element_order_t; +/*--------------------------------------------------------------- + Data Structure for Color Pixel Unit +---------------------------------------------------------------*/ + +/** + * @brief Data structure for ARGB8888 pixel unit + */ +typedef union { + struct { + uint32_t b: 8; /*!< B component [0, 255] */ + uint32_t g: 8; /*!< G component [0, 255] */ + uint32_t r: 8; /*!< R component [0, 255] */ + uint32_t a: 8; /*!< A component [0, 255] */ + }; + uint32_t val; /*!< 32-bit ARGB8888 value */ +} color_pixel_argb8888_data_t; + +/** + * @brief Data structure for RGB888 pixel unit + */ +typedef struct { + uint8_t b; /*!< B component [0, 255] */ + uint8_t g; /*!< G component [0, 255] */ + uint8_t r; /*!< R component [0, 255] */ +} color_pixel_rgb888_data_t; + +/** + * @brief Data structure for RGB565 pixel unit + */ +typedef union { + struct { + uint16_t b: 5; /*!< B component [0, 31] */ + uint16_t g: 6; /*!< G component [0, 63] */ + uint16_t r: 5; /*!< R component [0, 31] */ + }; + uint16_t val; /*!< 16-bit RGB565 value */ +} color_pixel_rgb565_data_t; + #ifdef __cplusplus } #endif diff --git a/components/hal/include/hal/ppa_types.h b/components/hal/include/hal/ppa_types.h index 8bbe0fd5c9..30fba5ebc9 100644 --- a/components/hal/include/hal/ppa_types.h +++ b/components/hal/include/hal/ppa_types.h @@ -81,6 +81,22 @@ typedef enum { If input format does not contain alpha info, A' = 0, i.e. a layer with 0% opacity. */ } ppa_alpha_update_mode_t; +/** + * @brief Enumeration of PPA supported color conversion standard between RGB and YUV (determines the YUV<->RGB conversion equation) + */ +typedef enum { + PPA_COLOR_CONV_STD_RGB_YUV_BT601 = COLOR_CONV_STD_RGB_YUV_BT601, /*!< YUV<->RGB conversion standard: BT.601 */ + PPA_COLOR_CONV_STD_RGB_YUV_BT709 = COLOR_CONV_STD_RGB_YUV_BT709, /*!< YUV<->RGB conversion standard: BT.709 */ +} ppa_color_conv_std_rgb_yuv_t; + +/** + * @brief Enumeration of PPA supported color range (determines the YUV<->RGB conversion equation) + */ +typedef enum { + PPA_COLOR_RANGE_LIMIT = COLOR_RANGE_LIMIT, /*!< Limited color range, 16 is the darkest black and 235 is the brightest white */ + PPA_COLOR_RANGE_FULL = COLOR_RANGE_FULL, /*!< Full color range, 0 is the darkest black and 255 is the brightest white */ +} ppa_color_range_t; + #ifdef __cplusplus } #endif diff --git a/docs/_static/diagrams/ppa/pic_blk_concept.png b/docs/_static/diagrams/ppa/pic_blk_concept.png new file mode 100644 index 0000000000000000000000000000000000000000..21eadd08264db764dfc3fdd8a413ea30219375db GIT binary patch literal 26960 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 a{rLamA)kh+xnv_~SkBYc&t;ucLK6U!Mj+7u literal 0 HcmV?d00001 diff --git a/docs/conf_common.py b/docs/conf_common.py index 3aedc28b2a..2bdebec6ca 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -176,6 +176,8 @@ SPI_SLAVE_HD_DOCS = ['api-reference/peripherals/spi_slave_hd.rst'] JPEG_DOCS = ['api-reference/peripherals/jpeg.rst'] +PPA_DOCS = ['api-reference/peripherals/ppa.rst'] + QEMU_DOCS = ['api-guides/tools/qemu.rst'] ESP32_DOCS = ['api-reference/system/himem.rst', @@ -278,6 +280,7 @@ conditional_include_dict = {'SOC_BT_SUPPORTED':BT_DOCS, 'SOC_SPI_SUPPORT_SLAVE_HD_VER2':SPI_SLAVE_HD_DOCS, 'SOC_WIFI_NAN_SUPPORT':NAN_DOCS, 'SOC_JPEG_CODEC_SUPPORTED':JPEG_DOCS, + 'SOC_PPA_SUPPORTED':PPA_DOCS, 'SOC_GP_LDO_SUPPORTED':LDO_DOCS, 'esp32':ESP32_DOCS, 'esp32s2':ESP32S2_DOCS, diff --git a/docs/doxygen/Doxyfile_esp32p4 b/docs/doxygen/Doxyfile_esp32p4 index 70c98b4c05..32f0c5817b 100644 --- a/docs/doxygen/Doxyfile_esp32p4 +++ b/docs/doxygen/Doxyfile_esp32p4 @@ -17,12 +17,14 @@ INPUT += \ $(PROJECT_PATH)/components/esp_driver_cam/include/esp_cam_ctlr_types.h \ $(PROJECT_PATH)/components/esp_driver_cam/csi/include/esp_cam_ctlr_csi.h \ $(PROJECT_PATH)/components/hal/include/hal/jpeg_types.h \ + $(PROJECT_PATH)/components/hal/include/hal/ppa_types.h \ $(PROJECT_PATH)/components/esp_driver_jpeg/include/driver/jpeg_types.h \ $(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp.h \ $(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_types.h \ $(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_af.h \ $(PROJECT_PATH)/components/esp_driver_jpeg/include/driver/jpeg_decode.h \ $(PROJECT_PATH)/components/esp_driver_jpeg/include/driver/jpeg_encode.h \ + $(PROJECT_PATH)/components/esp_driver_ppa/include/driver/ppa.h \ $(PROJECT_PATH)/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h \ $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/adc_channel.h \ $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/clk_tree_defs.h \ diff --git a/docs/en/api-reference/peripherals/index.rst b/docs/en/api-reference/peripherals/index.rst index 5094e38dd7..fa847069b1 100644 --- a/docs/en/api-reference/peripherals/index.rst +++ b/docs/en/api-reference/peripherals/index.rst @@ -30,6 +30,7 @@ Peripherals API :SOC_MCPWM_SUPPORTED: mcpwm :SOC_PARLIO_SUPPORTED: parlio :SOC_PCNT_SUPPORTED: pcnt + :SOC_PPA_SUPPORTED: ppa :SOC_RMT_SUPPORTED: rmt :SOC_SDMMC_HOST_SUPPORTED or SOC_SDIO_SLAVE_SUPPORTED: sd_pullup_requirements :SOC_SDMMC_HOST_SUPPORTED: sdmmc_host diff --git a/docs/en/api-reference/peripherals/ppa.rst b/docs/en/api-reference/peripherals/ppa.rst new file mode 100644 index 0000000000..67c2825d31 --- /dev/null +++ b/docs/en/api-reference/peripherals/ppa.rst @@ -0,0 +1,149 @@ +Pixel-Processing Accelerator (PPA) +================================== + +Introduction +------------ + +{IDF_TARGET_NAME} includes a pixel-processing accelerator (PPA) module, to realize hardware-level acceleration of image algorithms, such as image rotation, scaling, mirroring, and blending. + +Terminology +----------- + +The terms used in relation to the PPA driver are given in the table and the diagram below. + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Term + - Definition + * - Picture (pic) + - A complete image stored in the system memory. + * - Block + - A portion cropped from a picture at a certain size, with the maximum size equivalent to the entire picture. + * - Pixel + - The unit to be used in the PPA context. + * - PPA Operation + - Types of image algorithm accelerations, includes scale-rotate-mirror (SRM), blend, and fill. + * - PPA Client + - Who wants to do the PPA operations. Typically, every PPA client is hold by a specific task. + * - PPA Transaction + - One request from a PPA client to do a PPA operation is one PPA transaction. + +.. figure:: ../../../_static/diagrams/ppa/pic_blk_concept.png + :align: center + :alt: PPA picture/block terminology + + PPA picture/block terminology + +Functional Overview +------------------- + +The following sections detail the design of the PPA driver: + +- :ref:`ppa-client-registration` - Covers how to register a PPA client to perform any PPA operations. +- :ref:`ppa-register-callback` - Covers how to hook user specific code to PPA driver event callback function. +- :ref:`ppa-perform-operation` - Covers how to perform a PPA operation. +- :ref:`ppa-thread-safety` - Covers the usage of the PPA operation APIs in thread safety aspect. +- :ref:`ppa-performance-overview` - Covers the performance of PPA operations. + +.. _ppa-client-registration: + +Register PPA Client +^^^^^^^^^^^^^^^^^^^ + +Requests to perform PPA operations are made by PPA clients. Therefore, PPA clients need to be registered first before doing any PPA operations. Call :cpp:func:`ppa_register_client` function to register a new client. :cpp:type:`ppa_client_config_t` structure is used to specific the properties of the client. + +- :cpp:member:`ppa_client_config_t::oper_type` - Each PPA operation type corresponds to one PPA client type, a registered PPA client can only request one specific type of PPA operations. +- :cpp:member:`ppa_client_config_t::max_pending_trans_num` - Decides the maximum number of pending PPA transactions the client can hold. + +It is recommended that every task to register its own PPA clients. For example, an application contains two tasks: Task A requires both the PPA SRM and the PPA fill functionalities, so one PPA SRM client and one PPA fill client should be registered in Task A; While Task B also requires the PPA SRM functionality, then another PPA SRM client should be registered in Task B. + +If the task no longer needs to do PPA operations, the corresponding PPA clients can be deregistered with :cpp:func:`ppa_unregister_client` function. + +.. _ppa-register-callback: + +Register PPA Event Callbacks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When an event occurs (e.g., a PPA transaction is completed), the CPU is notified of this event via an interrupt. If some specific functions need to be called when a particular event occurs, a callback can be registered for that event by calling :cpp:func:`ppa_client_register_event_callbacks`. This can be specifically useful when ``PPA_TRANS_MODE_NON_BLOCKING`` mode is selected to perform the PPA operations. It is worth noticing that the event callbacks are bound to PPA clients, but the user context is provided per transaction in the call to the PPA operation APIs. This allows the maximum flexibility in utilizing the event callbacks. + +The registered callback functions are called in the interrupt context, therefore, the callback functions should follow common ISR (Interrupt Service Routine) rules. + +.. _ppa-perform-operation: + +Perform PPA Operations +^^^^^^^^^^^^^^^^^^^^^^ + +Once the PPA client is registered, a PPA operation can be requested with the returned :cpp:type:`ppa_client_handle_t`. + +PPA operations includes: + +Scale, Rotate, Mirror (SRM) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Call :cpp:func:`ppa_do_scale_rotate_mirror` to apply one or more of the scaling, rotation, mirroring operations to the target block inside a picture. + +Some notes to avoid confusion in configuring :cpp:type:`ppa_srm_oper_config_t`: + +.. list:: + - :cpp:member:`ppa_in_pic_blk_config_t::buffer` and :cpp:member:`ppa_out_pic_blk_config_t::buffer` have to be the pointers to different picture buffers for a SRM operation. + - The precision of :cpp:member:`ppa_srm_oper_config_t::scale_x` and :cpp:member:`ppa_srm_oper_config_t::scale_y` will be truncated to a step size of 1/16. + - Output block's width/height is totally determined by the input block's width/height, scaling factor, and rotation angle, so output block's width/height does not need to be configured. However, please make sure the output block can fit at the offset location in the output picture. + - If the color mode of the input or output picture is ``PPA_SRM_COLOR_MODE_YUV420``, then its ``pic_w``, ``pic_h``, ``block_w``, ``block_h``, ``block_offset_x``, ``block_offset_y`` fields must be even. + +Blend +~~~~~ + +Call :cpp:func:`ppa_do_blend` to blend the two target blocks of two so-called foreground (FG) and background (BG) pictures. + +Blend follows the normal Alpha Blending formula: + +:math:`A_{out} = A_b + A_f - A_b \times A_f` + +:math:`C_{out} = (C_b \times A_b \times (1 - A_f) + C_f \times A_f) / (A_b + A_f - A_b \times A_f)` + +where :math:`A_b` is the Alpha channel of the background layer, :math:`A_f` is the Alpha channel of the foreground layer, :math:`C_b` corresponds to the R, G, B components of the background layer, and :math:`C_f` corresponds to the R, G, B components of the foreground layer. + +Note that this formula is not symmetric to FG and BG. When :math:`A_f = 1`, it calculates :math:`C_{out} = C_f`, :math:`A_{out} = 1`, which means if the color mode of the FG picture is ``PPA_BLEND_COLOR_MODE_RGB565`` or ``PPA_BLEND_COLOR_MODE_RGB888``, since a Alpha value of 255 will be filled by the PPA hardware (i.e. :math:`A_f = 1`), the blended result will be identical to the FG block. + +If :cpp:member:`ppa_blend_oper_config_t::bg_ck_en` or :cpp:member:`ppa_blend_oper_config_t::fg_ck_en` is set to ``true``, the pixels fall into the color-key (also known as Chroma-key) range does not follow Alpha Blending process. Please check **{IDF_TARGET_NAME} Technical Reference Manual** > **Pixel-Processing Accelerator (PPA)** > **Functional Description** > **Layer Blending (BLEND)** [`PDF <{IDF_TARGET_TRM_EN_URL}#ppa>`__] for the detailed rules. + +Similarly, some notes to avoid confusion in configuring :cpp:type:`ppa_blend_oper_config_t`: + +.. list:: + - :cpp:member:`ppa_out_pic_blk_config_t::buffer` can be the same pointer to one of the input's :cpp:member:`ppa_in_pic_blk_config_t::buffer` for a blend operation. + - The blocks' width/height of FG and BG should be identical, and are the width/height values for the output block. + - If the color mode of the input picture is ``PPA_BLEND_COLOR_MODE_A4`` or ``PPA_BLEND_COLOR_MODE_L4``, then its ``block_w`` and ``block_offset_x`` fields must be even. + +Fill +~~~~ + +Call :cpp:func:`ppa_do_fill` to fill a target block inside a picture. + +:cpp:type:`ppa_trans_mode_t` is a field configurable to all the PPA operation APIs. It decides whether you want the call to the PPA operation API to block until the transaction finishes or to return immediately after the transaction is pushed to the internal queue. + +.. _ppa-thread-safety: + +Thread Safety +^^^^^^^^^^^^^ + +The PPA driver has guaranteed the thread safety of calling the PPA operation APIs in all following situations: + +.. list:: + - Among clients of different types in one task + - Among clients of same type in different tasks + - Among clients of different types in different tasks + +.. _ppa-performance-overview: + +Performance Overview +^^^^^^^^^^^^^^^^^^^^ + +The PPA operations are acted on the target block of an input picture. Therefore, the time it takes to complete a PPA transaction is proportional to the amount of the data of the block. The size of the entire picture has no influence on the performance. More importantly, the PPA performance highly relies on the PSRAM bandwidth if the pictures are located in the PSRAM section. When there are quite a few peripherals reading and writing to the PSRAM at the same time, the performance of PPA operation will be greatly reduced. + +API Reference +------------- + +.. include-build-file:: inc/ppa.inc +.. include-build-file:: inc/ppa_types.inc diff --git a/docs/zh_CN/api-reference/peripherals/index.rst b/docs/zh_CN/api-reference/peripherals/index.rst index 36c28c3131..0171c6a5d7 100644 --- a/docs/zh_CN/api-reference/peripherals/index.rst +++ b/docs/zh_CN/api-reference/peripherals/index.rst @@ -29,6 +29,7 @@ :SOC_MCPWM_SUPPORTED: mcpwm :SOC_PARLIO_SUPPORTED: parlio :SOC_PCNT_SUPPORTED: pcnt + :SOC_PPA_SUPPORTED: ppa :SOC_RMT_SUPPORTED: rmt :SOC_SDMMC_HOST_SUPPORTED or SOC_SDIO_SLAVE_SUPPORTED: sd_pullup_requirements :SOC_SDMMC_HOST_SUPPORTED: sdmmc_host diff --git a/docs/zh_CN/api-reference/peripherals/ppa.rst b/docs/zh_CN/api-reference/peripherals/ppa.rst new file mode 100644 index 0000000000..80c3ffb369 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/ppa.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/peripherals/ppa.rst