Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Keke Li 4560 100.00% 1 100.00%
Total 4560 1


// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
/*
 * Copyright (C) 2024 Amlogic, Inc. All rights reserved
 */

#include <linux/pm_runtime.h>

#include "c3-isp-common.h"
#include "c3-isp-regs.h"

#define C3_ISP_RSZ_DEF_PAD_FMT		MEDIA_BUS_FMT_YUV10_1X30
#define C3_ISP_DISP_REG(base, id)	((base) + (id) * 0x400)
#define C3_ISP_PPS_LUT_H_NUM		33
#define C3_ISP_PPS_LUT_CTYPE_0		0
#define C3_ISP_PPS_LUT_CTYPE_2		2
#define C3_ISP_SCL_EN			1
#define C3_ISP_SCL_DIS			0

/*
 * struct c3_isp_rsz_format_info - ISP resizer format information
 *
 * @mbus_code: the mbus code
 * @pads: bitmask detailing valid pads for this mbus_code
 * @is_raw: the raw format flag of mbus code
 */
struct c3_isp_rsz_format_info {
	u32 mbus_code;
	u32 pads;
	bool is_raw;
};

static const struct c3_isp_rsz_format_info c3_isp_rsz_fmts[] = {
	/* RAW formats */
	{
		.mbus_code	= MEDIA_BUS_FMT_SBGGR16_1X16,
		.pads		= BIT(C3_ISP_RSZ_PAD_SINK)
				| BIT(C3_ISP_RSZ_PAD_SOURCE),
		.is_raw = true,
	}, {
		.mbus_code	= MEDIA_BUS_FMT_SGBRG16_1X16,
		.pads		= BIT(C3_ISP_RSZ_PAD_SINK)
				| BIT(C3_ISP_RSZ_PAD_SOURCE),
		.is_raw = true,
	}, {
		.mbus_code	= MEDIA_BUS_FMT_SGRBG16_1X16,
		.pads		= BIT(C3_ISP_RSZ_PAD_SINK)
				| BIT(C3_ISP_RSZ_PAD_SOURCE),
		.is_raw = true,
	}, {
		.mbus_code	= MEDIA_BUS_FMT_SRGGB16_1X16,
		.pads		= BIT(C3_ISP_RSZ_PAD_SINK)
				| BIT(C3_ISP_RSZ_PAD_SOURCE),
		.is_raw = true,
	},
	/* YUV formats */
	{
		.mbus_code	= MEDIA_BUS_FMT_YUV10_1X30,
		.pads		= BIT(C3_ISP_RSZ_PAD_SINK)
				| BIT(C3_ISP_RSZ_PAD_SOURCE),
		.is_raw = false,
	},
};

/*
 * struct c3_isp_pps_io_size - ISP scaler input and output size
 *
 * @thsize: input horizontal size of after preprocessing
 * @tvsize: input vertical size of after preprocessing
 * @ohsize: output horizontal size
 * @ovsize: output vertical size
 * @ihsize: input horizontal size
 * @max_hsize: maximum horizontal size
 */
struct c3_isp_pps_io_size {
	u32 thsize;
	u32 tvsize;
	u32 ohsize;
	u32 ovsize;
	u32 ihsize;
	u32 max_hsize;
};

