1025 lines
26 KiB
C
1025 lines
26 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_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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int c4_ipp_cfg_single_vc_rdma_exit(struct c4_ipp *ipp)
|
|
{
|
|
struct c4_isp_device *isp = ipp->isp;
|
|
int ret;
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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_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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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_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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
ipp->isp->streaming = false;
|
|
|
|
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;
|
|
}
|