Merge branch 'feature/p4_lcdcam_dvp_cam_driver_v5.3' into 'release/v5.3'

feat(cam): add esp32-p4 lcd_cam dvp driver (v5.3)

See merge request espressif/esp-idf!31454
This commit is contained in:
morris 2024-06-13 11:04:54 +08:00
commit f750f4c6f7
28 changed files with 2422 additions and 4 deletions

View File

@ -22,8 +22,15 @@ if(NOT ${target} STREQUAL "linux")
list(APPEND requires esp_mm) list(APPEND requires esp_mm)
endif() endif()
if(CONFIG_SOC_LCDCAM_CAM_SUPPORTED)
list(APPEND srcs "dvp/src/esp_cam_ctlr_dvp_gdma.c" "dvp/src/esp_cam_ctlr_dvp_cam.c")
list(APPEND includes "dvp/include")
list(APPEND priv_include_dirs "dvp/private_include")
endif()
idf_component_register(SRCS ${srcs} idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${includes} INCLUDE_DIRS ${includes}
PRIV_INCLUDE_DIRS ${priv_include_dirs}
REQUIRES ${requires} REQUIRES ${requires}
PRIV_REQUIRES ${priv_requires} PRIV_REQUIRES ${priv_requires}
) )

View File

@ -1,9 +1,10 @@
menu "ESP-Driver:Camera Controller Configurations" menu "ESP-Driver:Camera Controller Configurations"
depends on SOC_MIPI_CSI_SUPPORTED depends on SOC_MIPI_CSI_SUPPORTED || SOC_LCDCAM_CAM_SUPPORTED
config CAM_CTLR_MIPI_CSI_ISR_IRAM_SAFE config CAM_CTLR_MIPI_CSI_ISR_IRAM_SAFE
bool "CSI ISR IRAM-Safe" bool "CSI ISR IRAM-Safe"
depends on SOC_MIPI_CSI_SUPPORTED
default n default n
select DW_GDMA_ISR_IRAM_SAFE select DW_GDMA_ISR_IRAM_SAFE
select DW_GDMA_CTRL_FUNC_IN_IRAM select DW_GDMA_CTRL_FUNC_IN_IRAM
@ -13,7 +14,7 @@ menu "ESP-Driver:Camera Controller Configurations"
Ensure the CSI driver ISR is IRAM-Safe. When enabled, the ISR handler Ensure the CSI driver ISR is IRAM-Safe. When enabled, the ISR handler
will be available when the cache is disabled. will be available when the cache is disabled.
config CAM_CTLR_ISP_DVP_ISR_IRAM_SAFE config CAM_CTLR_ISP_DVP_ISR_IRAM_SAFE # IDF-10093
bool "ISP_DVP ISR IRAM-Safe" bool "ISP_DVP ISR IRAM-Safe"
default n default n
select DW_GDMA_ISR_IRAM_SAFE select DW_GDMA_ISR_IRAM_SAFE
@ -24,4 +25,16 @@ menu "ESP-Driver:Camera Controller Configurations"
Ensure the ISP_DVP driver ISR is IRAM-Safe. When enabled, the ISR handler Ensure the ISP_DVP driver ISR is IRAM-Safe. When enabled, the ISR handler
will be available when the cache is disabled. will be available when the cache is disabled.
config CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE
bool "DVP ISR IRAM-Safe"
depends on SOC_LCDCAM_CAM_SUPPORTED
default n
select DW_GDMA_ISR_IRAM_SAFE
select DW_GDMA_CTRL_FUNC_IN_IRAM
select DW_GDMA_SETTER_FUNC_IN_IRAM
select DW_GDMA_GETTER_FUNC_IN_IRAM
help
Ensure the DVP driver ISR is IRAM-Safe. When enabled, the ISR handler
will be available when the cache is disabled.
endmenu # ESP Camera Controller Configurations endmenu # ESP Camera Controller Configurations

View File