/* The normal parameters of pps module */
static const int c3_isp_pps_lut[C3_ISP_PPS_LUT_H_NUM][4] =  {
	{  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
	{-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
	{-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
	{-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
	{-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
	{-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
	{-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
	{-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
	{-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
	{-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
	{-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
};

static const struct c3_isp_rsz_format_info
*rsz_find_format_by_code(u32 code, u32 pad)
{
	for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_rsz_fmts); i++) {
		const struct c3_isp_rsz_format_info *info = &c3_isp_rsz_fmts[i];

		if (info->mbus_code == code && info->pads & BIT(pad))
			return info;
	}

	return NULL;
}

static const struct c3_isp_rsz_format_info
*rsz_find_format_by_index(u32 index, u32 pad)
{
	for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_rsz_fmts); i++) {
		const struct c3_isp_rsz_format_info *info = &c3_isp_rsz_fmts[i];

		if (!(info->pads & BIT(pad)))
			continue;

		if (!index)
			return info;

		index--;
	}

	return NULL;
}

static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
				struct c3_isp_pps_io_size *io_size)
{
	int thsize = io_size->thsize;
	int tvsize = io_size->tvsize;
	u32 ohsize = io_size->ohsize;
	u32 ovsize = io_size->ovsize;
	u32 ihsize = io_size->ihsize;
	u32 max_hsize = io_size->max_hsize;
	int h_int;
	int v_int;
	int h_fract;
	int v_fract;
	int yuv444to422_en;

	/* Calculate the integer part of horizonal scaler step */
	h_int = thsize / ohsize;

	/* Calculate the vertical part of horizonal scaler step */
	v_int = tvsize / ovsize;

	/*
	 * Calculate the fraction part of horizonal scaler step.
	 * step_h_fraction = (source / dest) * 2^24,
	 * so step_h_fraction = ((source << 12) / dest) << 12.
	 */
	h_fract = ((thsize << 12) / ohsize) << 12;

	/*
	 * Calculate the fraction part of vertical scaler step
	 * step_v_fraction = (source / dest) * 2^24,
	 * so step_v_fraction = ((source << 12) / dest) << 12.
	 */
	v_fract = ((tvsize << 12) / ovsize) << 12;

	yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_444TO422, rsz->id),
			   DISP0_PPS_444TO422_EN_MASK,
			   DISP0_PPS_444TO422_EN(yuv444to422_en));

	c3_isp_write(rsz->isp,
		     C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
		     DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(v_fract) |
		     DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(v_int));

	c3_isp_write(rsz->isp,
		     C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
		     DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(h_fract) |
		     DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(h_int));
}

static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
{
	unsigned int i;

	/*
	 * Default value of this register is 0, so only need to set
	 * SCALE_LUMA_COEF_S11_MODE and SCALE_LUMA_CTYPE. This register needs
	 * to be written in one time.
	 */
	c3_isp_write(rsz->isp,
		     C3_ISP_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
		     ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN |
		     ISP_SCALE0_COEF_IDX_LUMA_CTYPE(ctype));

	for (i = 0; i < C3_ISP_PPS_LUT_H_NUM; i++) {
		c3_isp_write(rsz->isp,
			     C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
			     ISP_SCALE0_COEF_LUMA_DATA0(c3_isp_pps_lut[i][0]) |
			     ISP_SCALE0_COEF_LUMA_DATA1(c3_isp_pps_lut[i][1]));
		c3_isp_write(rsz->isp,
			     C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
			     ISP_SCALE0_COEF_LUMA_DATA0(c3_isp_pps_lut[i][2]) |
			     ISP_SCALE0_COEF_LUMA_DATA1(c3_isp_pps_lut[i][3]));
	}

	/*
	 * Default value of this register is 0, so only need to set
	 * SCALE_CHRO_COEF_S11_MODE and SCALE_CHRO_CTYPE. This register needs
	 * to be written in one time.
	 */
	c3_isp_write(rsz->isp,
		     C3_ISP_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
		     ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN |
		     ISP_SCALE0_COEF_IDX_CHRO_CTYPE(ctype));

	for (i = 0; i < C3_ISP_PPS_LUT_H_NUM; i++) {
		c3_isp_write(rsz->isp,
			     C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
			     ISP_SCALE0_COEF_CHRO_DATA0(c3_isp_pps_lut[i][0]) |
			     ISP_SCALE0_COEF_CHRO_DATA1(c3_isp_pps_lut[i][1]));
		c3_isp_write(rsz->isp,
			     C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
			     ISP_SCALE0_COEF_CHRO_DATA0(c3_isp_pps_lut[i][2]) |
			     ISP_SCALE0_COEF_CHRO_DATA1(c3_isp_pps_lut[i][3]));
	}
}

static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
{
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_HSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_HSC_DIS);
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_VSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_VSC_DIS);
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREVSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_PREVSC_DIS);
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREHSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_PREHSC_DIS);
}

