Files
BPI-CM9-Linux/aml-comp/hardware/amlogic/arm-isp-6.12/aml_isp5/driver/isp5/c4-ipp-sub.c
T

1058 lines
27 KiB
C

// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
/*
* Copyright (C) 2024 Amlogic, Inc. All rights reserved
*/
#include <linux/cleanup.h>
#include <media/v4l2-event.h>
#include "c4-isp-common.h"
#include "hw/c4-isp-hw.h"
#include "hw/c4-isp-regs.h"
#define C4_IPP_SUBDEV_NAME "c4-ipp"
#define C4_IPP_DEF_PAD_FMT MEDIA_BUS_FMT_SRGGB10_1X10
static const struct c4_ipp_format_info c4_ipp_fmts[] = {
/* RAW formats */
{
.code = MEDIA_BUS_FMT_SBGGR10_1X10,
.bpp = 10,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SGBRG10_1X10,
.bpp = 10,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SGRBG10_1X10,
.bpp = 10,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SRGGB10_1X10,
.bpp = 10,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SBGGR12_1X12,
.bpp = 12,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SGBRG12_1X12,
.bpp = 12,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SGRBG12_1X12,
.bpp = 12,
.is_raw = true,
}, {
.code = MEDIA_BUS_FMT_SRGGB12_1X12,
.bpp = 12,
.is_raw = true,
},
/* YUV422 */
{
.code = MEDIA_BUS_FMT_UYVY8_2X8,
.bpp = 8,
.is_raw = false,
},
};
static const struct c4_ipp_format_info
*ipp_find_format_by_code(u32 code)
{
for (u32 i = 0; i < ARRAY_SIZE(c4_ipp_fmts); i++) {
const struct c4_ipp_format_info *info = &c4_ipp_fmts[i];
if (info->code == code)
return info;
}
return NULL;
}
static const struct c4_ipp_format_info
*ipp_find_format_by_index(u32 index)
{
if (index >= ARRAY_SIZE(c4_ipp_fmts))
return NULL;
return &c4_ipp_fmts[index];
}
static int c4_ipp_cfg_vc_crop(struct c4_ipp *ipp, u32 pad,
struct v4l2_subdev_state *state)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
struct v4l2_mbus_framefmt *fmt;
const struct c4_ipp_format_info *info;
struct v4l2_rect *crop;
struct v4l2_rect vc_crop;
crop = v4l2_subdev_state_get_crop(state, pad);
fmt = v4l2_subdev_state_get_format(state, C4_IPP_PAD_SINK_VIDEO);
info = ipp_find_format_by_code(fmt->code);
if (!info) {
dev_err(ipp->isp->dev,
"Failed to find format info: 0x%x\n", fmt->code);
return -EINVAL;
}
vc_crop = *crop;
if (!info->is_raw) {
vc_crop.left /= 2;
vc_crop.width /= 2;
}
if (cfg_info->work_mode == C4_IPP_SINGLE_VC_MODE)
c4_isp_top_cfg_ipp_sgl_vc_ctrl(ipp->isp, cfg_info->img_mode, &vc_crop);
else
c4_isp_top_cfg_ipp_multi_vc_crop(ipp->isp,
pad - C4_IPP_PAD_SOURCE_VIDEO_0, &vc_crop);
return 0;
}
static int c4_ipp_cfg_bits_mode(struct c4_ipp *ipp,
struct v4l2_mbus_framefmt *fmt,
const struct c4_ipp_format_info *info)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
u8 ipp_id;
c4_isp_intf_cfg_bits_mode(ipp->isp, ipp->isp->info->id, info->bpp);
if (cfg_info->img_mode == C4_IPP_IMAGE_HDR) {
ipp_id = ipp->isp->info->id == C4_ISP_VIRT_0 ? 1 :
ipp->isp->info->id == C4_ISP_VIRT_1 ? 0 :
ipp->isp->info->id == C4_ISP_VIRT_2 ? 3 : 2;
c4_isp_intf_cfg_bits_mode(ipp->isp, ipp_id, info->bpp);
}
return 0;
}
static void c4_ipp_cfg_4vc_intr(struct c4_ipp *ipp, bool enable)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
enum c4_isp_pyr_wmif_ids wmif_id;
if (cfg_info->work_mode == C4_IPP_SINGLE_VC_MODE) {
if (ipp->isp->info->id == C4_ISP_VIRT_0 ||
ipp->isp->info->id == C4_ISP_VIRT_1) {
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER0_WMIF, false);
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER1_WMIF, false);
} else if (ipp->isp->info->id == C4_ISP_VIRT_2 ||
ipp->isp->info->id == C4_ISP_VIRT_3) {
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER2_WMIF, false);
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER3_WMIF, false);
}
} else if (cfg_info->work_mode == C4_IPP_MULTI_VC_SYNC_MODE) {
wmif_id = ipp->isp->info->id == C4_ISP_VIRT_0 ? C4_ISP_USER0_WMIF :
ipp->isp->info->id == C4_ISP_VIRT_1 ? C4_ISP_USER1_WMIF :
ipp->isp->info->id == C4_ISP_VIRT_2 ? C4_ISP_USER2_WMIF :
C4_ISP_USER3_WMIF;
c4_isp_intf_cfg_4vc_intr(ipp->isp, wmif_id, enable);
} else {
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER0_WMIF, enable);
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER1_WMIF, enable);
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER2_WMIF, enable);
c4_isp_intf_cfg_4vc_intr(ipp->isp, C4_ISP_USER3_WMIF, enable);
}
}
static int c4_ipp_cfg_multi_vc_sync(struct c4_ipp *ipp)
{
int ret;
ret = c4_isp_intf_get_4vc_sep_status(ipp->isp);
if (ret) {
dev_err(ipp->isp->dev, "Failed to get the 4VC sep resource\n");
return ret;
}
c4_isp_intf_cfg_force_other(ipp->isp, ipp->isp->info->id, true);
c4_isp_top_cfg_ipp_multi_vc_ctrl(ipp->isp, true);
return 0;
}
static int c4_ipp_cfg_multi_vc_sep(struct c4_ipp *ipp)
{
int ret;
ret = c4_isp_intf_get_4vc_sep_status(ipp->isp);
if (ret) {
dev_err(ipp->isp->dev, "Failed to get the 4VC sep resource\n");
return ret;
}
c4_isp_intf_cfg_force_other(ipp->isp, ipp->isp->info->id, true);
c4_isp_top_cfg_ipp_multi_vc_ctrl(ipp->isp, true);
c4_isp_intf_cfg_4vc_sep(ipp->isp, ipp->isp->info->id, true);
return 0;
}
static void c4_ipp_multi_vc_check_error(struct c4_ipp *ipp, u32 status)
{
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF0_ERR)
dev_err(ipp->isp->dev, "User mif0 error\n");
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF1_ERR)
dev_err(ipp->isp->dev, "User mif1 error\n");
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF2_ERR)
dev_err(ipp->isp->dev, "User mif2 error\n");
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF3_ERR)
dev_err(ipp->isp->dev, "User mif3 error\n");
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF4_ERR)
dev_err(ipp->isp->dev, "User mif4 error\n");
}
static int c4_ipp_cfg_single_vc_rdma_init(struct c4_ipp *ipp)
{
struct c4_isp_device *isp = ipp->isp;
int ret;
ret = c4_ipp_rdma_runtime_mutex_trylock_time(isp, 1000);
/* ret: 1 on success, 0 on timeout, <0 if interrupted (e.g. -EINTR) */
if (ret <= 0) {
dev_err(isp->dev, "Timeout waiting for rdma lock\n");
return ret ?: -ETIMEDOUT;
}
ret = c4_isp_rdma_manual_start(isp, &isp->febe_rdma_init,
isp->febe_rdma_init_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start febe_rdma_init\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &isp->febe_rdma_cfg,
isp->febe_rdma_cfg_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start febe_rdma_cfg\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &isp->disp_rdma_init,
isp->disp_rdma_init_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start disp_rdma_init\n");
goto rdma_runtime_unlock;
}
rdma_runtime_unlock:
c4_ipp_rdma_runtime_mutex_lock(isp, false);
return ret;
}
static int c4_ipp_cfg_single_vc_rdma_exit(struct c4_ipp *ipp)
{
struct c4_isp_device *isp = ipp->isp;
int ret;
ret = c4_ipp_rdma_runtime_mutex_trylock_time(isp, 1000);
if (ret <= 0) {
dev_err(isp->dev, "Timeout waiting for rdma lock\n");
return ret ?: -ETIMEDOUT;
}
ret = c4_isp_rdma_manual_start(isp, &isp->disp_rdma_init,
isp->disp_rdma_init_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start disp_rdma_init\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &isp->febe_rdma_init,
isp->febe_rdma_init_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start febe_rdma_init\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &isp->febe_rdma_cfg,
isp->febe_rdma_cfg_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start febe_rdma_cfg\n");
goto rdma_runtime_unlock;
}
rdma_runtime_unlock:
c4_ipp_rdma_runtime_mutex_lock(isp, false);
return ret;
}
static int c4_ipp_cfg_multi_vc_rdma_init(struct c4_ipp *ipp)
{
struct c4_isp_device *isp = ipp->isp;
struct c4_ipp_rdma_info *ipp_rdma_info = isp->ipp_rdma_info;
int ret;
ret = c4_ipp_rdma_runtime_mutex_trylock_time(isp, 1000);
if (ret <= 0) {
dev_err(isp->dev, "Timeout waiting for rdma lock\n");
return ret ?: -ETIMEDOUT;
}
ret = c4_isp_rdma_manual_start(isp, &ipp_rdma_info->rdma_init,
ipp_rdma_info->rdma_init_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start ipp_rdma_init\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &ipp_rdma_info->rdma_cfg,
ipp_rdma_info->rdma_cfg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start ipp_rdma_cfg\n");
goto rdma_runtime_unlock;
}
rdma_runtime_unlock:
c4_ipp_rdma_runtime_mutex_lock(isp, false);
return ret;
}
static int c4_ipp_cfg_multi_vc_rdma_exit(struct c4_ipp *ipp)
{
struct c4_isp_device *isp = ipp->isp;
struct c4_ipp_rdma_info *ipp_rdma_info = isp->ipp_rdma_info;
int ret;
ret = c4_ipp_rdma_runtime_mutex_trylock_time(isp, 1000);
if (ret <= 0) {
dev_err(isp->dev, "Timeout waiting for rdma lock\n");
return ret ?: -ETIMEDOUT;
}
ret = c4_isp_rdma_manual_start(isp, &ipp_rdma_info->rdma_init,
ipp_rdma_info->rdma_init_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start ipp_rdma_init\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &ipp_rdma_info->rdma_cfg,
ipp_rdma_info->rdma_cfg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start ipp_rdma_cfg\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &isp->disp_rdma_init,
isp->disp_rdma_init_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start disp_rdma_init\n");
goto rdma_runtime_unlock;
}
ret = c4_isp_rdma_manual_start(isp, &isp->febe_rdma_cfg,
isp->febe_rdma_cfg_reg_cnt);
if (ret) {
dev_err(isp->dev, "Failed to manual start febe_rdma_cfg\n");
goto rdma_runtime_unlock;
}
rdma_runtime_unlock:
c4_ipp_rdma_runtime_mutex_lock(isp, false);
return ret;
}
static int c4_ipp_cfg_vc_mode_rdma_init(struct c4_ipp *ipp)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
int ret;
if (!ipp->isp->rdma_en)
return 0;
if (cfg_info->work_mode == C4_IPP_SINGLE_VC_MODE)
ret = c4_ipp_cfg_single_vc_rdma_init(ipp);
else
ret = c4_ipp_cfg_multi_vc_rdma_init(ipp);
return ret;
}
static int c4_ipp_cfg_vc_mode_rdma_exit(struct c4_ipp *ipp)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
int ret;
if (!ipp->isp->rdma_en)
return 0;
if (cfg_info->work_mode == C4_IPP_SINGLE_VC_MODE)
ret = c4_ipp_cfg_single_vc_rdma_exit(ipp);
else
ret = c4_ipp_cfg_multi_vc_rdma_exit(ipp);
return ret;
}
static void c4_ipp_cfg_pre_params(struct c4_ipp *ipp)
{
/* Switch to write rdma_cfg when rdma_en is true */
if (ipp->isp->rdma_en)
ipp->isp->rdma_wr_phase = C4_ISP_RDMA_WR_CFG;
c4_isp_params_pre_cfg(ipp->isp);
c4_isp_stats_pre_cfg(ipp->isp);
}
static int c4_ipp_enable(struct c4_ipp *ipp, struct v4l2_subdev_state *state)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
struct v4l2_mbus_framefmt *fmt;
const struct c4_ipp_format_info *info;
int ret;
fmt = v4l2_subdev_state_get_format(state, C4_IPP_PAD_SINK_VIDEO);
info = ipp_find_format_by_code(fmt->code);
if (!info) {
dev_err(ipp->isp->dev,
"Failed to find format info: 0x%x\n", fmt->code);
return -EINVAL;
}
ret = c4_ipp_cfg_bits_mode(ipp, fmt, info);
if (ret)
return ret;
c4_ipp_cfg_4vc_intr(ipp, true);
c4_isp_top_cfg_ipp_pattern(ipp->isp, cfg_info->patgen_en);
if (cfg_info->work_mode == C4_IPP_MULTI_VC_SYNC_MODE)
ret = c4_ipp_cfg_multi_vc_sync(ipp);
else if (cfg_info->work_mode == C4_IPP_MULTI_VC_SEP_MODE)
ret = c4_ipp_cfg_multi_vc_sep(ipp);
else
ret = 0;
dev_info(ipp->isp->dev, "IPP work mode %u, image mode %u, ret %d\n",
cfg_info->work_mode, cfg_info->img_mode, ret);
return ret;
}
static void c4_ipp_disable(struct c4_ipp *ipp)
{
struct c4_ipp_cfg_info *cfg_info = &ipp->cfg_info;
c4_isp_top_cfg_ipp_pattern(ipp->isp, false);
c4_ipp_cfg_4vc_intr(ipp, false);
if (cfg_info->work_mode == C4_IPP_MULTI_VC_SYNC_MODE) {
c4_isp_intf_cfg_force_other(ipp->isp, ipp->isp->info->id, false);
c4_isp_top_cfg_ipp_multi_vc_ctrl(ipp->isp, false);
} else if (cfg_info->work_mode == C4_IPP_MULTI_VC_SEP_MODE) {
c4_isp_intf_cfg_force_other(ipp->isp, ipp->isp->info->id, false);
c4_isp_top_cfg_ipp_multi_vc_ctrl(ipp->isp, false);
c4_isp_intf_cfg_4vc_sep(ipp->isp, ipp->isp->info->id, false);
} else {
return;
}
}
static bool c4_ipp_streams_ready(struct c4_ipp *ipp)
{
unsigned int n_links = 0;
struct media_link *link;
ipp->start_count++;
for_each_media_entity_data_link(&ipp->sd.entity, link) {
if ((link->source->index == C4_IPP_PAD_SOURCE_VIDEO_0 ||
link->source->index == C4_IPP_PAD_SOURCE_VIDEO_1 ||
link->source->index == C4_IPP_PAD_SOURCE_VIDEO_2 ||
link->source->index == C4_IPP_PAD_SOURCE_VIDEO_3 ||
link->source->index == C4_IPP_PAD_SOURCE_VIDEO_ISP) &&
link->flags == MEDIA_LNK_FL_ENABLED)
n_links++;
}
return n_links == ipp->start_count;
}
static int c4_ipp_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct c4_ipp *ipp = v4l2_get_subdevdata(sd);
struct media_pad *sink_pad;
struct v4l2_subdev *src_sd;
int ret;
c4_ipp_rdma_mutex_lock(ipp->isp, true);
c4_ipp_rdma_work_flag(ipp->isp, true);
ret = c4_ipp_cfg_vc_crop(ipp, pad, state);
if (ret) {
dev_err(ipp->isp->dev, "Failed to cfg vc crop\n");
return ret;
}
if (!c4_ipp_streams_ready(ipp)) {
c4_ipp_rdma_work_flag(ipp->isp, false);
c4_ipp_rdma_mutex_lock(ipp->isp, false);
return 0;
}
ipp->isp->ipp_frm_seq = 0;
ret = c4_ipp_enable(ipp, state);
if (ret) {
c4_ipp_rdma_work_flag(ipp->isp, false);
c4_ipp_rdma_mutex_lock(ipp->isp, false);
dev_err(ipp->isp->dev, "Failed to enable ipp %u\n",
ipp->isp->info->id);
goto err_ipp_sub_start_count;
}
c4_ipp_rdma_start_count(ipp->isp, true);
c4_ipp_rdma_work_flag(ipp->isp, false);
c4_ipp_rdma_mutex_lock(ipp->isp, false);
c4_ipp_cfg_pre_params(ipp);
ret = c4_ipp_cfg_vc_mode_rdma_init(ipp);
if (ret)
goto err_ipp_rdma_start_count;
sink_pad = &ipp->pads[C4_IPP_PAD_SINK_VIDEO];
ipp->src_pad = media_pad_remote_pad_unique(sink_pad);
if (IS_ERR(ipp->src_pad)) {
ret = -EPIPE;
dev_dbg(ipp->isp->dev,
"Failed to get source pad for ISP IPP\n");
goto err_ipp_rdma_start_count;
}
src_sd = media_entity_to_v4l2_subdev(ipp->src_pad->entity);
ret = v4l2_subdev_enable_streams(src_sd, ipp->src_pad->index, BIT(0));
if (ret)
goto err_ipp_rdma_start_count;
ipp->isp->streaming = true;
return 0;
err_ipp_rdma_start_count:
c4_ipp_rdma_start_count(ipp->isp, false);
c4_ipp_disable(ipp);
err_ipp_sub_start_count:
ipp->start_count--;
return ret;
}
static int c4_ipp_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct c4_ipp *ipp = v4l2_get_subdevdata(sd);
struct v4l2_subdev *src_sd;
int ret;
c4_ipp_rdma_mutex_lock(ipp->isp, true);
c4_ipp_rdma_work_flag(ipp->isp, true);
ipp->start_count--;
if (ipp->start_count) {
c4_ipp_rdma_work_flag(ipp->isp, false);
c4_ipp_rdma_mutex_lock(ipp->isp, false);
return 0;
}
ipp->isp->streaming = false;
if (ipp->src_pad) {
src_sd = media_entity_to_v4l2_subdev(ipp->src_pad->entity);
v4l2_subdev_disable_streams(src_sd, ipp->src_pad->index, BIT(0));
}
ipp->src_pad = NULL;
c4_ipp_disable(ipp);
c4_ipp_rdma_start_count(ipp->isp, false);
c4_ipp_rdma_work_flag(ipp->isp, false);
c4_ipp_rdma_mutex_lock(ipp->isp, false);
ret = c4_ipp_cfg_vc_mode_rdma_exit(ipp);
if (ret)
return ret;
return 0;
}
static int c4_ipp_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum *code)
{
const struct c4_ipp_format_info *info;
if (code->pad >= C4_IPP_PAD_MAX)
return -EINVAL;
info = ipp_find_format_by_index(code->index);
if (!info)
return -EINVAL;
code->code = info->code;
return 0;
}
static void c4_ipp_set_sink_fmt(struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
struct v4l2_mbus_framefmt *sink_fmt;
struct v4l2_mbus_framefmt *src_fmt;
const struct c4_ipp_format_info *isp_fmt;
struct v4l2_rect *crop;
sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
isp_fmt = ipp_find_format_by_code(format->format.code);
if (!isp_fmt)
sink_fmt->code = C4_IPP_DEF_PAD_FMT;
else
sink_fmt->code = format->format.code;
sink_fmt->width = clamp_t(u32, format->format.width,
C4_ISP_MIN_WIDTH, C4_ISP_MAX_WIDTH);
sink_fmt->height = clamp_t(u32, format->format.height,
C4_ISP_MIN_HEIGHT, C4_ISP_MAX_HEIGHT);
sink_fmt->field = V4L2_FIELD_NONE;
sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
if (isp_fmt && isp_fmt->is_raw) {
sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
} else {
sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
}
format->format = *sink_fmt;
for (u32 i = C4_IPP_PAD_SOURCE_VIDEO_ISP; i < C4_IPP_PAD_MAX; i++) {
src_fmt = v4l2_subdev_state_get_format(state, i);
src_fmt->width = sink_fmt->width;
src_fmt->height = sink_fmt->height;
crop = v4l2_subdev_state_get_crop(state, i);
crop->left = 0;
crop->top = 0;
crop->width = src_fmt->width;
crop->height = src_fmt->height;
}
}
static void c4_ipp_set_source_fmt(struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
const struct c4_ipp_format_info *isp_fmt;
struct v4l2_mbus_framefmt *src_fmt;
struct v4l2_rect *crop;
src_fmt = v4l2_subdev_state_get_format(state, format->pad);
isp_fmt = ipp_find_format_by_code(format->format.code);
if (!isp_fmt)
src_fmt->code = C4_IPP_DEF_PAD_FMT;
else
src_fmt->code = format->format.code;
src_fmt->width = clamp_t(u32, format->format.width,
C4_ISP_MIN_WIDTH, C4_ISP_MAX_WIDTH);
src_fmt->height = clamp_t(u32, format->format.height,
C4_ISP_MIN_HEIGHT, C4_ISP_MAX_HEIGHT);
src_fmt->field = V4L2_FIELD_NONE;
src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
if (isp_fmt && isp_fmt->is_raw) {
src_fmt->colorspace = V4L2_COLORSPACE_RAW;
src_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
src_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
} else {
src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
}
format->format = *src_fmt;
crop = v4l2_subdev_state_get_crop(state, format->pad);
crop->left = 0;
crop->top = 0;
crop->width = src_fmt->width;
crop->height = src_fmt->height;
}
static int c4_ipp_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
if (format->pad == C4_IPP_PAD_SINK_VIDEO)
c4_ipp_set_sink_fmt(state, format);
else
c4_ipp_set_source_fmt(state, format);
return 0;
}
static int c4_ipp_get_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel)
{
struct v4l2_rect *crop;
if (sel->pad == C4_IPP_PAD_SINK_VIDEO)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_CROP:
crop = v4l2_subdev_state_get_crop(state, sel->pad);
sel->r = *crop;
break;
default:
return -EINVAL;
}
return 0;
}
static int c4_ipp_set_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel)
{
struct v4l2_mbus_framefmt *src_fmt;
struct v4l2_rect *crop;
if (sel->pad == C4_IPP_PAD_SINK_VIDEO)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_CROP:
src_fmt = v4l2_subdev_state_get_format(state, sel->pad);
crop = v4l2_subdev_state_get_crop(state, sel->pad);
crop->width = sel->r.width;
crop->height = sel->r.height;
crop->left = sel->r.left;
crop->top = sel->r.top;
break;
default:
return -EINVAL;
}
return 0;
}
static int c4_ipp_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_mbus_framefmt *fmt;
struct v4l2_rect *crop;
fmt = v4l2_subdev_state_get_format(state, C4_IPP_PAD_SINK_VIDEO);
fmt->width = C4_ISP_DEFAULT_WIDTH;
fmt->height = C4_ISP_DEFAULT_HEIGHT;
fmt->field = V4L2_FIELD_NONE;
fmt->code = C4_IPP_DEF_PAD_FMT;
fmt->colorspace = V4L2_COLORSPACE_RAW;
fmt->xfer_func = V4L2_XFER_FUNC_NONE;
fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
for (u32 i = C4_IPP_PAD_SOURCE_VIDEO_ISP; i < C4_IPP_PAD_MAX; i++) {
fmt = v4l2_subdev_state_get_format(state, i);
fmt->width = C4_ISP_DEFAULT_WIDTH;
fmt->height = C4_ISP_DEFAULT_HEIGHT;
fmt->field = V4L2_FIELD_NONE;
fmt->code = C4_IPP_DEF_PAD_FMT;
fmt->colorspace = V4L2_COLORSPACE_RAW;
fmt->xfer_func = V4L2_XFER_FUNC_NONE;
fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
crop = v4l2_subdev_state_get_crop(state, i);
crop->left = 0;
crop->top = 0;
crop->width = fmt->width;
crop->height = fmt->height;
}
return 0;
}
static const struct v4l2_subdev_pad_ops c4_ipp_pad_ops = {
.enum_mbus_code = c4_ipp_enum_mbus_code,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = c4_ipp_set_fmt,
.get_selection = c4_ipp_get_selection,
.set_selection = c4_ipp_set_selection,
.enable_streams = c4_ipp_enable_streams,
.disable_streams = c4_ipp_disable_streams,
};
static const struct v4l2_subdev_ops c4_ipp_subdev_ops = {
.pad = &c4_ipp_pad_ops,
};
static const struct v4l2_subdev_internal_ops c4_ipp_internal_ops = {
.init_state = c4_ipp_init_state,
};
/* Media entity operations */
static const struct media_entity_operations c4_ipp_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
};
static int c4_ipp_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct c4_ipp *ipp = container_of(ctrl->handler,
struct c4_ipp, ctrls);
switch (ctrl->id) {
case V4L2_CID_C4_IPP_WORK_MODE:
ipp->cfg_info.work_mode = ctrl->val;
break;
case V4L2_CID_C4_IPP_IMAGE_MODE:
ipp->cfg_info.img_mode = ctrl->val;
break;
case V4L2_CID_C4_IPP_PATTERN_GEN:
ipp->cfg_info.patgen_en = ctrl->val ? true : false;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct v4l2_ctrl_ops c4_ipp_ctrl_ops = {
.s_ctrl = c4_ipp_set_ctrl,
};
static const struct v4l2_ctrl_config c4_ipp_work_mode_cfg = {
.ops = &c4_ipp_ctrl_ops,
.id = V4L2_CID_C4_IPP_WORK_MODE,
.name = "ISP IPP work mode",
.type = V4L2_CTRL_TYPE_INTEGER,
.min = C4_IPP_SINGLE_VC_MODE,
.max = C4_IPP_WORK_MODE_MAX - 1,
.step = 1,
.def = C4_IPP_SINGLE_VC_MODE,
};
static const struct v4l2_ctrl_config c4_ipp_image_mode_cfg = {
.ops = &c4_ipp_ctrl_ops,
.id = V4L2_CID_C4_IPP_IMAGE_MODE,
.name = "ISP IPP image mode",
.type = V4L2_CTRL_TYPE_INTEGER,
.min = C4_IPP_IMAGE_SDR,
.max = C4_IPP_IMAGE_MAX - 1,
.step = 1,
.def = C4_IPP_IMAGE_SDR,
};
static const struct v4l2_ctrl_config c4_ipp_patgen_cfg = {
.ops = &c4_ipp_ctrl_ops,
.id = V4L2_CID_C4_IPP_PATTERN_GEN,
.name = "ISP IPP pattern generate enable",
.type = V4L2_CTRL_TYPE_INTEGER,
.min = 0,
.max = 2,
.step = 1,
.def = 0,
};
static int c4_ipp_ctrl_init(struct c4_ipp *ipp)
{
struct v4l2_ctrl *ctrl;
ipp->cfg_info.work_mode = C4_IPP_SINGLE_VC_MODE;
ipp->cfg_info.img_mode = C4_IPP_IMAGE_SDR;
ipp->cfg_info.patgen_en = false;
v4l2_ctrl_handler_init(&ipp->ctrls, 3);
ctrl = v4l2_ctrl_new_custom(&ipp->ctrls, &c4_ipp_work_mode_cfg, NULL);
if (!ctrl) {
dev_err(ipp->isp->dev, "Failed to set ipp mode cfg ctrl\n");
return -EINVAL;
}
ctrl = v4l2_ctrl_new_custom(&ipp->ctrls, &c4_ipp_image_mode_cfg, NULL);
if (!ctrl) {
dev_err(ipp->isp->dev, "Failed to set ipp hdr cfg ctrl\n");
return -EINVAL;
}
ctrl = v4l2_ctrl_new_custom(&ipp->ctrls, &c4_ipp_patgen_cfg, NULL);
if (!ctrl) {
dev_err(ipp->isp->dev, "Failed to set ipp pattern ctrl\n");
return -EINVAL;
}
ipp->sd.ctrl_handler = &ipp->ctrls;
return 0;
}
static void c4_ipp_ctrl_deinit(struct c4_ipp *ipp)
{
v4l2_ctrl_handler_free(&ipp->ctrls);
}
int c4_ipp_register(struct c4_isp_device *isp)
{
struct c4_ipp *ipp = &isp->ipp;
struct v4l2_subdev *sd = &ipp->sd;
int ret;
v4l2_subdev_init(sd, &c4_ipp_subdev_ops);
sd->owner = THIS_MODULE;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
sd->internal_ops = &c4_ipp_internal_ops;
snprintf(sd->name, sizeof(sd->name), "%s", C4_IPP_SUBDEV_NAME);
sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
sd->entity.ops = &c4_ipp_entity_ops;
ipp->start_count = 0;
ipp->isp = isp;
sd->dev = isp->dev;
v4l2_set_subdevdata(sd, ipp);
ipp->pads[C4_IPP_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
ipp->pads[C4_IPP_PAD_SOURCE_VIDEO_ISP].flags = MEDIA_PAD_FL_SOURCE;
ipp->pads[C4_IPP_PAD_SOURCE_VIDEO_0].flags = MEDIA_PAD_FL_SOURCE;
ipp->pads[C4_IPP_PAD_SOURCE_VIDEO_1].flags = MEDIA_PAD_FL_SOURCE;
ipp->pads[C4_IPP_PAD_SOURCE_VIDEO_2].flags = MEDIA_PAD_FL_SOURCE;
ipp->pads[C4_IPP_PAD_SOURCE_VIDEO_3].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, C4_IPP_PAD_MAX, ipp->pads);
if (ret)
return ret;
ret = c4_ipp_ctrl_init(ipp);
if (ret)
goto err_entity_cleanup;
ret = v4l2_subdev_init_finalize(sd);
if (ret)
goto err_ctrl_deinit;
ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
if (ret)
goto err_subdev_cleanup;
return 0;
err_subdev_cleanup:
v4l2_subdev_cleanup(sd);
err_ctrl_deinit:
c4_ipp_ctrl_deinit(ipp);
err_entity_cleanup:
media_entity_cleanup(&sd->entity);
return ret;
}
void c4_ipp_unregister(struct c4_isp_device *isp)
{
struct c4_ipp *ipp = &isp->ipp;
struct v4l2_subdev *sd = &ipp->sd;
v4l2_device_unregister_subdev(sd);
v4l2_subdev_cleanup(sd);
c4_ipp_ctrl_deinit(ipp);
media_entity_cleanup(&sd->entity);
}
static void c4_ipp_multi_vc_sync_isr(struct c4_isp_device *isp)
{
u32 ipp_id = isp->info->id;
u32 status;
status = c4_isp_intf_get_user_idmif_irq_status(isp);
if (ipp_id == 0)
status &= MIPI_INTF_IRQ_STATUS_USR_MIF0_END |
MIPI_INTF_IRQ_STATUS_USR_MIF0_ERR;
else if (ipp_id == 1)
status &= MIPI_INTF_IRQ_STATUS_USR_MIF1_END |
MIPI_INTF_IRQ_STATUS_USR_MIF1_ERR;
else if (ipp_id == 2)
status &= MIPI_INTF_IRQ_STATUS_USR_MIF2_END |
MIPI_INTF_IRQ_STATUS_USR_MIF2_ERR;
else
status &= MIPI_INTF_IRQ_STATUS_USR_MIF3_END |
MIPI_INTF_IRQ_STATUS_USR_MIF3_ERR;
c4_isp_intf_clr_user_idmif_irq_status(isp, status);
c4_ipp_multi_vc_check_error(&isp->ipp, status);
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF0_END ||
status & MIPI_INTF_IRQ_STATUS_USR_MIF1_END ||
status & MIPI_INTF_IRQ_STATUS_USR_MIF2_END ||
status & MIPI_INTF_IRQ_STATUS_USR_MIF3_END) {
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_0);
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_1);
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_2);
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_3);
isp->ipp_frm_seq++;
}
}
static void c4_ipp_multi_vc_sep_isr(struct c4_isp_device *isp)
{
u32 status;
status = c4_isp_intf_get_user_idmif_irq_status(isp);
c4_isp_intf_clr_user_idmif_irq_status(isp, status);
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF0_END)
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_0);
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF1_END)
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_1);
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF2_END)
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_2);
if (status & MIPI_INTF_IRQ_STATUS_USR_MIF3_END)
c4_ipp_caps_isr(isp, C4_IPP_CAP_DEV_3);
isp->ipp_frm_seq++;
}
void c4_ipp_multi_vc_isr(struct c4_isp_device *isp)
{
struct c4_ipp_cfg_info *cfg_info = &isp->ipp.cfg_info;
if (cfg_info->work_mode == C4_IPP_MULTI_VC_SYNC_MODE)
c4_ipp_multi_vc_sync_isr(isp);
else if (cfg_info->work_mode == C4_IPP_MULTI_VC_SEP_MODE)
c4_ipp_multi_vc_sep_isr(isp);
else
return;
}