@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "hal/cam_types.h"
#include "esp_cam_ctlr.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief ESP CAM DVP pins configuration
*/
typedef struct esp_cam_ctlr_dvp_pin_config {
cam_ctlr_data_width_t data_width; /*!< Number of data lines */
int data_io[CAM_DVP_DATA_SIG_NUM]; /*!< DVP data pin number */
int vsync_io; /*!< DVP V-Sync pin number */
int de_io; /*!< DVP DE pin number */
int pclk_io; /*!< DVP PCLK input pin number, clock is from camera sensor */
int xclk_io; /*!< DVP output clock pin number */
} esp_cam_ctlr_dvp_pin_config_t;
/**
* @brief ESP CAM DVP controller configurations
*/
typedef struct esp_cam_ctlr_dvp_config {
int ctlr_id; /*!< DVP controller ID */
cam_clock_source_t clk_src; /*!< DVP clock source */
uint32_t h_res; /*!< Input horizontal resolution, i.e. the number of pixels in a line */
uint32_t v_res; /*!< Input vertical resolution, i.e. the number of lines in a frame */
cam_ctlr_color_t input_data_color_type; /*!< Input pixel format */
struct {
uint32_t byte_swap_en : 1; /*!< Enable byte swap */
uint32_t bk_buffer_dis : 1; /*!< Disable backup buffer */
uint32_t pin_dont_init : 1; /*!< Don't initialize DVP pins if users have called "esp_cam_ctlr_dvp_init" before */
uint32_t pic_format_jpeg : 1; /*!< Input picture format is JPEG, if set this flag and "input_data_color_type" will be ignored */
}; /*!< Boolean Flags */
uint32_t dma_burst_size; /*!< DVP DMA burst transmission block size, set to 0 means to disable the data burst,
other value must be power of 2, e.g., 4/8/16/32/64/128 */
const esp_cam_ctlr_dvp_pin_config_t *pin; /*!< DVP pin configuration, this will be ignored by "esp_cam_new_dvp_ctlr" if "pin_dont_init" is set */
} esp_cam_ctlr_dvp_config_t;
/**
* @brief New ESP CAM DVP controller
*
* @param config DVP controller configurations
* @param ret_handle Returned CAM controller handle
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: Out of memory
* - ESP_ERR_NOT_SUPPORTED: Currently not support modes or types
* - ESP_ERR_NOT_FOUND: CSI is registered already
*/
esp_err_t esp_cam_new_dvp_ctlr(const esp_cam_ctlr_dvp_config_t *config, esp_cam_ctlr_handle_t *ret_handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_cam_ctlr_dvp.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief ESP CAM DVP initialzie clock and GPIO.
*
* @param ctlr_id CAM DVP controller ID
* @param clk_src CAM DVP clock source
* @param pin CAM DVP pin configuration
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_init(int ctlr_id, cam_clock_source_t clk_src, const esp_cam_ctlr_dvp_pin_config_t *pin);
/**
* @brief ESP CAM DVP output hardware clock
*
* @param ctlr_id CAM DVP controller ID
* @param clk_src CAM DVP clock source
* @param xclk_freq CAM DVP output clock frequency in HZ
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_output_clock(int ctlr_id, cam_clock_source_t clk_src, uint32_t xclk_freq);
/**
* @brief ESP CAM DVP de-initialzie.
*
* @param ctlr_id DVP controller ID
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_deinit(int ctlr_id);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "freertos/FreeRTOS.h"
#include "esp_cam_ctlr_interface.h"
#include "hal/cam_hal.h"
#include "esp_cam_ctlr_dvp_dma.h"
#include "esp_cam_ctlr_dvp.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief DVP CAM finite state machine
*/
typedef enum esp_cam_ctlr_dvp_cam_fsm {
ESP_CAM_CTLR_DVP_CAM_FSM_INIT = 1, /*!< DVP CAM initialization state, and next state is "enabled" */
ESP_CAM_CTLR_DVP_CAM_FSM_ENABLED, /*!< DVP CAM enabled state, and next state is "init" or "started" */
ESP_CAM_CTLR_DVP_CAM_FSM_STARTED, /*!< DVP CAM started state, and next state is "init" or "enabled" */
} esp_cam_ctlr_dvp_cam_fsm_t;
/**
* @brief DVP CAM object data
*/
typedef struct esp_cam_ctlr_dvp_cam {
esp_cam_ctlr_t base; /*!< Camera controller base object data */
esp_cam_ctlr_evt_cbs_t cbs; /*!< Camera controller callback functions */
void *cbs_user_data; /*!< Camera controller callback private data */
int ctlr_id; /*!< DVP CAM port ID */
cam_ctlr_color_t input_data_color_type; /*!< DVP CAM input pixel format */
struct {
uint32_t bk_buffer_dis : 1; /*!< Disable backup buffer */
uint32_t pin_dont_init : 1; /*!< Don't initialize DVP pins if users have called "esp_cam_ctlr_dvp_init" before */
uint32_t pic_format_jpeg : 1; /*!< Input picture format is JPEG, if set this flag and "input_data_color_type" will be ignored */
}; /*!< Boolean Flags */
cam_hal_context_t hal; /*!< DVP CAM HAL object data */
esp_cam_ctlr_dvp_dma_t dma; /*!< DVP CAM DMA object data */
size_t fb_size_in_bytes; /*!< DVP CAM frame buffer maximum size in byte */
esp_cam_ctlr_dvp_cam_fsm_t dvp_fsm; /*!< DVP CAM finite state machine */
uint8_t *cur_buf; /*!< DVP CAM current buffer which is receiving stream */
uint8_t *backup_buffer; /*!< DVP CAM backup buffer */
bool bk_buffer_exposed; /*!< status of if back_buffer is exposed to users */
portMUX_TYPE spinlock; /*!< DVP CAM spinlock */
} esp_cam_ctlr_dvp_cam_t;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "hal/dma_types.h"
#include "esp_private/gdma.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_CAM_CTLR_DVP_DMA_DESC_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED
/**
* @brief DVP DMA description object
*/
typedef dma_descriptor_align8_t esp_cam_ctlr_dvp_dma_desc_t;
/**
* @brief DVP DMA object
*/
typedef struct esp_cam_ctlr_dvp_dma {
gdma_channel_handle_t dma_chan; /*!< DVP DMA channel handle */
size_t size; /*!< DVP DMA buffer size */
esp_cam_ctlr_dvp_dma_desc_t *desc; /*!< DVP DMA description buffer */
size_t desc_count; /*!< DVP DMA description count */
size_t desc_size; /*!< DVP DMA description buffer size in byte */
} esp_cam_ctlr_dvp_dma_t;
/**
* @brief Initialize DVP DMA object
*
* @param dma DVP DMA object pointer
* @param burst_size DVP DMA burst transmission block size
* @param size DVP DMA buffer size
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_init(esp_cam_ctlr_dvp_dma_t *dma, uint32_t burst_size, size_t size);
/**
* @brief De-initialize DVP DMA object
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_deinit(esp_cam_ctlr_dvp_dma_t *dma);
/**
* @brief Set DVP DMA descriptor address and start engine
*
* @param dma DVP DMA object pointer
* @param buffer DVP DMA buffer pointer
* @param size DVP DMA buffer size
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_start(esp_cam_ctlr_dvp_dma_t *dma, uint8_t *buffer, size_t size);
/**
* @brief Stop DVP DMA engine
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_stop(esp_cam_ctlr_dvp_dma_t *dma);
/**
* @brief Reset DVP DMA FIFO and internal finite state machine
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_reset(esp_cam_ctlr_dvp_dma_t *dma);
/**
* @brief Get DMA received data size
*
* @param dma DVP DMA object pointer
*
* @return DMA received data size
*/
size_t esp_cam_ctlr_dvp_dma_get_recv_size(esp_cam_ctlr_dvp_dma_t *dma);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,764 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/param.h>
#include "hal/gpio_ll.h"
#include "hal/cam_ll.h"
#include "driver/gpio.h"
#include "esp_cache.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/esp_cache_private.h"
#include "esp_private/gpio.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_clk_tree.h"
#include "soc/cam_periph.h"
#include "esp_cam_ctlr_dvp_cam.h"
#include "esp_private/esp_cam_dvp.h"
#include "../../dvp_share_ctrl.h"
#ifdef CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE
#define CAM_DVP_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define CAM_DVP_MEM_ALLOC_CAPS (MALLOC_CAP_DEFAULT)
#endif
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define DVP_CAM_CONFIG_INPUT_PIN(pin, sig, inv) \
{ \
ret = esp_cam_ctlr_dvp_config_input_gpio(pin, sig, inv); \
if (ret != ESP_OK) { \
ESP_LOGE(TAG, "failed to configure pin=%d sig=%d", \
pin, sig); \
return ret; \
} \
}
typedef struct dvp_platform {
_lock_t mutex;
esp_cam_ctlr_dvp_cam_t *ctlrs[CAP_DVP_PERIPH_NUM];
} dvp_platform_t;
static dvp_platform_t s_platform;
static const char *TAG = "dvp_cam";
/**
* @brief Claim DVP controller
*
* @param ctlr_id DVP controller ID
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t s_dvp_claim_ctlr(int ctlr_id, esp_cam_ctlr_dvp_cam_t *ctlr)
{
esp_err_t ret = ESP_ERR_NOT_FOUND;
_lock_acquire(&s_platform.mutex);
if (!s_platform.ctlrs[ctlr_id]) {
s_platform.ctlrs[ctlr_id] = ctlr;
ret = ESP_OK;
}
_lock_release(&s_platform.mutex);
return ret;
}
/**
* @brief Declaim DVP controller
*
* @param ctlr_id DVP controller ID
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t s_dvp_declaim_ctlr(int ctlr_id)
{
esp_err_t ret = ESP_ERR_NOT_FOUND;
_lock_acquire(&s_platform.mutex);
if (s_platform.ctlrs[ctlr_id]) {
s_platform.ctlrs[ctlr_id] = NULL;
ret = ESP_OK;
}
_lock_release(&s_platform.mutex);
return ret;
}
/**
* @brief CAM DVP initialzie input GPIO pin.
*
* @param pin DVP pin number
* @param signal DVP pin mapping signal
* @param inv true: DVP pin is inverted, false: DVP pin is not inverted
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_config_input_gpio(int pin, int signal, bool inv)
{
esp_err_t ret;
gpio_func_sel(pin, PIN_FUNC_GPIO);
ret = gpio_set_direction(pin, GPIO_MODE_INPUT);
if (ret != ESP_OK) {
return ret;
}
ret = gpio_set_pull_mode(pin, GPIO_FLOATING);
if (ret != ESP_OK) {
return ret;
}
esp_rom_gpio_connect_in_signal(pin, signal, inv);
return ESP_OK;
}
/**
* @brief CAM DVP start receiving frame.
*
* @param ctlr CAM DVP controller
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static IRAM_ATTR esp_err_t esp_cam_ctlr_dvp_start_trans(esp_cam_ctlr_dvp_cam_t *ctlr)
{
bool buffer_ready = false;
esp_cam_ctlr_trans_t trans;
if (ctlr->cur_buf) {
ctlr->cur_buf = NULL;
cam_hal_stop_streaming(&ctlr->hal);
ESP_RETURN_ON_ERROR_ISR(esp_cam_ctlr_dvp_dma_stop(&ctlr->dma), TAG, "failed to stop DMA");
}
if (ctlr->cbs.on_get_new_trans && ctlr->cbs.on_get_new_trans(&(ctlr->base), &trans, ctlr->cbs_user_data)) {
buffer_ready = true;
} else if (!ctlr->bk_buffer_dis) {
trans.buffer = ctlr->backup_buffer;
trans.buflen = ctlr->fb_size_in_bytes;
buffer_ready = true;
}
if (!buffer_ready) {
assert(false && "no new buffer, and no driver internal buffer");
}
ESP_RETURN_ON_ERROR_ISR(esp_cam_ctlr_dvp_dma_reset(&ctlr->dma), TAG, "failed to reset DMA");
ESP_RETURN_ON_ERROR_ISR(esp_cam_ctlr_dvp_dma_start(&ctlr->dma, trans.buffer, ctlr->fb_size_in_bytes), TAG, "failed to start DMA");
cam_hal_start_streaming(&ctlr->hal);
ctlr->cur_buf = trans.buffer;
return ESP_OK;
}
/**
* @brief Check JPEG file and return JPEG frame actual size
*
* @param buffer JPEG buffer pointer
* @param size JPEG buffer size
*
* @return JPEG frame actual size if success or 0 if not JPEG header or tail TAG is found
*/
static uint32_t IRAM_ATTR esp_cam_ctlr_dvp_get_jpeg_size(const uint8_t *buffer, uint32_t size)
{
/* Check JPEG header TAG: ff:d8 */
if (buffer[0] != 0xff || buffer[1] != 0xd8) {
return 0;
}
for (uint32_t off = size - 2; off > 2; off--) {
/* Check JPEG tail TAG: ff:d9 */
if (buffer[off] == 0xff && buffer[off + 1] == 0xd9) {
return off + 2;
}
}
return 0;
}
/**
* @brief CAM DVP get actually received data size in byte
*
* @param ctlr CAM DVP controller
* @param rx_buffer CAM DVP receive buffer
* @param dma_recv_size CAM DVP DMA receive buffer size
*
* @return Received data size if success or 0 if failed
*/
static uint32_t IRAM_ATTR esp_cam_ctlr_dvp_get_recved_size(esp_cam_ctlr_dvp_cam_t *ctlr, uint8_t *rx_buffer, uint32_t dma_recv_size)
{
esp_err_t ret;
uint32_t recv_buffer_size;
if (ctlr->pic_format_jpeg) {
recv_buffer_size = ALIGN_UP_BY(MIN(dma_recv_size, ctlr->fb_size_in_bytes), 64);
} else {
recv_buffer_size = ctlr->fb_size_in_bytes;
}
ret = esp_cache_msync(rx_buffer, recv_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
assert(ret == ESP_OK);
if (ctlr->pic_format_jpeg) {
recv_buffer_size = esp_cam_ctlr_dvp_get_jpeg_size(rx_buffer, dma_recv_size);
}
return recv_buffer_size;
}
/**
* @brief CAM DVP calculate frame receive buffer size.
*
* @param config CAM DVP controller configurations
* @param p_size CAM DVP frame receive buffer size buffer pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_get_frame_size(const esp_cam_ctlr_dvp_config_t *config, size_t *p_size)
{
esp_err_t ret = ESP_OK;
if (config->pic_format_jpeg) {
*p_size = config->h_res * config->v_res;
} else {
switch (config->input_data_color_type) {
case CAM_CTLR_COLOR_RGB565:
*p_size = config->h_res * config->v_res * 2;
break;
case CAM_CTLR_COLOR_YUV422:
*p_size = config->h_res * config->v_res * 2;
break;
case CAM_CTLR_COLOR_YUV420:
*p_size = (config->h_res * config->v_res / 2) * 3;
break;
case CAM_CTLR_COLOR_RGB888:
*p_size = config->h_res * config->v_res * 3;
break;
default:
ret = ESP_ERR_INVALID_ARG;
break;
}
}
return ret;
}
/**
* @brief CAM DVP receive frame done interrupt callback function.
*
* @param dma_chan DMA channel
* @param event_data DMA event data
* @param user_data DMA interrupt callback user data
*
* @return
* - true: trigger task switch
* - false: don't trigger task switch
*/
static IRAM_ATTR bool esp_cam_ctlr_recv_frame_done_isr(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
bool need_switch = false;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)user_data;
size_t dma_recv_size = esp_cam_ctlr_dvp_dma_get_recv_size(&ctlr->dma);
esp_cam_ctlr_trans_t trans = {
.buffer = ctlr->cur_buf,
};
esp_cam_ctlr_dvp_start_trans(ctlr);
if ((trans.buffer != ctlr->backup_buffer) || ctlr->bk_buffer_exposed) {
trans.buflen = ctlr->fb_size_in_bytes;
trans.received_size = esp_cam_ctlr_dvp_get_recved_size(ctlr, trans.buffer, dma_recv_size);
need_switch = ctlr->cbs.on_trans_finished(&ctlr->base, &trans, ctlr->cbs_user_data);
}
return need_switch;
}
/**
* @brief ESP CAM DVP initialzie clock and GPIO.
*
* @param ctlr_id CAM DVP controller ID
* @param clk_src CAM DVP clock source
* @param pin CAM DVP pin configuration
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_init(int ctlr_id, cam_clock_source_t clk_src, const esp_cam_ctlr_dvp_pin_config_t *pin)
{
esp_err_t ret;
ESP_RETURN_ON_FALSE(ctlr_id < CAP_DVP_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid argument: ctlr_id >= %d", CAP_DVP_PERIPH_NUM);
ESP_RETURN_ON_FALSE(pin->data_width == CAM_CTLR_DATA_WIDTH_8, ESP_ERR_INVALID_ARG, TAG, "invalid argument: data_width != CAM_CTLR_DATA_WIDTH_8");
ESP_RETURN_ON_FALSE(pin, ESP_ERR_INVALID_ARG, TAG, "invalid argument: pin is null");
DVP_CAM_CONFIG_INPUT_PIN(pin->vsync_io, cam_periph_signals.buses[ctlr_id].vsync_sig, true);
DVP_CAM_CONFIG_INPUT_PIN(pin->de_io, cam_periph_signals.buses[ctlr_id].de_sig, false);
DVP_CAM_CONFIG_INPUT_PIN(pin->pclk_io, cam_periph_signals.buses[ctlr_id].pclk_sig, false);
for (int i = 0; i < pin->data_width; i++) {
DVP_CAM_CONFIG_INPUT_PIN(pin->data_io[i], cam_periph_signals.buses[ctlr_id].data_sigs[i], false);
}
ret = gpio_set_direction(pin->xclk_io, GPIO_MODE_OUTPUT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "failed to configure pin=%d", pin->xclk_io);
return ret;
}
esp_rom_gpio_connect_out_signal(pin->xclk_io, cam_periph_signals.buses[ctlr_id].clk_sig, false, false);
PERIPH_RCC_ACQUIRE_ATOMIC(cam_periph_signals.buses[ctlr_id].module, ref_count) {
if (ref_count == 0) {
cam_ll_enable_bus_clock(ctlr_id, true);
cam_ll_reset_register(ctlr_id);
}
}
PERIPH_RCC_ATOMIC() {
cam_ll_enable_clk(ctlr_id, true);
cam_ll_select_clk_src(ctlr_id, clk_src);
};
return ESP_OK;
}
/**
* @brief ESP CAM DVP output hardware clock
*
* @param ctlr_id CAM DVP controller ID
* @param clk_src CAM DVP clock source
* @param xclk_freq CAM DVP output clock frequency in HZ
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_output_clock(int ctlr_id, cam_clock_source_t clk_src, uint32_t xclk_freq)
{
esp_err_t ret = ESP_ERR_INVALID_ARG;
uint32_t src_clk_hz;
ESP_RETURN_ON_FALSE(ctlr_id < CAP_DVP_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid argument: ctlr_id >= %d", CAP_DVP_PERIPH_NUM);
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
TAG, "failed to get clock source frequency");
ESP_LOGD(TAG, "DVP clock source frequency %" PRIu32 "Hz", src_clk_hz);
if ((src_clk_hz % xclk_freq) == 0) {
PERIPH_RCC_ATOMIC() {
cam_ll_set_group_clock_coeff(ctlr_id, src_clk_hz / xclk_freq, 0, 0);
};
ret = ESP_OK;
}
return ret;
}
/**
* @brief ESP CAM DVP de-initialzie.
*
* @param ctlr_id DVP controller ID
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_deinit(int ctlr_id)
{
ESP_RETURN_ON_FALSE(ctlr_id < CAP_DVP_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid argument: ctlr_id >= %d", CAP_DVP_PERIPH_NUM);
PERIPH_RCC_ATOMIC() {
cam_ll_enable_clk(ctlr_id, false);
};
PERIPH_RCC_ACQUIRE_ATOMIC(cam_periph_signals.buses[ctlr_id].module, ref_count) {
if (ref_count == 0) {
cam_ll_reset_register(ctlr_id);
cam_ll_enable_bus_clock(ctlr_id, false);
}
}
return ESP_OK;
}
/**
* @brief Enable CAM DVP camera controller
*
* @param handle ESP CAM controller handle
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_enable(esp_cam_ctlr_handle_t handle)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle is null");
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_INIT) {
ctlr->dvp_fsm = ESP_CAM_CTLR_DVP_CAM_FSM_ENABLED;
ret = ESP_OK;
}
portEXIT_CRITICAL(&ctlr->spinlock);
return ret;
}
/**
* @brief Disable CAM DVP camera controller
*
* @param handle ESP CAM controller handle
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_disable(esp_cam_ctlr_handle_t handle)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle is null");
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_ENABLED) {
ctlr->dvp_fsm = ESP_CAM_CTLR_DVP_CAM_FSM_INIT;
ret = ESP_OK;
}
portEXIT_CRITICAL(&ctlr->spinlock);
return ret;
}
/**
* @brief Start CAM DVP camera controller
*
* @param handle ESP CAM controller handle
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_start(esp_cam_ctlr_handle_t handle)
{
bool start = false;
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle is null");
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_ENABLED) {
ctlr->cur_buf = NULL;
ctlr->dvp_fsm = ESP_CAM_CTLR_DVP_CAM_FSM_STARTED;
start = true;
}
portEXIT_CRITICAL(&ctlr->spinlock);
if (start) {
ret = esp_cam_ctlr_dvp_start_trans(ctlr);
}
return ret;
}
/**
* @brief Stop CAM DVP camera controller
*
* @param handle ESP CAM controller handle
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_stop(esp_cam_ctlr_handle_t handle)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle is null");
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_STARTED) {
cam_hal_stop_streaming(&ctlr->hal);
esp_cam_ctlr_dvp_dma_stop(&ctlr->dma);
ctlr->dvp_fsm = ESP_CAM_CTLR_DVP_CAM_FSM_ENABLED;
ret = ESP_OK;
}
portEXIT_CRITICAL(&ctlr->spinlock);
return ret;
}
/**
* @brief Delete CAM DVP camera controller
*
* @param handle ESP CAM controller handle
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_del(esp_cam_ctlr_handle_t handle)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle is null");
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_INIT) {
esp_cam_ctlr_dvp_dma_stop(&ctlr->dma);
cam_hal_stop_streaming(&ctlr->hal);
esp_cam_ctlr_dvp_dma_deinit(&ctlr->dma);
if (!ctlr->pin_dont_init) {
esp_cam_ctlr_dvp_deinit(ctlr->ctlr_id);
}
cam_hal_deinit(&ctlr->hal);
if (!ctlr->bk_buffer_dis) {
heap_caps_free(ctlr->backup_buffer);
}
s_dvp_declaim_ctlr(ctlr->ctlr_id);
heap_caps_free(ctlr);
dvp_shared_ctrl_declaim_io_signals();
ret = ESP_OK;
}
return ret;
}
/**
* @brief Register CAM DVP camera controller event callbacks
*
* @param handle ESP CAM controller handle
* @param cbs ESP CAM controller event callbacks
* @param user_data ESP CAM controller event user data
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_register_event_callbacks(esp_cam_ctlr_handle_t handle, const esp_cam_ctlr_evt_cbs_t *cbs, void *user_data)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(handle && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle or cbs is null");
ESP_RETURN_ON_FALSE(cbs->on_trans_finished, ESP_ERR_INVALID_ARG, TAG, "invalid argument: on_trans_finished is null");
ESP_RETURN_ON_FALSE(cbs->on_get_new_trans || !ctlr->bk_buffer_dis, ESP_ERR_INVALID_ARG, TAG, "invalid argument: on_get_new_trans is null");
#if CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE
if (cbs->on_get_new_trans) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_get_new_trans), ESP_ERR_INVALID_ARG, TAG, "on_get_new_trans callback not in IRAM");
}
if (cbs->on_trans_finished) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_finished), ESP_ERR_INVALID_ARG, TAG, "on_trans_finished callback not in IRAM");
}
#endif
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_INIT) {
ctlr->cbs.on_get_new_trans = cbs->on_get_new_trans;
ctlr->cbs.on_trans_finished = cbs->on_trans_finished;
ctlr->cbs_user_data = user_data;
ret = ESP_OK;
}
portEXIT_CRITICAL(&ctlr->spinlock);
return ret;
}
/**
* @brief Get AM DVP camera controller backup buffer pointer
*
* @param handle ESP CAM controller handle
* @param fb_num Backup buffer pointer storage buffer number
* @param fb0 Backup buffer pointer storage buffer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_dvp_cam_get_internal_buffer(esp_cam_ctlr_handle_t handle, uint32_t fb_num, const void **fb0, ...)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr->backup_buffer, ESP_ERR_INVALID_STATE, TAG, "back_buffer is not available");
ESP_RETURN_ON_FALSE(fb_num && fb_num <= 1, ESP_ERR_INVALID_ARG, TAG, "invalid frame buffer number");
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_INIT) {
va_list args;
const void **fb_itor = fb0;
va_start(args, fb0);
for (uint32_t i = 0; i < fb_num; i++) {
if (fb_itor) {
*fb_itor = ctlr->backup_buffer;
fb_itor = va_arg(args, const void **);
}
}
va_end(args);
ctlr->bk_buffer_exposed = true;
ret = ESP_OK;
}
portEXIT_CRITICAL(&ctlr->spinlock);
return ret;
}
/**
* @brief Get CAM DVP camera controller frame buffer length
*
* @param handle ESP CAM controller handle
* @param ret_fb_len The size of each frame buffer in bytes
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static esp_err_t esp_cam_ctlr_get_dvp_cam_frame_buffer_len(esp_cam_ctlr_handle_t handle, size_t *ret_fb_len)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
esp_cam_ctlr_dvp_cam_t *ctlr = (esp_cam_ctlr_dvp_cam_t *)handle;
ESP_RETURN_ON_FALSE(ctlr && ret_fb_len, ESP_ERR_INVALID_ARG, TAG, "invalid argument: handle or ret_fb_len is null");
portENTER_CRITICAL(&ctlr->spinlock);
if (ctlr->dvp_fsm == ESP_CAM_CTLR_DVP_CAM_FSM_INIT) {
*ret_fb_len = ctlr->fb_size_in_bytes;
ret = ESP_OK;
}
portEXIT_CRITICAL(&ctlr->spinlock);
return ret;
}
/**
* @brief New ESP CAM DVP controller
*
* @param config DVP controller configurations
* @param ret_handle Returned CAM controller handle
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: Out of memory
* - ESP_ERR_NOT_SUPPORTED: Currently not support modes or types
* - ESP_ERR_NOT_FOUND: CSI is registered already
*/
esp_err_t esp_cam_new_dvp_ctlr(const esp_cam_ctlr_dvp_config_t *config, esp_cam_ctlr_handle_t *ret_handle)
{
esp_err_t ret;
size_t fb_size_in_bytes;
size_t alignment_size;
ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument: config or ret_handle is null");
ESP_RETURN_ON_FALSE(config->ctlr_id < CAP_DVP_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid argument: ctlr_id >= %d", CAP_DVP_PERIPH_NUM);
ESP_RETURN_ON_FALSE(config->pin_dont_init || config->pin, ESP_ERR_INVALID_ARG, TAG, "invalid argument: pin_dont_init is unset and pin is null");
ESP_RETURN_ON_ERROR(esp_cache_get_alignment(MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA, &alignment_size), TAG, "failed to get cache alignment");
ESP_RETURN_ON_ERROR(esp_cam_ctlr_dvp_cam_get_frame_size(config, &fb_size_in_bytes), TAG, "invalid argument: input frame pixel format is not supported");
ESP_RETURN_ON_ERROR(dvp_shared_ctrl_claim_io_signals(), TAG, "failed to claim io signals");
esp_cam_ctlr_dvp_cam_t *ctlr = heap_caps_calloc(1, sizeof(esp_cam_ctlr_dvp_cam_t), CAM_DVP_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr, ESP_ERR_NO_MEM, fail0, TAG, "no mem for CAM DVP controller context");
ESP_GOTO_ON_ERROR(s_dvp_claim_ctlr(config->ctlr_id, ctlr), fail1, TAG, "no available DVP controller");
ESP_LOGD(TAG, "alignment: 0x%x\n", alignment_size);
fb_size_in_bytes = ALIGN_UP_BY(fb_size_in_bytes, alignment_size);
if (!config->bk_buffer_dis) {
ctlr->backup_buffer = heap_caps_aligned_alloc(alignment_size, fb_size_in_bytes, MALLOC_CAP_SPIRAM);
ESP_GOTO_ON_FALSE(ctlr->backup_buffer, ESP_ERR_NO_MEM, fail2, TAG, "no mem for DVP backup buffer");
}
ESP_GOTO_ON_ERROR(esp_cam_ctlr_dvp_dma_init(&ctlr->dma, config->dma_burst_size, fb_size_in_bytes),
fail3, TAG, "failed to initialize DVP DMA");
gdma_rx_event_callbacks_t cbs = {
.on_recv_eof = esp_cam_ctlr_recv_frame_done_isr
};
ESP_GOTO_ON_ERROR(gdma_register_rx_event_callbacks(ctlr->dma.dma_chan, &cbs, ctlr),
fail4, TAG, "failed to register DMA event callbacks");
/* Initialize DVP controller */
cam_hal_config_t cam_hal_config = {
.port = config->ctlr_id,
.byte_swap_en = config->byte_swap_en,
};
cam_hal_init(&ctlr->hal, &cam_hal_config);
if (!config->pin_dont_init) {
ESP_GOTO_ON_ERROR(esp_cam_ctlr_dvp_init(config->ctlr_id, config->clk_src, config->pin),
fail5, TAG, "failed to initialize clock and GPIO");
}
ctlr->ctlr_id = config->ctlr_id;
ctlr->fb_size_in_bytes = fb_size_in_bytes;
ctlr->dvp_fsm = ESP_CAM_CTLR_DVP_CAM_FSM_INIT;
ctlr->input_data_color_type = config->input_data_color_type;
ctlr->pic_format_jpeg = config->pic_format_jpeg;
ctlr->bk_buffer_dis = config->bk_buffer_dis;
ctlr->pin_dont_init = config->pin_dont_init;
ctlr->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
ctlr->base.del = esp_cam_ctlr_dvp_cam_del;
ctlr->base.enable = esp_cam_ctlr_dvp_cam_enable;
ctlr->base.start = esp_cam_ctlr_dvp_cam_start;
ctlr->base.stop = esp_cam_ctlr_dvp_cam_stop;
ctlr->base.disable = esp_cam_ctlr_dvp_cam_disable;
ctlr->base.register_event_callbacks = esp_cam_ctlr_dvp_cam_register_event_callbacks;
ctlr->base.get_internal_buffer = esp_cam_ctlr_dvp_cam_get_internal_buffer;
ctlr->base.get_buffer_len = esp_cam_ctlr_get_dvp_cam_frame_buffer_len;
*ret_handle = &ctlr->base;
return ESP_OK;
fail5:
cam_hal_deinit(&ctlr->hal);
fail4:
esp_cam_ctlr_dvp_dma_deinit(&ctlr->dma);
fail3:
if (!config->bk_buffer_dis) {
heap_caps_free(ctlr->backup_buffer);
}
fail2:
s_dvp_declaim_ctlr(config->ctlr_id);
fail1:
heap_caps_free(ctlr);
fail0:
dvp_shared_ctrl_declaim_io_signals();
return ret;
}

View File

@ -0,0 +1,202 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/param.h>
#include "esp_heap_caps.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_private/esp_cache_private.h"
#include "esp_cam_ctlr_dvp_dma.h"
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
static const char *TAG = "dvp_gdma";
/**
* @brief Configure DMA description
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
static void IRAM_ATTR esp_cam_ctlr_dvp_config_dma_desc(esp_cam_ctlr_dvp_dma_desc_t *desc, uint8_t *buffer, uint32_t size)
{
size_t n = 0;
while (size) {
uint32_t node_size = MIN(size, ESP_CAM_CTLR_DVP_DMA_DESC_BUFFER_MAX_SIZE);
desc[n].dw0.size = node_size;
desc[n].dw0.length = 0;
desc[n].dw0.err_eof = 0;
desc[n].dw0.suc_eof = 0;
desc[n].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc[n].buffer = (uint8_t *)buffer;
desc[n].next = &desc[n + 1];
size -= node_size;
buffer += node_size;
n++;
}
desc[n - 1].next = NULL;
}
/**
* @brief Initialize DVP DMA object
*
* @param dma DVP DMA object pointer
* @param burst_size DVP DMA burst transmission block size
* @param size DVP DMA buffer size
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_init(esp_cam_ctlr_dvp_dma_t *dma, uint32_t burst_size, size_t size)
{
esp_err_t ret = ESP_OK;
size_t alignment_size;
gdma_channel_alloc_config_t rx_alloc_config = {
.direction = GDMA_CHANNEL_DIRECTION_RX,
};
ESP_RETURN_ON_ERROR(esp_cache_get_alignment(MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA, &alignment_size), TAG, "failed to get cache alignment");
ESP_RETURN_ON_ERROR(gdma_new_axi_channel(&rx_alloc_config, &dma->dma_chan), TAG, "new channel failed");
ESP_GOTO_ON_ERROR(gdma_connect(dma->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_CAM, 0)), fail0, TAG, "connect failed");
gdma_strategy_config_t strategy_config = {
.auto_update_desc = false,
.owner_check = true
};
ESP_GOTO_ON_ERROR(gdma_apply_strategy(dma->dma_chan, &strategy_config), fail1, TAG, "apply strategy failed");
// set DMA transfer ability
gdma_transfer_config_t transfer_config = {
.max_data_burst_size = burst_size,
.access_ext_mem = true,
};
ESP_GOTO_ON_ERROR(gdma_config_transfer(dma->dma_chan, &transfer_config), fail1, TAG, "set trans ability failed");
dma->desc_count = size / ESP_CAM_CTLR_DVP_DMA_DESC_BUFFER_MAX_SIZE;
if (size % ESP_CAM_CTLR_DVP_DMA_DESC_BUFFER_MAX_SIZE) {
dma->desc_count++;
}
dma->size = size;
ESP_LOGD(TAG, "alignment: 0x%x\n", alignment_size);
dma->desc_size = ALIGN_UP_BY(dma->desc_count * sizeof(esp_cam_ctlr_dvp_dma_desc_t), alignment_size);
dma->desc = heap_caps_aligned_alloc(alignment_size, dma->desc_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(dma->desc, ESP_ERR_NO_MEM, fail1, TAG, "no mem for DVP DMA descriptor");
return ESP_OK;
fail1:
gdma_disconnect(dma->dma_chan);
fail0:
gdma_del_channel(dma->dma_chan);
return ret;
}
/**
* @brief De-initialize DVP DMA object
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t esp_cam_ctlr_dvp_dma_deinit(esp_cam_ctlr_dvp_dma_t *dma)
{
ESP_RETURN_ON_ERROR(gdma_disconnect(dma->dma_chan), TAG, "disconnect dma channel failed");
ESP_RETURN_ON_ERROR(gdma_del_channel(dma->dma_chan), TAG, "delete dma channel failed");
heap_caps_free(dma->desc);
return ESP_OK;
}
/**
* @brief Set DVP DMA descriptor address and start engine
*
* @param dma DVP DMA object pointer
* @param buffer DVP DMA buffer pointer
* @param size DVP DMA buffer size
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t IRAM_ATTR esp_cam_ctlr_dvp_dma_start(esp_cam_ctlr_dvp_dma_t *dma, uint8_t *buffer, size_t size)
{
esp_err_t ret;
ESP_RETURN_ON_FALSE_ISR(dma, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE_ISR(dma->size >= size, ESP_ERR_INVALID_ARG, TAG, "input buffer size is out of range");
esp_cam_ctlr_dvp_config_dma_desc(dma->desc, buffer, size);
ret = esp_cache_msync(dma->desc, dma->desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
assert(ret == ESP_OK);
return gdma_start(dma->dma_chan, (intptr_t)dma->desc);
}
/**
* @brief Stop DVP DMA engine
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t IRAM_ATTR esp_cam_ctlr_dvp_dma_stop(esp_cam_ctlr_dvp_dma_t *dma)
{
return gdma_stop(dma->dma_chan);
}
/**
* @brief Reset DVP DMA FIFO and internal finite state machine
*
* @param dma DVP DMA object pointer
*
* @return
* - ESP_OK on success
* - Others if failed
*/
esp_err_t IRAM_ATTR esp_cam_ctlr_dvp_dma_reset(esp_cam_ctlr_dvp_dma_t *dma)
{
return gdma_reset(dma->dma_chan);
}
/**
* @brief Get DMA received data size
*
* @param dma DVP DMA object pointer
*
* @return DMA received data size
*/
size_t IRAM_ATTR esp_cam_ctlr_dvp_dma_get_recv_size(esp_cam_ctlr_dvp_dma_t *dma)
{
size_t recv_size = 0;
for (int i = 0; i < dma->desc_count; i++) {
recv_size += dma->desc[i].dw0.length;
if (dma->desc[i].dw0.suc_eof) {
break;
}
}
return recv_size;
}

View File

@ -4,6 +4,12 @@ components/esp_driver_cam/test_apps/csi:
depends_components: depends_components:
- esp_driver_cam - esp_driver_cam
components/esp_driver_cam/test_apps/dvp:
disable:
- if: SOC_LCDCAM_CAM_SUPPORTED != 1
depends_components:
- esp_driver_cam
components/esp_driver_cam/test_apps/isp_dvp: components/esp_driver_cam/test_apps/isp_dvp:
disable: disable:
- if: SOC_ISP_DVP_SUPPORTED != 1 - if: SOC_ISP_DVP_SUPPORTED != 1

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.16)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_dvp)

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |

View File

@ -0,0 +1,16 @@
set(srcs "test_app_main.c")
if(CONFIG_SOC_LCDCAM_CAM_SUPPORTED)
list(APPEND srcs "test_dvp_driver.c")
endif()
set(priv_requires
unity
esp_driver_cam
esp_psram
)
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
PRIV_REQUIRES ${priv_requires}
WHOLE_ARCHIVE TRUE)

View File

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#include "unity_test_utils.h"
#include "esp_heap_caps.h"
#include "sdkconfig.h"
#define TEST_MEMORY_LEAK_THRESHOLD (400)
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD);
}
void app_main(void)
{
unity_run_menu();
}

View File

@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "unity.h"
#include "esp_cam_ctlr_dvp.h"
#include "esp_cam_ctlr.h"
TEST_CASE("TEST DVP driver allocation", "[DVP]")
{
esp_cam_ctlr_dvp_config_t dvp_config = {
.ctlr_id = 0,
.clk_src = CAM_CLK_SRC_DEFAULT,
.h_res = 800,
.v_res = 640,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.byte_swap_en = false,
.pin_dont_init = true,
};
esp_cam_ctlr_handle_t handle = NULL;
TEST_ESP_OK(esp_cam_new_dvp_ctlr(&dvp_config, &handle));
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, esp_cam_new_dvp_ctlr(&dvp_config, &handle));
uint8_t *bk_buffer = NULL;
size_t bk_buffer_len = 0;
TEST_ESP_OK(esp_cam_ctlr_get_frame_buffer(handle, 1, (const void **)&bk_buffer));
TEST_ESP_OK(esp_cam_ctlr_get_frame_buffer_len(handle, &bk_buffer_len));
TEST_ASSERT_NOT_NULL(bk_buffer);
TEST_ASSERT_EQUAL((dvp_config.h_res * dvp_config.v_res * 2), bk_buffer_len); // type RGB565 using 2 byte / pixel
TEST_ESP_OK(esp_cam_ctlr_del(handle));
}
TEST_CASE("TEST DVP driver allocation with JPEG input", "[DVP]")
{
esp_cam_ctlr_dvp_config_t dvp_config = {
.ctlr_id = 0,
.clk_src = CAM_CLK_SRC_DEFAULT,
.h_res = 800,
.v_res = 640,
.dma_burst_size = 128,
.byte_swap_en = false,
.pin_dont_init = true,
.pic_format_jpeg = true,
};
esp_cam_ctlr_handle_t handle = NULL;
TEST_ESP_OK(esp_cam_new_dvp_ctlr(&dvp_config, &handle));
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, esp_cam_new_dvp_ctlr(&dvp_config, &handle));
uint8_t *bk_buffer = NULL;
size_t bk_buffer_len = 0;
TEST_ESP_OK(esp_cam_ctlr_get_frame_buffer(handle, 1, (const void **)&bk_buffer));
TEST_ESP_OK(esp_cam_ctlr_get_frame_buffer_len(handle, &bk_buffer_len));
TEST_ASSERT_NOT_NULL(bk_buffer);
TEST_ASSERT_EQUAL((dvp_config.h_res * dvp_config.v_res * 1), bk_buffer_len); // type JPEG using 1 byte / pixel
TEST_ESP_OK(esp_cam_ctlr_del(handle));
}
TEST_CASE("TEST DVP driver no backup buffer usage", "[DVP]")
{
esp_cam_ctlr_dvp_config_t dvp_config = {
.ctlr_id = 0,
.clk_src = CAM_CLK_SRC_DEFAULT,
.h_res = 800,
.v_res = 640,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.byte_swap_en = false,
.bk_buffer_dis = true,
.pin_dont_init = true,
};
esp_cam_ctlr_handle_t handle = NULL;
TEST_ESP_OK(esp_cam_new_dvp_ctlr(&dvp_config, &handle));
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, esp_cam_new_dvp_ctlr(&dvp_config, &handle));
uint8_t *bk_buffer = NULL;
size_t bk_buffer_len = 0;
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_cam_ctlr_get_frame_buffer(handle, 1, (const void **)&bk_buffer));
TEST_ESP_OK(esp_cam_ctlr_get_frame_buffer_len(handle, &bk_buffer_len));
TEST_ASSERT_NULL(bk_buffer);
TEST_ASSERT_EQUAL((dvp_config.h_res * dvp_config.v_res * 2), bk_buffer_len); // out type RGB565 using 2 byte / pixel
TEST_ESP_OK(esp_cam_ctlr_del(handle));
}