static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
				 struct v4l2_subdev_state *state)
{
	struct v4l2_rect *crop;
	struct v4l2_rect *cmps;
	int max_hsize;
	int hsc_en, vsc_en;
	int preh_en, prev_en;
	u32 prehsc_rate;
	u32 prevsc_flt_num;
	int pre_vscale_max_hsize;
	u32 ihsize_after_pre_hsc;
	u32 ihsize_after_pre_hsc_alt;
	u32 vsc_tap_num_alt;
	u32 ihsize;
	u32 ivsize;
	struct c3_isp_pps_io_size io_size;

	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RSZ_PAD_SINK);
	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RSZ_PAD_SINK);

	ihsize = crop->width;
	ivsize = crop->height;

	hsc_en = (ihsize == cmps->width) ? C3_ISP_SCL_DIS : C3_ISP_SCL_EN;
	vsc_en = (ivsize == cmps->height) ? C3_ISP_SCL_DIS : C3_ISP_SCL_EN;

	/* Disable pps when there no need to use pps */
	if (!hsc_en && !vsc_en) {
		c3_isp_rsz_pps_disable(rsz);
		return 0;
	}

	/* Pre-scale needs to be enable if the down scaling factor exceeds 4 */
	preh_en = (ihsize > cmps->width * 4) ? C3_ISP_SCL_EN : C3_ISP_SCL_DIS;
	prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCL_EN : C3_ISP_SCL_DIS;

	if (rsz->id == C3_ISP_RSZ_2) {
		max_hsize = C3_ISP_MAX_WIDTH;

		/* Set vertical tap number */
		prevsc_flt_num = 4;

		/* Set the max hsize of pre-vertical scale */
		pre_vscale_max_hsize = max_hsize / 2;
	} else {
		max_hsize = C3_ISP_DEFAULT_WIDTH;

		/* Set vertical tap number and the max hsize of pre-vertical */
		if (ihsize > (max_hsize / 2) &&
		    ihsize <= max_hsize && prev_en) {
			prevsc_flt_num = 2;
			pre_vscale_max_hsize = max_hsize;
		} else {
			prevsc_flt_num = 4;
			pre_vscale_max_hsize = max_hsize / 2;
		}
	}

	/*
	 * Set pre-horizonal scale rate and the hsize of after
	 * pre-horizonal scale.
	 */
	if (preh_en) {
		prehsc_rate = 1;
		ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
	} else {
		prehsc_rate = 0;
		ihsize_after_pre_hsc = ihsize;
	}

	/* Change pre-horizonal scale rate */
	if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
		prehsc_rate += 1;

	/* Set the actual hsize of after pre-horizonal scale */
	if (preh_en)
		ihsize_after_pre_hsc_alt =
			DIV_ROUND_UP(ihsize, 1 << prehsc_rate);
	else
		ihsize_after_pre_hsc_alt = ihsize;

	/* Set vertical scaler bank length */
	if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
		vsc_tap_num_alt = 4;
	else if (ihsize_after_pre_hsc_alt <= max_hsize)
		vsc_tap_num_alt = prev_en ? 2 : 4;
	else
		vsc_tap_num_alt = prev_en ? 4 : 2;

	io_size.thsize = ihsize_after_pre_hsc_alt;
	io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
	io_size.ohsize = cmps->width;
	io_size.ovsize = cmps->height;
	io_size.ihsize = ihsize;
	io_size.max_hsize = max_hsize;

	c3_isp_rsz_pps_size(rsz, &io_size);
	c3_isp_rsz_pps_lut(rsz, C3_ISP_PPS_LUT_CTYPE_0);
	c3_isp_rsz_pps_lut(rsz, C3_ISP_PPS_LUT_CTYPE_2);

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK,
			   DISP0_PPS_SCALE_EN_VSC_TAP_NUM(vsc_tap_num_alt));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK,
			   DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(prevsc_flt_num));

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK,
			   DISP0_PPS_SCALE_EN_PREVSC_RATE(prev_en));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK,
			   DISP0_PPS_SCALE_EN_PREHSC_RATE(prehsc_rate));

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_HSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_HSC_EN(hsc_en));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_VSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_VSC_EN(vsc_en));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREVSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_PREVSC_EN(prev_en));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_PREHSC_EN_MASK,
			   DISP0_PPS_SCALE_EN_PREHSC_EN(preh_en));

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK,
			   DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(9));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
			   DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK,
			   DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(9));

	return 0;
}

