| // SPDX-License-Identifier: (GPL-2.0-only OR MIT) |
| /* |
| * Copyright (C) 2024 Amlogic, Inc. All rights reserved |
| */ |
| |
| #include <linux/build_bug.h> |
| #include <linux/cleanup.h> |
| #include <linux/media/amlogic/c3-isp-config.h> |
| #include <linux/pm_runtime.h> |
| |
| #include <media/v4l2-ioctl.h> |
| #include <media/v4l2-isp.h> |
| #include <media/v4l2-mc.h> |
| #include <media/videobuf2-vmalloc.h> |
| |
| #include "c3-isp-common.h" |
| #include "c3-isp-regs.h" |
| |
| /* |
| * union c3_isp_params_block - Generalisation of a parameter block |
| * |
| * This union allows the driver to treat a block as a generic struct to this |
| * union and safely access the header and block-specific struct without having |
| * to resort to casting. The header member is accessed first, and the type field |
| * checked which allows the driver to determine which of the other members |
| * should be used. |
| * |
| * @header: The shared header struct embedded as the first member |
| * of all the possible other members. This member would be |
| * accessed first and the type field checked to determine |
| * which of the other members should be accessed. |
| * @awb_gains: For header.type == C3_ISP_PARAMS_BLOCK_AWB_GAINS |
| * @awb_cfg: For header.type == C3_ISP_PARAMS_BLOCK_AWB_CONFIG |
| * @ae_cfg: For header.type == C3_ISP_PARAMS_BLOCK_AE_CONFIG |
| * @af_cfg: For header.type == C3_ISP_PARAMS_BLOCK_AF_CONFIG |
| * @pst_gamma: For header.type == C3_ISP_PARAMS_BLOCK_PST_GAMMA |
| * @ccm: For header.type == C3_ISP_PARAMS_BLOCK_CCM |
| * @csc: For header.type == C3_ISP_PARAMS_BLOCK_CSC |
| * @blc: For header.type == C3_ISP_PARAMS_BLOCK_BLC |
| */ |
| union c3_isp_params_block { |
| struct c3_isp_params_block_header header; |
| struct c3_isp_params_awb_gains awb_gains; |
| struct c3_isp_params_awb_config awb_cfg; |
| struct c3_isp_params_ae_config ae_cfg; |
| struct c3_isp_params_af_config af_cfg; |
| struct c3_isp_params_pst_gamma pst_gamma; |
| struct c3_isp_params_ccm ccm; |
| struct c3_isp_params_csc csc; |
| struct c3_isp_params_blc blc; |
| }; |
| |
| typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block); |
| |
| #define to_c3_isp_params_buffer(vbuf) \ |
| container_of(vbuf, struct c3_isp_params_buffer, vb) |
| |
| /* Hardware configuration */ |
| |
| static void c3_isp_params_cfg_awb_gains(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_awb_gains *awb_gains = &block->awb_gains; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, |
| ISP_TOP_BEO_CTRL_WB_EN_MASK, |
| ISP_TOP_BEO_CTRL_WB_DIS); |
| return; |
| } |
| |
| c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, |
| ISP_LSWB_WB_GAIN0_GR_GAIN_MASK, |
| ISP_LSWB_WB_GAIN0_GR_GAIN(awb_gains->gr_gain)); |
| c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, |
| ISP_LSWB_WB_GAIN0_R_GAIN_MASK, |
| ISP_LSWB_WB_GAIN0_R_GAIN(awb_gains->r_gain)); |
| c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, |
| ISP_LSWB_WB_GAIN1_B_GAIN_MASK, |
| ISP_LSWB_WB_GAIN1_B_GAIN(awb_gains->b_gain)); |
| c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, |
| ISP_LSWB_WB_GAIN1_GB_GAIN_MASK, |
| ISP_LSWB_WB_GAIN1_GB_GAIN(awb_gains->gb_gain)); |
| c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2, |
| ISP_LSWB_WB_GAIN2_IR_GAIN_MASK, |
| ISP_LSWB_WB_GAIN2_IR_GAIN(awb_gains->gb_gain)); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, |
| ISP_TOP_BEO_CTRL_WB_EN_MASK, |
| ISP_TOP_BEO_CTRL_WB_EN); |
| } |
| |
| static void c3_isp_params_awb_wt(struct c3_isp_device *isp, |
| const struct c3_isp_params_awb_config *cfg) |
| { |
| unsigned int zones_num; |
| unsigned int base; |
| unsigned int data; |
| unsigned int i; |
| |
| /* Set the weight address to 0 position */ |
| c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0); |
| |
| zones_num = cfg->horiz_zones_num * cfg->vert_zones_num; |
| |
| /* Need to write 8 weights at once */ |
| for (i = 0; i < zones_num / 8; i++) { |
| base = i * 8; |
| data = ISP_AWB_BLK_WT_DATA_WT(0, cfg->zone_weight[base + 0]) | |
| ISP_AWB_BLK_WT_DATA_WT(1, cfg->zone_weight[base + 1]) | |
| ISP_AWB_BLK_WT_DATA_WT(2, cfg->zone_weight[base + 2]) | |
| ISP_AWB_BLK_WT_DATA_WT(3, cfg->zone_weight[base + 3]) | |
| ISP_AWB_BLK_WT_DATA_WT(4, cfg->zone_weight[base + 4]) | |
| ISP_AWB_BLK_WT_DATA_WT(5, cfg->zone_weight[base + 5]) | |
| ISP_AWB_BLK_WT_DATA_WT(6, cfg->zone_weight[base + 6]) | |
| ISP_AWB_BLK_WT_DATA_WT(7, cfg->zone_weight[base + 7]); |
| c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data); |
| } |
| |
| if (zones_num % 8 == 0) |
| return; |
| |
| data = 0; |
| base = i * 8; |
| |
| for (i = 0; i < zones_num % 8; i++) |
| data |= ISP_AWB_BLK_WT_DATA_WT(i, cfg->zone_weight[base + i]); |
| |
| c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data); |
| } |
| |
| static void c3_isp_params_awb_cood(struct c3_isp_device *isp, |
| const struct c3_isp_params_awb_config *cfg) |
| { |
| unsigned int max_point_num; |
| |
| /* The number of points is one more than the number of edges */ |
| max_point_num = max(cfg->horiz_zones_num, cfg->vert_zones_num) + 1; |
| |
| /* Set the index address to 0 position */ |
| c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0); |
| |
| for (unsigned int i = 0; i < max_point_num; i++) |
| c3_isp_write(isp, ISP_AWB_IDX_DATA, |
| ISP_AWB_IDX_DATA_HIDX_DATA(cfg->horiz_coord[i]) | |
| ISP_AWB_IDX_DATA_VIDX_DATA(cfg->vert_coord[i])); |
| } |
| |
| static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_awb_config *awb_cfg = &block->awb_cfg; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS); |
| return; |
| } |
| |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AWB_POINT_MASK, |
| ISP_TOP_3A_STAT_CRTL_AWB_POINT(awb_cfg->tap_point)); |
| |
| c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, |
| ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK, |
| ISP_AWB_STAT_CTRL2_SATUR_CTRL(awb_cfg->satur_vald)); |
| |
| c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, |
| ISP_AWB_HV_BLKNUM_H_NUM_MASK, |
| ISP_AWB_HV_BLKNUM_H_NUM(awb_cfg->horiz_zones_num)); |
| c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, |
| ISP_AWB_HV_BLKNUM_V_NUM_MASK, |
| ISP_AWB_HV_BLKNUM_V_NUM(awb_cfg->vert_zones_num)); |
| |
| c3_isp_update_bits(isp, ISP_AWB_STAT_RG, ISP_AWB_STAT_RG_MIN_VALUE_MASK, |
| ISP_AWB_STAT_RG_MIN_VALUE(awb_cfg->rg_min)); |
| c3_isp_update_bits(isp, ISP_AWB_STAT_RG, ISP_AWB_STAT_RG_MAX_VALUE_MASK, |
| ISP_AWB_STAT_RG_MAX_VALUE(awb_cfg->rg_max)); |
| |
| c3_isp_update_bits(isp, ISP_AWB_STAT_BG, ISP_AWB_STAT_BG_MIN_VALUE_MASK, |
| ISP_AWB_STAT_BG_MIN_VALUE(awb_cfg->bg_min)); |
| c3_isp_update_bits(isp, ISP_AWB_STAT_BG, ISP_AWB_STAT_BG_MAX_VALUE_MASK, |
| ISP_AWB_STAT_BG_MAX_VALUE(awb_cfg->bg_max)); |
| |
| c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, |
| ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK, |
| ISP_AWB_STAT_RG_HL_LOW_VALUE(awb_cfg->rg_low)); |
| c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, |
| ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK, |
| ISP_AWB_STAT_RG_HL_HIGH_VALUE(awb_cfg->rg_high)); |
| |
| c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, |
| ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK, |
| ISP_AWB_STAT_BG_HL_LOW_VALUE(awb_cfg->bg_low)); |
| c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, |
| ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK, |
| ISP_AWB_STAT_BG_HL_HIGH_VALUE(awb_cfg->bg_high)); |
| |
| c3_isp_params_awb_wt(isp, awb_cfg); |
| c3_isp_params_awb_cood(isp, awb_cfg); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN); |
| } |
| |
| static void c3_isp_params_ae_wt(struct c3_isp_device *isp, |
| const struct c3_isp_params_ae_config *cfg) |
| { |
| unsigned int zones_num; |
| unsigned int base; |
| unsigned int data; |
| unsigned int i; |
| |
| /* Set the weight address to 0 position */ |
| c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0); |
| |
| zones_num = cfg->horiz_zones_num * cfg->vert_zones_num; |
| |
| /* Need to write 8 weights at once */ |
| for (i = 0; i < zones_num / 8; i++) { |
| base = i * 8; |
| data = ISP_AE_BLK_WT_DATA_WT(0, cfg->zone_weight[base + 0]) | |
| ISP_AE_BLK_WT_DATA_WT(1, cfg->zone_weight[base + 1]) | |
| ISP_AE_BLK_WT_DATA_WT(2, cfg->zone_weight[base + 2]) | |
| ISP_AE_BLK_WT_DATA_WT(3, cfg->zone_weight[base + 3]) | |
| ISP_AE_BLK_WT_DATA_WT(4, cfg->zone_weight[base + 4]) | |
| ISP_AE_BLK_WT_DATA_WT(5, cfg->zone_weight[base + 5]) | |
| ISP_AE_BLK_WT_DATA_WT(6, cfg->zone_weight[base + 6]) | |
| ISP_AE_BLK_WT_DATA_WT(7, cfg->zone_weight[base + 7]); |
| c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data); |
| } |
| |
| if (zones_num % 8 == 0) |
| return; |
| |
| data = 0; |
| base = i * 8; |
| |
| /* Write the last weights data */ |
| for (i = 0; i < zones_num % 8; i++) |
| data |= ISP_AE_BLK_WT_DATA_WT(i, cfg->zone_weight[base + i]); |
| |
| c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data); |
| } |
| |
| static void c3_isp_params_ae_cood(struct c3_isp_device *isp, |
| const struct c3_isp_params_ae_config *cfg) |
| { |
| unsigned int max_point_num; |
| |
| /* The number of points is one more than the number of edges */ |
| max_point_num = max(cfg->horiz_zones_num, cfg->vert_zones_num) + 1; |
| |
| /* Set the index address to 0 position */ |
| c3_isp_write(isp, ISP_AE_IDX_ADDR, 0); |
| |
| for (unsigned int i = 0; i < max_point_num; i++) |
| c3_isp_write(isp, ISP_AE_IDX_DATA, |
| ISP_AE_IDX_DATA_HIDX_DATA(cfg->horiz_coord[i]) | |
| ISP_AE_IDX_DATA_VIDX_DATA(cfg->vert_coord[i])); |
| } |
| |
| static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_ae_config *ae_cfg = &block->ae_cfg; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS); |
| return; |
| } |
| |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AE_POINT_MASK, |
| ISP_TOP_3A_STAT_CRTL_AE_POINT(ae_cfg->tap_point)); |
| |
| if (ae_cfg->tap_point == C3_ISP_AE_STATS_TAP_GE) |
| c3_isp_update_bits(isp, ISP_AE_CTRL, |
| ISP_AE_CTRL_INPUT_2LINE_MASK, |
| ISP_AE_CTRL_INPUT_2LINE_EN); |
| else |
| c3_isp_update_bits(isp, ISP_AE_CTRL, |
| ISP_AE_CTRL_INPUT_2LINE_MASK, |
| ISP_AE_CTRL_INPUT_2LINE_DIS); |
| |
| c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, |
| ISP_AE_HV_BLKNUM_H_NUM_MASK, |
| ISP_AE_HV_BLKNUM_H_NUM(ae_cfg->horiz_zones_num)); |
| c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, |
| ISP_AE_HV_BLKNUM_V_NUM_MASK, |
| ISP_AE_HV_BLKNUM_V_NUM(ae_cfg->vert_zones_num)); |
| |
| c3_isp_params_ae_wt(isp, ae_cfg); |
| c3_isp_params_ae_cood(isp, ae_cfg); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AE_STAT_EN); |
| } |
| |
| static void c3_isp_params_af_cood(struct c3_isp_device *isp, |
| const struct c3_isp_params_af_config *cfg) |
| { |
| unsigned int max_point_num; |
| |
| /* The number of points is one more than the number of edges */ |
| max_point_num = max(cfg->horiz_zones_num, cfg->vert_zones_num) + 1; |
| |
| /* Set the index address to 0 position */ |
| c3_isp_write(isp, ISP_AF_IDX_ADDR, 0); |
| |
| for (unsigned int i = 0; i < max_point_num; i++) |
| c3_isp_write(isp, ISP_AF_IDX_DATA, |
| ISP_AF_IDX_DATA_HIDX_DATA(cfg->horiz_coord[i]) | |
| ISP_AF_IDX_DATA_VIDX_DATA(cfg->vert_coord[i])); |
| } |
| |
| static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_af_config *af_cfg = &block->af_cfg; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS); |
| return; |
| } |
| |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AF_POINT_MASK, |
| ISP_TOP_3A_STAT_CRTL_AF_POINT(af_cfg->tap_point)); |
| |
| c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, |
| ISP_AF_HV_BLKNUM_H_NUM_MASK, |
| ISP_AF_HV_BLKNUM_H_NUM(af_cfg->horiz_zones_num)); |
| c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, |
| ISP_AF_HV_BLKNUM_V_NUM_MASK, |
| ISP_AF_HV_BLKNUM_V_NUM(af_cfg->vert_zones_num)); |
| |
| c3_isp_params_af_cood(isp, af_cfg); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AF_STAT_EN); |
| } |
| |
| static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_pst_gamma *gm = &block->pst_gamma; |
| unsigned int base; |
| unsigned int i; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK, |
| ISP_TOP_BED_CTRL_PST_GAMMA_DIS); |
| return; |
| } |
| |
| /* R, G and B channels use the same gamma lut */ |
| for (unsigned int j = 0; j < 3; j++) { |
| /* Set the channel lut address */ |
| c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, |
| ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(j)); |
| |
| /* Need to write 2 lut values at once */ |
| for (i = 0; i < ARRAY_SIZE(gm->lut) / 2; i++) { |
| base = i * 2; |
| c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA, |
| ISP_PST_GM_LUT_DATA0(gm->lut[base]) | |
| ISP_PST_GM_LUT_DATA1(gm->lut[base + 1])); |
| } |
| |
| /* Write the last one */ |
| if (ARRAY_SIZE(gm->lut) % 2) { |
| base = i * 2; |
| c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA, |
| ISP_PST_GM_LUT_DATA0(gm->lut[base])); |
| } |
| } |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK, |
| ISP_TOP_BED_CTRL_PST_GAMMA_EN); |
| } |
| |
| /* Configure 3 x 3 ccm matrix */ |
| static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_ccm *ccm = &block->ccm; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_CCM_EN_MASK, |
| ISP_TOP_BED_CTRL_CCM_DIS); |
| return; |
| } |
| |
| c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, |
| ISP_CCM_MTX_00_01_MTX_00_MASK, |
| ISP_CCM_MTX_00_01_MTX_00(ccm->matrix[0][0])); |
| c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, |
| ISP_CCM_MTX_00_01_MTX_01_MASK, |
| ISP_CCM_MTX_00_01_MTX_01(ccm->matrix[0][1])); |
| c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, |
| ISP_CCM_MTX_02_03_MTX_02_MASK, |
| ISP_CCM_MTX_02_03_MTX_02(ccm->matrix[0][2])); |
| |
| c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, |
| ISP_CCM_MTX_10_11_MTX_10_MASK, |
| ISP_CCM_MTX_10_11_MTX_10(ccm->matrix[1][0])); |
| c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, |
| ISP_CCM_MTX_10_11_MTX_11_MASK, |
| ISP_CCM_MTX_10_11_MTX_11(ccm->matrix[1][1])); |
| c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, |
| ISP_CCM_MTX_12_13_MTX_12_MASK, |
| ISP_CCM_MTX_12_13_MTX_12(ccm->matrix[1][2])); |
| |
| c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, |
| ISP_CCM_MTX_20_21_MTX_20_MASK, |
| ISP_CCM_MTX_20_21_MTX_20(ccm->matrix[2][0])); |
| c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, |
| ISP_CCM_MTX_20_21_MTX_21_MASK, |
| ISP_CCM_MTX_20_21_MTX_21(ccm->matrix[2][1])); |
| c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, |
| ISP_CCM_MTX_22_23_RS_MTX_22_MASK, |
| ISP_CCM_MTX_22_23_RS_MTX_22(ccm->matrix[2][2])); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_CCM_EN_MASK, |
| ISP_TOP_BED_CTRL_CCM_EN); |
| } |
| |
| /* Configure color space conversion matrix parameters */ |
| static void c3_isp_params_cfg_csc(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_csc *csc = &block->csc; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_CM0_EN_MASK, |
| ISP_TOP_BED_CTRL_CM0_DIS); |
| return; |
| } |
| |
| c3_isp_update_bits(isp, ISP_CM0_COEF00_01, |
| ISP_CM0_COEF00_01_MTX_00_MASK, |
| ISP_CM0_COEF00_01_MTX_00(csc->matrix[0][0])); |
| c3_isp_update_bits(isp, ISP_CM0_COEF00_01, |
| ISP_CM0_COEF00_01_MTX_01_MASK, |
| ISP_CM0_COEF00_01_MTX_01(csc->matrix[0][1])); |
| c3_isp_update_bits(isp, ISP_CM0_COEF02_10, |
| ISP_CM0_COEF02_10_MTX_02_MASK, |
| ISP_CM0_COEF02_10_MTX_02(csc->matrix[0][2])); |
| |
| c3_isp_update_bits(isp, ISP_CM0_COEF02_10, |
| ISP_CM0_COEF02_10_MTX_10_MASK, |
| ISP_CM0_COEF02_10_MTX_10(csc->matrix[1][0])); |
| c3_isp_update_bits(isp, ISP_CM0_COEF11_12, |
| ISP_CM0_COEF11_12_MTX_11_MASK, |
| ISP_CM0_COEF11_12_MTX_11(csc->matrix[1][1])); |
| c3_isp_update_bits(isp, ISP_CM0_COEF11_12, |
| ISP_CM0_COEF11_12_MTX_12_MASK, |
| ISP_CM0_COEF11_12_MTX_12(csc->matrix[1][2])); |
| |
| c3_isp_update_bits(isp, ISP_CM0_COEF20_21, |
| ISP_CM0_COEF20_21_MTX_20_MASK, |
| ISP_CM0_COEF20_21_MTX_20(csc->matrix[2][0])); |
| c3_isp_update_bits(isp, ISP_CM0_COEF20_21, |
| ISP_CM0_COEF20_21_MTX_21_MASK, |
| ISP_CM0_COEF20_21_MTX_21(csc->matrix[2][1])); |
| c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, |
| ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK, |
| ISP_CM0_COEF22_OUP_OFST0_MTX_22(csc->matrix[2][2])); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_CM0_EN_MASK, |
| ISP_TOP_BED_CTRL_CM0_EN); |
| } |
| |
| /* Set blc offset of each color channel */ |
| static void c3_isp_params_cfg_blc(struct c3_isp_device *isp, |
| const union c3_isp_params_block *block) |
| { |
| const struct c3_isp_params_blc *blc = &block->blc; |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { |
| c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, |
| ISP_TOP_BEO_CTRL_BLC_EN_MASK, |
| ISP_TOP_BEO_CTRL_BLC_DIS); |
| return; |
| } |
| |
| c3_isp_write(isp, ISP_LSWB_BLC_OFST0, |
| ISP_LSWB_BLC_OFST0_R_OFST(blc->r_ofst) | |
| ISP_LSWB_BLC_OFST0_GR_OFST(blc->gr_ofst)); |
| c3_isp_write(isp, ISP_LSWB_BLC_OFST1, |
| ISP_LSWB_BLC_OFST1_GB_OFST(blc->gb_ofst) | |
| ISP_LSWB_BLC_OFST1_B_OFST(blc->b_ofst)); |
| |
| if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) |
| c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, |
| ISP_TOP_BEO_CTRL_BLC_EN_MASK, |
| ISP_TOP_BEO_CTRL_BLC_EN); |
| } |
| |
| static const c3_isp_block_handler c3_isp_params_handlers[] = { |
| [C3_ISP_PARAMS_BLOCK_AWB_GAINS] = c3_isp_params_cfg_awb_gains, |
| [C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = c3_isp_params_cfg_awb_config, |
| [C3_ISP_PARAMS_BLOCK_AE_CONFIG] = c3_isp_params_cfg_ae_config, |
| [C3_ISP_PARAMS_BLOCK_AF_CONFIG] = c3_isp_params_cfg_af_config, |
| [C3_ISP_PARAMS_BLOCK_PST_GAMMA] = c3_isp_params_cfg_pst_gamma, |
| [C3_ISP_PARAMS_BLOCK_CCM] = c3_isp_params_cfg_ccm, |
| [C3_ISP_PARAMS_BLOCK_CSC] = c3_isp_params_cfg_csc, |
| [C3_ISP_PARAMS_BLOCK_BLC] = c3_isp_params_cfg_blc, |
| }; |
| |
| #define C3_ISP_PARAMS_BLOCK_INFO(block, data) \ |
| [C3_ISP_PARAMS_BLOCK_ ## block] = { \ |
| .size = sizeof(struct c3_isp_params_ ## data), \ |
| } |
| |
| static const struct v4l2_isp_params_block_type_info |
| c3_isp_params_block_types_info[] = { |
| C3_ISP_PARAMS_BLOCK_INFO(AWB_GAINS, awb_gains), |
| C3_ISP_PARAMS_BLOCK_INFO(AWB_CONFIG, awb_config), |
| C3_ISP_PARAMS_BLOCK_INFO(AE_CONFIG, ae_config), |
| C3_ISP_PARAMS_BLOCK_INFO(AF_CONFIG, af_config), |
| C3_ISP_PARAMS_BLOCK_INFO(PST_GAMMA, pst_gamma), |
| C3_ISP_PARAMS_BLOCK_INFO(CCM, ccm), |
| C3_ISP_PARAMS_BLOCK_INFO(CSC, csc), |
| C3_ISP_PARAMS_BLOCK_INFO(BLC, blc), |
| }; |
| |
| static_assert(ARRAY_SIZE(c3_isp_params_handlers) == |
| ARRAY_SIZE(c3_isp_params_block_types_info)); |
| |
| static void c3_isp_params_cfg_blocks(struct c3_isp_params *params) |
| { |
| struct c3_isp_params_cfg *config = params->buff->cfg; |
| size_t block_offset = 0; |
| |
| if (WARN_ON(!config)) |
| return; |
| |
| /* Walk the list of parameter blocks and process them */ |
| while (block_offset < config->data_size) { |
| const union c3_isp_params_block *block; |
| c3_isp_block_handler block_handler; |
| |
| block = (const union c3_isp_params_block *) |
| &config->data[block_offset]; |
| |
| block_handler = c3_isp_params_handlers[block->header.type]; |
| block_handler(params->isp, block); |
| |
| block_offset += block->header.size; |
| } |
| } |
| |
| void c3_isp_params_pre_cfg(struct c3_isp_device *isp) |
| { |
| struct c3_isp_params *params = &isp->params; |
| |
| /* Disable some unused modules */ |
| c3_isp_update_bits(isp, ISP_TOP_FEO_CTRL0, |
| ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK, |
| ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS); |
| |
| c3_isp_update_bits(isp, ISP_TOP_FEO_CTRL1_0, |
| ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK, |
| ISP_TOP_FEO_CTRL1_0_DPC_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FEO_CTRL1_0, |
| ISP_TOP_FEO_CTRL1_0_OG_EN_MASK, |
| ISP_TOP_FEO_CTRL1_0_OG_DIS); |
| |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_PDPC_EN_MASK, |
| ISP_TOP_FED_CTRL_PDPC_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, |
| ISP_TOP_FED_CTRL_RAWCNR_EN_MASK, |
| ISP_TOP_FED_CTRL_RAWCNR_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_SNR1_EN_MASK, |
| ISP_TOP_FED_CTRL_SNR1_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_TNR0_EN_MASK, |
| ISP_TOP_FED_CTRL_TNR0_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, |
| ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK, |
| ISP_TOP_FED_CTRL_CUBIC_CS_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_SQRT_EN_MASK, |
| ISP_TOP_FED_CTRL_SQRT_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, |
| ISP_TOP_FED_CTRL_DGAIN_EN_MASK, |
| ISP_TOP_FED_CTRL_DGAIN_DIS); |
| |
| c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, |
| ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK, |
| ISP_TOP_BEO_CTRL_INV_DGAIN_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_EOTF_EN_MASK, |
| ISP_TOP_BEO_CTRL_EOTF_DIS); |
| |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK, |
| ISP_TOP_BED_CTRL_YHS_STAT_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK, |
| ISP_TOP_BED_CTRL_GRPH_STAT_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_FMETER_EN_MASK, |
| ISP_TOP_BED_CTRL_FMETER_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_BSC_EN_MASK, |
| ISP_TOP_BED_CTRL_BSC_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_CNR2_EN_MASK, |
| ISP_TOP_BED_CTRL_CNR2_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_CM1_EN_MASK, |
| ISP_TOP_BED_CTRL_CM1_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_LUT3D_EN_MASK, |
| ISP_TOP_BED_CTRL_LUT3D_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, |
| ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK, |
| ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_AMCM_EN_MASK, |
| ISP_TOP_BED_CTRL_AMCM_DIS); |
| |
| /* |
| * Disable AE, AF and AWB stat module. Please configure the parameters |
| * in userspace algorithm if need to enable these switch. |
| */ |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS); |
| c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, |
| ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK, |
| ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS); |
| |
| c3_isp_write(isp, ISP_LSWB_WB_LIMIT0, |
| ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX | |
| ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX); |
| c3_isp_write(isp, ISP_LSWB_WB_LIMIT1, |
| ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX | |
| ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX); |
| |
| guard(spinlock_irqsave)(¶ms->buff_lock); |
| |
| /* Only use the first buffer to initialize ISP */ |
| params->buff = |
| list_first_entry_or_null(¶ms->pending, |
| struct c3_isp_params_buffer, list); |
| if (params->buff) |
| c3_isp_params_cfg_blocks(params); |
| } |
| |
| /* V4L2 video operations */ |
| |
| static int c3_isp_params_querycap(struct file *file, void *fh, |
| struct v4l2_capability *cap) |
| { |
| strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver)); |
| strscpy(cap->card, "AML C3 ISP", sizeof(cap->card)); |
| |
| return 0; |
| } |
| |
| static int c3_isp_params_enum_fmt(struct file *file, void *fh, |
| struct v4l2_fmtdesc *f) |
| { |
| if (f->index) |
| return -EINVAL; |
| |
| f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS; |
| |
| return 0; |
| } |
| |
| static int c3_isp_params_g_fmt(struct file *file, void *fh, |
| struct v4l2_format *f) |
| { |
| struct c3_isp_params *params = video_drvdata(file); |
| |
| f->fmt.meta = params->vfmt.fmt.meta; |
| |
| return 0; |
| } |
| |
| static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = { |
| .vidioc_querycap = c3_isp_params_querycap, |
| .vidioc_enum_fmt_meta_out = c3_isp_params_enum_fmt, |
| .vidioc_g_fmt_meta_out = c3_isp_params_g_fmt, |
| .vidioc_s_fmt_meta_out = c3_isp_params_g_fmt, |
| .vidioc_try_fmt_meta_out = c3_isp_params_g_fmt, |
| .vidioc_reqbufs = vb2_ioctl_reqbufs, |
| .vidioc_querybuf = vb2_ioctl_querybuf, |
| .vidioc_qbuf = vb2_ioctl_qbuf, |
| .vidioc_expbuf = vb2_ioctl_expbuf, |
| .vidioc_dqbuf = vb2_ioctl_dqbuf, |
| .vidioc_prepare_buf = vb2_ioctl_prepare_buf, |
| .vidioc_create_bufs = vb2_ioctl_create_bufs, |
| .vidioc_streamon = vb2_ioctl_streamon, |
| .vidioc_streamoff = vb2_ioctl_streamoff, |
| }; |
| |
| static const struct v4l2_file_operations isp_params_v4l2_fops = { |
| .open = v4l2_fh_open, |
| .release = vb2_fop_release, |
| .poll = vb2_fop_poll, |
| .unlocked_ioctl = video_ioctl2, |
| .mmap = vb2_fop_mmap, |
| }; |
| |
| static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q, |
| unsigned int *num_buffers, |
| unsigned int *num_planes, |
| unsigned int sizes[], |
| struct device *alloc_devs[]) |
| { |
| if (*num_planes) { |
| if (*num_planes != 1) |
| return -EINVAL; |
| |
| if (sizes[0] < sizeof(struct c3_isp_params_cfg)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| *num_planes = 1; |
| sizes[0] = sizeof(struct c3_isp_params_cfg); |
| |
| return 0; |
| } |
| |
| static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); |
| struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(v4l2_buf); |
| struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); |
| |
| guard(spinlock_irqsave)(¶ms->buff_lock); |
| |
| list_add_tail(&buf->list, ¶ms->pending); |
| } |
| |
| static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(vbuf); |
| struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); |
| struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0); |
| size_t payload_size = vb2_get_plane_payload(vb, 0); |
| struct c3_isp_params_cfg *cfg = buf->cfg; |
| int ret; |
| |
| ret = v4l2_isp_params_validate_buffer_size(params->isp->dev, vb, |
| params->vfmt.fmt.meta.buffersize); |
| if (ret) |
| return ret; |
| |
| /* |
| * Use the internal scratch buffer to avoid userspace modifying |
| * the buffer content while the driver is processing it. |
| */ |
| memcpy(cfg, usr_cfg, payload_size); |
| |
| return v4l2_isp_params_validate_buffer(params->isp->dev, vb, |
| (struct v4l2_isp_params_buffer *)cfg, |
| c3_isp_params_block_types_info, |
| ARRAY_SIZE(c3_isp_params_block_types_info)); |
| } |
| |
| static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); |
| struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); |
| struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(v4l2_buf); |
| |
| buf->cfg = kvmalloc(params->vfmt.fmt.meta.buffersize, GFP_KERNEL); |
| if (!buf->cfg) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static void c3_isp_params_vb2_buf_cleanup(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); |
| struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(v4l2_buf); |
| |
| kvfree(buf->cfg); |
| buf->cfg = NULL; |
| } |
| |
| static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q) |
| { |
| struct c3_isp_params *params = vb2_get_drv_priv(q); |
| struct c3_isp_params_buffer *buff; |
| |
| guard(spinlock_irqsave)(¶ms->buff_lock); |
| |
| while (!list_empty(¶ms->pending)) { |
| buff = list_first_entry(¶ms->pending, |
| struct c3_isp_params_buffer, list); |
| list_del(&buff->list); |
| vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR); |
| } |
| } |
| |
| static const struct vb2_ops isp_params_vb2_ops = { |
| .queue_setup = c3_isp_params_vb2_queue_setup, |
| .buf_queue = c3_isp_params_vb2_buf_queue, |
| .buf_prepare = c3_isp_params_vb2_buf_prepare, |
| .buf_init = c3_isp_params_vb2_buf_init, |
| .buf_cleanup = c3_isp_params_vb2_buf_cleanup, |
| .stop_streaming = c3_isp_params_vb2_stop_streaming, |
| }; |
| |
| int c3_isp_params_register(struct c3_isp_device *isp) |
| { |
| struct c3_isp_params *params = &isp->params; |
| struct video_device *vdev = ¶ms->vdev; |
| struct vb2_queue *vb2_q = ¶ms->vb2_q; |
| int ret; |
| |
| memset(params, 0, sizeof(*params)); |
| params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS; |
| params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_cfg); |
| params->isp = isp; |
| INIT_LIST_HEAD(¶ms->pending); |
| spin_lock_init(¶ms->buff_lock); |
| mutex_init(¶ms->lock); |
| |
| snprintf(vdev->name, sizeof(vdev->name), "c3-isp-params"); |
| vdev->fops = &isp_params_v4l2_fops; |
| vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops; |
| vdev->v4l2_dev = &isp->v4l2_dev; |
| vdev->lock = ¶ms->lock; |
| vdev->minor = -1; |
| vdev->queue = vb2_q; |
| vdev->release = video_device_release_empty; |
| vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING; |
| vdev->vfl_dir = VFL_DIR_TX; |
| video_set_drvdata(vdev, params); |
| |
| vb2_q->drv_priv = params; |
| vb2_q->mem_ops = &vb2_vmalloc_memops; |
| vb2_q->ops = &isp_params_vb2_ops; |
| vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT; |
| vb2_q->io_modes = VB2_DMABUF | VB2_MMAP; |
| vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; |
| vb2_q->buf_struct_size = sizeof(struct c3_isp_params_buffer); |
| vb2_q->dev = isp->dev; |
| vb2_q->lock = ¶ms->lock; |
| vb2_q->min_queued_buffers = 1; |
| |
| ret = vb2_queue_init(vb2_q); |
| if (ret) |
| goto err_detroy; |
| |
| params->pad.flags = MEDIA_PAD_FL_SOURCE; |
| ret = media_entity_pads_init(&vdev->entity, 1, ¶ms->pad); |
| if (ret) |
| goto err_queue_release; |
| |
| ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); |
| if (ret < 0) { |
| dev_err(isp->dev, |
| "Failed to register %s: %d\n", vdev->name, ret); |
| goto err_entity_cleanup; |
| } |
| |
| return 0; |
| |
| err_entity_cleanup: |
| media_entity_cleanup(&vdev->entity); |
| err_queue_release: |
| vb2_queue_release(vb2_q); |
| err_detroy: |
| mutex_destroy(¶ms->lock); |
| return ret; |
| } |
| |
| void c3_isp_params_unregister(struct c3_isp_device *isp) |
| { |
| struct c3_isp_params *params = &isp->params; |
| |
| vb2_queue_release(¶ms->vb2_q); |
| media_entity_cleanup(¶ms->vdev.entity); |
| video_unregister_device(¶ms->vdev); |
| mutex_destroy(¶ms->lock); |
| } |
| |
| void c3_isp_params_isr(struct c3_isp_device *isp) |
| { |
| struct c3_isp_params *params = &isp->params; |
| |
| guard(spinlock_irqsave)(¶ms->buff_lock); |
| |
| params->buff = |
| list_first_entry_or_null(¶ms->pending, |
| struct c3_isp_params_buffer, list); |
| if (!params->buff) |
| return; |
| |
| list_del(¶ms->buff->list); |
| |
| c3_isp_params_cfg_blocks(params); |
| |
| params->buff->vb.sequence = params->isp->frm_sequence; |
| params->buff->vb.vb2_buf.timestamp = ktime_get(); |
| params->buff->vb.field = V4L2_FIELD_NONE; |
| vb2_buffer_done(¶ms->buff->vb.vb2_buf, VB2_BUF_STATE_DONE); |
| } |