View File

@ -0,0 +1,10 @@
# 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
def test_dvp(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@ -0,0 +1,6 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
#
CONFIG_SPIRAM=y
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_FREERTOS_HZ=1000

View File

@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="esp32p4"
CONFIG_SPIRAM_SPEED_200M=y
CONFIG_IDF_EXPERIMENTAL_FEATURES=y

View File

@ -266,6 +266,10 @@ if(NOT BOOTLOADER_BUILD)
list(APPEND srcs "ds_hal.c") list(APPEND srcs "ds_hal.c")
endif() endif()
if(CONFIG_SOC_LCDCAM_CAM_SUPPORTED)
list(APPEND srcs "cam_hal.c")
endif()
if(CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED) if(CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED)
list(APPEND srcs "usb_serial_jtag_hal.c") list(APPEND srcs "usb_serial_jtag_hal.c")
endif() endif()

121
components/hal/cam_hal.c Normal file
View File

@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/cam_ll.h"
#include "hal/cam_hal.h"
#include "soc/soc_caps.h"
#include "soc/cam_periph.h"
/**
* @brief Configure line number to trigger interrupt
*
* @param hal CAM object data pointer
* @param num line number
*
* @return None
*/
static void cam_hal_set_line_int_num(cam_hal_context_t *hal, uint32_t num)
{
if (num > 0) {
cam_ll_enable_hs_line_int(hal->hw, 1);
cam_ll_set_line_int_num(hal->hw, num);
} else {
cam_ll_enable_hs_line_int(hal->hw, 0);
cam_ll_set_line_int_num(hal->hw, 0);
}
}
/**
* @brief Configure V-SYNC filter threshold
*
* @param hal CAM object data pointer
* @param num V-SYNC filter threshold
*
* @return None
*/
static void cam_hal_set_vsync_filter_num(cam_hal_context_t *hal, uint32_t num)
{
if (num > 0) {
cam_ll_enable_vsync_filter(hal->hw, 1);
cam_ll_set_vsync_filter_thres(hal->hw, num);
} else {
cam_ll_enable_vsync_filter(hal->hw, 0);
cam_ll_set_vsync_filter_thres(hal->hw, 0);
}
}
/**
* @brief Initialize CAM hardware
*
* @param hal CAM object data pointer
* @param config CAM configuration
*
* @return None
*/
void cam_hal_init(cam_hal_context_t *hal, const cam_hal_config_t *config)
{
memset(hal, 0, sizeof(cam_hal_context_t));
hal->hw = CAM_LL_GET_HW(0);
cam_ll_enable_stop_signal(hal->hw, 0);
cam_ll_swap_dma_data_byte_order(hal->hw, config->byte_swap_en);
cam_ll_reverse_dma_data_bit_order(hal->hw, 0);
cam_ll_enable_vsync_generate_eof(hal->hw, 1);
cam_hal_set_line_int_num(hal, 0);
cam_hal_set_vsync_filter_num(hal, 0);
cam_ll_enable_invert_pclk(hal->hw, 0);
cam_ll_set_input_data_width(hal->hw, 8);
cam_ll_enable_invert_de(hal->hw, 0);
cam_ll_enable_invert_vsync(hal->hw, 0);
cam_ll_enable_invert_hsync(hal->hw, 0);
cam_ll_set_vh_de_mode(hal->hw, 0); // Disable vh_de mode default
cam_ll_enable_rgb_yuv_convert(hal->hw, 0); // bypass conv module default
}
/**
* @brief De-initialize CAM hardware
*
* @note Stop stream before deinit
* @param hal CAM object data pointer
*
* @return None
*/
void cam_hal_deinit(cam_hal_context_t *hal)
{
cam_ll_stop(hal->hw);
cam_ll_reset(hal->hw);
cam_ll_fifo_reset(hal->hw);
}
/**
* @brief Start CAM to receive frame data
*
* @param hal CAM object data pointer
*
* @return None
*/
void cam_hal_start_streaming(cam_hal_context_t *hal)
{
cam_ll_reset(hal->hw);
cam_ll_fifo_reset(hal->hw);
cam_ll_start(hal->hw);
}
/**
* @brief Stop CAM receiving frame data
*
* @param hal CAM object data pointer
*
* @return None
*/
void cam_hal_stop_streaming(cam_hal_context_t *hal)
{
cam_ll_stop(hal->hw);
}

View File

@ -0,0 +1,634 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "hal/misc.h"
#include "hal/assert.h"
#include "soc/lcd_cam_struct.h"
#include "soc/hp_sys_clkrst_struct.h"
#include "hal/cam_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CAM_LL_GET_HW(id) (((id) == 0) ? (&LCD_CAM) : NULL)
#define CAM_LL_CLK_FRAC_DIV_N_MAX 256 // CAM_CLK = CAM_CLK_S / (N + b/a), the N register is 8 bit-width
#define CAM_LL_CLK_FRAC_DIV_AB_MAX 64 // CAM_CLK = CAM_CLK_S / (N + b/a), the a/b register is 6 bit-width
/**
* @brief Enable the bus clock for CAM module
*
* @param group_id Group ID
* @param enable true to enable, false to disable
*/
static inline void cam_ll_enable_bus_clock(int group_id, bool en)
{
(void)group_id;
HP_SYS_CLKRST.soc_clk_ctrl3.reg_lcdcam_apb_clk_en = en;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_RC_ATOMIC_ENV variable in advance
#define cam_ll_enable_bus_clock(...) (void)__DECLARE_RCC_RC_ATOMIC_ENV; cam_ll_enable_bus_clock(__VA_ARGS__)
/**
* @brief Reset the CAM module
*
* @param group_id Group ID
*/
static inline void cam_ll_reset_register(int group_id)
{
(void)group_id;
HP_SYS_CLKRST.hp_rst_en2.reg_rst_en_lcdcam = 1;
HP_SYS_CLKRST.hp_rst_en2.reg_rst_en_lcdcam = 0;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_RC_ATOMIC_ENV variable in advance
#define cam_ll_reset_register(...) (void)__DECLARE_RCC_RC_ATOMIC_ENV; cam_ll_reset_register(__VA_ARGS__)
/**
* @brief Enable clock gating
*
* @param group_id Group ID
* @param en True to enable, False to disable
*/
static inline void cam_ll_enable_clk(int group_id, bool en)
{
HP_SYS_CLKRST.peri_clk_ctrl119.reg_cam_clk_en = en;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define cam_ll_enable_clk(...) (void)__DECLARE_RCC_ATOMIC_ENV; cam_ll_enable_clk(__VA_ARGS__)
/**
* @brief Get the clock status for the CAM module
*
* @return True when enabled, false when disabled
*/
static inline bool cam_ll_get_clk_status(int group_id)
{
(void)group_id;
return HP_SYS_CLKRST.peri_clk_ctrl119.reg_cam_clk_en;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define cam_ll_get_clk_status(...) (void)__DECLARE_RCC_ATOMIC_ENV; cam_ll_get_clk_status(__VA_ARGS__)
/**
* @brief Select clock source for CAM peripheral
*
* @param group_id Group ID
* @param src Clock source
*/
static inline void cam_ll_select_clk_src(int group_id, cam_clock_source_t src)
{
switch (src) {
case CAM_CLK_SRC_XTAL:
HP_SYS_CLKRST.peri_clk_ctrl119.reg_cam_clk_src_sel = 0;
break;
case CAM_CLK_SRC_PLL160M:
HP_SYS_CLKRST.peri_clk_ctrl119.reg_cam_clk_src_sel = 1;
break;
case CAM_CLK_SRC_APLL:
HP_SYS_CLKRST.peri_clk_ctrl119.reg_cam_clk_src_sel = 2;
break;
default:
HAL_ASSERT(false);
break;
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define cam_ll_select_clk_src(...) (void)__DECLARE_RCC_ATOMIC_ENV; cam_ll_select_clk_src(__VA_ARGS__)
/**
* @brief Get the CAM source clock type
*
* @param dev CAM register base address
* @param src The pointer to accept the CAM source clock type
*/
static inline void cam_ll_get_clk_src(lcd_cam_dev_t *dev, cam_clock_source_t *src)
{
switch (HP_SYS_CLKRST.peri_clk_ctrl119.reg_cam_clk_src_sel) {
case 0:
*src = CAM_CLK_SRC_XTAL;
break;
case 1:
*src = CAM_CLK_SRC_PLL160M;
break;
case 2:
*src = CAM_CLK_SRC_APLL;
break;
default:
HAL_ASSERT(false);
break;
}
}
/**
* @brief Set clock coefficient of CAM peripheral
*
* @param group_id Group ID
* @param div_num Integer part of the divider
* @param div_a denominator of the divider
* @param div_b numerator of the divider
*/
__attribute__((always_inline))
static inline void cam_ll_set_group_clock_coeff(int group_id, int div_num, int div_a, int div_b)
{
// cam_clk = module_clock_src / (div_num + div_b / div_a)
HAL_ASSERT(div_num >= 2 && div_num < CAM_LL_CLK_FRAC_DIV_N_MAX);
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.peri_clk_ctrl120, reg_cam_clk_div_num, div_num);
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.peri_clk_ctrl120, reg_cam_clk_div_denominator, div_a);
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.peri_clk_ctrl120, reg_cam_clk_div_numerator, div_b);
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define cam_ll_set_group_clock_coeff(...) (void)__DECLARE_RCC_ATOMIC_ENV; cam_ll_set_group_clock_coeff(__VA_ARGS__)
/**
* @brief Enable stop signal for CAM peripheral
*
* @param dev CAM register base address
* @param en True to stop when GDMA Rx FIFO is full, False to not stop
*/
static inline void cam_ll_enable_stop_signal(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_stop_en = en;
}
/**
* @brief Set vsync filter threshold value
*
* @param dev CAM register base address
* @param value Filter threshold value for CAM_VSYNC_SIGNAL, range [0, 7]
*/
static inline void cam_ll_set_vsync_filter_thres(lcd_cam_dev_t *dev, uint32_t value)
{
dev->cam_ctrl.cam_vsync_filter_thres = value;
}
/**
* @brief Enable to generate LCD_CAM_CAM_HS_INT
*
* @param dev CAM register base address
* @param en True to enable to generate LCD_CAM_CAM_HS_INT, False to disable
*/
static inline void cam_ll_enable_hs_line_int(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_line_int_en = en;
}
/**
* @brief Enable CAM_VSYNC to generate in_suc_eof
*
* @param dev CAM register base address
* @param en True to enable CAM_VSYNC to generate in_suc_eof, False to use LCD_CAM_CAM_REC_DATA_BYTELEN to control in_suc_eof
*/
static inline void cam_ll_enable_vsync_generate_eof(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_vs_eof_en = en;
}
/**
* @brief Enable to swap every two 8-bit input data
*
* @param dev CAM register base address
* @param en True to enable invert, False to disable invert
*/
static inline void cam_ll_enable_8bits_data_invert(lcd_cam_dev_t *dev, bool en)
{
dev->cam_rgb_yuv.cam_conv_8bits_data_inv = en;
}
/**
* @brief Enable YUV-RGB converter
*
* @param dev CAM register base address
* @param en True to enable converter, False to disable converter
*/
static inline void cam_ll_enable_rgb_yuv_convert(lcd_cam_dev_t *dev, bool en)
{
dev->cam_rgb_yuv.cam_conv_enable = en;
}
/**
* @brief Set convert data line width
*
* @param dev CAM register base address
* @param width data line width (8 or 16)
*/
static inline void cam_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width)
{
HAL_ASSERT(width == 8 || width == 16);
dev->cam_rgb_yuv.cam_conv_mode_8bits_on = (width == 8) ? 1 : 0;
}
/**
* @brief Set the color range of input data
*
* @param dev CAM register base address
* @param range Color range
*/
static inline void cam_ll_set_input_color_range(lcd_cam_dev_t *dev, color_range_t range)
{
if (range == COLOR_RANGE_LIMIT) {
dev->cam_rgb_yuv.cam_conv_data_in_mode = 0;
} else if (range == COLOR_RANGE_FULL) {
dev->cam_rgb_yuv.cam_conv_data_in_mode = 1;
}
}
/**
* @brief Set the color range of output data
*
* @param dev CAM register base address
* @param range Color range
*/
static inline void cam_ll_set_output_color_range(lcd_cam_dev_t *dev, color_range_t range)
{
if (range == COLOR_RANGE_LIMIT) {
dev->cam_rgb_yuv.cam_conv_data_out_mode = 0;
} else if (range == COLOR_RANGE_FULL) {
dev->cam_rgb_yuv.cam_conv_data_out_mode = 1;
}
}
/**
* @brief Set YUV conversion standard
*
* @param dev CAM register base address
* @param std YUV conversion standard
*/
static inline void cam_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, color_conv_std_rgb_yuv_t std)
{
if (std == COLOR_CONV_STD_RGB_YUV_BT601) {
dev->cam_rgb_yuv.cam_conv_protocol_mode = 0;
} else if (std == COLOR_CONV_STD_RGB_YUV_BT709) {
dev->cam_rgb_yuv.cam_conv_protocol_mode = 1;
}
}
/**
* @brief Set the converter mode: RGB565 to YUV
*
* @param dev CAM register base address
* @param yuv_sample YUV sample mode
*/
static inline void cam_ll_set_convert_mode_rgb_to_yuv(lcd_cam_dev_t *dev, color_pixel_yuv_format_t yuv_sample)
{
dev->cam_rgb_yuv.cam_conv_trans_mode = 1;
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 3;
switch (yuv_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 2;
break;
default:
abort();
}
}
/**
* @brief Set the converter mode: YUV to RGB565
*
* @param dev CAM register base address
* @param yuv_sample YUV sample mode
*/
static inline void cam_ll_set_convert_mode_yuv_to_rgb(lcd_cam_dev_t *dev, color_pixel_yuv_format_t yuv_sample)
{
dev->cam_rgb_yuv.cam_conv_trans_mode = 0;
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 3;
switch (yuv_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 2;
break;
default:
abort();
}
}
/**
* @brief Set the converter mode: YUV to YUV
*
* @param dev CAM register base address
* @param src_sample Source YUV sample mode
* @param dst_sample Destination YUV sample mode
*/
static inline void cam_ll_set_convert_mode_yuv_to_yuv(lcd_cam_dev_t *dev, color_pixel_yuv_format_t src_sample, color_pixel_yuv_format_t dst_sample)
{
HAL_ASSERT(src_sample != dst_sample);
dev->cam_rgb_yuv.cam_conv_trans_mode = 1;
switch (src_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 2;
break;
default:
abort();
}
switch (dst_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 2;
break;
default:
abort();
}
}
/**
* @brief Set camera received data byte length
*
* @param dev CAM register base address
* @param length received data byte length, range [0, 0xFFFF]
*/
static inline void cam_ll_set_recv_data_bytelen(lcd_cam_dev_t *dev, uint32_t length)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(dev->cam_ctrl1, cam_rec_data_bytelen, length);
}
/**
* @brief Set line number to trigger interrupt
*
* @param dev CAM register base address
* @param number line number to trigger hs interrupt, range [0, 0x3F]
*/
static inline void cam_ll_set_line_int_num(lcd_cam_dev_t *dev, uint32_t number)
{
dev->cam_ctrl1.cam_line_int_num = number;
}
/**
* @brief Whether to invert the input signal CAM_PCLK
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_pclk(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_clk_inv = en;
}
/**
* @brief Enable CAM_VSYNC filter function
*
* @param dev CAM register base address
* @param en True to enable, False to bypass
*/
static inline void cam_ll_enable_vsync_filter(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_vsync_filter_en = en;
}
/**
* @brief Set CAM input data width
*
* @param dev CAM register base address
* @param stride 16: The bit number of input data is 9~16. 8: The bit number of input data is 0~8.
*/
static inline void cam_ll_set_input_data_width(lcd_cam_dev_t *dev, uint32_t stride)
{
switch (stride) {
case 8:
dev->cam_ctrl1.cam_2byte_en = 0;
break;
case 16:
dev->cam_ctrl1.cam_2byte_en = 1;
break;
default:
HAL_ASSERT(false);
break;
}
}
/**
* @brief Whether to invert CAM_DE
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_de(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_de_inv = en;
}
/**
* @brief Whether to invert CAM_HSYNC
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_hsync(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_hsync_inv = en;
}
/**
* @brief Whether to invert CAM_VSYNC
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_vsync(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_vsync_inv = en;
}
/**
* @brief Enable the mode to control the input control signals
*
* @param dev CAM register base address
* @param mode 1: Input control signals are CAM_DE, CAM_HSYNC and CAM_VSYNC;
* 0: Input control signals are CAM_DE and CAM_VSYNC. CAM_HSYNC and CAM_DE are all 1 the the same time.
*/
static inline void cam_ll_set_vh_de_mode(lcd_cam_dev_t *dev, bool enable)
{
dev->cam_ctrl1.cam_vh_de_mode_en = enable;
}
/**
* @brief Get the mode of input control signals
*
* @param dev CAM register base address
* @param en The pointer to accept the vh_de mode status. 1: Input control signals are CAM_DE, CAM_HSYNC and CAM_VSYNC;
* 0: Input control signals are CAM_DE and CAM_VSYNC. CAM_HSYNC and CAM_DE are all 1 the the same time.
*/
static inline void cam_ll_get_vh_de_mode(lcd_cam_dev_t *dev, bool *en)
{
*en = dev->cam_ctrl1.cam_vh_de_mode_en;
}
/**
* @brief Set the wire width of CAM output
*
* @param dev CAM register base address
* @param width CAM output wire width
*/
static inline void cam_ll_set_data_wire_width(lcd_cam_dev_t *dev, uint32_t width)
{
// data line width is same as data stride that set in `cam_ll_set_input_data_width`
}
/**
* @brief Start the CAM transaction
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_start(lcd_cam_dev_t *dev)
{
dev->cam_ctrl.cam_update_reg = 1;
dev->cam_ctrl1.cam_start = 1;
}
/**
* @brief Stop the CAM transaction
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_stop(lcd_cam_dev_t *dev)
{
dev->cam_ctrl1.cam_start = 0;
dev->cam_ctrl.cam_update_reg = 1; // self clear
}
/**
* @brief Whether to reverse the data bit order
*
* @note It acts before the YUV-RGB converter
*
* @param dev CAM register base address
* @param en True to reverse, False to not reverse
*/
__attribute__((always_inline))
static inline void cam_ll_reverse_dma_data_bit_order(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_bit_order = en;
}
/**
* @brief Whether to swap adjacent two bytes
*
* @note This acts before the YUV-RGB converter, mainly to change the data endian.
* {B1,B0},{B3,B2} => {B0,B1}{B2,B3}
*
* @param dev CAM register base address
* @param en True to swap the byte order, False to not swap
*/
__attribute__((always_inline))
static inline void cam_ll_swap_dma_data_byte_order(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_byte_order = en;
}
/**
* @brief Reset camera module
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_reset(lcd_cam_dev_t *dev)
{
dev->cam_ctrl1.cam_reset = 1; // self clear
}
/**
* @brief Reset Async RX FIFO
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_fifo_reset(lcd_cam_dev_t *dev)
{
dev->cam_ctrl1.cam_afifo_reset = 1; // self clear
}
/**
* @brief Enable/disable interrupt by mask
*
* @param dev CAM register base address
* @param mask Interrupt mask
* @param en True to enable interrupt, False to disable interrupt
*/
static inline void cam_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bool en)
{
if (en) {
dev->lc_dma_int_ena.val |= mask & 0x0c;
} else {
dev->lc_dma_int_ena.val &= ~(mask & 0x0c);
}
}
/**
* @brief Get interrupt status value
*
* @param dev CAM register base address
* @return Interrupt status value
*/
__attribute__((always_inline))
static inline uint32_t cam_ll_get_interrupt_status(lcd_cam_dev_t *dev)
{
return dev->lc_dma_int_st.val & 0x0c;
}
/**
* @brief Clear interrupt status by mask
*
* @param dev CAM register base address
* @param mask Interrupt status mask
*/
__attribute__((always_inline))
static inline void cam_ll_clear_interrupt_status(lcd_cam_dev_t *dev, uint32_t mask)
{
dev->lc_dma_int_clr.val = mask & 0x0c;
}
/**
* @brief Get address of interrupt status register address
*
* @param dev CAM register base address
* @return Interrupt status register address
*/
static inline volatile void *cam_ll_get_interrupt_status_reg(lcd_cam_dev_t *dev)
{
return &dev->lc_dma_int_st;
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "hal/cam_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct lcd_cam_dev_t cam_dev_t; // CAM SOC layer handle
/**
* @brief CAM hardware interface object data
*/
typedef struct cam_hal_context {
cam_dev_t *hw; /*!< Beginning address of the CAM peripheral registers. */
} cam_hal_context_t;
/**
* @brief CAM HAL driver configuration
*/
typedef struct cam_hal_config {
int port; /*!< CAM port */
bool byte_swap_en; /*!< CAM enable byte swap */
} cam_hal_config_t;
/**
* @brief Initialize CAM hardware
*
* @param hal CAM object data pointer
* @param config CAM configuration
*
* @return None
*/
void cam_hal_init(cam_hal_context_t *hal, const cam_hal_config_t *config);
/**
* @brief De-initialize CAM hardware
*
* @param hal CAM object data pointer
*
* @return None
*/
void cam_hal_deinit(cam_hal_context_t *hal);
/**
* @brief Start CAM to receive frame data
*
* @param hal CAM object data pointer
*
* @return None
*/
void cam_hal_start_streaming(cam_hal_context_t *hal);
/**
* @brief Stop CAM receiving frame data
*
* @param hal CAM object data pointer
*
* @return None
*/
void cam_hal_stop_streaming(cam_hal_context_t *hal);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
#include "soc/clk_tree_defs.h"
#include "hal/color_types.h"
#include "hal/cam_ctlr_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_LCDCAM_CAM_DATA_WIDTH_MAX
#define CAP_DVP_PERIPH_NUM SOC_LCDCAM_CAM_PERIPH_NUM /*!< DVP port number */
#define CAM_DVP_DATA_SIG_NUM SOC_LCDCAM_CAM_DATA_WIDTH_MAX /*!< DVP data bus width of CAM */
#else
#define CAP_DVP_PERIPH_NUM 0 /*!< Default value */
#define CAM_DVP_DATA_SIG_NUM 0 /*!< Default value */
#endif
#if SOC_LCDCAM_CAM_SUPPORTED
typedef soc_periph_cam_clk_src_t cam_clock_source_t; /*!< Clock source type of CAM */
#else
typedef int cam_clock_source_t; /*!< Default type */
#endif
#ifdef __cplusplus
}
#endif

View File

@ -156,6 +156,10 @@ if(CONFIG_SOC_PAU_SUPPORTED)
list(APPEND srcs "${target_folder}/system_retention_periph.c") list(APPEND srcs "${target_folder}/system_retention_periph.c")
endif() endif()
if(CONFIG_SOC_LCDCAM_CAM_SUPPORTED)
list(APPEND srcs "${target_folder}/cam_periph.c")
endif()
idf_component_register(SRCS ${srcs} idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${includes} INCLUDE_DIRS ${includes}
LDFRAGMENTS "linker.lf") LDFRAGMENTS "linker.lf")

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/gpio_sig_map.h"
#include "soc/cam_periph.h"
const cam_signal_conn_t cam_periph_signals = {
.buses = {
[0] = {
.module = PERIPH_LCD_CAM_MODULE,
.irq_id = ETS_LCD_CAM_INTR_SOURCE,
.data_sigs = {
CAM_DATA_IN_PAD_IN0_IDX,
CAM_DATA_IN_PAD_IN1_IDX,
CAM_DATA_IN_PAD_IN2_IDX,
CAM_DATA_IN_PAD_IN3_IDX,
CAM_DATA_IN_PAD_IN4_IDX,
CAM_DATA_IN_PAD_IN5_IDX,
CAM_DATA_IN_PAD_IN6_IDX,
CAM_DATA_IN_PAD_IN7_IDX,
CAM_DATA_IN_PAD_IN8_IDX,
CAM_DATA_IN_PAD_IN9_IDX,
CAM_DATA_IN_PAD_IN10_IDX,
CAM_DATA_IN_PAD_IN11_IDX,
CAM_DATA_IN_PAD_IN12_IDX,
CAM_DATA_IN_PAD_IN13_IDX,
CAM_DATA_IN_PAD_IN14_IDX,
CAM_DATA_IN_PAD_IN15_IDX,
},
.hsync_sig = CAM_H_SYNC_PAD_IN_IDX,
.vsync_sig = CAM_V_SYNC_PAD_IN_IDX,
.pclk_sig = CAM_PCLK_PAD_IN_IDX,
.de_sig = CAM_H_ENABLE_PAD_IN_IDX,
.clk_sig = CAM_CLK_PAD_OUT_IDX
}
}
};