static void c3_isp_rsz_start(struct c3_isp_resizer *rsz,
			     struct v4l2_subdev_state *state)
{
	struct v4l2_mbus_framefmt *sink_fmt;
	struct v4l2_mbus_framefmt *src_fmt;
	const struct c3_isp_rsz_format_info *rsz_fmt;
	struct v4l2_rect *sink_crop;
	u32 mask;
	u32 val;

	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SINK);
	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RSZ_PAD_SINK);
	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SOURCE);
	rsz_fmt = rsz_find_format_by_code(sink_fmt->code, C3_ISP_RSZ_PAD_SINK);

	if (rsz->id == C3_ISP_RSZ_0) {
		mask = ISP_TOP_DISPIN_SEL_DISP0_MASK;
		val = rsz_fmt->is_raw ? ISP_TOP_DISPIN_SEL_DISP0_MIPI_OUT
				      : ISP_TOP_DISPIN_SEL_DISP0_CORE_OUT;
	} else if (rsz->id == C3_ISP_RSZ_1) {
		mask = ISP_TOP_DISPIN_SEL_DISP1_MASK;
		val = rsz_fmt->is_raw ? ISP_TOP_DISPIN_SEL_DISP1_MIPI_OUT
				      : ISP_TOP_DISPIN_SEL_DISP1_CORE_OUT;
	} else {
		mask = ISP_TOP_DISPIN_SEL_DISP2_MASK;
		val = rsz_fmt->is_raw ? ISP_TOP_DISPIN_SEL_DISP2_MIPI_OUT
				      : ISP_TOP_DISPIN_SEL_DISP2_CORE_OUT;
	}

	c3_isp_update_bits(rsz->isp, ISP_TOP_DISPIN_SEL, mask, val);

	c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
		     ISP_DISP0_TOP_IN_SIZE_HSIZE(sink_fmt->width) |
		     ISP_DISP0_TOP_IN_SIZE_VSIZE(sink_fmt->height));

	c3_isp_write(rsz->isp, C3_ISP_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
		     DISP0_TOP_CRP2_START_V_START(sink_crop->top) |
		     DISP0_TOP_CRP2_START_H_START(sink_crop->left));

	c3_isp_write(rsz->isp, C3_ISP_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
		     DISP0_TOP_CRP2_SIZE_V_SIZE(sink_crop->height) |
		     DISP0_TOP_CRP2_SIZE_H_SIZE(sink_crop->width));

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
			   DISP0_TOP_TOP_CTRL_CROP2_EN_MASK,
			   DISP0_TOP_TOP_CTRL_CROP2_EN);

	if (!rsz_fmt->is_raw)
		c3_isp_rsz_pps_enable(rsz, state);

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_TOP_OUT_SIZE, rsz->id),
			   DISP0_TOP_OUT_SIZE_SCL_OUT_HEIGHT_MASK,
			   DISP0_TOP_OUT_SIZE_SCL_OUT_HEIGHT(src_fmt->height));
	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_TOP_OUT_SIZE, rsz->id),
			   DISP0_TOP_OUT_SIZE_SCL_OUT_WIDTH_MASK,
			   DISP0_TOP_OUT_SIZE_SCL_OUT_WIDTH(src_fmt->width));

	if (rsz->id == C3_ISP_RSZ_0) {
		mask = ISP_TOP_PATH_EN_DISP0_EN_MASK;
		val = ISP_TOP_PATH_EN_DISP0_EN;
	} else if (rsz->id == C3_ISP_RSZ_1) {
		mask = ISP_TOP_PATH_EN_DISP1_EN_MASK;
		val = ISP_TOP_PATH_EN_DISP1_EN;
	} else {
		mask = ISP_TOP_PATH_EN_DISP2_EN_MASK;
		val = ISP_TOP_PATH_EN_DISP2_EN;
	}

	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, mask, val);
}

