Files
esp-idf/components/esp_driver_isp/src/isp.c

302 lines
9.5 KiB
C

/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <esp_types.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "esp_clk_tree.h"
#include "driver/isp.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/mipi_csi_share_hw_ctrl.h"
#include "hal/hal_utils.h"
#include "hal/isp_types.h"
#include "hal/isp_hal.h"
#include "hal/isp_ll.h"
#include "soc/mipi_csi_bridge_struct.h"
#include "soc/isp_periph.h"
#include "isp_internal.h"
#if CONFIG_ISP_ISR_IRAM_SAFE
#define ISP_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM)
#else
#define ISP_INTR_ALLOC_FLAGS 0
#endif
#if CONFIG_ISP_ISR_IRAM_SAFE
#define ISP_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define ISP_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif
typedef struct isp_platform_t {
_lock_t mutex;
isp_processor_t *processors[SOC_ISP_NUMS];
} isp_platform_t;
static const char *TAG = "ISP";
static isp_platform_t s_platform;
static esp_err_t s_isp_claim_processor(isp_processor_t *proc)
{
assert(proc);
_lock_acquire(&s_platform.mutex);
bool found = false;
for (int i = 0; i < SOC_ISP_NUMS; i ++) {
found = !s_platform.processors[i];
if (found) {
s_platform.processors[i] = proc;
proc->proc_id = i;
PERIPH_RCC_ATOMIC() {
isp_ll_enable_module_clock(proc->hal.hw, true);
isp_ll_reset_module_clock(proc->hal.hw);
}
break;
}
}
_lock_release(&s_platform.mutex);
if (!found) {
return ESP_ERR_NOT_FOUND;
}
return ESP_OK;
}
static esp_err_t s_isp_declaim_processor(isp_processor_t *proc)
{
assert(proc);
_lock_acquire(&s_platform.mutex);
s_platform.processors[proc->proc_id] = NULL;
PERIPH_RCC_ATOMIC() {
isp_ll_enable_module_clock(proc->hal.hw, false);
}
_lock_release(&s_platform.mutex);
return ESP_OK;
}
esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_proc_handle_t *ret_proc)
{
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(proc_config && ret_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_CSI, ESP_ERR_NOT_SUPPORTED, TAG, "only support CSI as input source at this moment");
isp_processor_t *proc = heap_caps_calloc(1, sizeof(isp_processor_t), ISP_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(proc, ESP_ERR_NO_MEM, TAG, "no mem");
//claim a processor, then do assignment
ESP_GOTO_ON_ERROR(s_isp_claim_processor(proc), err, TAG, "no available isp processor");
#if SOC_ISP_SHARE_CSI_BRG
ESP_GOTO_ON_ERROR(mipi_csi_brg_claim(MIPI_CSI_BRG_USER_SHARE, &proc->csi_brg_id), err, TAG, "csi bridge is in use already");
#endif
isp_clk_src_t clk_src = !proc_config->clk_src ? ISP_CLK_SRC_DEFAULT : proc_config->clk_src;
uint32_t clk_src_freq_hz = 0;
ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_src_freq_hz), err, TAG, "clock source setting fail");
ESP_GOTO_ON_FALSE((proc_config->clk_hz > 0 && proc_config->clk_hz <= clk_src_freq_hz), ESP_ERR_INVALID_ARG, err, TAG, "clk hz not supported");
uint32_t out_clk_freq_hz = 0;
hal_utils_clk_div_t clk_div = {};
hal_utils_clk_info_t clk_info = {
.src_freq_hz = clk_src_freq_hz,
.exp_freq_hz = proc_config->clk_hz,
.max_integ = ISP_LL_TX_MAX_CLK_INT_DIV,
.min_integ = 1,
.round_opt = HAL_DIV_ROUND,
};
out_clk_freq_hz = hal_utils_calc_clk_div_integer(&clk_info, &clk_div.integer);
if (out_clk_freq_hz != proc_config->clk_hz) {
ESP_LOGW(TAG, "precision loss, real output frequency: %"PRIu32"Hz", out_clk_freq_hz);
}
isp_hal_init(&proc->hal, proc->proc_id);
//necessary ISP submodules that needs basic initialisation
isp_ll_bf_clk_enable(proc->hal.hw, true);
isp_ll_bf_enable(proc->hal.hw, true);
isp_ll_ccm_clk_enable(proc->hal.hw, true);
isp_ll_ccm_enable(proc->hal.hw, true);
isp_ll_color_clk_enable(proc->hal.hw, true);
isp_ll_color_enable(proc->hal.hw, true);
PERIPH_RCC_ATOMIC() {
isp_ll_select_clk_source(proc->hal.hw, clk_src);
isp_ll_set_clock_div(proc->hal.hw, &clk_div);
}
proc->isp_fsm = ISP_FSM_INIT;
proc->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
//Input color format
bool valid_format = false;
color_space_pixel_format_t in_color_format = {
.color_type_id = proc_config->input_data_color_type,
};
valid_format = isp_ll_set_input_data_color_format(proc->hal.hw, in_color_format);
ESP_GOTO_ON_FALSE(valid_format, ESP_ERR_INVALID_ARG, err, TAG, "invalid input color space config");
//Output color format
valid_format = false;
color_space_pixel_format_t out_color_format = {
.color_type_id = proc_config->output_data_color_type,
};
valid_format = isp_ll_set_output_data_color_format(proc->hal.hw, out_color_format);
ESP_GOTO_ON_FALSE(valid_format, ESP_ERR_INVALID_ARG, err, TAG, "invalid output color space config");
isp_ll_clk_enable(proc->hal.hw, true);
isp_ll_set_input_data_source(proc->hal.hw, proc_config->input_data_source);
isp_ll_enable_line_start_packet_exist(proc->hal.hw, proc_config->has_line_start_packet);
isp_ll_enable_line_end_packet_exist(proc->hal.hw, proc_config->has_line_end_packet);
isp_ll_set_intput_data_h_pixel_num(proc->hal.hw, proc_config->h_res);
isp_ll_set_intput_data_v_row_num(proc->hal.hw, proc_config->v_res);
*ret_proc = proc;
return ESP_OK;
err:
free(proc);
return ret;
}
esp_err_t esp_isp_del_processor(isp_proc_handle_t proc)
{
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(proc->isp_fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "processor isn't in init state");
//declaim first, then do free
ESP_RETURN_ON_ERROR(s_isp_declaim_processor(proc), TAG, "declaim processor fail");
#if SOC_ISP_SHARE_CSI_BRG
ESP_RETURN_ON_ERROR(mipi_csi_brg_declaim(proc->csi_brg_id), TAG, "declaim csi bridge fail");
#endif
free(proc);
return ESP_OK;
}
esp_err_t esp_isp_enable(isp_proc_handle_t proc)
{
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(proc->isp_fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "processor isn't in init state");
isp_ll_enable(proc->hal.hw, true);
proc->isp_fsm = ISP_FSM_ENABLE;
return ESP_OK;
}
esp_err_t esp_isp_disable(isp_proc_handle_t proc)
{
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(proc->isp_fsm == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "processor isn't in enable state");
isp_ll_enable(proc->hal.hw, false);
proc->isp_fsm = ISP_FSM_INIT;
return ESP_OK;
}
/*---------------------------------------------------------------
INTR
---------------------------------------------------------------*/
static void IRAM_ATTR s_isp_isr_dispatcher(void *arg)
{
isp_processor_t *proc = (isp_processor_t *)arg;
bool need_yield = false;
//Check and clear hw events
uint32_t af_events = isp_hal_check_clear_intr_event(&proc->hal, ISP_LL_EVENT_AF_MASK);
bool do_dispatch = false;
//Deal with hw events
if (af_events) {
portENTER_CRITICAL_ISR(&proc->spinlock);
do_dispatch = proc->af_isr_added;
portEXIT_CRITICAL_ISR(&proc->spinlock);
if (do_dispatch) {
need_yield |= esp_isp_af_isr(proc, af_events);
}
do_dispatch = false;
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}
esp_err_t esp_isp_register_isr(isp_proc_handle_t proc, isp_submodule_t submodule)
{
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
bool do_alloc = false;
portENTER_CRITICAL(&proc->spinlock);
proc->isr_ref_counts++;
if (proc->isr_ref_counts == 1) {
assert(!proc->intr_hdl);
do_alloc = true;
}
switch (submodule) {
case ISP_SUBMODULE_AF:
proc->af_isr_added = true;
break;
default:
assert(false);
}
portEXIT_CRITICAL(&proc->spinlock);
if (do_alloc) {
ret = esp_intr_alloc(isp_hw_info.instances[proc->proc_id].irq, ISP_INTR_ALLOC_FLAGS, s_isp_isr_dispatcher, (void *)proc, &proc->intr_hdl);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "no intr source");
return ret;
}
esp_intr_enable(proc->intr_hdl);
}
return ESP_OK;
}
esp_err_t esp_isp_deregister_isr(isp_proc_handle_t proc, isp_submodule_t submodule)
{
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
bool do_free = false;
portENTER_CRITICAL(&proc->spinlock);
proc->isr_ref_counts--;
assert(proc->isr_ref_counts >= 0);
if (proc->isr_ref_counts == 0) {
assert(proc->intr_hdl);
do_free = true;
}
switch (submodule) {
case ISP_SUBMODULE_AF:
proc->af_isr_added = false;
break;
default:
assert(false);
}
portEXIT_CRITICAL(&proc->spinlock);
if (do_free) {
esp_intr_disable(proc->intr_hdl);
ret = esp_intr_free(proc->intr_hdl);
if (ret != ESP_OK) {
return ret;
}
}
return ESP_OK;
}