View File

@ -47,6 +47,10 @@ config SOC_PCNT_SUPPORTED
bool bool
default y default y
config SOC_LCDCAM_CAM_SUPPORTED
bool
default y
config SOC_MIPI_CSI_SUPPORTED config SOC_MIPI_CSI_SUPPORTED
bool bool
default y default y
@ -1662,3 +1666,15 @@ config SOC_JPEG_DECODE_SUPPORTED
config SOC_JPEG_ENCODE_SUPPORTED config SOC_JPEG_ENCODE_SUPPORTED
bool bool
default y default y
config SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV
bool
default y
config SOC_LCDCAM_CAM_PERIPH_NUM
int
default 1
config SOC_LCDCAM_CAM_DATA_WIDTH_MAX
int
default 16

View File

@ -373,6 +373,23 @@ typedef enum {
LCD_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */ LCD_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
} soc_periph_lcd_clk_src_t; } soc_periph_lcd_clk_src_t;
//////////////////////////////////////////////////LCD///////////////////////////////////////////////////////////////////
/**
* @brief Array initializer for all supported clock sources of CAM
*/
#define SOC_CAM_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_XTAL, SOC_MOD_CLK_APLL}
/**
* @brief Type of CAM clock source
*/
typedef enum {
CAM_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
CAM_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
CAM_CLK_SRC_APLL = SOC_MOD_CLK_APLL, /*!< Select APLL as the source clock */
CAM_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
} soc_periph_cam_clk_src_t;
/////////////////////////////////////////////////MIPI/////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////MIPI///////////////////////////////////////////////////////////////////
/** /**

View File

@ -29,6 +29,7 @@
#define SOC_GPTIMER_SUPPORTED 1 #define SOC_GPTIMER_SUPPORTED 1
#define SOC_PCNT_SUPPORTED 1 #define SOC_PCNT_SUPPORTED 1
// #define SOC_LCDCAM_SUPPORTED 1 // TODO: IDF-7465 // #define SOC_LCDCAM_SUPPORTED 1 // TODO: IDF-7465
#define SOC_LCDCAM_CAM_SUPPORTED 1
#define SOC_MIPI_CSI_SUPPORTED 1 #define SOC_MIPI_CSI_SUPPORTED 1
#define SOC_MIPI_DSI_SUPPORTED 1 #define SOC_MIPI_DSI_SUPPORTED 1
#define SOC_MCPWM_SUPPORTED 1 #define SOC_MCPWM_SUPPORTED 1
@ -542,8 +543,8 @@
/*-------------------------- TOUCH SENSOR CAPS -------------------------------*/ /*-------------------------- TOUCH SENSOR CAPS -------------------------------*/
#define SOC_TOUCH_SENSOR_VERSION (3) // Hardware version of touch sensor #define SOC_TOUCH_SENSOR_VERSION (3) // Hardware version of touch sensor
#define SOC_TOUCH_SENSOR_NUM (14) // Touch available channel number. Actually there are 15 Touch channels, but channel 14 is not pinned out, limit to 14 channels #define SOC_TOUCH_SENSOR_NUM (14) // Touch available channel number. Actually there are 15 Touch channels, but channel 14 is not pinned out, limit to 14 channels
#define SOC_TOUCH_PROXIMITY_CHANNEL_NUM (3) // Sopport touch proximity channel number. #define SOC_TOUCH_PROXIMITY_CHANNEL_NUM (3) // Support touch proximity channel number.
#define SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED (1) // Sopport touch proximity channel measure done interrupt type. #define SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED (1) // Support touch proximity channel measure done interrupt type.
#define SOC_TOUCH_SAMPLER_NUM (3) // The sampler number in total, each sampler can be used to sample on one frequency #define SOC_TOUCH_SAMPLER_NUM (3) // The sampler number in total, each sampler can be used to sample on one frequency
/*-------------------------- TWAI CAPS ---------------------------------------*/ /*-------------------------- TWAI CAPS ---------------------------------------*/
@ -661,3 +662,8 @@
#define SOC_JPEG_CODEC_SUPPORTED (1) #define SOC_JPEG_CODEC_SUPPORTED (1)
#define SOC_JPEG_DECODE_SUPPORTED (1) #define SOC_JPEG_DECODE_SUPPORTED (1)
#define SOC_JPEG_ENCODE_SUPPORTED (1) #define SOC_JPEG_ENCODE_SUPPORTED (1)
/*--------------------------- CAM ---------------------------------*/
#define SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV (1)
#define SOC_LCDCAM_CAM_PERIPH_NUM (1U)
#define SOC_LCDCAM_CAM_DATA_WIDTH_MAX (16U)

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
#include "soc/periph_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_LCDCAM_CAM_SUPPORTED
typedef struct {
struct {
const periph_module_t module;
const int irq_id;
const int data_sigs[SOC_LCDCAM_CAM_DATA_WIDTH_MAX];
const int hsync_sig;
const int vsync_sig;
const int pclk_sig;
const int de_sig;
const int clk_sig;
} buses[SOC_LCDCAM_CAM_PERIPH_NUM];
} cam_signal_conn_t;
extern const cam_signal_conn_t cam_periph_signals;
#endif // SOC_LCDCAM_CAM_SUPPORTED
#ifdef __cplusplus
}
#endif