static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
{
	u32 mask;
	u32 val;

	if (rsz->id == C3_ISP_RSZ_0) {
		mask = ISP_TOP_PATH_EN_DISP0_EN_MASK;
		val = ISP_TOP_PATH_EN_DISP0_DIS;
	} else if (rsz->id == C3_ISP_RSZ_1) {
		mask = ISP_TOP_PATH_EN_DISP1_EN_MASK;
		val = ISP_TOP_PATH_EN_DISP1_DIS;
	} else {
		mask = ISP_TOP_PATH_EN_DISP2_EN_MASK;
		val = ISP_TOP_PATH_EN_DISP2_DIS;
	}

	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, mask, val);

	c3_isp_update_bits(rsz->isp,
			   C3_ISP_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
			   DISP0_TOP_TOP_CTRL_CROP2_EN_MASK,
			   DISP0_TOP_TOP_CTRL_CROP2_DIS);

	c3_isp_rsz_pps_disable(rsz);
}

static int c3_isp_rsz_enable_streams(struct v4l2_subdev *sd,
				     struct v4l2_subdev_state *state,
				     u32 pad, u64 streams_mask)
{
	struct c3_isp_resizer *rsz = v4l2_get_subdevdata(sd);

	c3_isp_rsz_start(rsz, state);

	c3_isp_params_pre_cfg(rsz->isp);
	c3_isp_stats_pre_cfg(rsz->isp);

	return v4l2_subdev_enable_streams(rsz->src_sd, rsz->src_pad, BIT(0));
}

static int c3_isp_rsz_disable_streams(struct v4l2_subdev *sd,
				      struct v4l2_subdev_state *state,
				      u32 pad, u64 streams_mask)
{
	struct c3_isp_resizer *rsz = v4l2_get_subdevdata(sd);

	c3_isp_rsz_stop(rsz);

	return v4l2_subdev_disable_streams(rsz->src_sd, rsz->src_pad, BIT(0));
}

static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
				     struct v4l2_subdev_state *state,
				     struct v4l2_subdev_mbus_code_enum *code)
{
	const struct c3_isp_rsz_format_info *info;

	info = rsz_find_format_by_index(code->index, code->pad);
	if (!info)
		return -EINVAL;

	code->code = info->mbus_code;

	return 0;
}

static void c3_isp_rsz_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;
	struct v4l2_rect *sink_crop;
	struct v4l2_rect *sink_cmps;
	const struct c3_isp_rsz_format_info *rsz_fmt;

	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
	sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
	sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SOURCE);

	rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
	if (rsz_fmt)
		sink_fmt->code = format->format.code;
	else
		sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;

	sink_fmt->width = clamp_t(u32, format->format.width,
				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
	sink_fmt->height = clamp_t(u32, format->format.height,
				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
	sink_fmt->field = V4L2_FIELD_NONE;
	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;

	if (rsz_fmt && rsz_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;
	}

	sink_crop->width = sink_fmt->width;
	sink_crop->height = sink_fmt->height;
	sink_crop->left = 0;
	sink_crop->top = 0;

	sink_cmps->width = sink_crop->width;
	sink_cmps->height = sink_crop->height;
	sink_cmps->left = 0;
	sink_cmps->top = 0;

	src_fmt->code = sink_fmt->code;
	src_fmt->width = sink_cmps->width;
	src_fmt->height = sink_cmps->height;

	format->format = *sink_fmt;
}

static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
				      struct v4l2_subdev_format *format)
{
	struct v4l2_mbus_framefmt *src_fmt;
	struct v4l2_mbus_framefmt *sink_fmt;
	struct v4l2_rect *sink_cmps;
	const struct c3_isp_rsz_format_info *rsz_fmt;

	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SINK);
	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RSZ_PAD_SINK);

	src_fmt->code = sink_fmt->code;
	src_fmt->width = sink_cmps->width;
	src_fmt->height = sink_cmps->height;
	src_fmt->field = V4L2_FIELD_NONE;
	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;

	rsz_fmt = rsz_find_format_by_code(src_fmt->code, format->pad);
	if (rsz_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;
}

static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
			      struct v4l2_subdev_state *state,
			      struct v4l2_subdev_format *format)
{
	if (format->pad == C3_ISP_RSZ_PAD_SINK)
		c3_isp_rsz_set_sink_fmt(state, format);
	else
		c3_isp_rsz_set_source_fmt(state, format);

	return 0;
}

static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
				    struct v4l2_subdev_state *state,
				    struct v4l2_subdev_selection *sel)
{
	struct v4l2_mbus_framefmt *fmt;
	struct v4l2_rect *crop;
	struct v4l2_rect *cmps;

	if (sel->pad == C3_ISP_RSZ_PAD_SOURCE)
		return -EINVAL;

	switch (sel->target) {
	case V4L2_SEL_TGT_CROP_BOUNDS:
		fmt = v4l2_subdev_state_get_format(state, sel->pad);
		sel->r.width = fmt->width;
		sel->r.height = fmt->height;
		sel->r.left = 0;
		sel->r.top = 0;
		break;
	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
		crop = v4l2_subdev_state_get_crop(state, sel->pad);
		sel->r.width = crop->width;
		sel->r.height = crop->height;
		sel->r.left = 0;
		sel->r.top = 0;
		break;
	case V4L2_SEL_TGT_CROP:
		crop = v4l2_subdev_state_get_crop(state, sel->pad);
		sel->r = *crop;
		break;
	case V4L2_SEL_TGT_COMPOSE:
		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
		sel->r = *cmps;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
				    struct v4l2_subdev_state *state,
				    struct v4l2_subdev_selection *sel)
{
	struct v4l2_mbus_framefmt *fmt;
	struct v4l2_mbus_framefmt *src_fmt;
	struct v4l2_rect *crop;
	struct v4l2_rect *cmps;

	if (sel->pad == C3_ISP_RSZ_PAD_SOURCE)
		return -EINVAL;

	switch (sel->target) {
	case V4L2_SEL_TGT_CROP:
		fmt = v4l2_subdev_state_get_format(state, sel->pad);
		crop = v4l2_subdev_state_get_crop(state, sel->pad);
		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
		src_fmt = v4l2_subdev_state_get_format(state,
						       C3_ISP_RSZ_PAD_SOURCE);

		sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
		sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
				     fmt->width - sel->r.left);
		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
				      fmt->height - sel->r.top);

		crop->width = ALIGN(sel->r.width, 2);
		crop->height = ALIGN(sel->r.height, 2);
		crop->left = sel->r.left;
		crop->top = sel->r.top;

		*cmps = *crop;

		src_fmt->code = fmt->code;
		src_fmt->width = cmps->width;
		src_fmt->height = cmps->height;

		sel->r = *crop;
		break;
	case V4L2_SEL_TGT_COMPOSE:
		crop = v4l2_subdev_state_get_crop(state, sel->pad);
		cmps = v4l2_subdev_state_get_compose(state, sel->pad);

		sel->r.left = 0;
		sel->r.top = 0;
		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
				     crop->width);
		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
				      crop->height);

		cmps->width = ALIGN(sel->r.width, 2);
		cmps->height = ALIGN(sel->r.height, 2);
		cmps->left = sel->r.left;
		cmps->top = sel->r.top;

		sel->r = *cmps;

		fmt = v4l2_subdev_state_get_format(state,
						   C3_ISP_RSZ_PAD_SOURCE);
		fmt->width = cmps->width;
		fmt->height = cmps->height;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
				 struct v4l2_subdev_state *state)
{
	struct v4l2_mbus_framefmt *fmt;
	struct v4l2_rect *crop;
	struct v4l2_rect *cmps;

	fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SINK);
	fmt->width = C3_ISP_DEFAULT_WIDTH;
	fmt->height = C3_ISP_DEFAULT_HEIGHT;
	fmt->field = V4L2_FIELD_NONE;
	fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
	fmt->colorspace = V4L2_COLORSPACE_SRGB;
	fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
	fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
	fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;

	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RSZ_PAD_SINK);
	crop->width = C3_ISP_DEFAULT_WIDTH;
	crop->height = C3_ISP_DEFAULT_HEIGHT;
	crop->left = 0;
	crop->top = 0;

	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RSZ_PAD_SINK);
	cmps->width = C3_ISP_DEFAULT_WIDTH;
	cmps->height = C3_ISP_DEFAULT_HEIGHT;
	cmps->left = 0;
	cmps->top = 0;

	fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SOURCE);
	fmt->width = cmps->width;
	fmt->height = cmps->height;
	fmt->field = V4L2_FIELD_NONE;
	fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
	fmt->colorspace = V4L2_COLORSPACE_SRGB;
	fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
	fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
	fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;

	return 0;
}

static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
	.enum_mbus_code = c3_isp_rsz_enum_mbus_code,
	.get_fmt = v4l2_subdev_get_fmt,
	.set_fmt = c3_isp_rsz_set_fmt,
	.get_selection = c3_isp_rsz_get_selection,
	.set_selection = c3_isp_rsz_set_selection,
	.enable_streams = c3_isp_rsz_enable_streams,
	.disable_streams = c3_isp_rsz_disable_streams,
};

static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
	.pad = &c3_isp_rsz_pad_ops,
};

static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
	.init_state = c3_isp_rsz_init_state,
};

/* Media entity operations */
static const struct media_entity_operations c3_isp_rsz_entity_ops = {
	.link_validate = v4l2_subdev_link_validate,
};

static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
{
	struct v4l2_subdev *sd = &rsz->sd;
	int ret;

	v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
	sd->owner = THIS_MODULE;
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	sd->internal_ops = &c3_isp_rsz_internal_ops;
	snprintf(sd->name, sizeof(sd->name), "c3-isp-resizer%u", rsz->id);

	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
	sd->entity.ops = &c3_isp_rsz_entity_ops;

	sd->dev = rsz->isp->dev;
	v4l2_set_subdevdata(sd, rsz);

	rsz->pads[C3_ISP_RSZ_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
	rsz->pads[C3_ISP_RSZ_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
	ret = media_entity_pads_init(&sd->entity, C3_ISP_RSZ_PAD_MAX,
				     rsz->pads);
	if (ret)
		return ret;

	ret = v4l2_subdev_init_finalize(sd);
	if (ret)
		goto err_entity_cleanup;

	ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
	if (ret)
		goto err_subdev_cleanup;

	return 0;

err_subdev_cleanup:
	v4l2_subdev_cleanup(sd);
err_entity_cleanup:
	media_entity_cleanup(&sd->entity);
	return ret;
}

static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
{
	struct v4l2_subdev *sd = &rsz->sd;

	v4l2_device_unregister_subdev(sd);
	v4l2_subdev_cleanup(sd);
	media_entity_cleanup(&sd->entity);
}

int c3_isp_resizers_register(struct c3_isp_device *isp)
{
	int ret;

	for (unsigned int i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
		struct c3_isp_resizer *rsz = &isp->resizers[i];

		rsz->id = i;
		rsz->isp = isp;
		rsz->src_sd = &isp->core.sd;
		rsz->src_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO_0 + i;

		ret = c3_isp_rsz_register(rsz);
		if (ret) {
			rsz->isp = NULL;
			c3_isp_resizers_unregister(isp);
			return ret;
		}
	}

	return 0;
}

void c3_isp_resizers_unregister(struct c3_isp_device *isp)
{
	for (unsigned int i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
		struct c3_isp_resizer *rsz = &isp->resizers[i];

		if (rsz->isp)
			c3_isp_rsz_unregister(rsz);
	}
}