diff mbox series

[v4,08/10] media: platform: Add c3 ISP driver

Message ID 20241205-c3isp-v4-8-cb1868be0105@amlogic.com (mailing list archive)
State New
Headers show
Series Amlogic C3 ISP support | expand

Commit Message

Keke Li via B4 Relay Dec. 5, 2024, 9:04 a.m. UTC
From: Keke Li <keke.li@amlogic.com>

The C3 ISP supports multi-camera and muti-exposure
high dynamic range (HDR). It brings together some
advanced imaging technologies to provide good image quality.
This driver mainly responsible for driving ISP pipeline
to process raw image.

Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 drivers/media/platform/amlogic/c3/Kconfig          |   1 +
 drivers/media/platform/amlogic/c3/Makefile         |   1 +
 drivers/media/platform/amlogic/c3/isp/Kconfig      |  17 +
 drivers/media/platform/amlogic/c3/isp/Makefile     |  10 +
 .../media/platform/amlogic/c3/isp/c3-isp-capture.c | 790 +++++++++++++++++
 .../media/platform/amlogic/c3/isp/c3-isp-common.h  | 352 ++++++++
 .../media/platform/amlogic/c3/isp/c3-isp-core.c    | 553 ++++++++++++
 drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c | 459 ++++++++++
 .../media/platform/amlogic/c3/isp/c3-isp-params.c  | 942 +++++++++++++++++++++
 .../media/platform/amlogic/c3/isp/c3-isp-regs.h    | 599 +++++++++++++
 .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 779 +++++++++++++++++
 .../media/platform/amlogic/c3/isp/c3-isp-stats.c   | 319 +++++++
 12 files changed, 4822 insertions(+)

Comments

Jacopo Mondi Dec. 12, 2024, 3:39 p.m. UTC | #1
Hi Keke

On Thu, Dec 05, 2024 at 05:04:34PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> The C3 ISP supports multi-camera and muti-exposure
> high dynamic range (HDR). It brings together some
> advanced imaging technologies to provide good image quality.
> This driver mainly responsible for driving ISP pipeline
> to process raw image.
>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  drivers/media/platform/amlogic/c3/Kconfig          |   1 +
>  drivers/media/platform/amlogic/c3/Makefile         |   1 +
>  drivers/media/platform/amlogic/c3/isp/Kconfig      |  17 +
>  drivers/media/platform/amlogic/c3/isp/Makefile     |  10 +
>  .../media/platform/amlogic/c3/isp/c3-isp-capture.c | 790 +++++++++++++++++
>  .../media/platform/amlogic/c3/isp/c3-isp-common.h  | 352 ++++++++
>  .../media/platform/amlogic/c3/isp/c3-isp-core.c    | 553 ++++++++++++
>  drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c | 459 ++++++++++
>  .../media/platform/amlogic/c3/isp/c3-isp-params.c  | 942 +++++++++++++++++++++
>  .../media/platform/amlogic/c3/isp/c3-isp-regs.h    | 599 +++++++++++++
>  .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 779 +++++++++++++++++
>  .../media/platform/amlogic/c3/isp/c3-isp-stats.c   | 319 +++++++
>  12 files changed, 4822 insertions(+)
>
> diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig
> index a504a1eb22e6..d355d3a9358d 100644
> --- a/drivers/media/platform/amlogic/c3/Kconfig
> +++ b/drivers/media/platform/amlogic/c3/Kconfig
> @@ -1,4 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>
> +source "drivers/media/platform/amlogic/c3/isp/Kconfig"
>  source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig"
>  source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig"
> diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile
> index 770b2a2903ad..14f305a493d2 100644
> --- a/drivers/media/platform/amlogic/c3/Makefile
> +++ b/drivers/media/platform/amlogic/c3/Makefile
> @@ -1,4 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>
> +obj-y += isp/
>  obj-y += mipi-adapter/
>  obj-y += mipi-csi2/
> diff --git a/drivers/media/platform/amlogic/c3/isp/Kconfig b/drivers/media/platform/amlogic/c3/isp/Kconfig
> new file mode 100644
> index 000000000000..de68d19ca2d1
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/Kconfig
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_C3_ISP
> +	tristate "Amlogic C3 Image Signal Processor (ISP) driver"
> +	depends on ARCH_MESON || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	depends on OF
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	select VIDEOBUF2_DMA_CONTIG

also
        select VIDEOBUF2_VMALLOC

for the params node


> +	help
> +	  Video4Linux2 driver for Amlogic C3 ISP pipeline.
> +	  The C3 ISP is used for processing raw images and
> +	  outputing results to memory.
> +
> +	  To compile this driver as a module choose m here.
> diff --git a/drivers/media/platform/amlogic/c3/isp/Makefile b/drivers/media/platform/amlogic/c3/isp/Makefile
> new file mode 100644
> index 000000000000..b1b064170b57
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/Makefile
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +c3-isp-objs := c3-isp-dev.o \
> +	       c3-isp-params.o \
> +	       c3-isp-stats.o \
> +	       c3-isp-capture.o \
> +	       c3-isp-core.o \
> +	       c3-isp-resizer.o
> +
> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
> new file mode 100644
> index 000000000000..e1bbb3fa8da2
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
> @@ -0,0 +1,790 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +static const struct c3_isp_capture_format cap_formats[] = {
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> +		.fourcc = V4L2_PIX_FMT_GREY,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> +		.fourcc = V4L2_PIX_FMT_NV12,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> +		.fourcc = V4L2_PIX_FMT_NV21,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> +		.fourcc = V4L2_PIX_FMT_NV16,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> +		.fourcc = V4L2_PIX_FMT_NV61,
> +	},
> +};
> +
> +/* Hardware configuration */
> +
> +/* Set the address of wrmifx3(write memory interface) */
> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +	dma_addr_t dma_addr;
> +
> +	if (cap->buff)
> +		dma_addr = cap->buff->dma_addr;
> +	else
> +		dma_addr = cap->dummy_buff.dma_addr;
> +
> +	c3_isp_write(cap->isp,
> +		     C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
> +		     ISP_WRMIFX3_0_CH0_BASE_ADDR(dma_addr));
> +
> +	if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV21 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV16 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV61) {
> +		u32 offset = ALIGN(pix->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES) * pix->height;

I'm sorry but V4L2 has a requirement about 80-cols stricter than other
subsystem (I know..)

Could you run
./scripts/checkpatch.pl --strict --max-line-length=80

on your patches and see how you can fix these ?

> +
> +		c3_isp_write(cap->isp,
> +			     C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
> +			     ISP_WRMIFX3_0_CH1_BASE_ADDR(dma_addr + offset));
> +	}
> +}
> +
> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
> +
> +	/* Grey has 1 plane*/
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1);
> +
> +	/* Set Y only as output format */
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY);
> +
> +	/* The unit of stride is 16 bytes */
> +	stride = DIV_ROUND_UP(fmt->width * 1, 16);
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
> +			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
> +			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
> +}
> +
> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, bool swap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +	u32 swap_uv;
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
> +
> +	swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
> +			 ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
> +
> +	/* NV12 or NV21 has 2 planes*/
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
> +
> +	/* Set YUV420 as output format */
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420);
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> +			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
> +			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width >> 1));
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> +			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
> +			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
> +
> +	/* The unit of Y and UV strides is 16 bytes */
> +	stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
> +			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> +			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
> +			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
> +			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> +			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
> +			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
> +}
> +
> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, bool swap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +	u32 swap_uv;
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT);
> +
> +	swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
> +			 ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
> +
> +	/* NV16 or NV61 has 2 planes*/
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
> +
> +	/* Set YUV422 as output format */
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
> +			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422);
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> +			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
> +			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width));
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> +			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
> +			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
> +
> +	/* The unit of Y stride is 16 bytes */
> +	stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
> +			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
> +
> +	/* The unit of UV stride is 16 bytes */
> +	stride = DIV_ROUND_UP(fmt->width * 2, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> +			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
> +			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
> +			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> +			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
> +			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
> +}

I feel like the above functions could 'easily' be unified in a single
one that computes the right parameters using the pixel format info.

Feel free to expand 'struct c3_isp_capture_format' with all the info
you need to compute the correct register values if you think unifying
the above functions is helpful

> +
> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +
> +	if (fmt->pixelformat == V4L2_PIX_FMT_GREY)
> +		c3_isp_cap_wrmifx3_grey(cap);
> +	else if (fmt->pixelformat == V4L2_PIX_FMT_NV12)
> +		c3_isp_cap_wrmifx3_yuv420(cap, true);
> +	else if (fmt->pixelformat == V4L2_PIX_FMT_NV21)
> +		c3_isp_cap_wrmifx3_yuv420(cap, false);
> +	else if (fmt->pixelformat == V4L2_PIX_FMT_NV16)
> +		c3_isp_cap_wrmifx3_yuv422(cap, true);
> +	else if (fmt->pixelformat == V4L2_PIX_FMT_NV61)
> +		c3_isp_cap_wrmifx3_yuv422(cap, false);
> +
> +	c3_isp_write(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
> +		     ISP_WRMIFX3_0_FMT_SIZE_HSIZE(fmt->width) |
> +		     ISP_WRMIFX3_0_FMT_SIZE_VSIZE(fmt->height));
> +
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
> +			   ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK,
> +			   ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(fmt->width));
> +	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
> +			   ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK,
> +			   ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(fmt->height));
> +}
> +
> +static int c3_isp_cap_dummy_buff_create(struct c3_isp_capture *cap)
> +{
> +	struct c3_isp_dummy_buffer *dummy_buff = &cap->dummy_buff;
> +
> +	dummy_buff->size = cap->vfmt.fmt.pix.sizeimage;
> +
> +	/* The driver never access vaddr, no mapping is required */
> +	dummy_buff->vaddr = dma_alloc_attrs(cap->isp->dev,
> +					    dummy_buff->size,
> +					    &dummy_buff->dma_addr,
> +					    GFP_KERNEL,
> +					    DMA_ATTR_NO_KERNEL_MAPPING);
> +	if (!dummy_buff->vaddr)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
> +static void c3_isp_cap_dummy_buff_destroy(struct c3_isp_capture *cap)
> +{
> +	dma_free_attrs(cap->isp->dev,
> +		       cap->dummy_buff.size, cap->dummy_buff.vaddr,
> +		       cap->dummy_buff.dma_addr, DMA_ATTR_NO_KERNEL_MAPPING);
> +}
> +
> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
> +{
> +	cap->buff = list_first_entry_or_null(&cap->pending,
> +					     struct c3_isp_buffer, list);
> +
> +	c3_isp_cap_wrmifx3_buff(cap);
> +
> +	if (cap->buff)
> +		list_del(&cap->buff->list);
> +}
> +
> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
> +{
> +	scoped_guard(spinlock_irqsave, &cap->buff_lock)
> +		c3_isp_cap_cfg_buff(cap);
> +
> +	c3_isp_cap_wrmifx3_size(cap);
> +
> +	if (cap->id == C3_ISP_CAP_DEV_0) {
> +		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
> +				   ISP_TOP_PATH_EN_WRMIF0_EN);
> +	} else if (cap->id == C3_ISP_CAP_DEV_1) {
> +		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
> +				   ISP_TOP_PATH_EN_WRMIF1_EN);
> +	} else if (cap->id == C3_ISP_CAP_DEV_2) {
> +		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
> +				   ISP_TOP_PATH_EN_WRMIF2_EN);
> +	}

You could spare the {} for single line statements

Could this maybe be simplified as

	u32 val = cap->id == C3_ISP_CAP_DEV_0 ? ISP_TOP_PATH_EN_WRMIF0_EN :
		  cap->id == C3_ISP_CAP_DEV_1 ? ISP_TOP_PATH_EN_WRMIF1_EN :
		  cap->id == C3_ISP_CAP_DEV_2 ? ISP_TOP_PATH_EN_WRMIF2_EN : 0;

	scoped_guard(spinlock_irqsave, &cap->buff_lock)
		c3_isp_cap_cfg_buff(cap);

	c3_isp_cap_wrmifx3_size(cap);
	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, val, val);

> +}
> +
> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
> +{
> +	if (cap->id == C3_ISP_CAP_DEV_0) {
> +		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
> +				   ISP_TOP_PATH_EN_WRMIF0_DIS);
> +	} else if (cap->id == C3_ISP_CAP_DEV_1) {
> +		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
> +				   ISP_TOP_PATH_EN_WRMIF1_DIS);
> +	} else if (cap->id == C3_ISP_CAP_DEV_2) {
> +		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
> +				   ISP_TOP_PATH_EN_WRMIF2_DIS);
> +	}

same here

> +}
> +
> +static void c3_isp_cap_done(struct c3_isp_capture *cap)
> +{
> +	struct c3_isp_buffer *buff = cap->buff;
> +
> +	guard(spinlock_irqsave)(&cap->buff_lock);
> +
> +	if (buff) {
> +		buff->vb.sequence = cap->isp->frm_sequence;
> +		buff->vb.vb2_buf.timestamp = ktime_get();
> +		buff->vb.field = V4L2_FIELD_NONE;
> +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
> +	c3_isp_cap_cfg_buff(cap);
> +}
> +
> +/* V4L2 video operations */
> +
> +static const struct c3_isp_capture_format
> +*c3_cap_find_fmt(u32 fourcc)

fits in one line

> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {

nit: you can declare i inside the for loop

> +		if (cap_formats[i].fourcc == fourcc)
> +			return &cap_formats[i];
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
> +			   struct v4l2_pix_format *pix)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +	const struct v4l2_format_info *info;
> +
> +	fmt = c3_cap_find_fmt(pix->pixelformat);
> +	if (!fmt)
> +		fmt = &cap_formats[0];
> +
> +	pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +	pix->pixelformat = fmt->fourcc;
> +	pix->field = V4L2_FIELD_NONE;
> +	pix->colorspace = V4L2_COLORSPACE_SRGB;
> +	pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	info = v4l2_format_info(fmt->fourcc);
> +	pix->bytesperline = pix->width * info->bpp[0] / info->bpp_div[0];
> +
> +	/* ISP hardware requires 16 bytes alignment */
> +	pix->bytesperline = ALIGN(pix->bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> +
> +	pix->sizeimage = 0;
> +	for (unsigned int i = 0; i < info->comp_planes; i++) {
> +		unsigned int hdiv = (i == 0) ? 1 : info->hdiv;
> +		unsigned int vdiv = (i == 0) ? 1 : info->vdiv;
> +		unsigned int bytesperline;
> +
> +		bytesperline = info->bpp[i] * DIV_ROUND_UP(pix->width, hdiv) / info->bpp_div[i];
> +
> +		/* ISP hardware requires 16 bytes alignment */
> +		bytesperline = ALIGN(bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> +		pix->sizeimage += bytesperline * DIV_ROUND_UP(pix->height, vdiv);
> +	}
> +}
> +
> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
> +				      enum vb2_buffer_state state)
> +{
> +	struct c3_isp_buffer *buff;
> +
> +	guard(spinlock_irqsave)(&cap->buff_lock);
> +
> +	if (cap->buff) {
> +		vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
> +		cap->buff = NULL;
> +	}
> +
> +	while (!list_empty(&cap->pending)) {
> +		buff = list_first_entry(&cap->pending,
> +					struct c3_isp_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +}
> +
> +static int c3_isp_cap_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_cap_enum_fmt(struct file *file, void *fh,
> +			       struct v4l2_fmtdesc *f)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +	unsigned int index = 0;
> +	unsigned int i;
> +
> +	if (!f->mbus_code) {
> +		if (f->index >= ARRAY_SIZE(cap_formats))
> +			return -EINVAL;
> +
> +		fmt = &cap_formats[f->index];
> +		f->pixelformat = fmt->fourcc;
> +		return 0;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> +		fmt = &cap_formats[i];
> +		if (f->mbus_code != fmt->mbus_code)
> +			continue;
> +
> +		if (index++ == f->index) {
> +			f->pixelformat = cap_formats[i].fourcc;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
> +			    struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	f->fmt.pix = cap->vfmt.fmt.pix;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
> +			    struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	c3_cap_try_fmt(cap, &f->fmt.pix);
> +	cap->vfmt = *f;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
> +			      struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	c3_cap_try_fmt(cap, &f->fmt.pix);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
> +				   struct v4l2_frmsizeenum *fsize)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +
> +	if (fsize->index)
> +		return -EINVAL;
> +
> +	fmt = c3_cap_find_fmt(fsize->pixel_format);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
> +	fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
> +	fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
> +	fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
> +	fsize->stepwise.step_width = 2;
> +	fsize->stepwise.step_height = 2;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
> +	.vidioc_querycap		= c3_isp_cap_querycap,
> +	.vidioc_enum_fmt_vid_cap	= c3_isp_cap_enum_fmt,
> +	.vidioc_g_fmt_vid_cap		= c3_isp_cap_g_fmt,
> +	.vidioc_s_fmt_vid_cap		= c3_isp_cap_s_fmt,
> +	.vidioc_try_fmt_vid_cap		= c3_isp_cap_try_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,
> +	.vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
> +	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_cap_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_cap_link_validate(struct media_link *link)
> +{
> +	struct video_device *vdev =
> +		media_entity_to_video_device(link->sink->entity);
> +	struct v4l2_subdev *sd =
> +		media_entity_to_v4l2_subdev(link->source->entity);
> +	struct c3_isp_capture *cap = video_get_drvdata(vdev);
> +	struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
> +	struct v4l2_subdev_format src_fmt = {
> +		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
> +		.pad = link->source->index,
> +	};
> +	const struct c3_isp_capture_format *cap_fmt =
> +				c3_cap_find_fmt(pix_fmt->pixelformat);
> +	int ret;
> +
> +	ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
> +	if (ret)
> +		return ret;
> +
> +	if (src_fmt.format.width != pix_fmt->width ||
> +	    src_fmt.format.height != pix_fmt->height ||
> +	    src_fmt.format.code != cap_fmt->mbus_code) {
> +		dev_err(cap->isp->dev,
> +			"link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
> +			link->source->entity->name, link->source->index,
> +			link->sink->entity->name, link->sink->index,
> +			src_fmt.format.code, src_fmt.format.width,
> +			src_fmt.format.height, cap_fmt->mbus_code,
> +			pix_fmt->width, pix_fmt->height);
> +
> +		return -EPIPE;
> +	};
> +
> +	return 0;
> +}
> +
> +static const struct media_entity_operations isp_cap_entity_ops = {
> +	.link_validate = c3_isp_cap_link_validate,
> +};
> +
> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
> +				  unsigned int *num_buffers,
> +				  unsigned int *num_planes,
> +				  unsigned int sizes[],
> +				  struct device *alloc_devs[])
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < pix->sizeimage)
> +			return -EINVAL;

I'm not sure this is correct. This capture device supports formats
with multiple planes, you should verify that *num_planes is equal to
the number of planes of the currently applied format and that each
plane has the correct size

And, if you return here

> +	} else {

You can skip the else {}

> +		*num_planes = 1;
> +		sizes[0] = pix->sizeimage;

And here set the number of planes to the correct number and set the
size of each plane ?

And now I realized you're using the single-planar API for buffer
management

	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

I was expecting it to rather be

	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

Even more so as you support multiplanar YUV formats.

Any reason why you chose to use the single planar API ?

> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_buffer, vb);
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	guard(spinlock_irqsave)(&cap->buff_lock);
> +
> +	list_add_tail(&buf->list, &cap->pending);
> +}
> +
> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = cap->vfmt.fmt.pix.sizeimage;
> +
> +	if (vb2_plane_size(vb, 0) < size) {
> +		dev_err(cap->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_buffer *buf =
> +		container_of(v4l2_buf, struct c3_isp_buffer, vb);
> +
> +	buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
> +				      unsigned int count)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	ret = pm_runtime_resume_and_get(cap->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
> +	if (ret) {
> +		dev_err(cap->isp->dev,
> +			"Failed to start cap%u pipeline: %d\n", cap->id, ret);
> +		goto err_pm_put;
> +	}

You could pipeline_start() after pm_runtime_resume_and_get() and save
this jump.

> +
> +	ret = c3_isp_cap_dummy_buff_create(cap);
> +	if (ret)
> +		goto err_pipeline_stop;
> +
> +	c3_isp_cap_start(cap);
> +
> +	ret = v4l2_subdev_enable_streams(&cap->rsz->sd,
> +					 C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
> +	if (ret)
> +		goto err_dummy_destroy;
> +
> +	return 0;
> +
> +err_dummy_destroy:
> +	c3_isp_cap_dummy_buff_destroy(cap);
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&cap->vdev);
> +err_pm_put:
> +	pm_runtime_put(cap->isp->dev);
> +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +
> +	c3_isp_cap_stop(cap);
> +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
> +
> +	v4l2_subdev_disable_streams(&cap->rsz->sd,
> +				    C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
> +
> +	c3_isp_cap_dummy_buff_destroy(cap);
> +
> +	video_device_pipeline_stop(&cap->vdev);
> +	pm_runtime_put(cap->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_video_vb2_ops = {
> +	.queue_setup = c3_isp_vb2_queue_setup,
> +	.buf_queue = c3_isp_vb2_buf_queue,
> +	.buf_prepare = c3_isp_vb2_buf_prepare,
> +	.buf_init = c3_isp_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_vb2_start_streaming,
> +	.stop_streaming = c3_isp_vb2_stop_streaming,
> +};
> +
> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
> +{
> +	struct video_device *vdev = &cap->vdev;
> +	struct vb2_queue *vb2_q = &cap->vb2_q;
> +	int ret;
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "c3-isp-cap%u", cap->id);
> +	vdev->fops = &isp_cap_v4l2_fops;
> +	vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &cap->isp->v4l2_dev;
> +	vdev->entity.ops = &isp_cap_entity_ops;
> +	vdev->lock = &cap->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, cap);
> +
> +	vb2_q->drv_priv = cap;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_video_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	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_buffer);
> +	vb2_q->dev = cap->isp->dev;
> +	vb2_q->lock = &cap->lock;
> +	vb2_q->min_queued_buffers = 2;

Now that you're using a dummy buffer you can drop this

> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret < 0)
> +		goto err_destroy;
> +
> +	cap->pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
> +	if (ret < 0)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0) {
> +		dev_err(cap->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_destroy:
> +	mutex_destroy(&cap->lock);
> +	return ret;
> +}
> +
> +int c3_isp_captures_register(struct c3_isp_device *isp)
> +{
> +	int ret;
> +	unsigned int i;
> +	struct c3_isp_capture *cap;
> +
> +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> +		cap = &isp->caps[i];
> +		memset(cap, 0, sizeof(*cap));
> +
> +		cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
> +		cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
> +		cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
> +		cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
> +		cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +
> +		c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
> +
> +		cap->id = i;
> +		cap->rsz = &isp->resizers[i];
> +		cap->isp = isp;
> +		INIT_LIST_HEAD(&cap->pending);
> +		spin_lock_init(&cap->buff_lock);
> +		mutex_init(&cap->lock);
> +
> +		ret = c3_isp_register_capture(cap);
> +		if (ret) {
> +			cap->isp = NULL;
> +			mutex_destroy(&cap->lock);
> +			c3_isp_captures_unregister(isp);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +	struct c3_isp_capture *cap;
> +
> +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> +		cap = &isp->caps[i];
> +
> +		if (!cap->isp)
> +			continue;
> +		vb2_queue_release(&cap->vb2_q);
> +		media_entity_cleanup(&cap->vdev.entity);
> +		video_unregister_device(&cap->vdev);
> +		mutex_destroy(&cap->lock);
> +	}
> +}
> +
> +void c3_isp_captures_isr(struct c3_isp_device *isp)
> +{
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
> +}
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
> new file mode 100644
> index 000000000000..d2c0b9aacf36
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
> @@ -0,0 +1,352 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_COMMON_H__
> +#define __C3_ISP_COMMON_H__
> +
> +#include <linux/clk.h>
> +
> +#include <media/media-device.h>
> +#include <media/videobuf2-core.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define C3_ISP_DRIVER_NAME            "c3-isp"
> +#define C3_ISP_CLOCK_NUM_MAX          3
> +
> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUV8_1X24
> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUV8_1X24
> +#define C3_ISP_DEFAULT_WIDTH          1920
> +#define C3_ISP_DEFAULT_HEIGHT         1080
> +#define C3_ISP_MAX_WIDTH              2888
> +#define C3_ISP_MAX_HEIGHT             2240
> +#define C3_ISP_MIN_WIDTH              160
> +#define C3_ISP_MIN_HEIGHT             120
> +
> +#define C3_ISP_DMA_SIZE_ALIGN_BYTES   16
> +#define C3_ISP_DISP_REG(base, id)     ((base) + (id) * 0x400)
> +#define C3_ISP_WRMIFX3_REG(base, id)  ((base) + (id) * 0x100)
> +#define C3_ISP_PPS_TAP4_S11_H_NUM     33
> +#define C3_ISP_PPS_LUT_CTYPE_0        0
> +#define C3_ISP_PPS_LUT_CTYPE_2        2
> +#define C3_ISP_SCALE_EN               1
> +#define C3_ISP_SCALE_DIS              0
> +
> +#define C3_ISP_PHASE_OFFSET_0         0
> +#define C3_ISP_PHASE_OFFSET_1         1
> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
> +
> +enum c3_isp_core_pads {
> +	C3_ISP_CORE_PAD_SINK_VIDEO,
> +	C3_ISP_CORE_PAD_SINK_PARAMS,
> +	C3_ISP_CORE_PAD_SOURCE_STATS,
> +	C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +	C3_ISP_CORE_PAD_MAX
> +};
> +
> +enum c3_isp_resizer_ids {
> +	C3_ISP_RSZ_0,
> +	C3_ISP_RSZ_1,
> +	C3_ISP_RSZ_2,
> +	C3_ISP_NUM_RSZ
> +};
> +
> +enum c3_isp_resizer_pads {
> +	C3_ISP_RESIZER_PAD_SINK,
> +	C3_ISP_RESIZER_PAD_SOURCE,
> +	C3_ISP_RESIZER_PAD_MAX
> +};
> +
> +enum c3_isp_cap_devs {
> +	C3_ISP_CAP_DEV_0,
> +	C3_ISP_CAP_DEV_1,
> +	C3_ISP_CAP_DEV_2,
> +	C3_ISP_NUM_CAP_DEVS
> +};
> +
> +/*
> + * 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;
> +};
> +
> +/*
> + * struct c3_isp_mbus_format_info - MBUS format information
> + *
> + * @mbus_code: the mbus code
> + * @pads: bitmask detailing valid pads for this mbus_code
> + * @xofst: horizontal phase offset of hardware
> + * @yofst: vertical phase offset of hardware
> + */
> +struct c3_isp_mbus_format_info {

It seems to me some of these types are used only inside a single .c
file

$ git grep c3_isp_mbus_format_info drivers/media/platform/amlogic/c3/isp/
drivers/media/platform/amlogic/c3/isp/c3-isp-common.h: * struct c3_isp_mbus_format_info - MBUS format information
drivers/media/platform/amlogic/c3/isp/c3-isp-common.h:struct c3_isp_mbus_format_info {
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *info;
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;

Could they be declared there ?

> +	u32 mbus_code;
> +	u32 pads;
> +	u8 xofst;
> +	u8 yofst;
> +};
> +
> +/*
> + * struct c3_isp_capture_format - The image format of capture device
> + *
> + * @mbus_code: the mbus code
> + * @fourcc: pixel format
> + */
> +struct c3_isp_capture_format {
> +	u32 mbus_code;
> +	u32 fourcc;
> +};
> +
> +/*
> + * struct c3_isp_buffer - A container of vb2 buffer used by the video devices:
> + *                        capture and stats video devices
> + *
> + * @vb: vb2 buffer
> + * @dma_addr: buffer physical address
> + * @list: entry of the buffer in the queue
> + */
> +struct c3_isp_buffer {
> +	struct vb2_v4l2_buffer vb;
> +	dma_addr_t dma_addr;
> +	struct list_head list;
> +};
> +
> +/*
> + * struct c3_isp_params_buffer - A container of vb2 buffer used by the
> + *                               params video device
> + *
> + * @vb: vb2 buffer
> + * @cfg: scratch buffer used for caching the ISP configuration parameters
> + * @list: entry of the buffer in the queue
> + */
> +struct c3_isp_params_buffer {
> +	struct vb2_v4l2_buffer vb;
> +	void *cfg;
> +	struct list_head list;
> +};
> +
> +/*
> + * struct c3_isp_dummy_buffer - A buffer to write the next frame to in case
> + *				there are no vb2 buffers available.
> + *
> + * @vaddr:	return value of call to dma_alloc_attrs
> + * @dma_addr:	dma address of the buffer
> + * @size:	size of the buffer
> + */
> +struct c3_isp_dummy_buffer {
> +	void *vaddr;
> +	dma_addr_t dma_addr;
> +	u32 size;
> +};
> +
> +/*
> + * struct c3_isp_core - ISP core subdev
> + *
> + * @sd: ISP sub-device
> + * @pads: ISP sub-device pads
> + * @src_sd: source sub-device
> + * @src_sd_pad: source sub-device pad
> + * @isp: pointer to c3_isp_device
> + */
> +struct c3_isp_core {
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[C3_ISP_CORE_PAD_MAX];
> +	struct v4l2_subdev *src_sd;
> +	u16 src_sd_pad;
> +	struct c3_isp_device *isp;
> +};
> +
> +/*
> + * struct c3_isp_resizer - ISP resizer subdev
> + *
> + * @id: resizer id
> + * @sd: resizer sub-device
> + * @pads: resizer sub-device pads
> + * @src_sd: source sub-device
> + * @src_sd_pad: source sub-device pad
> + * @isp: pointer to c3_isp_device
> + */
> +struct c3_isp_resizer {
> +	enum c3_isp_resizer_ids id;
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
> +	struct v4l2_subdev *src_sd;
> +	u32 src_sd_pad;
> +	struct c3_isp_device *isp;
> +};
> +
> +/*
> + * struct c3_isp_stats - ISP statistics device
> + *
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the metadata format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @isp: pointer to c3_isp_device
> + * @buff: in use buffer
> + * @buff_lock: protects stats buffer
> + * @pending: stats buffer list head
> + */
> +struct c3_isp_stats {
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	struct c3_isp_device *isp;
> +
> +	struct c3_isp_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stats buffer */
> +	struct list_head pending;
> +};
> +
> +/*
> + * struct c3_isp_params - ISP parameters device
> + *
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the metadata format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @isp: pointer to c3_isp_device
> + * @buff: in use buffer
> + * @buff_lock: protects stats buffer
> + * @pending: stats buffer list head
> + */
> +struct c3_isp_params {
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	struct c3_isp_device *isp;
> +
> +	struct c3_isp_params_buffer *buff;
> +	spinlock_t buff_lock; /* Protects params buffer */
> +	struct list_head pending;
> +};
> +
> +/*
> + * struct c3_isp_capture - ISP capture device
> + *
> + * @id: capture device ID
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the capture format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @isp: pointer to c3_isp_device
> + * @rsz: pointer to c3_isp_resizer
> + * @buff: in use buffer
> + * @buff_lock: protects capture buffer
> + * @pending: capture buffer list head
> + */
> +struct c3_isp_capture {
> +	enum c3_isp_cap_devs id;
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	struct c3_isp_device *isp;
> +	struct c3_isp_resizer *rsz;
> +
> +	struct c3_isp_dummy_buffer dummy_buff;
> +	struct c3_isp_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_info - ISP information
> + *
> + * @clocks: array of ISP clock names
> + * @clock_rates: array of ISP clock rate
> + * @clock_num: actual clock number
> + */
> +struct c3_isp_info {
> +	char *clocks[C3_ISP_CLOCK_NUM_MAX];
> +	u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
> +	u32 clock_num;
> +};
> +
> +/**
> + * struct c3_isp_device - ISP platform device
> + *
> + * @dev: pointer to the struct device
> + * @base: base register address
> + * @clks: array of clocks
> + * @notifier: notifier to register on the v4l2-async API
> + * @v4l2_dev: v4l2_device variable
> + * @media_dev: media device variable
> + * @pipe: media pipeline
> + * @core: ISP core subdev
> + * @resizer: ISP resizer subdev
> + * @stats: ISP stats device
> + * @params: ISP params device
> + * @caps: array of ISP capture device
> + * @frm_sequence: used to record frame id
> + * @info: version-specific ISP information
> + */
> +struct c3_isp_device {
> +	struct device *dev;
> +	void __iomem *base;
> +	struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
> +
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_device v4l2_dev;
> +	struct media_device media_dev;
> +	struct media_pipeline pipe;
> +
> +	struct c3_isp_core core;
> +	struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
> +	struct c3_isp_stats stats;
> +	struct c3_isp_params params;
> +	struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
> +
> +	u32 frm_sequence;
> +	const struct c3_isp_info *info;
> +};
> +
> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
> +
> +void c3_isp_core_queue_eof(struct c3_isp_device *isp);
> +int c3_isp_core_register(struct c3_isp_device *isp);
> +void c3_isp_core_unregister(struct c3_isp_device *isp);
> +int c3_isp_resizers_register(struct c3_isp_device *isp);
> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
> +int c3_isp_captures_register(struct c3_isp_device *isp);
> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
> +void c3_isp_captures_isr(struct c3_isp_device *isp);
> +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp);
> +int c3_isp_stats_register(struct c3_isp_device *isp);
> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
> +void c3_isp_stats_isr(struct c3_isp_device *isp);
> +void c3_isp_params_pre_cfg(struct c3_isp_device *isp);
> +int c3_isp_params_register(struct c3_isp_device *isp);
> +void c3_isp_params_unregister(struct c3_isp_device *isp);
> +void c3_isp_params_isr(struct c3_isp_device *isp);
> +
> +#endif
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
> new file mode 100644
> index 000000000000..bf83e73dd744
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
> @@ -0,0 +1,553 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/media/amlogic/c3-isp-config.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-event.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +#define C3_ISP_CORE_SUBDEV_NAME        "c3-isp-core"
> +
> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> +	/* RAW formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	},
> +	/* YUV formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_YUV8_1X24,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	},
> +};
> +
> +static const struct c3_isp_mbus_format_info
> +*core_find_format_by_code(u32 code, u32 pad)
> +{
> +	for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_core_mbus_formats[i];
> +
> +		if (info->mbus_code == code && info->pads & BIT(pad))
> +			return info;
> +	}
> +
> +	return NULL;
> +}
> +
> +static const struct c3_isp_mbus_format_info
> +*core_find_format_by_index(u32 index, u32 pad)
> +{
> +	for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_core_mbus_formats[i];
> +
> +		if (!(info->pads & BIT(pad)))
> +			continue;
> +
> +		if (!index)
> +			return info;
> +
> +		index--;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_isp_core_enable(struct c3_isp_device *isp)
> +{
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
> +			   ISP_TOP_IRQ_EN_FRAME_END_EN);
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
> +			   ISP_TOP_IRQ_EN_3A_DMA_ERR_EN);
> +
> +	/* Enable image data to ISP core */
> +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
> +			   ISP_TOP_PATH_SEL_CORE_MIPI_CORE);
> +}
> +
> +static void c3_isp_core_disable(struct c3_isp_device *isp)
> +{
> +	/* Disable image data to ISP core */
> +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
> +			   ISP_TOP_PATH_SEL_CORE_CORE_DIS);
> +
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
> +			   ISP_TOP_IRQ_EN_FRAME_END_DIS);
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
> +			   ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS);
> +}
> +
> +/* Set the phase offset of blc, wb and lns */
> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
> +				  u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK,
> +			   ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK,
> +			   ISP_LSWB_BLC_PHSOFST_VERT_OFST(yofst));
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK,
> +			   ISP_LSWB_WB_PHSOFST_HORIZ_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK,
> +			   ISP_LSWB_WB_PHSOFST_VERT_OFST(yofst));
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK,
> +			   ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK,
> +			   ISP_LSWB_LNS_PHSOFST_VERT_OFST(yofst));
> +}
> +
> +/* Set the phase offset of af, ae and awb */
> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
> +				u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_HORIZ_OFST_MASK,
> +			   ISP_AF_CTRL_HORIZ_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_VERT_OFST_MASK,
> +			   ISP_AF_CTRL_VERT_OFST(yofst));
> +
> +	c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_HORIZ_OFST_MASK,
> +			   ISP_AE_CTRL_HORIZ_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_VERT_OFST_MASK,
> +			   ISP_AE_CTRL_VERT_OFST(yofst));
> +
> +	c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_HORIZ_OFST_MASK,
> +			   ISP_AWB_CTRL_HORIZ_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_VERT_OFST_MASK,
> +			   ISP_AWB_CTRL_VERT_OFST(yofst));
> +}
> +
> +/* Set the phase offset of demosaic */
> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
> +				 u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK,
> +			   ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(xofst));
> +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK,
> +			   ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(yofst));
> +}
> +
> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +	isp_fmt = core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
> +
> +	c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
> +		     ISP_TOP_INPUT_SIZE_HORIZ_SIZE(fmt->width) |
> +		     ISP_TOP_INPUT_SIZE_VERT_SIZE(fmt->height));
> +	c3_isp_write(isp, ISP_TOP_FRM_SIZE,
> +		     ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(fmt->width) |
> +		     ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(fmt->height));
> +
> +	c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE,
> +			   ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK,
> +			   ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(fmt->width));
> +
> +	c3_isp_write(isp, ISP_AF_HV_SIZE,
> +		     ISP_AF_HV_SIZE_GLB_WIN_XSIZE(fmt->width) |
> +		     ISP_AF_HV_SIZE_GLB_WIN_YSIZE(fmt->height));
> +	c3_isp_write(isp, ISP_AE_HV_SIZE,
> +		     ISP_AE_HV_SIZE_HORIZ_SIZE(fmt->width) |
> +		     ISP_AE_HV_SIZE_VERT_SIZE(fmt->height));
> +	c3_isp_write(isp, ISP_AWB_HV_SIZE,
> +		     ISP_AWB_HV_SIZE_HORIZ_SIZE(fmt->width) |
> +		     ISP_AWB_HV_SIZE_VERT_SIZE(fmt->height));
> +
> +	c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +	c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +	c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +}
> +
> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      u32 pad, u64 streams_mask)
> +{
> +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	core->isp->frm_sequence = 0;
> +	c3_isp_core_cfg_format(core->isp, state);
> +	c3_isp_core_enable(core->isp);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> +						       &streams_mask);
> +	ret = v4l2_subdev_enable_streams(core->src_sd,
> +					 core->src_sd_pad, sink_streams);
> +	if (ret) {
> +		c3_isp_core_disable(core->isp);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> +						       &streams_mask);

You can drop xlate_streams if you drop routing support

> +	ret = v4l2_subdev_disable_streams(core->src_sd,
> +					  core->src_sd_pad, sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	c3_isp_core_disable(core->isp);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{

As per the other drivers, routing support can be dropped

> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = C3_ISP_DEFAULT_WIDTH,
> +		.height = C3_ISP_DEFAULT_HEIGHT,
> +		.code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route route;
> +	struct v4l2_subdev_krouting routing;
> +
> +	route.sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> +	route.sink_stream = 0;
> +	route.source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> +	route.source_stream = 0;
> +	route.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &route;
> +
> +	return c3_isp_core_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	const struct c3_isp_mbus_format_info *info;
> +	int ret = 0;
> +
> +	switch (code->pad) {
> +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> +		info = core_find_format_by_index(code->index, code->pad);
> +		if (!info)
> +			ret = -EINVAL;

                        return -EINVAL;

> +		else

so you can drop the else

> +			code->code = info->mbus_code;
> +
> +		break;
> +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> +		if (code->index)
> +			ret = -EINVAL;
> +		else

here as well

> +			code->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +		break;
> +	default:
> +		ret = -EINVAL;

                return -EINVAL;

> +		break;
> +	}
> +
> +	return ret;

        return 0;

> +}
> +
> +static void c3_isp_core_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 c3_isp_mbus_format_info *isp_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> +
> +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> +	else
> +		sink_fmt->code = format->format.code;
> +
> +	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);
> +

Should you set the colorspace related information like you do in
init_state() ?

	sink_fmt->field = V4L2_FIELD_NONE;
	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;

> +	src_fmt->width  = sink_fmt->width;
> +	src_fmt->height = sink_fmt->height;
> +
> +	format->format = *sink_fmt;
> +}
> +
> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
> +				       struct v4l2_subdev_format *format)
> +{
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +
> +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> +	else
> +		src_fmt->code = format->format.code;
> +
> +	format->format = *src_fmt;

mm, this doesn't check the sizes... if the ISP cannot scale/crop the
sizes here should come from the sink format, don't they ? Also
remember to set the colorspace information

> +}
> +
> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	if (format->pad == C3_ISP_CORE_PAD_SINK_VIDEO) {
> +		c3_isp_core_set_sink_fmt(state, format);
> +	} else if (format->pad == C3_ISP_CORE_PAD_SINK_PARAMS ||
> +		   format->pad == C3_ISP_CORE_PAD_SOURCE_STATS) {
> +		fmt = v4l2_subdev_state_get_format(state, format->pad);
> +		format->format = *fmt;

or

		format->format =
                        *v4l2_subdev_state_get_format(state, format->pad);

so you can drop *fmt and the {} or maybe turn this into a
        switch (format->pad) {

        }

Up to you

> +	} else if (format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO) {
> +		c3_isp_core_set_source_fmt(state, format);
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;

Or simply use one

	struct v4l2_mbus_framefmt *fmt;

> +
> +	/* Video sink pad */
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> +
> +	/* Video source pad */
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> +	src_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	src_fmt->field = V4L2_FIELD_NONE;
> +	src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> +	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +	src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> +	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	/* Parameters pad */
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
> +	sink_fmt->width = 0;
> +	sink_fmt->height = 0;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +	/* Statistics pad */
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
> +	src_fmt->width = 0;
> +	src_fmt->height = 0;
> +	src_fmt->field = V4L2_FIELD_NONE;
> +	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +	return c3_isp_core_init_routing(sd, state);

Drop routing support

> +}
> +
> +static int c3_isp_core_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
> +				       struct v4l2_event_subscription *sub)
> +{
> +	if (sub->type != V4L2_EVENT_FRAME_SYNC)
> +		return -EINVAL;
> +
> +	/* V4L2_EVENT_FRAME_SYNC doesn't need id, so should set 0 */
> +	if (sub->id != 0)
> +		return -EINVAL;
> +
> +	return v4l2_event_subscribe(fh, sub, 0, NULL);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
> +	.enum_mbus_code = c3_isp_core_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_isp_core_set_fmt,
> +	.enable_streams = c3_isp_core_enable_streams,
> +	.disable_streams = c3_isp_core_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_core_ops c3_isp_core_core_ops = {
> +	.subscribe_event = c3_isp_core_subscribe_event,
> +	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +};
> +
> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
> +	.core = &c3_isp_core_core_ops,
> +	.pad = &c3_isp_core_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
> +	.init_state = c3_isp_core_init_state,
> +};
> +
> +static int c3_isp_core_link_validate(struct media_link *link)
> +{
> +	if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
> +		return 0;
> +
> +	return v4l2_subdev_link_validate(link);
> +}
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_isp_core_entity_ops = {
> +	.link_validate = c3_isp_core_link_validate,
> +};
> +
> +void c3_isp_core_queue_eof(struct c3_isp_device *isp)
> +{

This gets called in the end of frame IRQ right ?

	if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
		c3_isp_core_queue_eof(isp);

When processing is completed.

FRAME_SYNC should ideally be emitted as soon as the processing of a
frame start. Do you have other interrupt sources you can use to detect
"start processing of a frame" ?

> +	struct v4l2_event event = {
> +		.type = V4L2_EVENT_FRAME_SYNC,
> +	};
> +
> +	event.u.frame_sync.frame_sequence = isp->frm_sequence;
> +	v4l2_event_queue(isp->core.sd.devnode, &event);
> +}
> +
> +int c3_isp_core_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_core *core = &isp->core;
> +	struct v4l2_subdev *sd = &core->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> +	sd->internal_ops = &c3_isp_core_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
> +
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> +	sd->entity.ops = &c3_isp_core_entity_ops;
> +
> +	core->isp = isp;
> +	sd->dev = isp->dev;
> +	v4l2_set_subdevdata(sd, core);
> +
> +	core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> +	core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
> +	core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
> +	core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	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_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +	return ret;
> +}
> +
> +void c3_isp_core_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_core *core = &isp->core;
> +	struct v4l2_subdev *sd = &core->sd;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
> new file mode 100644
> index 000000000000..0c6795ab9925
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
> @@ -0,0 +1,459 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
> +{
> +	return readl(isp->base + reg);
> +}
> +
> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
> +{
> +	writel(val, isp->base + reg);
> +}
> +
> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
> +{
> +	u32 orig, tmp;
> +
> +	orig = c3_isp_read(isp, reg);
> +
> +	tmp = orig & ~mask;
> +	tmp |= val & mask;
> +
> +	if (tmp != orig)
> +		c3_isp_write(isp, reg, tmp);
> +}
> +
> +/* PM runtime suspend */
> +static int c3_isp_runtime_suspend(struct device *dev)
> +{
> +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
> +
> +	return 0;
> +}
> +
> +/* PM runtime resume */
> +static int c3_isp_runtime_resume(struct device *dev)
> +{
> +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> +
> +	return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
> +}
> +
> +static const struct dev_pm_ops c3_isp_pm_ops = {
> +	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +			    pm_runtime_force_resume)
> +	RUNTIME_PM_OPS(c3_isp_runtime_suspend,
> +		       c3_isp_runtime_resume, NULL)
> +};
> +
> +/* IRQ handling */
> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
> +{
> +	struct c3_isp_device *isp = dev;
> +	u32 status;
> +
> +	/* Get irq status and clear irq status */
> +	status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
> +	c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
> +
> +	if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
> +		c3_isp_core_queue_eof(isp);
> +		c3_isp_stats_isr(isp);
> +		c3_isp_params_isr(isp);
> +		c3_isp_captures_isr(isp);
> +		isp->frm_sequence++;
> +	}
> +
> +	if (status & ISP_TOP_IRQ_EN_3A_DMA_ERR_EN)
> +		dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/* Subdev notifier register */
> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
> +			       struct v4l2_subdev *sd,
> +			       struct v4l2_async_connection *asc)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(notifier, struct c3_isp_device, notifier);
> +	struct c3_isp_core *core = &isp->core;
> +	struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&sd->entity,
> +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
> +		return ret;
> +	}
> +
> +	core->src_sd = sd;
> +	core->src_sd_pad = ret;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(notifier, struct c3_isp_device, notifier);
> +	int ret;
> +
> +	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
> +	if (ret < 0) {
> +		dev_err(isp->dev,
> +			"Failed to register subdev nodes: %d\n", ret);
> +		return ret;
> +	}
> +
> +	dev_info(isp->dev, "notify complete\n");

Possibly drop this

> +
> +	return media_device_register(&isp->media_dev);

Any reason you're registering the media device so late when the async
chain completes instead of doing so in probe ? I think it's fine doing
this here or during probe (after registering the v4l2_dev the video
devices. If you do it here and the remote subdev doesn't probe you
won't have any media graph. If you do it in probe() and the remote
subdev fails to probe, the media graph will register but be unusable.
Not sure what's best, maybe an incomplete graph is easier to debug
than no graph at all, but I won't push for this. Up to you :)


> +}

I'm not against using notify_complete() but I suspect you have a
single remote async subdev, right ? (iow you could have done this in
_bound ? )

> +
> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(asc->notifier, struct c3_isp_device, notifier);
> +
> +	media_device_unregister(&isp->media_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
> +	.bound = c3_isp_notify_bound,
> +	.complete = c3_isp_notify_complete,
> +	.destroy = c3_isp_notify_destroy,
> +};
> +
> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
> +{
> +	struct v4l2_async_connection *asc;
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> +					      struct v4l2_async_connection);
> +	fwnode_handle_put(ep);
> +
> +	if (IS_ERR(asc))
> +		return PTR_ERR(asc);
> +
> +	isp->notifier.ops = &c3_isp_notify_ops;
> +	ret = v4l2_async_nf_register(&isp->notifier);
> +	if (ret)
> +		v4l2_async_nf_cleanup(&isp->notifier);
> +
> +	return ret;
> +}
> +
> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
> +{
> +	v4l2_async_nf_unregister(&isp->notifier);
> +	v4l2_async_nf_cleanup(&isp->notifier);
> +}
> +
> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
> +{
> +	struct media_device *media_dev = &isp->media_dev;
> +	struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
> +	int ret;
> +
> +	/* Initialize media device */
> +	strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
> +		sizeof(media_dev->model));
> +	media_dev->dev = isp->dev;
> +
> +	media_device_init(media_dev);
> +
> +	/* Initialize v4l2 device */
> +	v4l2_dev->mdev = media_dev;
> +	strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
> +		sizeof(v4l2_dev->name));
> +
> +	ret = v4l2_device_register(isp->dev, v4l2_dev);
> +	if (ret) {
> +		media_device_cleanup(media_dev);
> +		dev_err(isp->dev,
> +			"Failed to register V4L2 device: %d\n", ret);
> +	}
> +
> +	return ret;
> +}
> +
> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
> +{
> +	v4l2_device_unregister(&isp->v4l2_dev);
> +	media_device_cleanup(&isp->media_dev);
> +}
> +
> +static void c3_isp_remove_links(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +
> +	media_entity_remove_links(&isp->core.sd.entity);
> +
> +	for (i = 0; i < C3_ISP_NUM_RSZ; i++)
> +		media_entity_remove_links(&isp->resizers[i].sd.entity);
> +
> +	for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
> +		media_entity_remove_links(&isp->caps[i].vdev.entity);
> +}
> +
> +static int c3_isp_create_links(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
> +		ret = media_create_pad_link(&isp->resizers[i].sd.entity,
> +					    C3_ISP_RESIZER_PAD_SOURCE,
> +					    &isp->caps[i].vdev.entity,
> +					    0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
> +			goto err_remove_links;
> +		}
> +
> +		ret = media_create_pad_link(&isp->core.sd.entity,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    &isp->resizers[i].sd.entity,
> +					    C3_ISP_RESIZER_PAD_SINK,
> +					    MEDIA_LNK_FL_ENABLED);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
> +			goto err_remove_links;
> +		}
> +	}
> +
> +	ret = media_create_pad_link(&isp->core.sd.entity,
> +				    C3_ISP_CORE_PAD_SOURCE_STATS,
> +				    &isp->stats.vdev.entity,
> +				    0, MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(isp->dev, "Failed to link core and stats\n");
> +		goto err_remove_links;
> +	}
> +
> +	ret = media_create_pad_link(&isp->params.vdev.entity, 0,
> +				    &isp->core.sd.entity,
> +				    C3_ISP_CORE_PAD_SINK_PARAMS,
> +				    MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(isp->dev, "Failed to link params and core\n");
> +		goto err_remove_links;
> +	}
> +
> +	return 0;
> +
> +err_remove_links:
> +	c3_isp_remove_links(isp);
> +	return ret;
> +}
> +
> +static int c3_isp_videos_register(struct c3_isp_device *isp)
> +{
> +	int ret;
> +
> +	ret = c3_isp_captures_register(isp);
> +	if (ret)
> +		return ret;
> +
> +	ret = c3_isp_stats_register(isp);
> +	if (ret)
> +		goto err_captures_unregister;
> +
> +	ret = c3_isp_params_register(isp);
> +	if (ret)
> +		goto err_stats_unregister;
> +
> +	ret = c3_isp_create_links(isp);
> +	if (ret)
> +		goto err_params_unregister;
> +
> +	return 0;
> +
> +err_params_unregister:
> +	c3_isp_params_unregister(isp);
> +err_stats_unregister:
> +	c3_isp_stats_unregister(isp);
> +err_captures_unregister:
> +	c3_isp_captures_unregister(isp);
> +	return ret;
> +}
> +
> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
> +{
> +	c3_isp_remove_links(isp);
> +	c3_isp_params_unregister(isp);
> +	c3_isp_stats_unregister(isp);
> +	c3_isp_captures_unregister(isp);
> +}
> +
> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
> +{
> +	const struct c3_isp_info *info = isp->info;
> +	int ret;
> +	u32 i;
> +
> +	for (i = 0; i < info->clock_num; i++)
> +		isp->clks[i].id = info->clocks[i];
> +
> +	ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->clock_num; i++) {
> +		if (!info->clock_rates[i])
> +			continue;
> +		ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);

same comment as per the other drivers

> +		if (ret) {
> +			dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
> +				info->clock_rates[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct c3_isp_device *isp;
> +	int irq;
> +	int ret;
> +
> +	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
> +	if (!isp)
> +		return -ENOMEM;
> +
> +	isp->info = of_device_get_match_data(dev);
> +	isp->dev = dev;
> +
> +	isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
> +	if (IS_ERR(isp->base))
> +		return dev_err_probe(dev, PTR_ERR(isp->base),
> +				     "Failed to ioremap resource\n");
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	ret = c3_isp_cfg_clocks(isp);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> +
> +	platform_set_drvdata(pdev, isp);
> +
> +	pm_runtime_enable(dev);
> +
> +	ret = c3_isp_v4l2_register(isp);
> +	if (ret)
> +		goto err_runtime_disable;
> +
> +	ret = c3_isp_core_register(isp);
> +	if (ret)
> +		goto err_v4l2_unregister;
> +
> +	ret = c3_isp_resizers_register(isp);
> +	if (ret)
> +		goto err_core_unregister;
> +
> +	ret = c3_isp_async_nf_register(isp);
> +	if (ret)
> +		goto err_resizers_unregister;
> +
> +	ret = devm_request_irq(dev, irq,
> +			       c3_isp_irq_handler, IRQF_SHARED,
> +			       dev_driver_string(dev), isp);
> +	if (ret)
> +		goto err_nf_unregister;
> +
> +	ret = c3_isp_videos_register(isp);
> +	if (ret)
> +		goto err_nf_unregister;
> +
> +	return 0;
> +
> +err_nf_unregister:
> +	c3_isp_async_nf_unregister(isp);
> +err_resizers_unregister:
> +	c3_isp_resizers_unregister(isp);
> +err_core_unregister:
> +	c3_isp_core_unregister(isp);
> +err_v4l2_unregister:
> +	c3_isp_v4l2_unregister(isp);
> +err_runtime_disable:
> +	pm_runtime_disable(dev);
> +	return ret;
> +};
> +
> +static void c3_isp_remove(struct platform_device *pdev)
> +{
> +	struct c3_isp_device *isp = platform_get_drvdata(pdev);
> +
> +	c3_isp_videos_unregister(isp);
> +	c3_isp_async_nf_unregister(isp);
> +	c3_isp_core_unregister(isp);
> +	c3_isp_resizers_unregister(isp);
> +	c3_isp_v4l2_unregister(isp);
> +	pm_runtime_disable(isp->dev);
> +};
> +
> +static const struct c3_isp_info isp_info = {
> +	.clocks = {"vapb", "isp0"},
> +	.clock_rates = {0, 400000000},
> +	.clock_num = 2
> +};
> +
> +static const struct of_device_id c3_isp_of_match[] = {
> +	{ .compatible = "amlogic,c3-isp",
> +	  .data = &isp_info },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
> +
> +static struct platform_driver c3_isp_driver = {
> +	.probe = c3_isp_probe,
> +	.remove = c3_isp_remove,
> +	.driver = {
> +		.name = "c3-isp",
> +		.of_match_table = c3_isp_of_match,
> +		.pm = pm_ptr(&c3_isp_pm_ops),
> +	},
> +};
> +
> +module_platform_driver(c3_isp_driver);
> +
> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
> new file mode 100644
> index 000000000000..dc33a57dcd59
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
> @@ -0,0 +1,942 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#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-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 pointer 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. The data member at the end allows a pointer to an address
> + * within the data member of :c:type:`c3_isp_params_cfg` to initialise a
> + * union variable.
> + *
> + * @header:		Pointer to the shared header struct embedded as the
> + *			first member of all the possible other members (except
> + *			@data). 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_PST_CCM
> + * @csc:		For header->type == C3_ISP_PARAMS_BLOCK_PST_CSC
> + * @blc:		For header->type == C3_ISP_PARAMS_BLOCK_PST_BLC
> + * @data:		Allows easy initialisation of a union variable with a
> + *			pointer into a u8 array.
> + */
> +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;
> +	u8 *data;

Why do you need data here ?

Oh, I've see in c3_isp_params_cfg_blocks() now :)

The consequences of this is that all handlers take an

typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
				     union c3_isp_params_block block);

by value and not by pointer.

I know it's equivalent since the union only stores pointers but it
might be confusing... I don't know, some parts of me like it, some
doesn't and would prefer something like union
rkisp1_ext_params_config. Clever hack btw ;) If you prefer this style
I'm fine with you keeping it even if seeing a parameter passed by
value to the handlers makes me cringe a bit

> +};
> +
> +typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
> +				     union c3_isp_params_block block);
> +
> +struct c3_isp_params_handler {
> +	size_t size;
> +	c3_isp_block_handler handler;
> +};
> +
> +#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,
> +					union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_awb_gains *awb_gains = block.awb_gains;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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));
> +
> +	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_cfg_awb_weight(struct c3_isp_device *isp,
> +					 struct c3_isp_params_awb_config *awb_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 = awb_cfg->horiz_zones_num * awb_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_IDX_DATA(0, awb_cfg->zone_weight[base + 0]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(1, awb_cfg->zone_weight[base + 1]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(2, awb_cfg->zone_weight[base + 2]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(3, awb_cfg->zone_weight[base + 3]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(4, awb_cfg->zone_weight[base + 4]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(5, awb_cfg->zone_weight[base + 5]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(6, awb_cfg->zone_weight[base + 6]) |
> +		       ISP_AWB_BLK_WT_DATA_IDX_DATA(7, awb_cfg->zone_weight[base + 7]);
> +		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
> +	}
> +
> +	if (zones_num % 8) {
> +		data = 0;
> +		base = i * 8;
> +
> +		for (i = 0; i < zones_num % 8; i++)
> +			data |= ISP_AWB_BLK_WT_DATA_IDX_DATA(i, awb_cfg->zone_weight[base + i]);
> +
> +		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
> +	}
> +}
> +
> +static void c3_isp_params_cfg_awb_cood(struct c3_isp_device *isp,
> +				       struct c3_isp_params_awb_config *awb_cfg)
> +{
> +	unsigned int max_point_num;
> +
> +	/* The number of points is one more than the number of edges */
> +	max_point_num = max(awb_cfg->horiz_zones_num, awb_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(awb_cfg->horiz_cood[i]) |
> +			     ISP_AWB_IDX_DATA_VIDX_DATA(awb_cfg->vert_cood[i]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp,
> +					 union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_awb_config *awb_cfg = block.awb_cfg;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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_STAT_POINT_MASK,
> +			   ISP_TOP_3A_STAT_CRTL_AWB_STAT_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_HORIZ_BLKS_NUM_MASK,
> +			   ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(awb_cfg->horiz_zones_num));
> +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK,
> +			   ISP_AWB_HV_BLKNUM_VERT_BLKS_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_cfg_awb_weight(isp, awb_cfg);
> +	c3_isp_params_cfg_awb_cood(isp, awb_cfg);
> +
> +	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_cfg_ae_weight(struct c3_isp_device *isp,
> +					struct c3_isp_params_ae_config *ae_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 = ae_cfg->horiz_zones_num * ae_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_IDX_DATA(0, ae_cfg->zone_weight[base + 0]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(1, ae_cfg->zone_weight[base + 1]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(2, ae_cfg->zone_weight[base + 2]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(3, ae_cfg->zone_weight[base + 3]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(4, ae_cfg->zone_weight[base + 4]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(5, ae_cfg->zone_weight[base + 5]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(6, ae_cfg->zone_weight[base + 6]) |
> +		       ISP_AE_BLK_WT_DATA_IDX_DATA(7, ae_cfg->zone_weight[base + 7]);
> +		c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
> +	}
> +
> +	/* Write the last weights data */
> +	if (zones_num % 8) {
> +		data = 0;
> +		base = i * 8;
> +
> +		for (i = 0; i < zones_num % 8; i++)
> +			data |= ISP_AE_BLK_WT_DATA_IDX_DATA(i, ae_cfg->zone_weight[base + i]);
> +
> +		c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
> +	}
> +}
> +
> +static void c3_isp_params_cfg_ae_cood(struct c3_isp_device *isp,
> +				      struct c3_isp_params_ae_config *ae_cfg)
> +{
> +	unsigned int max_point_num;
> +
> +	/* The number of points is one more than the number of edges */
> +	max_point_num = max(ae_cfg->horiz_zones_num, ae_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(ae_cfg->horiz_cood[i]) |
> +			     ISP_AE_IDX_DATA_VIDX_DATA(ae_cfg->vert_cood[i]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp,
> +					union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_ae_config *ae_cfg = block.ae_cfg;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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_STAT_POINT_MASK,
> +			   ISP_TOP_3A_STAT_CRTL_AE_STAT_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_BLK_HORIZ_NUM_MASK,
> +			   ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(ae_cfg->horiz_zones_num));
> +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK,
> +			   ISP_AE_HV_BLKNUM_BLK_VERT_NUM(ae_cfg->vert_zones_num));
> +
> +	c3_isp_params_cfg_ae_weight(isp, ae_cfg);
> +	c3_isp_params_cfg_ae_cood(isp, ae_cfg);
> +
> +	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_cfg_af_cood(struct c3_isp_device *isp,
> +				      struct c3_isp_params_af_config *af_cfg)
> +{
> +	unsigned int max_point_num;
> +
> +	/* The number of points is one more than the number of edges */
> +	max_point_num = max(af_cfg->horiz_zones_num, af_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(af_cfg->horiz_cood[i]) |
> +			     ISP_AF_IDX_DATA_VIDX_DATA(af_cfg->vert_cood[i]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp,
> +					union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_af_config *af_cfg = block.af_cfg;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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_STAT_POINT_MASK,
> +			   ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(af_cfg->tap_point));
> +
> +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK,
> +			   ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(af_cfg->horiz_zones_num));
> +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK,
> +			   ISP_AF_HV_BLKNUM_BLK_VERT_NUM(af_cfg->vert_zones_num));
> +
> +	c3_isp_params_cfg_af_cood(isp, af_cfg);
> +
> +	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,
> +					union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_pst_gamma *gamma = block.pst_gamma;
> +	unsigned int base;
> +	unsigned int i;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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(gamma->lut) / 2; i++) {
> +			base = i * 2;
> +			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> +				     ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]) |
> +				     ISP_PST_GAMMA_LUT_DATA_DATA1(gamma->lut[base + 1]));
> +		}
> +
> +		/* Write the last one */
> +		if (ARRAY_SIZE(gamma->lut) % 2) {
> +			base = i * 2;
> +			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> +				     ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]));
> +		}
> +	}
> +
> +	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,
> +				  union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_ccm *ccm = block.ccm;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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]));
> +
> +	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,
> +				  union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_csc *csc = block.csc;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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]));
> +
> +	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,
> +				  union c3_isp_params_block block)
> +{
> +	struct c3_isp_params_blc *blc = block.blc;
> +
> +	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> +		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));
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_BLC_EN_MASK,
> +			   ISP_TOP_BEO_CTRL_BLC_EN);
> +}
> +
> +static const struct c3_isp_params_handler c3_isp_params_handlers[] = {
> +	[C3_ISP_PARAMS_BLOCK_AWB_GAINS] = {
> +		.size = sizeof(struct c3_isp_params_awb_gains),
> +		.handler = c3_isp_params_cfg_awb_gains,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = {
> +		.size = sizeof(struct c3_isp_params_awb_config),
> +		.handler = c3_isp_params_cfg_awb_config,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_AE_CONFIG] = {
> +		.size = sizeof(struct c3_isp_params_ae_config),
> +		.handler = c3_isp_params_cfg_ae_config,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_AF_CONFIG] = {
> +		.size = sizeof(struct c3_isp_params_af_config),
> +		.handler = c3_isp_params_cfg_af_config,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_PST_GAMMA] = {
> +		.size = sizeof(struct c3_isp_params_pst_gamma),
> +		.handler = c3_isp_params_cfg_pst_gamma,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_CCM] = {
> +		.size = sizeof(struct c3_isp_params_ccm),
> +		.handler = c3_isp_params_cfg_ccm,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_CSC] = {
> +		.size = sizeof(struct c3_isp_params_csc),
> +		.handler = c3_isp_params_cfg_csc,
> +	},
> +	[C3_ISP_PARAMS_BLOCK_BLC] = {
> +		.size = sizeof(struct c3_isp_params_blc),
> +		.handler = c3_isp_params_cfg_blc,
> +	},
> +};
> +
> +static enum vb2_buffer_state
> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
> +{
> +	struct c3_isp_params_cfg *config = params->buff->cfg;
> +	size_t block_offset = 0;
> +	size_t max_offset = 0;
> +
> +	if (WARN_ON(!config))
> +		return VB2_BUF_STATE_ERROR;
> +
> +	/* Ensure config->data has a full struct c3_isp_params_block_header */
> +	max_offset = config->data_size - sizeof(struct c3_isp_params_block_header);

I might have missed why you subtract this. Hasn't the package been
validated in c3_isp_params_vb2_buf_prepare() ?

> +
> +	/* Walk the list of parameter blocks and process them */
> +	while (block_offset <= max_offset) {
> +		const struct c3_isp_params_handler *block_handler;
> +		union c3_isp_params_block block;
> +
> +		block = (union c3_isp_params_block)
> +			 &config->data[block_offset];
> +
> +		block_handler = &c3_isp_params_handlers[block.header->type];
> +		block_handler->handler(params->isp, block);
> +
> +		block_offset += block.header->size;
> +	}
> +
> +	return VB2_BUF_STATE_DONE;
> +}
> +
> +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)(&params->buff_lock);
> +
> +	/* Only use the first buffer to initialize ISP */
> +	params->buff = list_first_entry_or_null(&params->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 here

> +	} else {

and you can spare the } else {

> +		*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)(&params->buff_lock);
> +
> +	list_add_tail(&buf->list, &params->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 *cfg = buf->cfg;
> +	struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0);
> +	size_t payload_size = vb2_get_plane_payload(vb, 0);
> +	size_t header_size = offsetof(struct c3_isp_params_cfg, data);
> +	size_t block_offset = 0;
> +	size_t cfg_size;
> +
> +	/* Payload size can't be greater than the destination buffer size */
> +	if (payload_size > params->vfmt.fmt.meta.buffersize) {
> +		dev_dbg(params->isp->dev,
> +			"Payload size is too large: %zu\n", payload_size);
> +		return -EINVAL;
> +	}
> +
> +	/* Payload size can't be smaller than the header size */
> +	if (payload_size < header_size) {
> +		dev_dbg(params->isp->dev,
> +			"Payload size is too small: %zu\n", payload_size);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Use the internal scratch buffer to avoid userspace modifying
> +	 * the buffer content while the driver is processing it.
> +	 */
> +	memcpy(cfg, usr_cfg, payload_size);
> +
> +	/* Only v0 is supported at the moment */
> +	if (cfg->version != C3_ISP_PARAMS_BUFFER_V0) {
> +		dev_dbg(params->isp->dev,
> +			"Unsupported params buffer version: %u\n", cfg->version);
> +		return -EINVAL;
> +	}
> +
> +	/* Validate the size reported in the parameter buffer header */
> +	cfg_size = header_size + cfg->data_size;
> +	if (cfg_size != payload_size) {
> +		dev_dbg(params->isp->dev,
> +			"Data size %zu and payload size %zu are different\n",
> +			cfg_size, payload_size);
> +		return -EINVAL;
> +	}
> +
> +	/* Walk the list of parameter blocks and validate them */
> +	cfg_size = cfg->data_size;
> +	while (cfg_size >= sizeof(struct c3_isp_params_block_header)) {
> +		const struct c3_isp_params_block_header *block;
> +		const struct c3_isp_params_handler *handler;
> +
> +		block = (struct c3_isp_params_block_header *)
> +			&cfg->data[block_offset];
> +
> +		if (block->type >= ARRAY_SIZE(c3_isp_params_handlers)) {
> +			dev_dbg(params->isp->dev,
> +				"Invalid params block type\n");
> +			return -EINVAL;
> +		}
> +
> +		if (block->size > cfg_size) {
> +			dev_dbg(params->isp->dev,
> +				"Block size is greater than cfg size\n");
> +			return -EINVAL;
> +		}
> +
> +		handler = &c3_isp_params_handlers[block->type];
> +		if (block->size != handler->size) {
> +			dev_dbg(params->isp->dev,
> +				"Invalid params block size\n");
> +			return -EINVAL;
> +		}
> +
> +		block_offset += block->size;
> +		cfg_size -= block->size;
> +	}
> +
> +	if (cfg_size) {
> +		dev_dbg(params->isp->dev,
> +			"Unexpected data after the params buffer end\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +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)(&params->buff_lock);
> +
> +	while (!list_empty(&params->pending)) {
> +		buff = list_first_entry(&params->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,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.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 = &params->vdev;
> +	struct vb2_queue *vb2_q = &params->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(&params->pending);
> +	spin_lock_init(&params->buff_lock);
> +	mutex_init(&params->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 = &params->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_buffer);
> +	vb2_q->dev = isp->dev;
> +	vb2_q->lock = &params->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, &params->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(&params->lock);
> +	return ret;
> +}
> +
> +void c3_isp_params_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +
> +	vb2_queue_release(&params->vb2_q);
> +	media_entity_cleanup(&params->vdev.entity);
> +	video_unregister_device(&params->vdev);
> +	mutex_destroy(&params->lock);
> +}
> +
> +void c3_isp_params_isr(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +	enum vb2_buffer_state state;
> +
> +	guard(spinlock_irqsave)(&params->buff_lock);
> +
> +	params->buff = list_first_entry_or_null(&params->pending,
> +						struct c3_isp_params_buffer, list);
> +	if (!params->buff)
> +		return;
> +
> +	list_del(&params->buff->list);
> +
> +	state = 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(&params->buff->vb.vb2_buf, state);
> +}
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
> new file mode 100644
> index 000000000000..c4d3b8c96479
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
> @@ -0,0 +1,599 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_REGS_H__
> +#define __C3_ISP_REGS_H__
> +
> +#define ISP_TOP_INPUT_SIZE                              0x0000
> +#define ISP_TOP_INPUT_SIZE_VERT_SIZE_MASK               GENMASK(15, 0)
> +#define ISP_TOP_INPUT_SIZE_VERT_SIZE(x)                 ((x) << 0)
> +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE_MASK              GENMASK(31, 16)
> +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE(x)                ((x) << 16)
> +
> +#define ISP_TOP_FRM_SIZE                                0x0004
> +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE_MASK            GENMASK(15, 0)
> +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(x)              ((x) << 0)
> +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE_MASK           GENMASK(31, 16)
> +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(x)             ((x) << 16)
> +
> +#define ISP_TOP_HOLD_SIZE                               0x0008
> +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK          GENMASK(31, 16)
> +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(x)            ((x) << 16)
> +
> +#define ISP_TOP_PATH_EN                                 0x0010
> +#define ISP_TOP_PATH_EN_DISP0_EN_MASK                   BIT(0)
> +#define ISP_TOP_PATH_EN_DISP0_EN                        BIT(0)
> +#define ISP_TOP_PATH_EN_DISP0_DIS                       (0 << 0)
> +#define ISP_TOP_PATH_EN_DISP1_EN_MASK                   BIT(1)
> +#define ISP_TOP_PATH_EN_DISP1_EN                        BIT(1)
> +#define ISP_TOP_PATH_EN_DISP1_DIS                       (0 << 1)
> +#define ISP_TOP_PATH_EN_DISP2_EN_MASK                   BIT(2)
> +#define ISP_TOP_PATH_EN_DISP2_EN                        BIT(2)
> +#define ISP_TOP_PATH_EN_DISP2_DIS                       (0 << 2)
> +#define ISP_TOP_PATH_EN_WRMIF0_EN_MASK                  BIT(8)
> +#define ISP_TOP_PATH_EN_WRMIF0_EN                       BIT(8)
> +#define ISP_TOP_PATH_EN_WRMIF0_DIS                      (0 << 8)
> +#define ISP_TOP_PATH_EN_WRMIF1_EN_MASK                  BIT(9)
> +#define ISP_TOP_PATH_EN_WRMIF1_EN                       BIT(9)
> +#define ISP_TOP_PATH_EN_WRMIF1_DIS                      (0 << 9)
> +#define ISP_TOP_PATH_EN_WRMIF2_EN_MASK                  BIT(10)
> +#define ISP_TOP_PATH_EN_WRMIF2_EN                       BIT(10)
> +#define ISP_TOP_PATH_EN_WRMIF2_DIS                      (0 << 10)
> +
> +#define ISP_TOP_PATH_SEL                                0x0014
> +#define ISP_TOP_PATH_SEL_CORE_MASK                      GENMASK(18, 16)
> +#define ISP_TOP_PATH_SEL_CORE_CORE_DIS                  (0 << 16)
> +#define ISP_TOP_PATH_SEL_CORE_MIPI_CORE                 BIT(16)
> +
> +#define ISP_TOP_IRQ_EN                                  0x0080
> +#define ISP_TOP_IRQ_EN_FRAME_END_MASK                   BIT(0)
> +#define ISP_TOP_IRQ_EN_FRAME_END_EN                     BIT(0)
> +#define ISP_TOP_IRQ_EN_FRAME_END_DIS                    (0 << 0)
> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK                  BIT(5)
> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_EN                    BIT(5)
> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS                   (0 << 5)
> +
> +#define ISP_TOP_IRQ_CLR                                 0x0084
> +#define ISP_TOP_RO_IRQ_STAT                             0x01c4
> +
> +#define ISP_TOP_MODE_CTRL                               0x0400
> +#define ISP_TOP_FEO_CTRL0                               0x040c
> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK             BIT(8)
> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS                 (0 << 8)
> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN                  BIT(8)
> +
> +#define ISP_TOP_FEO_CTRL1_0                             0x0410
> +#define ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK                 BIT(3)
> +#define ISP_TOP_FEO_CTRL1_0_DPC_DIS                     (0 << 3)
> +#define ISP_TOP_FEO_CTRL1_0_DPC_EN                      BIT(3)
> +#define ISP_TOP_FEO_CTRL1_0_OG_EN_MASK                  BIT(5)
> +#define ISP_TOP_FEO_CTRL1_0_OG_DIS                      (0 << 5)
> +#define ISP_TOP_FEO_CTRL1_0_OG_EN                       BIT(5)
> +
> +#define ISP_TOP_FED_CTRL                                0x0418
> +#define ISP_TOP_FED_CTRL_PDPC_EN_MASK                   BIT(1)
> +#define ISP_TOP_FED_CTRL_PDPC_DIS                       (0 << 1)
> +#define ISP_TOP_FED_CTRL_PDPC_EN                        BIT(1)
> +#define ISP_TOP_FED_CTRL_RAWCNR_EN_MASK                 GENMASK(6, 5)
> +#define ISP_TOP_FED_CTRL_RAWCNR_DIS                     (0 << 5)
> +#define ISP_TOP_FED_CTRL_RAWCNR_EN                      BIT(5)
> +#define ISP_TOP_FED_CTRL_SNR1_EN_MASK                   BIT(9)
> +#define ISP_TOP_FED_CTRL_SNR1_DIS                       (0 << 9)
> +#define ISP_TOP_FED_CTRL_SNR1_EN                        BIT(9)
> +#define ISP_TOP_FED_CTRL_TNR0_EN_MASK                   BIT(11)
> +#define ISP_TOP_FED_CTRL_TNR0_DIS                       (0 << 11)
> +#define ISP_TOP_FED_CTRL_TNR0_EN                        BIT(11)
> +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK               BIT(12)
> +#define ISP_TOP_FED_CTRL_CUBIC_CS_DIS                   (0 << 12)
> +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN                    BIT(12)
> +#define ISP_TOP_FED_CTRL_SQRT_EN_MASK                   BIT(14)
> +#define ISP_TOP_FED_CTRL_SQRT_DIS                       (0 << 14)
> +#define ISP_TOP_FED_CTRL_SQRT_EN                        BIT(14)
> +#define ISP_TOP_FED_CTRL_DGAIN_EN_MASK                  BIT(16)
> +#define ISP_TOP_FED_CTRL_DGAIN_DIS                      (0 << 16)
> +#define ISP_TOP_FED_CTRL_DGAIN_EN                       BIT(16)
> +
> +#define ISP_TOP_BEO_CTRL                                0x041c
> +#define ISP_TOP_BEO_CTRL_WB_EN_MASK                     BIT(6)
> +#define ISP_TOP_BEO_CTRL_WB_DIS                         (0 << 6)
> +#define ISP_TOP_BEO_CTRL_WB_EN                          BIT(6)
> +#define ISP_TOP_BEO_CTRL_BLC_EN_MASK                    BIT(7)
> +#define ISP_TOP_BEO_CTRL_BLC_DIS                        (0 << 7)
> +#define ISP_TOP_BEO_CTRL_BLC_EN                         BIT(7)
> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK              BIT(8)
> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_DIS                  (0 << 8)
> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN                   BIT(8)
> +#define ISP_TOP_BEO_CTRL_EOTF_EN_MASK                   BIT(9)
> +#define ISP_TOP_BEO_CTRL_EOTF_DIS                       (0 << 9)
> +#define ISP_TOP_BEO_CTRL_EOTF_EN                        BIT(9)
> +
> +#define ISP_TOP_BED_CTRL                                0x0420
> +#define ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK               GENMASK(1, 0)
> +#define ISP_TOP_BED_CTRL_YHS_STAT_DIS                   (0 << 0)
> +#define ISP_TOP_BED_CTRL_YHS_STAT_EN                    BIT(0)
> +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK              BIT(2)
> +#define ISP_TOP_BED_CTRL_GRPH_STAT_DIS                  (0 << 2)
> +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN                   BIT(2)
> +#define ISP_TOP_BED_CTRL_FMETER_EN_MASK                 BIT(3)
> +#define ISP_TOP_BED_CTRL_FMETER_DIS                     (0 << 3)
> +#define ISP_TOP_BED_CTRL_FMETER_EN                      BIT(3)
> +#define ISP_TOP_BED_CTRL_BSC_EN_MASK                    BIT(10)
> +#define ISP_TOP_BED_CTRL_BSC_DIS                        (0 << 10)
> +#define ISP_TOP_BED_CTRL_BSC_EN                         BIT(10)
> +#define ISP_TOP_BED_CTRL_CNR2_EN_MASK                   BIT(11)
> +#define ISP_TOP_BED_CTRL_CNR2_DIS                       (0 << 11)
> +#define ISP_TOP_BED_CTRL_CNR2_EN                        BIT(11)
> +#define ISP_TOP_BED_CTRL_CM1_EN_MASK                    BIT(13)
> +#define ISP_TOP_BED_CTRL_CM1_DIS                        (0 << 13)
> +#define ISP_TOP_BED_CTRL_CM1_EN                         BIT(13)
> +#define ISP_TOP_BED_CTRL_CM0_EN_MASK                    BIT(14)
> +#define ISP_TOP_BED_CTRL_CM0_DIS                        (0 << 14)
> +#define ISP_TOP_BED_CTRL_CM0_EN                         BIT(14)
> +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK              BIT(16)
> +#define ISP_TOP_BED_CTRL_PST_GAMMA_DIS                  (0 << 16)
> +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN                   BIT(16)
> +#define ISP_TOP_BED_CTRL_LUT3D_EN_MASK                  BIT(17)
> +#define ISP_TOP_BED_CTRL_LUT3D_DIS                      (0 << 17)
> +#define ISP_TOP_BED_CTRL_LUT3D_EN                       BIT(17)
> +#define ISP_TOP_BED_CTRL_CCM_EN_MASK                    BIT(18)
> +#define ISP_TOP_BED_CTRL_CCM_DIS                        (0 << 18)
> +#define ISP_TOP_BED_CTRL_CCM_EN                         BIT(18)
> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK           BIT(21)
> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS               (0 << 21)
> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN                BIT(21)
> +#define ISP_TOP_BED_CTRL_AMCM_EN_MASK                   BIT(25)
> +#define ISP_TOP_BED_CTRL_AMCM_DIS                       (0 << 25)
> +#define ISP_TOP_BED_CTRL_AMCM_EN                        BIT(25)
> +
> +#define ISP_TOP_3A_STAT_CRTL                            0x0424
> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK            BIT(0)
> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS                (0 << 0)
> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN                 BIT(0)
> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK           BIT(1)
> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS               (0 << 1)
> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN                BIT(1)
> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK            BIT(2)
> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS                (0 << 2)
> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN                 BIT(2)
> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT_MASK        GENMASK(6, 4)
> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT(x)          ((x) << 4)
> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT_MASK         GENMASK(9, 8)
> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT(x)           ((x) << 8)
> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT_MASK         GENMASK(13, 12)
> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(x)           ((x) << 12)
> +
> +#define ISP_LSWB_BLC_OFST0                              0x4028
> +#define ISP_LSWB_BLC_OFST0_R_OFST_MASK                  GENMASK(15, 0)
> +#define ISP_LSWB_BLC_OFST0_R_OFST(x)                    ((x) << 0)
> +#define ISP_LSWB_BLC_OFST0_GR_OFST_MASK                 GENMASK(31, 16)
> +#define ISP_LSWB_BLC_OFST0_GR_OFST(x)                   ((x) << 16)
> +
> +#define ISP_LSWB_BLC_OFST1                              0x402c
> +#define ISP_LSWB_BLC_OFST1_GB_OFST_MASK                 GENMASK(15, 0)
> +#define ISP_LSWB_BLC_OFST1_GB_OFST(x)                   ((x) << 0)
> +#define ISP_LSWB_BLC_OFST1_B_OFST_MASK                  GENMASK(31, 16)
> +#define ISP_LSWB_BLC_OFST1_B_OFST(x)                    ((x) << 16)
> +
> +#define ISP_LSWB_BLC_PHSOFST                            0x4034
> +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
> +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST(x)               ((x) << 0)
> +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
> +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
> +
> +#define ISP_LSWB_WB_GAIN0                               0x4038
> +#define ISP_LSWB_WB_GAIN0_R_GAIN_MASK                   GENMASK(11, 0)
> +#define ISP_LSWB_WB_GAIN0_R_GAIN(x)                     ((x) << 0)
> +#define ISP_LSWB_WB_GAIN0_GR_GAIN_MASK                  GENMASK(27, 16)
> +#define ISP_LSWB_WB_GAIN0_GR_GAIN(x)                    ((x) << 16)
> +
> +#define ISP_LSWB_WB_GAIN1                               0x403c
> +#define ISP_LSWB_WB_GAIN1_GB_GAIN_MASK                  GENMASK(11, 0)
> +#define ISP_LSWB_WB_GAIN1_GB_GAIN(x)                    ((x) << 0)
> +#define ISP_LSWB_WB_GAIN1_B_GAIN_MASK                   GENMASK(27, 16)
> +#define ISP_LSWB_WB_GAIN1_B_GAIN(x)                     ((x) << 16)
> +
> +#define ISP_LSWB_WB_GAIN2                               0x4040
> +#define ISP_LSWB_WB_GAIN2_IR_GAIN_MASK                  GENMASK(11, 0)
> +#define ISP_LSWB_WB_GAIN2_IR_GAIN(x)                    ((x) << 0)
> +
> +#define ISP_LSWB_WB_LIMIT0                              0x4044
> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MASK              GENMASK(15, 0)
> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R(x)                ((x) << 0)
> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX               (0x8fff << 0)
> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MASK             GENMASK(31, 16)
> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR(x)               ((x) << 16)
> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX              (0x8fff << 16)
> +
> +#define ISP_LSWB_WB_LIMIT1                              0x4048
> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MASK             GENMASK(15, 0)
> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB(x)               ((x) << 0)
> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX              (0x8fff << 0)
> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MASK              GENMASK(31, 16)
> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B(x)                ((x) << 16)
> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX               (0x8fff << 16)
> +
> +#define ISP_LSWB_WB_PHSOFST                             0x4050
> +#define ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK              GENMASK(1, 0)
> +#define ISP_LSWB_WB_PHSOFST_VERT_OFST(x)                ((x) << 0)
> +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK             GENMASK(3, 2)
> +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST(x)               ((x) << 2)
> +
> +#define ISP_LSWB_LNS_PHSOFST                            0x4054
> +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
> +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST(x)               ((x) << 0)
> +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
> +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
> +
> +#define ISP_DMS_COMMON_PARAM0                           0x5000
> +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK         GENMASK(1, 0)
> +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(x)          ((x) << 0)
> +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK        GENMASK(3, 2)
> +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(x)         ((x) << 2)
> +
> +#define ISP_CM0_COEF00_01                               0x6048
> +#define ISP_CM0_COEF00_01_MTX_00_MASK                   GENMASK(12, 0)
> +#define ISP_CM0_COEF00_01_MTX_00(x)                     ((x) << 0)
> +#define ISP_CM0_COEF00_01_MTX_01_MASK                   GENMASK(28, 16)
> +#define ISP_CM0_COEF00_01_MTX_01(x)                     ((x) << 16)
> +
> +#define ISP_CM0_COEF02_10                               0x604c
> +#define ISP_CM0_COEF02_10_MTX_02_MASK                   GENMASK(12, 0)
> +#define ISP_CM0_COEF02_10_MTX_02(x)                     ((x) << 0)
> +#define ISP_CM0_COEF02_10_MTX_10_MASK                   GENMASK(28, 16)
> +#define ISP_CM0_COEF02_10_MTX_10(x)                     ((x) << 16)
> +
> +#define ISP_CM0_COEF11_12                               0x6050
> +#define ISP_CM0_COEF11_12_MTX_11_MASK                   GENMASK(12, 0)
> +#define ISP_CM0_COEF11_12_MTX_11(x)                     ((x) << 0)
> +#define ISP_CM0_COEF11_12_MTX_12_MASK                   GENMASK(28, 16)
> +#define ISP_CM0_COEF11_12_MTX_12(x)                     ((x) << 16)
> +
> +#define ISP_CM0_COEF20_21                               0x6054
> +#define ISP_CM0_COEF20_21_MTX_20_MASK                   GENMASK(12, 0)
> +#define ISP_CM0_COEF20_21_MTX_20(x)                     ((x) << 0)
> +#define ISP_CM0_COEF20_21_MTX_21_MASK                   GENMASK(28, 16)
> +#define ISP_CM0_COEF20_21_MTX_21(x)                     ((x) << 16)
> +
> +#define ISP_CM0_COEF22_OUP_OFST0                        0x6058
> +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK            GENMASK(12, 0)
> +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22(x)              ((x) << 0)
> +
> +#define ISP_CCM_MTX_00_01                               0x6098
> +#define ISP_CCM_MTX_00_01_MTX_00_MASK                   GENMASK(12, 0)
> +#define ISP_CCM_MTX_00_01_MTX_00(x)                     ((x) << 0)
> +#define ISP_CCM_MTX_00_01_MTX_01_MASK                   GENMASK(28, 16)
> +#define ISP_CCM_MTX_00_01_MTX_01(x)                     ((x) << 16)
> +
> +#define ISP_CCM_MTX_02_03                               0x609c
> +#define ISP_CCM_MTX_02_03_MTX_02_MASK                   GENMASK(12, 0)
> +#define ISP_CCM_MTX_02_03_MTX_02(x)                     ((x) << 0)
> +
> +#define ISP_CCM_MTX_10_11                               0x60A0
> +#define ISP_CCM_MTX_10_11_MTX_10_MASK                   GENMASK(12, 0)
> +#define ISP_CCM_MTX_10_11_MTX_10(x)                     ((x) << 0)
> +#define ISP_CCM_MTX_10_11_MTX_11_MASK                   GENMASK(28, 16)
> +#define ISP_CCM_MTX_10_11_MTX_11(x)                     ((x) << 16)
> +
> +#define ISP_CCM_MTX_12_13                               0x60A4
> +#define ISP_CCM_MTX_12_13_MTX_12_MASK                   GENMASK(12, 0)
> +#define ISP_CCM_MTX_12_13_MTX_12(x)                     ((x) << 0)
> +
> +#define ISP_CCM_MTX_20_21                               0x60A8
> +#define ISP_CCM_MTX_20_21_MTX_20_MASK                   GENMASK(12, 0)
> +#define ISP_CCM_MTX_20_21_MTX_20(x)                     ((x) << 0)
> +#define ISP_CCM_MTX_20_21_MTX_21_MASK                   GENMASK(28, 16)
> +#define ISP_CCM_MTX_20_21_MTX_21(x)                     ((x) << 16)
> +
> +#define ISP_CCM_MTX_22_23_RS                            0x60Ac
> +#define ISP_CCM_MTX_22_23_RS_MTX_22_MASK                GENMASK(12, 0)
> +#define ISP_CCM_MTX_22_23_RS_MTX_22(x)                  ((x) << 0)
> +
> +#define ISP_PST_GAMMA_LUT_ADDR                          0x60cc
> +#define ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(x)              ((x) << 7)
> +
> +#define ISP_PST_GAMMA_LUT_DATA                          0x60d0
> +#define ISP_PST_GAMMA_LUT_DATA_DATA0(x)                 (((x) & GENMASK(15, 0)) << 0)
> +#define ISP_PST_GAMMA_LUT_DATA_DATA1(x)                 (((x) & GENMASK(15, 0)) << 16)
> +
> +#define DISP0_TOP_TOP_CTRL                              0x8000
> +#define DISP0_TOP_TOP_CTRL_CROP2_EN_MASK                BIT(5)
> +#define DISP0_TOP_TOP_CTRL_CROP2_EN                     BIT(5)
> +#define DISP0_TOP_TOP_CTRL_CROP2_DIS                    (0 << 5)
> +
> +#define DISP0_TOP_CRP2_START                            0x8004
> +#define DISP0_TOP_CRP2_START_V_START_MASK               GENMASK(15, 0)
> +#define DISP0_TOP_CRP2_START_V_START(x)                 ((x) << 0)
> +#define DISP0_TOP_CRP2_START_H_START_MASK               GENMASK(31, 16)
> +#define DISP0_TOP_CRP2_START_H_START(x)                 ((x) << 16)
> +
> +#define DISP0_TOP_CRP2_SIZE                             0x8008
> +#define DISP0_TOP_CRP2_SIZE_V_SIZE_MASK                 GENMASK(15, 0)
> +#define DISP0_TOP_CRP2_SIZE_V_SIZE(x)                   ((x) << 0)
> +#define DISP0_TOP_CRP2_SIZE_H_SIZE_MASK                 GENMASK(31, 16)
> +#define DISP0_TOP_CRP2_SIZE_H_SIZE(x)                   ((x) << 16)
> +
> +#define DISP0_TOP_OUT_SIZE                              0x800c
> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT_MASK       GENMASK(12, 0)
> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT(x)         ((x) << 0)
> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH_MASK        GENMASK(28, 16)
> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(x)          ((x) << 16)
> +
> +#define ISP_DISP0_TOP_IN_SIZE                           0x804c
> +#define ISP_DISP0_TOP_IN_SIZE_VSIZE_MASK                GENMASK(12, 0)
> +#define ISP_DISP0_TOP_IN_SIZE_VSIZE(x)                  ((x) << 0)
> +#define ISP_DISP0_TOP_IN_SIZE_HSIZE_MASK                GENMASK(28, 16)
> +#define ISP_DISP0_TOP_IN_SIZE_HSIZE(x)                  ((x) << 16)
> +
> +#define DISP0_PPS_SCALE_EN                              0x8200
> +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK             GENMASK(3, 0)
> +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM(x)               ((x) << 0)
> +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM_MASK             GENMASK(7, 4)
> +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM(x)               ((x) << 4)
> +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK          GENMASK(11, 8)
> +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(x)            ((x) << 8)
> +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM_MASK          GENMASK(15, 12)
> +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM(x)            ((x) << 12)
> +#define DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK             GENMASK(17, 16)
> +#define DISP0_PPS_SCALE_EN_PREVSC_RATE(x)               ((x) << 16)
> +#define DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK             GENMASK(19, 18)
> +#define DISP0_PPS_SCALE_EN_PREHSC_RATE(x)               ((x) << 18)
> +#define DISP0_PPS_SCALE_EN_HSC_EN_MASK                  BIT(20)
> +#define DISP0_PPS_SCALE_EN_HSC_EN(x)                    ((x) << 20)
> +#define DISP0_PPS_SCALE_EN_HSC_DIS                      (0 << 20)
> +#define DISP0_PPS_SCALE_EN_VSC_EN_MASK                  BIT(21)
> +#define DISP0_PPS_SCALE_EN_VSC_EN(x)                    ((x) << 21)
> +#define DISP0_PPS_SCALE_EN_VSC_DIS                      (0 << 21)
> +#define DISP0_PPS_SCALE_EN_PREVSC_EN_MASK               BIT(22)
> +#define DISP0_PPS_SCALE_EN_PREVSC_EN(x)                 ((x) << 22)
> +#define DISP0_PPS_SCALE_EN_PREVSC_DIS                   (0 << 22)
> +#define DISP0_PPS_SCALE_EN_PREHSC_EN_MASK               BIT(23)
> +#define DISP0_PPS_SCALE_EN_PREHSC_EN(x)                 ((x) << 23)
> +#define DISP0_PPS_SCALE_EN_PREHSC_DIS                   (0 << 23)
> +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK         GENMASK(27, 24)
> +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(x)           ((x) << 24)
> +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK         GENMASK(31, 28)
> +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(x)           ((x) << 28)
> +
> +#define DISP0_PPS_VSC_START_PHASE_STEP                  0x8224
> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK   GENMASK(23, 0)
> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(x)     ((x) << 0)
> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK   GENMASK(27, 24)
> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(x)     ((x) << 24)
> +
> +#define DISP0_PPS_HSC_START_PHASE_STEP                  0x8230
> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK  GENMASK(23, 0)
> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(x)    ((x) << 0)
> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK  GENMASK(27, 24)
> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(x)    ((x) << 24)
> +
> +#define DISP0_PPS_444TO422                              0x823c
> +#define DISP0_PPS_444TO422_EN_MASK                      BIT(0)
> +#define DISP0_PPS_444TO422_EN(x)                        ((x) << 0)
> +
> +#define ISP_SCALE0_COEF_IDX_LUMA                        0x8240
> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_MASK     BIT(9)
> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN       BIT(9)
> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_DIS      (0 << 9)
> +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE_MASK             GENMASK(12, 10)
> +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE(x)               ((x) << 10)
> +
> +#define ISP_SCALE0_COEF_LUMA                            0x8244
> +#define ISP_SCALE0_COEF_LUMA_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
> +#define ISP_SCALE0_COEF_LUMA_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
> +
> +#define ISP_SCALE0_COEF_IDX_CHRO                        0x8248
> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_MASK     BIT(9)
> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN       BIT(9)
> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_DIS      (0 << 9)
> +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE_MASK             GENMASK(12, 10)
> +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE(x)               ((x) << 10)
> +
> +#define ISP_SCALE0_COEF_CHRO                            0x824c
> +#define ISP_SCALE0_COEF_CHRO_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
> +#define ISP_SCALE0_COEF_CHRO_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
> +
> +#define ISP_AF_CTRL                                     0xa044
> +#define ISP_AF_CTRL_VERT_OFST_MASK                      GENMASK(15, 14)
> +#define ISP_AF_CTRL_VERT_OFST(x)                        ((x) << 14)
> +#define ISP_AF_CTRL_HORIZ_OFST_MASK                     GENMASK(17, 16)
> +#define ISP_AF_CTRL_HORIZ_OFST(x)                       ((x) << 16)
> +
> +#define ISP_AF_HV_SIZE                                  0xa04c
> +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE_MASK               GENMASK(15, 0)
> +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE(x)                 ((x) << 0)
> +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE_MASK               GENMASK(31, 16)
> +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE(x)                 ((x) << 16)
> +
> +#define ISP_AF_HV_BLKNUM                                0xa050
> +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(5, 0)
> +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
> +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(21, 16)
> +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
> +
> +#define ISP_AF_EN_CTRL                                  0xa054
> +#define ISP_AF_EN_CTRL_STAT_SEL_MASK                    BIT(21)
> +#define ISP_AF_EN_CTRL_STAT_SEL_OLD                     (0 << 21)
> +#define ISP_AF_EN_CTRL_STAT_SEL_NEW                     BIT(21)
> +
> +#define ISP_AF_IDX_ADDR                                 0xa1c0
> +#define ISP_AF_IDX_DATA                                 0xa1c4
> +#define ISP_AF_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
> +#define ISP_AF_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
> +
> +#define ISP_AE_CTRL                                     0xa448
> +#define ISP_AE_CTRL_INPUT_2LINE_MASK                    BIT(7)
> +#define ISP_AE_CTRL_INPUT_2LINE_EN                      BIT(7)
> +#define ISP_AE_CTRL_INPUT_2LINE_DIS                     (0 << 7)
> +#define ISP_AE_CTRL_LUMA_MODE_MASK                      GENMASK(9, 8)
> +#define ISP_AE_CTRL_LUMA_MODE_CUR                       (0 << 8)
> +#define ISP_AE_CTRL_LUMA_MODE_MAX                       BIT(8)
> +#define ISP_AE_CTRL_LUMA_MODE_FILTER                    (2 << 8)
> +#define ISP_AE_CTRL_VERT_OFST_MASK                      GENMASK(25, 24)
> +#define ISP_AE_CTRL_VERT_OFST(x)                        ((x) << 24)
> +#define ISP_AE_CTRL_HORIZ_OFST_MASK                     GENMASK(27, 26)
> +#define ISP_AE_CTRL_HORIZ_OFST(x)                       ((x) << 26)
> +
> +#define ISP_AE_HV_SIZE                                  0xa464
> +#define ISP_AE_HV_SIZE_VERT_SIZE_MASK                   GENMASK(15, 0)
> +#define ISP_AE_HV_SIZE_VERT_SIZE(x)                     ((x) << 0)
> +#define ISP_AE_HV_SIZE_HORIZ_SIZE_MASK                  GENMASK(31, 16)
> +#define ISP_AE_HV_SIZE_HORIZ_SIZE(x)                    ((x) << 16)
> +
> +#define ISP_AE_HV_BLKNUM                                0xa468
> +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(6, 0)
> +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
> +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(22, 16)
> +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
> +
> +#define ISP_AE_IDX_ADDR                                 0xa600
> +#define ISP_AE_IDX_DATA                                 0xa604
> +#define ISP_AE_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
> +#define ISP_AE_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
> +
> +#define ISP_AE_BLK_WT_ADDR                              0xa608
> +#define ISP_AE_BLK_WT_DATA                              0xa60c
> +#define ISP_AE_BLK_WT_DATA_IDX_DATA(i, x)               (((x) & GENMASK(3, 0)) << ((i) * 4))
> +
> +#define ISP_AWB_CTRL                                    0xa834
> +#define ISP_AWB_CTRL_VERT_OFST_MASK                     GENMASK(1, 0)
> +#define ISP_AWB_CTRL_VERT_OFST(x)                       ((x) << 0)
> +#define ISP_AWB_CTRL_HORIZ_OFST_MASK                    GENMASK(3, 2)
> +#define ISP_AWB_CTRL_HORIZ_OFST(x)                      ((x) << 2)
> +
> +#define ISP_AWB_HV_SIZE                                 0xa83c
> +#define ISP_AWB_HV_SIZE_VERT_SIZE_MASK                  GENMASK(15, 0)
> +#define ISP_AWB_HV_SIZE_VERT_SIZE(x)                    ((x) << 0)
> +#define ISP_AWB_HV_SIZE_HORIZ_SIZE_MASK                 GENMASK(31, 16)
> +#define ISP_AWB_HV_SIZE_HORIZ_SIZE(x)                   ((x) << 16)
> +
> +#define ISP_AWB_HV_BLKNUM                               0xa840
> +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK            GENMASK(5, 0)
> +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM(x)              ((x) << 0)
> +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM_MASK           GENMASK(21, 16)
> +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(x)             ((x) << 16)
> +
> +#define ISP_AWB_STAT_RG                                 0xa848
> +#define ISP_AWB_STAT_RG_MIN_VALUE_MASK                  GENMASK(11, 0)
> +#define ISP_AWB_STAT_RG_MIN_VALUE(x)                    ((x) << 0)
> +#define ISP_AWB_STAT_RG_MAX_VALUE_MASK                  GENMASK(27, 16)
> +#define ISP_AWB_STAT_RG_MAX_VALUE(x)                    ((x) << 16)
> +
> +#define ISP_AWB_STAT_BG                                 0xa84c
> +#define ISP_AWB_STAT_BG_MIN_VALUE_MASK                  GENMASK(11, 0)
> +#define ISP_AWB_STAT_BG_MIN_VALUE(x)                    ((x) << 0)
> +#define ISP_AWB_STAT_BG_MAX_VALUE_MASK                  GENMASK(27, 16)
> +#define ISP_AWB_STAT_BG_MAX_VALUE(x)                    ((x) << 16)
> +
> +#define ISP_AWB_STAT_RG_HL                              0xa850
> +#define ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
> +#define ISP_AWB_STAT_RG_HL_LOW_VALUE(x)                 ((x) << 0)
> +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
> +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE(x)                ((x) << 16)
> +
> +#define ISP_AWB_STAT_BG_HL                              0xa854
> +#define ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
> +#define ISP_AWB_STAT_BG_HL_LOW_VALUE(x)                 ((x) << 0)
> +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
> +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE(x)                ((x) << 16)
> +
> +#define ISP_AWB_STAT_CTRL2                              0xa858
> +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK              BIT(0)
> +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL(x)                ((x) << 0)
> +
> +#define ISP_AWB_IDX_ADDR                                0xaa00
> +#define ISP_AWB_IDX_DATA                                0xaa04
> +#define ISP_AWB_IDX_DATA_VIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 0)
> +#define ISP_AWB_IDX_DATA_HIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 16)
> +
> +#define ISP_AWB_BLK_WT_ADDR                             0xaa08
> +#define ISP_AWB_BLK_WT_DATA                             0xaa0c
> +#define ISP_AWB_BLK_WT_DATA_IDX_DATA(i, x)              (((x) & GENMASK(3, 0)) << ((i) * 4))
> +
> +#define ISP_WRMIFX3_0_CH0_CTRL0                         0xc400
> +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK             GENMASK(28, 16)
> +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(x)               ((x) << 16)
> +
> +#define ISP_WRMIFX3_0_CH0_CTRL1                         0xc404
> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS          BIT(27)
> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS         (2 << 27)
> +
> +#define ISP_WRMIFX3_0_CH1_CTRL0                         0xc408
> +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK             GENMASK(28, 16)
> +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(x)               ((x) << 16)
> +
> +#define ISP_WRMIFX3_0_CH1_CTRL1                         0xc40c
> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS         (2 << 27)
> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_32BITS         (3 << 27)
> +
> +#define ISP_WRMIFX3_0_CH0_BADDR                         0xc440
> +#define ISP_WRMIFX3_0_CH0_BASE_ADDR(x)                  ((x) >> 4)
> +
> +#define ISP_WRMIFX3_0_CH1_BADDR                         0xc444
> +#define ISP_WRMIFX3_0_CH1_BASE_ADDR(x)                  ((x) >> 4)
> +
> +#define ISP_WRMIFX3_0_FMT_SIZE                          0xc464
> +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE_MASK               GENMASK(15, 0)
> +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE(x)                 ((x) << 0)
> +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE_MASK               GENMASK(31, 16)
> +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE(x)                 ((x) << 16)
> +
> +#define ISP_WRMIFX3_0_FMT_CTRL                          0xc468
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK           GENMASK(1, 0)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT           (0 << 0)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_10BIT          BIT(0)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_12BIT          (2 << 0)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT          (3 << 0)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK         BIT(2)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU           (0 << 2)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV           BIT(2)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK           GENMASK(5, 4)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1             (0 << 4)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2             BIT(4)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK            GENMASK(18, 16)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422          BIT(16)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420          (2 << 16)
> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY          (3 << 16)
> +
> +#define ISP_WRMIFX3_0_WIN_LUMA_H                        0xc420
> +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK         GENMASK(28, 16)
> +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(x)           (((x) - 1) << 16)
> +
> +#define ISP_WRMIFX3_0_WIN_LUMA_V                        0xc424
> +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK         GENMASK(28, 16)
> +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(x)           (((x) - 1) << 16)
> +
> +#define ISP_WRMIFX3_0_WIN_CHROM_H                       0xc428
> +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK       GENMASK(28, 16)
> +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(x)         (((x) - 1) << 16)
> +
> +#define ISP_WRMIFX3_0_WIN_CHROM_V                       0xc42c
> +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK       GENMASK(28, 16)
> +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(x)         (((x) - 1) << 16)
> +
> +#define VIU_DMAWR_BADDR0                                0xc840
> +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
> +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(x)          ((x) >> 4)
> +
> +#define VIU_DMAWR_BADDR1                                0xc844
> +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK       GENMASK(27, 0)
> +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(x)         ((x) >> 4)
> +
> +#define VIU_DMAWR_BADDR2                                0xc848
> +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
> +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(x)          ((x) >> 4)
> +
> +#define VIU_DMAWR_SIZE0                                 0xc854
> +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK              GENMASK(15, 0)
> +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE(x)                ((x) << 0)
> +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK             GENMASK(31, 16)
> +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE(x)               ((x) << 16)
> +
> +#define VIU_DMAWR_SIZE1                                 0xc858
> +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK              GENMASK(15, 0)
> +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE(x)                ((x) << 0)
> +
> +#endif
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
> new file mode 100644
> index 000000000000..b179eff4cfd6
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
> @@ -0,0 +1,779 @@
> +// 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"
> +
> +/* The normal parameters of pps module */
> +static const int pps_lut_tap4_s11[C3_ISP_PPS_TAP4_S11_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 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 step_h_integer, step_v_integer;
> +	int step_h_fraction, step_v_fraction;
> +	int yuv444to422_en;
> +
> +	/* Calculate the integer part of horizonal scaler step */
> +	step_h_integer = thsize / ohsize;
> +
> +	/* Calculate the vertical part of horizonal scaler step */
> +	step_v_integer = 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.
> +	 */
> +	step_h_fraction = ((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.
> +	 */
> +	step_v_fraction = ((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_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> +			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK,
> +			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(step_v_fraction));
> +	c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> +			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK,
> +			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(step_v_integer));
> +
> +	c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> +			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK,
> +			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(step_h_fraction));
> +	c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> +			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK,
> +			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(step_h_integer));
> +}
> +
> +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_TAP4_S11_H_NUM; i++) {
> +		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> +			     ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
> +			     ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
> +		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> +			     ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
> +			     ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[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_TAP4_S11_H_NUM; i++) {
> +		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> +			     ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
> +			     ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
> +		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> +			     ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
> +			     ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[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_RESIZER_PAD_SINK);
> +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	ihsize = crop->width;
> +	ivsize = crop->height;
> +
> +	hsc_en = (ihsize == cmps->width) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_EN;
> +	vsc_en = (ivsize == cmps->height) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_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.

minor nit: seems like you should wrap comments here too

> +	 */
> +	preh_en = (ihsize > cmps->width * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
> +	prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
> +
> +	if (rsz->id == C3_ISP_RSZ_2) {
> +		max_hsize = C3_ISP_MAX_WIDTH;
> +		/*
> +		 * Set vertical tap number and
> +		 * the max hsize of pre-vertical scale.
> +		 */
> +		prevsc_flt_num = 4;
> +		pre_vscale_max_hsize = max_hsize / 2;
> +	} else {
> +		max_hsize = C3_ISP_DEFAULT_WIDTH;
> +		preh_en  = (ihsize > max_hsize) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
> +		/*
> +		 * Set vertical tap number and
> +		 * the max hsize of pre-vertical scale.
> +		 */
> +		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;
> +	struct v4l2_rect *sink_crop;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> +
> +	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);
> +
> +	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_SCALER_OUT_HEIGHT_MASK,
> +			   DISP0_TOP_OUT_SIZE_SCALER_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_SCALER_OUT_WIDTH_MASK,
> +			   DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(src_fmt->width));
> +
> +	if (rsz->id == C3_ISP_RSZ_0) {
> +		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
> +				   ISP_TOP_PATH_EN_DISP0_EN);
> +	} else if (rsz->id == C3_ISP_RSZ_1) {
> +		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
> +				   ISP_TOP_PATH_EN_DISP1_EN);
> +	} else if (rsz->id == C3_ISP_RSZ_2) {
> +		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
> +				   ISP_TOP_PATH_EN_DISP2_EN);
> +	}
> +}
> +
> +static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
> +{
> +	if (rsz->id == C3_ISP_RSZ_0) {
> +		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
> +				   ISP_TOP_PATH_EN_DISP0_DIS);
> +	} else if (rsz->id == C3_ISP_RSZ_1) {
> +		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
> +				   ISP_TOP_PATH_EN_DISP1_DIS);
> +	} else if (rsz->id == C3_ISP_RSZ_2) {
> +		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
> +				   ISP_TOP_PATH_EN_DISP2_DIS);
> +	}
> +
> +	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 bool c3_isp_rsz_streams_ready(struct c3_isp_resizer *rsz)
> +{
> +	unsigned int n_links = 0;
> +	struct media_link *link;
> +
> +	for_each_media_entity_data_link(&rsz->src_sd->entity, link) {
> +		if (link->source->index == rsz->src_sd_pad &&
> +		    link->flags == MEDIA_LNK_FL_ENABLED)
> +			n_links++;
> +	}
> +
> +	return n_links == rsz->isp->pipe.start_count;
> +}
> +
> +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);
> +	u64 sink_streams;
> +	int ret;
> +
> +	c3_isp_rsz_start(rsz, state);
> +
> +	if (!c3_isp_rsz_streams_ready(rsz))
> +		return 0;
> +
> +	c3_isp_params_pre_cfg(rsz->isp);
> +	c3_isp_stats_pre_cfg(rsz->isp);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_RESIZER_PAD_SINK,
> +						       &streams_mask);

Apart from the usual comments about routing, reflowing comments and
minor nits, this module looks ok to me. I admit I have not tried to
fully understand the scaler programming part at the beginning, but I
trust you know the HW better than me :)

The stats driver looks ok as well.

I hope next version will be good to go, and I hope to be able to test
this implementation soon using libcamera!

Great work!

Thanks
  j

> +	ret = v4l2_subdev_enable_streams(rsz->src_sd,
> +					 rsz->src_sd_pad, sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	return 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);
> +	u64 sink_streams;
> +	int ret;
> +
> +	c3_isp_rsz_stop(rsz);
> +
> +	if (rsz->isp->pipe.start_count != 1)
> +		return 0;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_RESIZER_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_disable_streams(rsz->src_sd,
> +					  rsz->src_sd_pad, sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = C3_ISP_DEFAULT_WIDTH,
> +		.height = C3_ISP_DEFAULT_HEIGHT,
> +		.code = C3_ISP_RSZ_DEF_PAD_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes;
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
> +	routes.sink_stream = 0;
> +	routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
> +	routes.source_stream = 0;
> +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &routes;
> +
> +	return c3_isp_rsz_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index)
> +		return -EINVAL;
> +
> +	code->code = MEDIA_BUS_FMT_YUV8_1X24;
> +
> +	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;
> +
> +	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_RESIZER_PAD_SOURCE);
> +
> +	sink_fmt->code = MEDIA_BUS_FMT_YUV8_1X24;
> +	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_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;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	src_fmt->code = sink_fmt->code;
> +	src_fmt->width = sink_cmps->width;
> +	src_fmt->height = sink_cmps->height;
> +
> +	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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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 *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +	sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_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_RESIZER_PAD_SINK);
> +	cmps->width = C3_ISP_DEFAULT_WIDTH;
> +	cmps->height = C3_ISP_DEFAULT_HEIGHT;
> +	cmps->left = 0;
> +	cmps->top = 0;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> +	*src_fmt = *sink_fmt;
> +
> +	return c3_isp_rsz_init_routing(sd, state);
> +}
> +
> +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_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_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)
> +{
> +	u32 i;
> +	int ret;
> +
> +	for (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_sd_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> +
> +		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)
> +{
> +	u32 i;
> +
> +	for (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);
> +	};
> +}
> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
> new file mode 100644
> index 000000000000..1c376c800176
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
> @@ -0,0 +1,319 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#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-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +/* Hardware configuration */
> +
> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
> +{
> +	u32 awb_dma_size = sizeof(struct c3_isp_awb_stats);
> +	u32 ae_dma_size = sizeof(struct c3_isp_ae_stats);
> +	u32 awb_dma_addr = stats->buff->dma_addr;
> +	u32 af_dma_addr;
> +	u32 ae_dma_addr;
> +
> +	ae_dma_addr = awb_dma_addr + awb_dma_size;
> +	af_dma_addr = ae_dma_addr + ae_dma_size;
> +
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR0,
> +			   VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK,
> +			   VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(af_dma_addr));
> +
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR1,
> +			   VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK,
> +			   VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(awb_dma_addr));
> +
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR2,
> +			   VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK,
> +			   VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(ae_dma_addr));
> +}
> +
> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
> +{
> +	stats->buff = list_first_entry_or_null(&stats->pending,
> +					       struct c3_isp_buffer, list);
> +	if (stats->buff) {
> +		c3_isp_stats_cfg_dmawr_addr(stats);
> +		list_del(&stats->buff->list);
> +	}
> +}
> +
> +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +	u32 dma_size;
> +
> +	c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, ISP_AF_EN_CTRL_STAT_SEL_MASK,
> +			   ISP_AF_EN_CTRL_STAT_SEL_NEW);
> +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL, ISP_AE_CTRL_LUMA_MODE_MASK,
> +			   ISP_AE_CTRL_LUMA_MODE_FILTER);
> +
> +	/* The unit of dma_size is 16 bytes */
> +	dma_size = sizeof(struct c3_isp_af_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK,
> +			   VIU_DMAWR_SIZE0_AF_STATS_SIZE(dma_size));
> +
> +	dma_size = sizeof(struct c3_isp_awb_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK,
> +			   VIU_DMAWR_SIZE0_AWB_STATS_SIZE(dma_size));
> +
> +	dma_size = sizeof(struct c3_isp_ae_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK,
> +			   VIU_DMAWR_SIZE1_AE_STATS_SIZE(dma_size));
> +
> +	guard(spinlock_irqsave)(&stats->buff_lock);
> +
> +	c3_isp_stats_cfg_buff(stats);
> +}
> +
> +static int c3_isp_stats_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_stats_enum_fmt(struct file *file, void *fh,
> +				 struct v4l2_fmtdesc *f)
> +{
> +	struct c3_isp_stats *stats = video_drvdata(file);
> +
> +	if (f->index > 0 || f->type != stats->vb2_q.type)
> +		return -EINVAL;
> +
> +	f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
> +			      struct v4l2_format *f)
> +{
> +	struct c3_isp_stats *stats = video_drvdata(file);
> +
> +	f->fmt.meta = stats->vfmt.fmt.meta;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
> +	.vidioc_querycap                = c3_isp_stats_querycap,
> +	.vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
> +	.vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
> +	.vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
> +	.vidioc_try_fmt_meta_cap        = c3_isp_stats_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_stats_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_stats_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_stats_buffer))
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = sizeof(struct c3_isp_stats_buffer);
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_buffer, vb);
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	guard(spinlock_irqsave)(&stats->buff_lock);
> +
> +	list_add_tail(&buf->list, &stats->pending);
> +}
> +
> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = stats->vfmt.fmt.meta.buffersize;
> +
> +	if (vb2_plane_size(vb, 0) < size) {
> +		dev_err(stats->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_buffer, vb);
> +
> +	buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	return 0;
> +}
> +
> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> +
> +	guard(spinlock_irqsave)(&stats->buff_lock);
> +
> +	if (stats->buff) {
> +		vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +		stats->buff = NULL;
> +	}
> +
> +	while (!list_empty(&stats->pending)) {
> +		struct c3_isp_buffer *buff;
> +
> +		buff = list_first_entry(&stats->pending,
> +					struct c3_isp_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +	}
> +}
> +
> +static const struct vb2_ops isp_stats_vb2_ops = {
> +	.queue_setup = c3_isp_stats_vb2_queue_setup,
> +	.buf_queue = c3_isp_stats_vb2_buf_queue,
> +	.buf_prepare = c3_isp_stats_vb2_buf_prepare,
> +	.buf_init = c3_isp_stats_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.stop_streaming = c3_isp_stats_vb2_stop_streaming,
> +};
> +
> +int c3_isp_stats_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +	struct video_device *vdev = &stats->vdev;
> +	struct vb2_queue *vb2_q = &stats->vb2_q;
> +	int ret;
> +
> +	memset(stats, 0, sizeof(*stats));
> +	stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
> +	stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_buffer);
> +	stats->isp = isp;
> +	INIT_LIST_HEAD(&stats->pending);
> +	spin_lock_init(&stats->buff_lock);
> +
> +	mutex_init(&stats->lock);
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "c3-isp-stats");
> +	vdev->fops = &isp_stats_v4l2_fops;
> +	vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &isp->v4l2_dev;
> +	vdev->lock = &stats->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, stats);
> +
> +	vb2_q->drv_priv = stats;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_stats_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
> +	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_buffer);
> +	vb2_q->dev = isp->dev;
> +	vb2_q->lock = &stats->lock;
> +	vb2_q->min_queued_buffers = 2;
> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret)
> +		goto err_destroy;
> +
> +	stats->pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
> +	if (ret)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		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_destroy:
> +	mutex_destroy(&stats->lock);
> +	return ret;
> +}
> +
> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +
> +	vb2_queue_release(&stats->vb2_q);
> +	media_entity_cleanup(&stats->vdev.entity);
> +	video_unregister_device(&stats->vdev);
> +	mutex_destroy(&stats->lock);
> +}
> +
> +void c3_isp_stats_isr(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +
> +	guard(spinlock_irqsave)(&stats->buff_lock);
> +
> +	if (stats->buff) {
> +		stats->buff->vb.sequence = stats->isp->frm_sequence;
> +		stats->buff->vb.vb2_buf.timestamp = ktime_get();
> +		stats->buff->vb.field = V4L2_FIELD_NONE;
> +		vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
> +	c3_isp_stats_cfg_buff(stats);
> +}
>
> --
> 2.47.0
>
>
>
Keke Li Dec. 13, 2024, 11:14 a.m. UTC | #2
Hi Jacopo

Thanks very much for your reply.

On 2024/12/12 23:39, Jacopo Mondi wrote:
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
> On Thu, Dec 05, 2024 at 05:04:34PM +0800, Keke Li via B4 Relay wrote:
>> From: Keke Li <keke.li@amlogic.com>
>>
>> The C3 ISP supports multi-camera and muti-exposure
>> high dynamic range (HDR). It brings together some
>> advanced imaging technologies to provide good image quality.
>> This driver mainly responsible for driving ISP pipeline
>> to process raw image.
>>
>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>> ---
>>   drivers/media/platform/amlogic/c3/Kconfig          |   1 +
>>   drivers/media/platform/amlogic/c3/Makefile         |   1 +
>>   drivers/media/platform/amlogic/c3/isp/Kconfig      |  17 +
>>   drivers/media/platform/amlogic/c3/isp/Makefile     |  10 +
>>   .../media/platform/amlogic/c3/isp/c3-isp-capture.c | 790 +++++++++++++++++
>>   .../media/platform/amlogic/c3/isp/c3-isp-common.h  | 352 ++++++++
>>   .../media/platform/amlogic/c3/isp/c3-isp-core.c    | 553 ++++++++++++
>>   drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c | 459 ++++++++++
>>   .../media/platform/amlogic/c3/isp/c3-isp-params.c  | 942 +++++++++++++++++++++
>>   .../media/platform/amlogic/c3/isp/c3-isp-regs.h    | 599 +++++++++++++
>>   .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 779 +++++++++++++++++
>>   .../media/platform/amlogic/c3/isp/c3-isp-stats.c   | 319 +++++++
>>   12 files changed, 4822 insertions(+)
>>
>> diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig
>> index a504a1eb22e6..d355d3a9358d 100644
>> --- a/drivers/media/platform/amlogic/c3/Kconfig
>> +++ b/drivers/media/platform/amlogic/c3/Kconfig
>> @@ -1,4 +1,5 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>>
>> +source "drivers/media/platform/amlogic/c3/isp/Kconfig"
>>   source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig"
>>   source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig"
>> diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile
>> index 770b2a2903ad..14f305a493d2 100644
>> --- a/drivers/media/platform/amlogic/c3/Makefile
>> +++ b/drivers/media/platform/amlogic/c3/Makefile
>> @@ -1,4 +1,5 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>>
>> +obj-y += isp/
>>   obj-y += mipi-adapter/
>>   obj-y += mipi-csi2/
>> diff --git a/drivers/media/platform/amlogic/c3/isp/Kconfig b/drivers/media/platform/amlogic/c3/isp/Kconfig
>> new file mode 100644
>> index 000000000000..de68d19ca2d1
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/Kconfig
>> @@ -0,0 +1,17 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_C3_ISP
>> +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
>> +     depends on ARCH_MESON || COMPILE_TEST
>> +     depends on VIDEO_DEV
>> +     depends on OF
>> +     select MEDIA_CONTROLLER
>> +     select V4L2_FWNODE
>> +     select VIDEO_V4L2_SUBDEV_API
>> +     select VIDEOBUF2_DMA_CONTIG
> also
>          select VIDEOBUF2_VMALLOC
>
> for the params node
>

Will add 'select VIDEOBUF2_VMALLOC'

>> +     help
>> +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
>> +       The C3 ISP is used for processing raw images and
>> +       outputing results to memory.
>> +
>> +       To compile this driver as a module choose m here.
>> diff --git a/drivers/media/platform/amlogic/c3/isp/Makefile b/drivers/media/platform/amlogic/c3/isp/Makefile
>> new file mode 100644
>> index 000000000000..b1b064170b57
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/Makefile
>> @@ -0,0 +1,10 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +c3-isp-objs := c3-isp-dev.o \
>> +            c3-isp-params.o \
>> +            c3-isp-stats.o \
>> +            c3-isp-capture.o \
>> +            c3-isp-core.o \
>> +            c3-isp-resizer.o
>> +
>> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
>> new file mode 100644
>> index 000000000000..e1bbb3fa8da2
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
>> @@ -0,0 +1,790 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +static const struct c3_isp_capture_format cap_formats[] = {
>> +     {
>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>> +             .fourcc = V4L2_PIX_FMT_GREY,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>> +             .fourcc = V4L2_PIX_FMT_NV12,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>> +             .fourcc = V4L2_PIX_FMT_NV21,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>> +             .fourcc = V4L2_PIX_FMT_NV16,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>> +             .fourcc = V4L2_PIX_FMT_NV61,
>> +     },
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +/* Set the address of wrmifx3(write memory interface) */
>> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +     dma_addr_t dma_addr;
>> +
>> +     if (cap->buff)
>> +             dma_addr = cap->buff->dma_addr;
>> +     else
>> +             dma_addr = cap->dummy_buff.dma_addr;
>> +
>> +     c3_isp_write(cap->isp,
>> +                  C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
>> +                  ISP_WRMIFX3_0_CH0_BASE_ADDR(dma_addr));
>> +
>> +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
>> +             u32 offset = ALIGN(pix->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES) * pix->height;
> I'm sorry but V4L2 has a requirement about 80-cols stricter than other
> subsystem (I know..)
>
> Could you run
> ./scripts/checkpatch.pl --strict --max-line-length=80
>
> on your patches and see how you can fix these ?


Will check this issue and adjust max-line-length to 80.

>> +
>> +             c3_isp_write(cap->isp,
>> +                          C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
>> +                          ISP_WRMIFX3_0_CH1_BASE_ADDR(dma_addr + offset));
>> +     }
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
>> +
>> +     /* Grey has 1 plane*/
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1);
>> +
>> +     /* Set Y only as output format */
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY);
>> +
>> +     /* The unit of stride is 16 bytes */
>> +     stride = DIV_ROUND_UP(fmt->width * 1, 16);
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, bool swap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +     u32 swap_uv;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
>> +
>> +     swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
>> +                      ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
>> +
>> +     /* NV12 or NV21 has 2 planes*/
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
>> +
>> +     /* Set YUV420 as output format */
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width >> 1));
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
>> +
>> +     /* The unit of Y and UV strides is 16 bytes */
>> +     stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, bool swap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +     u32 swap_uv;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT);
>> +
>> +     swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
>> +                      ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
>> +
>> +     /* NV16 or NV61 has 2 planes*/
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
>> +
>> +     /* Set YUV422 as output format */
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width));
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
>> +
>> +     /* The unit of Y stride is 16 bytes */
>> +     stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
>> +
>> +     /* The unit of UV stride is 16 bytes */
>> +     stride = DIV_ROUND_UP(fmt->width * 2, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
>> +}
> I feel like the above functions could 'easily' be unified in a single
> one that computes the right parameters using the pixel format info.
>
> Feel free to expand 'struct c3_isp_capture_format' with all the info
> you need to compute the correct register values if you think unifying
> the above functions is helpful
>

Will try to test your suggestion.

>> +
>> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +
>> +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY)
>> +             c3_isp_cap_wrmifx3_grey(cap);
>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV12)
>> +             c3_isp_cap_wrmifx3_yuv420(cap, true);
>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV21)
>> +             c3_isp_cap_wrmifx3_yuv420(cap, false);
>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV16)
>> +             c3_isp_cap_wrmifx3_yuv422(cap, true);
>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV61)
>> +             c3_isp_cap_wrmifx3_yuv422(cap, false);
>> +
>> +     c3_isp_write(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
>> +                  ISP_WRMIFX3_0_FMT_SIZE_HSIZE(fmt->width) |
>> +                  ISP_WRMIFX3_0_FMT_SIZE_VSIZE(fmt->height));
>> +
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
>> +                        ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK,
>> +                        ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(fmt->width));
>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
>> +                        ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK,
>> +                        ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(fmt->height));
>> +}
>> +
>> +static int c3_isp_cap_dummy_buff_create(struct c3_isp_capture *cap)
>> +{
>> +     struct c3_isp_dummy_buffer *dummy_buff = &cap->dummy_buff;
>> +
>> +     dummy_buff->size = cap->vfmt.fmt.pix.sizeimage;
>> +
>> +     /* The driver never access vaddr, no mapping is required */
>> +     dummy_buff->vaddr = dma_alloc_attrs(cap->isp->dev,
>> +                                         dummy_buff->size,
>> +                                         &dummy_buff->dma_addr,
>> +                                         GFP_KERNEL,
>> +                                         DMA_ATTR_NO_KERNEL_MAPPING);
>> +     if (!dummy_buff->vaddr)
>> +             return -ENOMEM;
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_cap_dummy_buff_destroy(struct c3_isp_capture *cap)
>> +{
>> +     dma_free_attrs(cap->isp->dev,
>> +                    cap->dummy_buff.size, cap->dummy_buff.vaddr,
>> +                    cap->dummy_buff.dma_addr, DMA_ATTR_NO_KERNEL_MAPPING);
>> +}
>> +
>> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
>> +{
>> +     cap->buff = list_first_entry_or_null(&cap->pending,
>> +                                          struct c3_isp_buffer, list);
>> +
>> +     c3_isp_cap_wrmifx3_buff(cap);
>> +
>> +     if (cap->buff)
>> +             list_del(&cap->buff->list);
>> +}
>> +
>> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
>> +{
>> +     scoped_guard(spinlock_irqsave, &cap->buff_lock)
>> +             c3_isp_cap_cfg_buff(cap);
>> +
>> +     c3_isp_cap_wrmifx3_size(cap);
>> +
>> +     if (cap->id == C3_ISP_CAP_DEV_0) {
>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
>> +                                ISP_TOP_PATH_EN_WRMIF0_EN);
>> +     } else if (cap->id == C3_ISP_CAP_DEV_1) {
>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
>> +                                ISP_TOP_PATH_EN_WRMIF1_EN);
>> +     } else if (cap->id == C3_ISP_CAP_DEV_2) {
>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
>> +                                ISP_TOP_PATH_EN_WRMIF2_EN);
>> +     }
> You could spare the {} for single line statements
>
> Could this maybe be simplified as
>
>          u32 val = cap->id == C3_ISP_CAP_DEV_0 ? ISP_TOP_PATH_EN_WRMIF0_EN :
>                    cap->id == C3_ISP_CAP_DEV_1 ? ISP_TOP_PATH_EN_WRMIF1_EN :
>                    cap->id == C3_ISP_CAP_DEV_2 ? ISP_TOP_PATH_EN_WRMIF2_EN : 0;
>
>          scoped_guard(spinlock_irqsave, &cap->buff_lock)
>                  c3_isp_cap_cfg_buff(cap);
>
>          c3_isp_cap_wrmifx3_size(cap);
>          c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, val, val);
>

Will use your method.

>> +}
>> +
>> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
>> +{
>> +     if (cap->id == C3_ISP_CAP_DEV_0) {
>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
>> +                                ISP_TOP_PATH_EN_WRMIF0_DIS);
>> +     } else if (cap->id == C3_ISP_CAP_DEV_1) {
>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
>> +                                ISP_TOP_PATH_EN_WRMIF1_DIS);
>> +     } else if (cap->id == C3_ISP_CAP_DEV_2) {
>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
>> +                                ISP_TOP_PATH_EN_WRMIF2_DIS);
>> +     }
> same here


Will use your method.

>> +}
>> +
>> +static void c3_isp_cap_done(struct c3_isp_capture *cap)
>> +{
>> +     struct c3_isp_buffer *buff = cap->buff;
>> +
>> +     guard(spinlock_irqsave)(&cap->buff_lock);
>> +
>> +     if (buff) {
>> +             buff->vb.sequence = cap->isp->frm_sequence;
>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>> +             buff->vb.field = V4L2_FIELD_NONE;
>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +     }
>> +
>> +     c3_isp_cap_cfg_buff(cap);
>> +}
>> +
>> +/* V4L2 video operations */
>> +
>> +static const struct c3_isp_capture_format
>> +*c3_cap_find_fmt(u32 fourcc)
> fits in one line


Will fit in one line.

>> +{
>> +     unsigned int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> nit: you can declare i inside the for loop


Will use 'unsigned int i = 0' in the for loop.

>> +             if (cap_formats[i].fourcc == fourcc)
>> +                     return &cap_formats[i];
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
>> +                        struct v4l2_pix_format *pix)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +     const struct v4l2_format_info *info;
>> +
>> +     fmt = c3_cap_find_fmt(pix->pixelformat);
>> +     if (!fmt)
>> +             fmt = &cap_formats[0];
>> +
>> +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>> +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>> +     pix->pixelformat = fmt->fourcc;
>> +     pix->field = V4L2_FIELD_NONE;
>> +     pix->colorspace = V4L2_COLORSPACE_SRGB;
>> +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     info = v4l2_format_info(fmt->fourcc);
>> +     pix->bytesperline = pix->width * info->bpp[0] / info->bpp_div[0];
>> +
>> +     /* ISP hardware requires 16 bytes alignment */
>> +     pix->bytesperline = ALIGN(pix->bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>> +
>> +     pix->sizeimage = 0;
>> +     for (unsigned int i = 0; i < info->comp_planes; i++) {
>> +             unsigned int hdiv = (i == 0) ? 1 : info->hdiv;
>> +             unsigned int vdiv = (i == 0) ? 1 : info->vdiv;
>> +             unsigned int bytesperline;
>> +
>> +             bytesperline = info->bpp[i] * DIV_ROUND_UP(pix->width, hdiv) / info->bpp_div[i];
>> +
>> +             /* ISP hardware requires 16 bytes alignment */
>> +             bytesperline = ALIGN(bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>> +             pix->sizeimage += bytesperline * DIV_ROUND_UP(pix->height, vdiv);
>> +     }
>> +}
>> +
>> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
>> +                                   enum vb2_buffer_state state)
>> +{
>> +     struct c3_isp_buffer *buff;
>> +
>> +     guard(spinlock_irqsave)(&cap->buff_lock);
>> +
>> +     if (cap->buff) {
>> +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
>> +             cap->buff = NULL;
>> +     }
>> +
>> +     while (!list_empty(&cap->pending)) {
>> +             buff = list_first_entry(&cap->pending,
>> +                                     struct c3_isp_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +}
>> +
>> +static int c3_isp_cap_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_cap_enum_fmt(struct file *file, void *fh,
>> +                            struct v4l2_fmtdesc *f)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +     unsigned int index = 0;
>> +     unsigned int i;
>> +
>> +     if (!f->mbus_code) {
>> +             if (f->index >= ARRAY_SIZE(cap_formats))
>> +                     return -EINVAL;
>> +
>> +             fmt = &cap_formats[f->index];
>> +             f->pixelformat = fmt->fourcc;
>> +             return 0;
>> +     }
>> +
>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>> +             fmt = &cap_formats[i];
>> +             if (f->mbus_code != fmt->mbus_code)
>> +                     continue;
>> +
>> +             if (index++ == f->index) {
>> +                     f->pixelformat = cap_formats[i].fourcc;
>> +                     return 0;
>> +             }
>> +     }
>> +
>> +     return -EINVAL;
>> +}
>> +
>> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
>> +                         struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     f->fmt.pix = cap->vfmt.fmt.pix;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
>> +                         struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>> +     cap->vfmt = *f;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
>> +                           struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
>> +                                struct v4l2_frmsizeenum *fsize)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +
>> +     if (fsize->index)
>> +             return -EINVAL;
>> +
>> +     fmt = c3_cap_find_fmt(fsize->pixel_format);
>> +     if (!fmt)
>> +             return -EINVAL;
>> +
>> +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>> +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
>> +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
>> +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
>> +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
>> +     fsize->stepwise.step_width = 2;
>> +     fsize->stepwise.step_height = 2;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_cap_querycap,
>> +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
>> +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
>> +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
>> +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_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,
>> +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_cap_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_cap_link_validate(struct media_link *link)
>> +{
>> +     struct video_device *vdev =
>> +             media_entity_to_video_device(link->sink->entity);
>> +     struct v4l2_subdev *sd =
>> +             media_entity_to_v4l2_subdev(link->source->entity);
>> +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
>> +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
>> +     struct v4l2_subdev_format src_fmt = {
>> +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>> +             .pad = link->source->index,
>> +     };
>> +     const struct c3_isp_capture_format *cap_fmt =
>> +                             c3_cap_find_fmt(pix_fmt->pixelformat);
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (src_fmt.format.width != pix_fmt->width ||
>> +         src_fmt.format.height != pix_fmt->height ||
>> +         src_fmt.format.code != cap_fmt->mbus_code) {
>> +             dev_err(cap->isp->dev,
>> +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
>> +                     link->source->entity->name, link->source->index,
>> +                     link->sink->entity->name, link->sink->index,
>> +                     src_fmt.format.code, src_fmt.format.width,
>> +                     src_fmt.format.height, cap_fmt->mbus_code,
>> +                     pix_fmt->width, pix_fmt->height);
>> +
>> +             return -EPIPE;
>> +     };
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct media_entity_operations isp_cap_entity_ops = {
>> +     .link_validate = c3_isp_cap_link_validate,
>> +};
>> +
>> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
>> +                               unsigned int *num_buffers,
>> +                               unsigned int *num_planes,
>> +                               unsigned int sizes[],
>> +                               struct device *alloc_devs[])
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < pix->sizeimage)
>> +                     return -EINVAL;
> I'm not sure this is correct. This capture device supports formats
> with multiple planes, you should verify that *num_planes is equal to
> the number of planes of the currently applied format and that each
> plane has the correct size


This capture device only supports 'V4L2_CAP_VIDEO_CAPTURE', so only need 
'*num_planes = 1'.

> And, if you return here


The reason for doing this is to pass the test of 'v4l2-compliance 
1.29.0-5246'.

>
>> +     } else {
> You can skip the else {}
>
>> +             *num_planes = 1;
>> +             sizes[0] = pix->sizeimage;
> And here set the number of planes to the correct number and set the
> size of each plane ?


The reason for doing this is to pass the test of 'v4l2-compliance 
1.29.0-5246'.

>
> And now I realized you're using the single-planar API for buffer
> management
>
>          vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>
> I was expecting it to rather be
>
>          vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
>
> Even more so as you support multiplanar YUV formats.
>
> Any reason why you chose to use the single planar API ?


I think the single-planar API is a bit simpler.

Will try to use multi-planar.

>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     guard(spinlock_irqsave)(&cap->buff_lock);
>> +
>> +     list_add_tail(&buf->list, &cap->pending);
>> +}
>> +
>> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>> +             dev_err(cap->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_buffer *buf =
>> +             container_of(v4l2_buf, struct c3_isp_buffer, vb);
>> +
>> +     buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
>> +                                   unsigned int count)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     ret = pm_runtime_resume_and_get(cap->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
>> +     if (ret) {
>> +             dev_err(cap->isp->dev,
>> +                     "Failed to start cap%u pipeline: %d\n", cap->id, ret);
>> +             goto err_pm_put;
>> +     }
> You could pipeline_start() after pm_runtime_resume_and_get() and save
> this jump.


Will firstly use pipeline_start() and then pm_runtime_resume_and_get().

>> +
>> +     ret = c3_isp_cap_dummy_buff_create(cap);
>> +     if (ret)
>> +             goto err_pipeline_stop;
>> +
>> +     c3_isp_cap_start(cap);
>> +
>> +     ret = v4l2_subdev_enable_streams(&cap->rsz->sd,
>> +                                      C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
>> +     if (ret)
>> +             goto err_dummy_destroy;
>> +
>> +     return 0;
>> +
>> +err_dummy_destroy:
>> +     c3_isp_cap_dummy_buff_destroy(cap);
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&cap->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(cap->isp->dev);
>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +
>> +     c3_isp_cap_stop(cap);
>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
>> +
>> +     v4l2_subdev_disable_streams(&cap->rsz->sd,
>> +                                 C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
>> +
>> +     c3_isp_cap_dummy_buff_destroy(cap);
>> +
>> +     video_device_pipeline_stop(&cap->vdev);
>> +     pm_runtime_put(cap->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_video_vb2_ops = {
>> +     .queue_setup = c3_isp_vb2_queue_setup,
>> +     .buf_queue = c3_isp_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_vb2_buf_prepare,
>> +     .buf_init = c3_isp_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_vb2_stop_streaming,
>> +};
>> +
>> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
>> +{
>> +     struct video_device *vdev = &cap->vdev;
>> +     struct vb2_queue *vb2_q = &cap->vb2_q;
>> +     int ret;
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "c3-isp-cap%u", cap->id);
>> +     vdev->fops = &isp_cap_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
>> +     vdev->entity.ops = &isp_cap_entity_ops;
>> +     vdev->lock = &cap->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_RX;
>> +     video_set_drvdata(vdev, cap);
>> +
>> +     vb2_q->drv_priv = cap;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_video_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>> +     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_buffer);
>> +     vb2_q->dev = cap->isp->dev;
>> +     vb2_q->lock = &cap->lock;
>> +     vb2_q->min_queued_buffers = 2;
> Now that you're using a dummy buffer you can drop this


Will drop this line.

>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret < 0)
>> +             goto err_destroy;
>> +
>> +     cap->pad.flags = MEDIA_PAD_FL_SINK;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
>> +     if (ret < 0)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret < 0) {
>> +             dev_err(cap->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_destroy:
>> +     mutex_destroy(&cap->lock);
>> +     return ret;
>> +}
>> +
>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +             memset(cap, 0, sizeof(*cap));
>> +
>> +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>> +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>> +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>> +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>> +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>> +
>> +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>> +
>> +             cap->id = i;
>> +             cap->rsz = &isp->resizers[i];
>> +             cap->isp = isp;
>> +             INIT_LIST_HEAD(&cap->pending);
>> +             spin_lock_init(&cap->buff_lock);
>> +             mutex_init(&cap->lock);
>> +
>> +             ret = c3_isp_register_capture(cap);
>> +             if (ret) {
>> +                     cap->isp = NULL;
>> +                     mutex_destroy(&cap->lock);
>> +                     c3_isp_captures_unregister(isp);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +
>> +             if (!cap->isp)
>> +                     continue;
>> +             vb2_queue_release(&cap->vb2_q);
>> +             media_entity_cleanup(&cap->vdev.entity);
>> +             video_unregister_device(&cap->vdev);
>> +             mutex_destroy(&cap->lock);
>> +     }
>> +}
>> +
>> +void c3_isp_captures_isr(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
>> new file mode 100644
>> index 000000000000..d2c0b9aacf36
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
>> @@ -0,0 +1,352 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_COMMON_H__
>> +#define __C3_ISP_COMMON_H__
>> +
>> +#include <linux/clk.h>
>> +
>> +#include <media/media-device.h>
>> +#include <media/videobuf2-core.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>> +#include <media/videobuf2-v4l2.h>
>> +
>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>> +#define C3_ISP_CLOCK_NUM_MAX          3
>> +
>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUV8_1X24
>> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUV8_1X24
>> +#define C3_ISP_DEFAULT_WIDTH          1920
>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>> +#define C3_ISP_MAX_WIDTH              2888
>> +#define C3_ISP_MAX_HEIGHT             2240
>> +#define C3_ISP_MIN_WIDTH              160
>> +#define C3_ISP_MIN_HEIGHT             120
>> +
>> +#define C3_ISP_DMA_SIZE_ALIGN_BYTES   16
>> +#define C3_ISP_DISP_REG(base, id)     ((base) + (id) * 0x400)
>> +#define C3_ISP_WRMIFX3_REG(base, id)  ((base) + (id) * 0x100)
>> +#define C3_ISP_PPS_TAP4_S11_H_NUM     33
>> +#define C3_ISP_PPS_LUT_CTYPE_0        0
>> +#define C3_ISP_PPS_LUT_CTYPE_2        2
>> +#define C3_ISP_SCALE_EN               1
>> +#define C3_ISP_SCALE_DIS              0
>> +
>> +#define C3_ISP_PHASE_OFFSET_0         0
>> +#define C3_ISP_PHASE_OFFSET_1         1
>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>> +
>> +enum c3_isp_core_pads {
>> +     C3_ISP_CORE_PAD_SINK_VIDEO,
>> +     C3_ISP_CORE_PAD_SINK_PARAMS,
>> +     C3_ISP_CORE_PAD_SOURCE_STATS,
>> +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +     C3_ISP_CORE_PAD_MAX
>> +};
>> +
>> +enum c3_isp_resizer_ids {
>> +     C3_ISP_RSZ_0,
>> +     C3_ISP_RSZ_1,
>> +     C3_ISP_RSZ_2,
>> +     C3_ISP_NUM_RSZ
>> +};
>> +
>> +enum c3_isp_resizer_pads {
>> +     C3_ISP_RESIZER_PAD_SINK,
>> +     C3_ISP_RESIZER_PAD_SOURCE,
>> +     C3_ISP_RESIZER_PAD_MAX
>> +};
>> +
>> +enum c3_isp_cap_devs {
>> +     C3_ISP_CAP_DEV_0,
>> +     C3_ISP_CAP_DEV_1,
>> +     C3_ISP_CAP_DEV_2,
>> +     C3_ISP_NUM_CAP_DEVS
>> +};
>> +
>> +/*
>> + * 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;
>> +};
>> +
>> +/*
>> + * struct c3_isp_mbus_format_info - MBUS format information
>> + *
>> + * @mbus_code: the mbus code
>> + * @pads: bitmask detailing valid pads for this mbus_code
>> + * @xofst: horizontal phase offset of hardware
>> + * @yofst: vertical phase offset of hardware
>> + */
>> +struct c3_isp_mbus_format_info {
> It seems to me some of these types are used only inside a single .c
> file
>
> $ git grep c3_isp_mbus_format_info drivers/media/platform/amlogic/c3/isp/
> drivers/media/platform/amlogic/c3/isp/c3-isp-common.h: * struct c3_isp_mbus_format_info - MBUS format information
> drivers/media/platform/amlogic/c3/isp/c3-isp-common.h:struct c3_isp_mbus_format_info {
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *info;
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
>
> Could they be declared there ?


OK, it will be declared in c3-isp-core.c file.

>> +     u32 mbus_code;
>> +     u32 pads;
>> +     u8 xofst;
>> +     u8 yofst;
>> +};
>> +
>> +/*
>> + * struct c3_isp_capture_format - The image format of capture device
>> + *
>> + * @mbus_code: the mbus code
>> + * @fourcc: pixel format
>> + */
>> +struct c3_isp_capture_format {
>> +     u32 mbus_code;
>> +     u32 fourcc;
>> +};
>> +
>> +/*
>> + * struct c3_isp_buffer - A container of vb2 buffer used by the video devices:
>> + *                        capture and stats video devices
>> + *
>> + * @vb: vb2 buffer
>> + * @dma_addr: buffer physical address
>> + * @list: entry of the buffer in the queue
>> + */
>> +struct c3_isp_buffer {
>> +     struct vb2_v4l2_buffer vb;
>> +     dma_addr_t dma_addr;
>> +     struct list_head list;
>> +};
>> +
>> +/*
>> + * struct c3_isp_params_buffer - A container of vb2 buffer used by the
>> + *                               params video device
>> + *
>> + * @vb: vb2 buffer
>> + * @cfg: scratch buffer used for caching the ISP configuration parameters
>> + * @list: entry of the buffer in the queue
>> + */
>> +struct c3_isp_params_buffer {
>> +     struct vb2_v4l2_buffer vb;
>> +     void *cfg;
>> +     struct list_head list;
>> +};
>> +
>> +/*
>> + * struct c3_isp_dummy_buffer - A buffer to write the next frame to in case
>> + *                           there are no vb2 buffers available.
>> + *
>> + * @vaddr:   return value of call to dma_alloc_attrs
>> + * @dma_addr:        dma address of the buffer
>> + * @size:    size of the buffer
>> + */
>> +struct c3_isp_dummy_buffer {
>> +     void *vaddr;
>> +     dma_addr_t dma_addr;
>> +     u32 size;
>> +};
>> +
>> +/*
>> + * struct c3_isp_core - ISP core subdev
>> + *
>> + * @sd: ISP sub-device
>> + * @pads: ISP sub-device pads
>> + * @src_sd: source sub-device
>> + * @src_sd_pad: source sub-device pad
>> + * @isp: pointer to c3_isp_device
>> + */
>> +struct c3_isp_core {
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>> +     struct v4l2_subdev *src_sd;
>> +     u16 src_sd_pad;
>> +     struct c3_isp_device *isp;
>> +};
>> +
>> +/*
>> + * struct c3_isp_resizer - ISP resizer subdev
>> + *
>> + * @id: resizer id
>> + * @sd: resizer sub-device
>> + * @pads: resizer sub-device pads
>> + * @src_sd: source sub-device
>> + * @src_sd_pad: source sub-device pad
>> + * @isp: pointer to c3_isp_device
>> + */
>> +struct c3_isp_resizer {
>> +     enum c3_isp_resizer_ids id;
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>> +     struct v4l2_subdev *src_sd;
>> +     u32 src_sd_pad;
>> +     struct c3_isp_device *isp;
>> +};
>> +
>> +/*
>> + * struct c3_isp_stats - ISP statistics device
>> + *
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the metadata format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @isp: pointer to c3_isp_device
>> + * @buff: in use buffer
>> + * @buff_lock: protects stats buffer
>> + * @pending: stats buffer list head
>> + */
>> +struct c3_isp_stats {
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     struct c3_isp_device *isp;
>> +
>> +     struct c3_isp_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stats buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/*
>> + * struct c3_isp_params - ISP parameters device
>> + *
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the metadata format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @isp: pointer to c3_isp_device
>> + * @buff: in use buffer
>> + * @buff_lock: protects stats buffer
>> + * @pending: stats buffer list head
>> + */
>> +struct c3_isp_params {
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     struct c3_isp_device *isp;
>> +
>> +     struct c3_isp_params_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects params buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/*
>> + * struct c3_isp_capture - ISP capture device
>> + *
>> + * @id: capture device ID
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the capture format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @isp: pointer to c3_isp_device
>> + * @rsz: pointer to c3_isp_resizer
>> + * @buff: in use buffer
>> + * @buff_lock: protects capture buffer
>> + * @pending: capture buffer list head
>> + */
>> +struct c3_isp_capture {
>> +     enum c3_isp_cap_devs id;
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     struct c3_isp_device *isp;
>> +     struct c3_isp_resizer *rsz;
>> +
>> +     struct c3_isp_dummy_buffer dummy_buff;
>> +     struct c3_isp_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_info - ISP information
>> + *
>> + * @clocks: array of ISP clock names
>> + * @clock_rates: array of ISP clock rate
>> + * @clock_num: actual clock number
>> + */
>> +struct c3_isp_info {
>> +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
>> +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>> +     u32 clock_num;
>> +};
>> +
>> +/**
>> + * struct c3_isp_device - ISP platform device
>> + *
>> + * @dev: pointer to the struct device
>> + * @base: base register address
>> + * @clks: array of clocks
>> + * @notifier: notifier to register on the v4l2-async API
>> + * @v4l2_dev: v4l2_device variable
>> + * @media_dev: media device variable
>> + * @pipe: media pipeline
>> + * @core: ISP core subdev
>> + * @resizer: ISP resizer subdev
>> + * @stats: ISP stats device
>> + * @params: ISP params device
>> + * @caps: array of ISP capture device
>> + * @frm_sequence: used to record frame id
>> + * @info: version-specific ISP information
>> + */
>> +struct c3_isp_device {
>> +     struct device *dev;
>> +     void __iomem *base;
>> +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>> +
>> +     struct v4l2_async_notifier notifier;
>> +     struct v4l2_device v4l2_dev;
>> +     struct media_device media_dev;
>> +     struct media_pipeline pipe;
>> +
>> +     struct c3_isp_core core;
>> +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>> +     struct c3_isp_stats stats;
>> +     struct c3_isp_params params;
>> +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>> +
>> +     u32 frm_sequence;
>> +     const struct c3_isp_info *info;
>> +};
>> +
>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
>> +
>> +void c3_isp_core_queue_eof(struct c3_isp_device *isp);
>> +int c3_isp_core_register(struct c3_isp_device *isp);
>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>> +void c3_isp_captures_isr(struct c3_isp_device *isp);
>> +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp);
>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>> +void c3_isp_stats_isr(struct c3_isp_device *isp);
>> +void c3_isp_params_pre_cfg(struct c3_isp_device *isp);
>> +int c3_isp_params_register(struct c3_isp_device *isp);
>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>> +void c3_isp_params_isr(struct c3_isp_device *isp);
>> +
>> +#endif
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
>> new file mode 100644
>> index 000000000000..bf83e73dd744
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
>> @@ -0,0 +1,553 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/media/amlogic/c3-isp-config.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-event.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +#define C3_ISP_CORE_SUBDEV_NAME        "c3-isp-core"
>> +
>> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>> +     /* RAW formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     },
>> +     /* YUV formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     },
>> +};
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*core_find_format_by_code(u32 code, u32 pad)
>> +{
>> +     for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_core_mbus_formats[i];
>> +
>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>> +                     return info;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*core_find_format_by_index(u32 index, u32 pad)
>> +{
>> +     for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_core_mbus_formats[i];
>> +
>> +             if (!(info->pads & BIT(pad)))
>> +                     continue;
>> +
>> +             if (!index)
>> +                     return info;
>> +
>> +             index--;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
>> +                        ISP_TOP_IRQ_EN_FRAME_END_EN);
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
>> +                        ISP_TOP_IRQ_EN_3A_DMA_ERR_EN);
>> +
>> +     /* Enable image data to ISP core */
>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
>> +                        ISP_TOP_PATH_SEL_CORE_MIPI_CORE);
>> +}
>> +
>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>> +{
>> +     /* Disable image data to ISP core */
>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
>> +                        ISP_TOP_PATH_SEL_CORE_CORE_DIS);
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
>> +                        ISP_TOP_IRQ_EN_FRAME_END_DIS);
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
>> +                        ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS);
>> +}
>> +
>> +/* Set the phase offset of blc, wb and lns */
>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>> +                               u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK,
>> +                        ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK,
>> +                        ISP_LSWB_BLC_PHSOFST_VERT_OFST(yofst));
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK,
>> +                        ISP_LSWB_WB_PHSOFST_HORIZ_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK,
>> +                        ISP_LSWB_WB_PHSOFST_VERT_OFST(yofst));
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK,
>> +                        ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK,
>> +                        ISP_LSWB_LNS_PHSOFST_VERT_OFST(yofst));
>> +}
>> +
>> +/* Set the phase offset of af, ae and awb */
>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>> +                             u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_HORIZ_OFST_MASK,
>> +                        ISP_AF_CTRL_HORIZ_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_VERT_OFST_MASK,
>> +                        ISP_AF_CTRL_VERT_OFST(yofst));
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_HORIZ_OFST_MASK,
>> +                        ISP_AE_CTRL_HORIZ_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_VERT_OFST_MASK,
>> +                        ISP_AE_CTRL_VERT_OFST(yofst));
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_HORIZ_OFST_MASK,
>> +                        ISP_AWB_CTRL_HORIZ_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_VERT_OFST_MASK,
>> +                        ISP_AWB_CTRL_VERT_OFST(yofst));
>> +}
>> +
>> +/* Set the phase offset of demosaic */
>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>> +                              u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK,
>> +                        ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(xofst));
>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK,
>> +                        ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(yofst));
>> +}
>> +
>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +     isp_fmt = core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +
>> +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>> +                  ISP_TOP_INPUT_SIZE_HORIZ_SIZE(fmt->width) |
>> +                  ISP_TOP_INPUT_SIZE_VERT_SIZE(fmt->height));
>> +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>> +                  ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(fmt->width) |
>> +                  ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(fmt->height));
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE,
>> +                        ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK,
>> +                        ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(fmt->width));
>> +
>> +     c3_isp_write(isp, ISP_AF_HV_SIZE,
>> +                  ISP_AF_HV_SIZE_GLB_WIN_XSIZE(fmt->width) |
>> +                  ISP_AF_HV_SIZE_GLB_WIN_YSIZE(fmt->height));
>> +     c3_isp_write(isp, ISP_AE_HV_SIZE,
>> +                  ISP_AE_HV_SIZE_HORIZ_SIZE(fmt->width) |
>> +                  ISP_AE_HV_SIZE_VERT_SIZE(fmt->height));
>> +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
>> +                  ISP_AWB_HV_SIZE_HORIZ_SIZE(fmt->width) |
>> +                  ISP_AWB_HV_SIZE_VERT_SIZE(fmt->height));
>> +
>> +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +}
>> +
>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   u32 pad, u64 streams_mask)
>> +{
>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     core->isp->frm_sequence = 0;
>> +     c3_isp_core_cfg_format(core->isp, state);
>> +     c3_isp_core_enable(core->isp);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_enable_streams(core->src_sd,
>> +                                      core->src_sd_pad, sink_streams);
>> +     if (ret) {
>> +             c3_isp_core_disable(core->isp);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    u32 pad, u64 streams_mask)
>> +{
>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>> +                                                    &streams_mask);
> You can drop xlate_streams if you drop routing support


OK, will drop routing.

>> +     ret = v4l2_subdev_disable_streams(core->src_sd,
>> +                                       core->src_sd_pad, sink_streams);
>> +     if (ret)
>> +             return ret;
>> +
>> +     c3_isp_core_disable(core->isp);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
> As per the other drivers, routing support can be dropped


Will drop routing.

>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = C3_ISP_DEFAULT_WIDTH,
>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>> +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route route;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     route.sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>> +     route.sink_stream = 0;
>> +     route.source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>> +     route.source_stream = 0;
>> +     route.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &route;
>> +
>> +     return c3_isp_core_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     const struct c3_isp_mbus_format_info *info;
>> +     int ret = 0;
>> +
>> +     switch (code->pad) {
>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>> +             info = core_find_format_by_index(code->index, code->pad);
>> +             if (!info)
>> +                     ret = -EINVAL;
>                          return -EINVAL;
>
>> +             else
> so you can drop the else
>

Yes, will drop 'else'

>> +                     code->code = info->mbus_code;
>> +
>> +             break;
>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>> +             if (code->index)
>> +                     ret = -EINVAL;
>> +             else
> here as well


Yes, will drop 'else'

>> +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>                  return -EINVAL;
>
>> +             break;
>> +     }
>> +
>> +     return ret;
>          return 0;
>
>> +}
>> +
>> +static void c3_isp_core_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 c3_isp_mbus_format_info *isp_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>> +
>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>> +     if (!isp_fmt)
>> +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>> +     else
>> +             sink_fmt->code = format->format.code;
>> +
>> +     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);
>> +
> Should you set the colorspace related information like you do in
> init_state() ?
>
>          sink_fmt->field = V4L2_FIELD_NONE;
>          sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>          sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>          sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>          sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;


OK , will add the information.

>> +     src_fmt->width  = sink_fmt->width;
>> +     src_fmt->height = sink_fmt->height;
>> +
>> +     format->format = *sink_fmt;
>> +}
>> +
>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>> +                                    struct v4l2_subdev_format *format)
>> +{
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +
>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>> +     if (!isp_fmt)
>> +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>> +     else
>> +             src_fmt->code = format->format.code;
>> +
>> +     format->format = *src_fmt;
> mm, this doesn't check the sizes... if the ISP cannot scale/crop the
> sizes here should come from the sink format, don't they ? Also
> remember to set the colorspace information
>

Will check the size.

>> +}
>> +
>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *state,
>> +                            struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     if (format->pad == C3_ISP_CORE_PAD_SINK_VIDEO) {
>> +             c3_isp_core_set_sink_fmt(state, format);
>> +     } else if (format->pad == C3_ISP_CORE_PAD_SINK_PARAMS ||
>> +                format->pad == C3_ISP_CORE_PAD_SOURCE_STATS) {
>> +             fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +             format->format = *fmt;
> or
>
>                  format->format =
>                          *v4l2_subdev_state_get_format(state, format->pad);
>
> so you can drop *fmt and the {} or maybe turn this into a
>          switch (format->pad) {
>
>          }
>
> Up to you
>

Will check this issue.

>> +     } else if (format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO) {
>> +             c3_isp_core_set_source_fmt(state, format);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
> Or simply use one
>
>          struct v4l2_mbus_framefmt *fmt;


Will use one '*fmt'.

>> +
>> +     /* Video sink pad */
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>> +
>> +     /* Video source pad */
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>> +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     src_fmt->field = V4L2_FIELD_NONE;
>> +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>> +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>> +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     /* Parameters pad */
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
>> +     sink_fmt->width = 0;
>> +     sink_fmt->height = 0;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +     /* Statistics pad */
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
>> +     src_fmt->width = 0;
>> +     src_fmt->height = 0;
>> +     src_fmt->field = V4L2_FIELD_NONE;
>> +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +     return c3_isp_core_init_routing(sd, state);
> Drop routing support
>

Will drop routing.

>> +}
>> +
>> +static int c3_isp_core_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
>> +                                    struct v4l2_event_subscription *sub)
>> +{
>> +     if (sub->type != V4L2_EVENT_FRAME_SYNC)
>> +             return -EINVAL;
>> +
>> +     /* V4L2_EVENT_FRAME_SYNC doesn't need id, so should set 0 */
>> +     if (sub->id != 0)
>> +             return -EINVAL;
>> +
>> +     return v4l2_event_subscribe(fh, sub, 0, NULL);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>> +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_isp_core_set_fmt,
>> +     .enable_streams = c3_isp_core_enable_streams,
>> +     .disable_streams = c3_isp_core_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_core_ops c3_isp_core_core_ops = {
>> +     .subscribe_event = c3_isp_core_subscribe_event,
>> +     .unsubscribe_event = v4l2_event_subdev_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>> +     .core = &c3_isp_core_core_ops,
>> +     .pad = &c3_isp_core_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
>> +     .init_state = c3_isp_core_init_state,
>> +};
>> +
>> +static int c3_isp_core_link_validate(struct media_link *link)
>> +{
>> +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>> +             return 0;
>> +
>> +     return v4l2_subdev_link_validate(link);
>> +}
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>> +     .link_validate = c3_isp_core_link_validate,
>> +};
>> +
>> +void c3_isp_core_queue_eof(struct c3_isp_device *isp)
>> +{
> This gets called in the end of frame IRQ right ?
>
>          if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
>                  c3_isp_core_queue_eof(isp);
>
> When processing is completed.
>
> FRAME_SYNC should ideally be emitted as soon as the processing of a
> frame start. Do you have other interrupt sources you can use to detect
> "start processing of a frame" ?
>

Will check the C308L datasheet.

>> +     struct v4l2_event event = {
>> +             .type = V4L2_EVENT_FRAME_SYNC,
>> +     };
>> +
>> +     event.u.frame_sync.frame_sequence = isp->frm_sequence;
>> +     v4l2_event_queue(isp->core.sd.devnode, &event);
>> +}
>> +
>> +int c3_isp_core_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct v4l2_subdev *sd = &core->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
>> +     sd->internal_ops = &c3_isp_core_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>> +     sd->entity.ops = &c3_isp_core_entity_ops;
>> +
>> +     core->isp = isp;
>> +     sd->dev = isp->dev;
>> +     v4l2_set_subdevdata(sd, core);
>> +
>> +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>> +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret)
>> +             goto err_entity_cleanup;
>> +
>> +     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_entity_cleanup:
>> +     media_entity_cleanup(&sd->entity);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct v4l2_subdev *sd = &core->sd;
>> +
>> +     v4l2_device_unregister_subdev(sd);
>> +     v4l2_subdev_cleanup(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
>> new file mode 100644
>> index 000000000000..0c6795ab9925
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
>> @@ -0,0 +1,459 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-common.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-mc.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>> +{
>> +     return readl(isp->base + reg);
>> +}
>> +
>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>> +{
>> +     writel(val, isp->base + reg);
>> +}
>> +
>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
>> +{
>> +     u32 orig, tmp;
>> +
>> +     orig = c3_isp_read(isp, reg);
>> +
>> +     tmp = orig & ~mask;
>> +     tmp |= val & mask;
>> +
>> +     if (tmp != orig)
>> +             c3_isp_write(isp, reg, tmp);
>> +}
>> +
>> +/* PM runtime suspend */
>> +static int c3_isp_runtime_suspend(struct device *dev)
>> +{
>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>> +
>> +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>> +
>> +     return 0;
>> +}
>> +
>> +/* PM runtime resume */
>> +static int c3_isp_runtime_resume(struct device *dev)
>> +{
>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>> +
>> +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>> +}
>> +
>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>> +     SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                         pm_runtime_force_resume)
>> +     RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>> +                    c3_isp_runtime_resume, NULL)
>> +};
>> +
>> +/* IRQ handling */
>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>> +{
>> +     struct c3_isp_device *isp = dev;
>> +     u32 status;
>> +
>> +     /* Get irq status and clear irq status */
>> +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>> +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>> +
>> +     if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
>> +             c3_isp_core_queue_eof(isp);
>> +             c3_isp_stats_isr(isp);
>> +             c3_isp_params_isr(isp);
>> +             c3_isp_captures_isr(isp);
>> +             isp->frm_sequence++;
>> +     }
>> +
>> +     if (status & ISP_TOP_IRQ_EN_3A_DMA_ERR_EN)
>> +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +/* Subdev notifier register */
>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>> +                            struct v4l2_subdev *sd,
>> +                            struct v4l2_async_connection *asc)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(notifier, struct c3_isp_device, notifier);
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>> +     int ret;
>> +
>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
>> +             return ret;
>> +     }
>> +
>> +     core->src_sd = sd;
>> +     core->src_sd_pad = ret;
>> +
>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>> +}
>> +
>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(notifier, struct c3_isp_device, notifier);
>> +     int ret;
>> +
>> +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register subdev nodes: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     dev_info(isp->dev, "notify complete\n");
> Possibly drop this


Will drop this line.

>> +
>> +     return media_device_register(&isp->media_dev);
> Any reason you're registering the media device so late when the async
> chain completes instead of doing so in probe ? I think it's fine doing
> this here or during probe (after registering the v4l2_dev the video
> devices. If you do it here and the remote subdev doesn't probe you
> won't have any media graph. If you do it in probe() and the remote
> subdev fails to probe, the media graph will register but be unusable.
> Not sure what's best, maybe an incomplete graph is easier to debug
> than no graph at all, but I won't push for this. Up to you :)
>

Will move it to probe()

>> +}
> I'm not against using notify_complete() but I suspect you have a
> single remote async subdev, right ? (iow you could have done this in
> _bound ? )


Yes,  will do this in _bound.

>> +
>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(asc->notifier, struct c3_isp_device, notifier);
>> +
>> +     media_device_unregister(&isp->media_dev);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
>> +     .bound = c3_isp_notify_bound,
>> +     .complete = c3_isp_notify_complete,
>> +     .destroy = c3_isp_notify_destroy,
>> +};
>> +
>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>> +{
>> +     struct v4l2_async_connection *asc;
>> +     struct fwnode_handle *ep;
>> +     int ret;
>> +
>> +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>> +
>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>> +     if (!ep)
>> +             return -ENOTCONN;
>> +
>> +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>> +                                           struct v4l2_async_connection);
>> +     fwnode_handle_put(ep);
>> +
>> +     if (IS_ERR(asc))
>> +             return PTR_ERR(asc);
>> +
>> +     isp->notifier.ops = &c3_isp_notify_ops;
>> +     ret = v4l2_async_nf_register(&isp->notifier);
>> +     if (ret)
>> +             v4l2_async_nf_cleanup(&isp->notifier);
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>> +{
>> +     v4l2_async_nf_unregister(&isp->notifier);
>> +     v4l2_async_nf_cleanup(&isp->notifier);
>> +}
>> +
>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>> +{
>> +     struct media_device *media_dev = &isp->media_dev;
>> +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>> +     int ret;
>> +
>> +     /* Initialize media device */
>> +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>> +             sizeof(media_dev->model));
>> +     media_dev->dev = isp->dev;
>> +
>> +     media_device_init(media_dev);
>> +
>> +     /* Initialize v4l2 device */
>> +     v4l2_dev->mdev = media_dev;
>> +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>> +             sizeof(v4l2_dev->name));
>> +
>> +     ret = v4l2_device_register(isp->dev, v4l2_dev);
>> +     if (ret) {
>> +             media_device_cleanup(media_dev);
>> +             dev_err(isp->dev,
>> +                     "Failed to register V4L2 device: %d\n", ret);
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>> +{
>> +     v4l2_device_unregister(&isp->v4l2_dev);
>> +     media_device_cleanup(&isp->media_dev);
>> +}
>> +
>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +
>> +     media_entity_remove_links(&isp->core.sd.entity);
>> +
>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>> +             media_entity_remove_links(&isp->resizers[i].sd.entity);
>> +
>> +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>> +             media_entity_remove_links(&isp->caps[i].vdev.entity);
>> +}
>> +
>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +     int ret;
>> +
>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>> +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>> +                                         C3_ISP_RESIZER_PAD_SOURCE,
>> +                                         &isp->caps[i].vdev.entity,
>> +                                         0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
>> +                     goto err_remove_links;
>> +             }
>> +
>> +             ret = media_create_pad_link(&isp->core.sd.entity,
>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                         &isp->resizers[i].sd.entity,
>> +                                         C3_ISP_RESIZER_PAD_SINK,
>> +                                         MEDIA_LNK_FL_ENABLED);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
>> +                     goto err_remove_links;
>> +             }
>> +     }
>> +
>> +     ret = media_create_pad_link(&isp->core.sd.entity,
>> +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
>> +                                 &isp->stats.vdev.entity,
>> +                                 0, MEDIA_LNK_FL_ENABLED);
>> +     if (ret) {
>> +             dev_err(isp->dev, "Failed to link core and stats\n");
>> +             goto err_remove_links;
>> +     }
>> +
>> +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>> +                                 &isp->core.sd.entity,
>> +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
>> +                                 MEDIA_LNK_FL_ENABLED);
>> +     if (ret) {
>> +             dev_err(isp->dev, "Failed to link params and core\n");
>> +             goto err_remove_links;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_remove_links:
>> +     c3_isp_remove_links(isp);
>> +     return ret;
>> +}
>> +
>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +
>> +     ret = c3_isp_captures_register(isp);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = c3_isp_stats_register(isp);
>> +     if (ret)
>> +             goto err_captures_unregister;
>> +
>> +     ret = c3_isp_params_register(isp);
>> +     if (ret)
>> +             goto err_stats_unregister;
>> +
>> +     ret = c3_isp_create_links(isp);
>> +     if (ret)
>> +             goto err_params_unregister;
>> +
>> +     return 0;
>> +
>> +err_params_unregister:
>> +     c3_isp_params_unregister(isp);
>> +err_stats_unregister:
>> +     c3_isp_stats_unregister(isp);
>> +err_captures_unregister:
>> +     c3_isp_captures_unregister(isp);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_remove_links(isp);
>> +     c3_isp_params_unregister(isp);
>> +     c3_isp_stats_unregister(isp);
>> +     c3_isp_captures_unregister(isp);
>> +}
>> +
>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>> +{
>> +     const struct c3_isp_info *info = isp->info;
>> +     int ret;
>> +     u32 i;
>> +
>> +     for (i = 0; i < info->clock_num; i++)
>> +             isp->clks[i].id = info->clocks[i];
>> +
>> +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>> +     if (ret)
>> +             return ret;
>> +
>> +     for (i = 0; i < info->clock_num; i++) {
>> +             if (!info->clock_rates[i])
>> +                     continue;
>> +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
> same comment as per the other drivers


OK!

>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
>> +                             info->clock_rates[i]);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct c3_isp_device *isp;
>> +     int irq;
>> +     int ret;
>> +
>> +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>> +     if (!isp)
>> +             return -ENOMEM;
>> +
>> +     isp->info = of_device_get_match_data(dev);
>> +     isp->dev = dev;
>> +
>> +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>> +     if (IS_ERR(isp->base))
>> +             return dev_err_probe(dev, PTR_ERR(isp->base),
>> +                                  "Failed to ioremap resource\n");
>> +
>> +     irq = platform_get_irq(pdev, 0);
>> +     if (irq < 0)
>> +             return irq;
>> +
>> +     ret = c3_isp_cfg_clocks(isp);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>> +
>> +     platform_set_drvdata(pdev, isp);
>> +
>> +     pm_runtime_enable(dev);
>> +
>> +     ret = c3_isp_v4l2_register(isp);
>> +     if (ret)
>> +             goto err_runtime_disable;
>> +
>> +     ret = c3_isp_core_register(isp);
>> +     if (ret)
>> +             goto err_v4l2_unregister;
>> +
>> +     ret = c3_isp_resizers_register(isp);
>> +     if (ret)
>> +             goto err_core_unregister;
>> +
>> +     ret = c3_isp_async_nf_register(isp);
>> +     if (ret)
>> +             goto err_resizers_unregister;
>> +
>> +     ret = devm_request_irq(dev, irq,
>> +                            c3_isp_irq_handler, IRQF_SHARED,
>> +                            dev_driver_string(dev), isp);
>> +     if (ret)
>> +             goto err_nf_unregister;
>> +
>> +     ret = c3_isp_videos_register(isp);
>> +     if (ret)
>> +             goto err_nf_unregister;
>> +
>> +     return 0;
>> +
>> +err_nf_unregister:
>> +     c3_isp_async_nf_unregister(isp);
>> +err_resizers_unregister:
>> +     c3_isp_resizers_unregister(isp);
>> +err_core_unregister:
>> +     c3_isp_core_unregister(isp);
>> +err_v4l2_unregister:
>> +     c3_isp_v4l2_unregister(isp);
>> +err_runtime_disable:
>> +     pm_runtime_disable(dev);
>> +     return ret;
>> +};
>> +
>> +static void c3_isp_remove(struct platform_device *pdev)
>> +{
>> +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
>> +
>> +     c3_isp_videos_unregister(isp);
>> +     c3_isp_async_nf_unregister(isp);
>> +     c3_isp_core_unregister(isp);
>> +     c3_isp_resizers_unregister(isp);
>> +     c3_isp_v4l2_unregister(isp);
>> +     pm_runtime_disable(isp->dev);
>> +};
>> +
>> +static const struct c3_isp_info isp_info = {
>> +     .clocks = {"vapb", "isp0"},
>> +     .clock_rates = {0, 400000000},
>> +     .clock_num = 2
>> +};
>> +
>> +static const struct of_device_id c3_isp_of_match[] = {
>> +     { .compatible = "amlogic,c3-isp",
>> +       .data = &isp_info },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>> +
>> +static struct platform_driver c3_isp_driver = {
>> +     .probe = c3_isp_probe,
>> +     .remove = c3_isp_remove,
>> +     .driver = {
>> +             .name = "c3-isp",
>> +             .of_match_table = c3_isp_of_match,
>> +             .pm = pm_ptr(&c3_isp_pm_ops),
>> +     },
>> +};
>> +
>> +module_platform_driver(c3_isp_driver);
>> +
>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
>> new file mode 100644
>> index 000000000000..dc33a57dcd59
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
>> @@ -0,0 +1,942 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#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-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 pointer 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. The data member at the end allows a pointer to an address
>> + * within the data member of :c:type:`c3_isp_params_cfg` to initialise a
>> + * union variable.
>> + *
>> + * @header:          Pointer to the shared header struct embedded as the
>> + *                   first member of all the possible other members (except
>> + *                   @data). 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_PST_CCM
>> + * @csc:             For header->type == C3_ISP_PARAMS_BLOCK_PST_CSC
>> + * @blc:             For header->type == C3_ISP_PARAMS_BLOCK_PST_BLC
>> + * @data:            Allows easy initialisation of a union variable with a
>> + *                   pointer into a u8 array.
>> + */
>> +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;
>> +     u8 *data;
> Why do you need data here ?
>
> Oh, I've see in c3_isp_params_cfg_blocks() now :)
>
> The consequences of this is that all handlers take an
>
> typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
>                                       union c3_isp_params_block block);
>
> by value and not by pointer.
>
> I know it's equivalent since the union only stores pointers but it
> might be confusing... I don't know, some parts of me like it, some
> doesn't and would prefer something like union
> rkisp1_ext_params_config. Clever hack btw ;) If you prefer this style
> I'm fine with you keeping it even if seeing a parameter passed by
> value to the handlers makes me cringe a bit
>

Will check this issue.

>> +};
>> +
>> +typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
>> +                                  union c3_isp_params_block block);
>> +
>> +struct c3_isp_params_handler {
>> +     size_t size;
>> +     c3_isp_block_handler handler;
>> +};
>> +
>> +#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,
>> +                                     union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_awb_gains *awb_gains = block.awb_gains;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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));
>> +
>> +     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_cfg_awb_weight(struct c3_isp_device *isp,
>> +                                      struct c3_isp_params_awb_config *awb_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 = awb_cfg->horiz_zones_num * awb_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_IDX_DATA(0, awb_cfg->zone_weight[base + 0]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(1, awb_cfg->zone_weight[base + 1]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(2, awb_cfg->zone_weight[base + 2]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(3, awb_cfg->zone_weight[base + 3]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(4, awb_cfg->zone_weight[base + 4]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(5, awb_cfg->zone_weight[base + 5]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(6, awb_cfg->zone_weight[base + 6]) |
>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(7, awb_cfg->zone_weight[base + 7]);
>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
>> +     }
>> +
>> +     if (zones_num % 8) {
>> +             data = 0;
>> +             base = i * 8;
>> +
>> +             for (i = 0; i < zones_num % 8; i++)
>> +                     data |= ISP_AWB_BLK_WT_DATA_IDX_DATA(i, awb_cfg->zone_weight[base + i]);
>> +
>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_awb_cood(struct c3_isp_device *isp,
>> +                                    struct c3_isp_params_awb_config *awb_cfg)
>> +{
>> +     unsigned int max_point_num;
>> +
>> +     /* The number of points is one more than the number of edges */
>> +     max_point_num = max(awb_cfg->horiz_zones_num, awb_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(awb_cfg->horiz_cood[i]) |
>> +                          ISP_AWB_IDX_DATA_VIDX_DATA(awb_cfg->vert_cood[i]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp,
>> +                                      union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_awb_config *awb_cfg = block.awb_cfg;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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_STAT_POINT_MASK,
>> +                        ISP_TOP_3A_STAT_CRTL_AWB_STAT_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_HORIZ_BLKS_NUM_MASK,
>> +                        ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(awb_cfg->horiz_zones_num));
>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK,
>> +                        ISP_AWB_HV_BLKNUM_VERT_BLKS_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_cfg_awb_weight(isp, awb_cfg);
>> +     c3_isp_params_cfg_awb_cood(isp, awb_cfg);
>> +
>> +     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_cfg_ae_weight(struct c3_isp_device *isp,
>> +                                     struct c3_isp_params_ae_config *ae_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 = ae_cfg->horiz_zones_num * ae_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_IDX_DATA(0, ae_cfg->zone_weight[base + 0]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(1, ae_cfg->zone_weight[base + 1]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(2, ae_cfg->zone_weight[base + 2]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(3, ae_cfg->zone_weight[base + 3]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(4, ae_cfg->zone_weight[base + 4]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(5, ae_cfg->zone_weight[base + 5]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(6, ae_cfg->zone_weight[base + 6]) |
>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(7, ae_cfg->zone_weight[base + 7]);
>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
>> +     }
>> +
>> +     /* Write the last weights data */
>> +     if (zones_num % 8) {
>> +             data = 0;
>> +             base = i * 8;
>> +
>> +             for (i = 0; i < zones_num % 8; i++)
>> +                     data |= ISP_AE_BLK_WT_DATA_IDX_DATA(i, ae_cfg->zone_weight[base + i]);
>> +
>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_ae_cood(struct c3_isp_device *isp,
>> +                                   struct c3_isp_params_ae_config *ae_cfg)
>> +{
>> +     unsigned int max_point_num;
>> +
>> +     /* The number of points is one more than the number of edges */
>> +     max_point_num = max(ae_cfg->horiz_zones_num, ae_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(ae_cfg->horiz_cood[i]) |
>> +                          ISP_AE_IDX_DATA_VIDX_DATA(ae_cfg->vert_cood[i]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp,
>> +                                     union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_ae_config *ae_cfg = block.ae_cfg;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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_STAT_POINT_MASK,
>> +                        ISP_TOP_3A_STAT_CRTL_AE_STAT_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_BLK_HORIZ_NUM_MASK,
>> +                        ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(ae_cfg->horiz_zones_num));
>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK,
>> +                        ISP_AE_HV_BLKNUM_BLK_VERT_NUM(ae_cfg->vert_zones_num));
>> +
>> +     c3_isp_params_cfg_ae_weight(isp, ae_cfg);
>> +     c3_isp_params_cfg_ae_cood(isp, ae_cfg);
>> +
>> +     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_cfg_af_cood(struct c3_isp_device *isp,
>> +                                   struct c3_isp_params_af_config *af_cfg)
>> +{
>> +     unsigned int max_point_num;
>> +
>> +     /* The number of points is one more than the number of edges */
>> +     max_point_num = max(af_cfg->horiz_zones_num, af_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(af_cfg->horiz_cood[i]) |
>> +                          ISP_AF_IDX_DATA_VIDX_DATA(af_cfg->vert_cood[i]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp,
>> +                                     union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_af_config *af_cfg = block.af_cfg;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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_STAT_POINT_MASK,
>> +                        ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(af_cfg->tap_point));
>> +
>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK,
>> +                        ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(af_cfg->horiz_zones_num));
>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK,
>> +                        ISP_AF_HV_BLKNUM_BLK_VERT_NUM(af_cfg->vert_zones_num));
>> +
>> +     c3_isp_params_cfg_af_cood(isp, af_cfg);
>> +
>> +     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,
>> +                                     union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_pst_gamma *gamma = block.pst_gamma;
>> +     unsigned int base;
>> +     unsigned int i;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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(gamma->lut) / 2; i++) {
>> +                     base = i * 2;
>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>> +                                  ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]) |
>> +                                  ISP_PST_GAMMA_LUT_DATA_DATA1(gamma->lut[base + 1]));
>> +             }
>> +
>> +             /* Write the last one */
>> +             if (ARRAY_SIZE(gamma->lut) % 2) {
>> +                     base = i * 2;
>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>> +                                  ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]));
>> +             }
>> +     }
>> +
>> +     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,
>> +                               union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_ccm *ccm = block.ccm;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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]));
>> +
>> +     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,
>> +                               union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_csc *csc = block.csc;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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]));
>> +
>> +     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,
>> +                               union c3_isp_params_block block)
>> +{
>> +     struct c3_isp_params_blc *blc = block.blc;
>> +
>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>> +             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));
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_BLC_EN_MASK,
>> +                        ISP_TOP_BEO_CTRL_BLC_EN);
>> +}
>> +
>> +static const struct c3_isp_params_handler c3_isp_params_handlers[] = {
>> +     [C3_ISP_PARAMS_BLOCK_AWB_GAINS] = {
>> +             .size = sizeof(struct c3_isp_params_awb_gains),
>> +             .handler = c3_isp_params_cfg_awb_gains,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = {
>> +             .size = sizeof(struct c3_isp_params_awb_config),
>> +             .handler = c3_isp_params_cfg_awb_config,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_AE_CONFIG] = {
>> +             .size = sizeof(struct c3_isp_params_ae_config),
>> +             .handler = c3_isp_params_cfg_ae_config,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_AF_CONFIG] = {
>> +             .size = sizeof(struct c3_isp_params_af_config),
>> +             .handler = c3_isp_params_cfg_af_config,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_PST_GAMMA] = {
>> +             .size = sizeof(struct c3_isp_params_pst_gamma),
>> +             .handler = c3_isp_params_cfg_pst_gamma,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_CCM] = {
>> +             .size = sizeof(struct c3_isp_params_ccm),
>> +             .handler = c3_isp_params_cfg_ccm,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_CSC] = {
>> +             .size = sizeof(struct c3_isp_params_csc),
>> +             .handler = c3_isp_params_cfg_csc,
>> +     },
>> +     [C3_ISP_PARAMS_BLOCK_BLC] = {
>> +             .size = sizeof(struct c3_isp_params_blc),
>> +             .handler = c3_isp_params_cfg_blc,
>> +     },
>> +};
>> +
>> +static enum vb2_buffer_state
>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>> +{
>> +     struct c3_isp_params_cfg *config = params->buff->cfg;
>> +     size_t block_offset = 0;
>> +     size_t max_offset = 0;
>> +
>> +     if (WARN_ON(!config))
>> +             return VB2_BUF_STATE_ERROR;
>> +
>> +     /* Ensure config->data has a full struct c3_isp_params_block_header */
>> +     max_offset = config->data_size - sizeof(struct c3_isp_params_block_header);
> I might have missed why you subtract this. Hasn't the package been
> validated in c3_isp_params_vb2_buf_prepare() ?


Yes, the package has been validated.

Will check this issue.

>> +
>> +     /* Walk the list of parameter blocks and process them */
>> +     while (block_offset <= max_offset) {
>> +             const struct c3_isp_params_handler *block_handler;
>> +             union c3_isp_params_block block;
>> +
>> +             block = (union c3_isp_params_block)
>> +                      &config->data[block_offset];
>> +
>> +             block_handler = &c3_isp_params_handlers[block.header->type];
>> +             block_handler->handler(params->isp, block);
>> +
>> +             block_offset += block.header->size;
>> +     }
>> +
>> +     return VB2_BUF_STATE_DONE;
>> +}
>> +
>> +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)(&params->buff_lock);
>> +
>> +     /* Only use the first buffer to initialize ISP */
>> +     params->buff = list_first_entry_or_null(&params->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 here
>

What is the reason to return 0?

I think need to consider the test of 'v4l2-compliance 1.29.0-5246'.

>> +     } else {
> and you can spare the } else {


Will check here.

>> +             *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)(&params->buff_lock);
>> +
>> +     list_add_tail(&buf->list, &params->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 *cfg = buf->cfg;
>> +     struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0);
>> +     size_t payload_size = vb2_get_plane_payload(vb, 0);
>> +     size_t header_size = offsetof(struct c3_isp_params_cfg, data);
>> +     size_t block_offset = 0;
>> +     size_t cfg_size;
>> +
>> +     /* Payload size can't be greater than the destination buffer size */
>> +     if (payload_size > params->vfmt.fmt.meta.buffersize) {
>> +             dev_dbg(params->isp->dev,
>> +                     "Payload size is too large: %zu\n", payload_size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     /* Payload size can't be smaller than the header size */
>> +     if (payload_size < header_size) {
>> +             dev_dbg(params->isp->dev,
>> +                     "Payload size is too small: %zu\n", payload_size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     /*
>> +      * Use the internal scratch buffer to avoid userspace modifying
>> +      * the buffer content while the driver is processing it.
>> +      */
>> +     memcpy(cfg, usr_cfg, payload_size);
>> +
>> +     /* Only v0 is supported at the moment */
>> +     if (cfg->version != C3_ISP_PARAMS_BUFFER_V0) {
>> +             dev_dbg(params->isp->dev,
>> +                     "Unsupported params buffer version: %u\n", cfg->version);
>> +             return -EINVAL;
>> +     }
>> +
>> +     /* Validate the size reported in the parameter buffer header */
>> +     cfg_size = header_size + cfg->data_size;
>> +     if (cfg_size != payload_size) {
>> +             dev_dbg(params->isp->dev,
>> +                     "Data size %zu and payload size %zu are different\n",
>> +                     cfg_size, payload_size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     /* Walk the list of parameter blocks and validate them */
>> +     cfg_size = cfg->data_size;
>> +     while (cfg_size >= sizeof(struct c3_isp_params_block_header)) {
>> +             const struct c3_isp_params_block_header *block;
>> +             const struct c3_isp_params_handler *handler;
>> +
>> +             block = (struct c3_isp_params_block_header *)
>> +                     &cfg->data[block_offset];
>> +
>> +             if (block->type >= ARRAY_SIZE(c3_isp_params_handlers)) {
>> +                     dev_dbg(params->isp->dev,
>> +                             "Invalid params block type\n");
>> +                     return -EINVAL;
>> +             }
>> +
>> +             if (block->size > cfg_size) {
>> +                     dev_dbg(params->isp->dev,
>> +                             "Block size is greater than cfg size\n");
>> +                     return -EINVAL;
>> +             }
>> +
>> +             handler = &c3_isp_params_handlers[block->type];
>> +             if (block->size != handler->size) {
>> +                     dev_dbg(params->isp->dev,
>> +                             "Invalid params block size\n");
>> +                     return -EINVAL;
>> +             }
>> +
>> +             block_offset += block->size;
>> +             cfg_size -= block->size;
>> +     }
>> +
>> +     if (cfg_size) {
>> +             dev_dbg(params->isp->dev,
>> +                     "Unexpected data after the params buffer end\n");
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +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)(&params->buff_lock);
>> +
>> +     while (!list_empty(&params->pending)) {
>> +             buff = list_first_entry(&params->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,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .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 = &params->vdev;
>> +     struct vb2_queue *vb2_q = &params->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(&params->pending);
>> +     spin_lock_init(&params->buff_lock);
>> +     mutex_init(&params->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 = &params->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_buffer);
>> +     vb2_q->dev = isp->dev;
>> +     vb2_q->lock = &params->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, &params->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(&params->lock);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +
>> +     vb2_queue_release(&params->vb2_q);
>> +     media_entity_cleanup(&params->vdev.entity);
>> +     video_unregister_device(&params->vdev);
>> +     mutex_destroy(&params->lock);
>> +}
>> +
>> +void c3_isp_params_isr(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +     enum vb2_buffer_state state;
>> +
>> +     guard(spinlock_irqsave)(&params->buff_lock);
>> +
>> +     params->buff = list_first_entry_or_null(&params->pending,
>> +                                             struct c3_isp_params_buffer, list);
>> +     if (!params->buff)
>> +             return;
>> +
>> +     list_del(&params->buff->list);
>> +
>> +     state = 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(&params->buff->vb.vb2_buf, state);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
>> new file mode 100644
>> index 000000000000..c4d3b8c96479
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
>> @@ -0,0 +1,599 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_REGS_H__
>> +#define __C3_ISP_REGS_H__
>> +
>> +#define ISP_TOP_INPUT_SIZE                              0x0000
>> +#define ISP_TOP_INPUT_SIZE_VERT_SIZE_MASK               GENMASK(15, 0)
>> +#define ISP_TOP_INPUT_SIZE_VERT_SIZE(x)                 ((x) << 0)
>> +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE_MASK              GENMASK(31, 16)
>> +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE(x)                ((x) << 16)
>> +
>> +#define ISP_TOP_FRM_SIZE                                0x0004
>> +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE_MASK            GENMASK(15, 0)
>> +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(x)              ((x) << 0)
>> +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE_MASK           GENMASK(31, 16)
>> +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(x)             ((x) << 16)
>> +
>> +#define ISP_TOP_HOLD_SIZE                               0x0008
>> +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK          GENMASK(31, 16)
>> +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(x)            ((x) << 16)
>> +
>> +#define ISP_TOP_PATH_EN                                 0x0010
>> +#define ISP_TOP_PATH_EN_DISP0_EN_MASK                   BIT(0)
>> +#define ISP_TOP_PATH_EN_DISP0_EN                        BIT(0)
>> +#define ISP_TOP_PATH_EN_DISP0_DIS                       (0 << 0)
>> +#define ISP_TOP_PATH_EN_DISP1_EN_MASK                   BIT(1)
>> +#define ISP_TOP_PATH_EN_DISP1_EN                        BIT(1)
>> +#define ISP_TOP_PATH_EN_DISP1_DIS                       (0 << 1)
>> +#define ISP_TOP_PATH_EN_DISP2_EN_MASK                   BIT(2)
>> +#define ISP_TOP_PATH_EN_DISP2_EN                        BIT(2)
>> +#define ISP_TOP_PATH_EN_DISP2_DIS                       (0 << 2)
>> +#define ISP_TOP_PATH_EN_WRMIF0_EN_MASK                  BIT(8)
>> +#define ISP_TOP_PATH_EN_WRMIF0_EN                       BIT(8)
>> +#define ISP_TOP_PATH_EN_WRMIF0_DIS                      (0 << 8)
>> +#define ISP_TOP_PATH_EN_WRMIF1_EN_MASK                  BIT(9)
>> +#define ISP_TOP_PATH_EN_WRMIF1_EN                       BIT(9)
>> +#define ISP_TOP_PATH_EN_WRMIF1_DIS                      (0 << 9)
>> +#define ISP_TOP_PATH_EN_WRMIF2_EN_MASK                  BIT(10)
>> +#define ISP_TOP_PATH_EN_WRMIF2_EN                       BIT(10)
>> +#define ISP_TOP_PATH_EN_WRMIF2_DIS                      (0 << 10)
>> +
>> +#define ISP_TOP_PATH_SEL                                0x0014
>> +#define ISP_TOP_PATH_SEL_CORE_MASK                      GENMASK(18, 16)
>> +#define ISP_TOP_PATH_SEL_CORE_CORE_DIS                  (0 << 16)
>> +#define ISP_TOP_PATH_SEL_CORE_MIPI_CORE                 BIT(16)
>> +
>> +#define ISP_TOP_IRQ_EN                                  0x0080
>> +#define ISP_TOP_IRQ_EN_FRAME_END_MASK                   BIT(0)
>> +#define ISP_TOP_IRQ_EN_FRAME_END_EN                     BIT(0)
>> +#define ISP_TOP_IRQ_EN_FRAME_END_DIS                    (0 << 0)
>> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK                  BIT(5)
>> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_EN                    BIT(5)
>> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS                   (0 << 5)
>> +
>> +#define ISP_TOP_IRQ_CLR                                 0x0084
>> +#define ISP_TOP_RO_IRQ_STAT                             0x01c4
>> +
>> +#define ISP_TOP_MODE_CTRL                               0x0400
>> +#define ISP_TOP_FEO_CTRL0                               0x040c
>> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK             BIT(8)
>> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS                 (0 << 8)
>> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN                  BIT(8)
>> +
>> +#define ISP_TOP_FEO_CTRL1_0                             0x0410
>> +#define ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK                 BIT(3)
>> +#define ISP_TOP_FEO_CTRL1_0_DPC_DIS                     (0 << 3)
>> +#define ISP_TOP_FEO_CTRL1_0_DPC_EN                      BIT(3)
>> +#define ISP_TOP_FEO_CTRL1_0_OG_EN_MASK                  BIT(5)
>> +#define ISP_TOP_FEO_CTRL1_0_OG_DIS                      (0 << 5)
>> +#define ISP_TOP_FEO_CTRL1_0_OG_EN                       BIT(5)
>> +
>> +#define ISP_TOP_FED_CTRL                                0x0418
>> +#define ISP_TOP_FED_CTRL_PDPC_EN_MASK                   BIT(1)
>> +#define ISP_TOP_FED_CTRL_PDPC_DIS                       (0 << 1)
>> +#define ISP_TOP_FED_CTRL_PDPC_EN                        BIT(1)
>> +#define ISP_TOP_FED_CTRL_RAWCNR_EN_MASK                 GENMASK(6, 5)
>> +#define ISP_TOP_FED_CTRL_RAWCNR_DIS                     (0 << 5)
>> +#define ISP_TOP_FED_CTRL_RAWCNR_EN                      BIT(5)
>> +#define ISP_TOP_FED_CTRL_SNR1_EN_MASK                   BIT(9)
>> +#define ISP_TOP_FED_CTRL_SNR1_DIS                       (0 << 9)
>> +#define ISP_TOP_FED_CTRL_SNR1_EN                        BIT(9)
>> +#define ISP_TOP_FED_CTRL_TNR0_EN_MASK                   BIT(11)
>> +#define ISP_TOP_FED_CTRL_TNR0_DIS                       (0 << 11)
>> +#define ISP_TOP_FED_CTRL_TNR0_EN                        BIT(11)
>> +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK               BIT(12)
>> +#define ISP_TOP_FED_CTRL_CUBIC_CS_DIS                   (0 << 12)
>> +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN                    BIT(12)
>> +#define ISP_TOP_FED_CTRL_SQRT_EN_MASK                   BIT(14)
>> +#define ISP_TOP_FED_CTRL_SQRT_DIS                       (0 << 14)
>> +#define ISP_TOP_FED_CTRL_SQRT_EN                        BIT(14)
>> +#define ISP_TOP_FED_CTRL_DGAIN_EN_MASK                  BIT(16)
>> +#define ISP_TOP_FED_CTRL_DGAIN_DIS                      (0 << 16)
>> +#define ISP_TOP_FED_CTRL_DGAIN_EN                       BIT(16)
>> +
>> +#define ISP_TOP_BEO_CTRL                                0x041c
>> +#define ISP_TOP_BEO_CTRL_WB_EN_MASK                     BIT(6)
>> +#define ISP_TOP_BEO_CTRL_WB_DIS                         (0 << 6)
>> +#define ISP_TOP_BEO_CTRL_WB_EN                          BIT(6)
>> +#define ISP_TOP_BEO_CTRL_BLC_EN_MASK                    BIT(7)
>> +#define ISP_TOP_BEO_CTRL_BLC_DIS                        (0 << 7)
>> +#define ISP_TOP_BEO_CTRL_BLC_EN                         BIT(7)
>> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK              BIT(8)
>> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_DIS                  (0 << 8)
>> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN                   BIT(8)
>> +#define ISP_TOP_BEO_CTRL_EOTF_EN_MASK                   BIT(9)
>> +#define ISP_TOP_BEO_CTRL_EOTF_DIS                       (0 << 9)
>> +#define ISP_TOP_BEO_CTRL_EOTF_EN                        BIT(9)
>> +
>> +#define ISP_TOP_BED_CTRL                                0x0420
>> +#define ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK               GENMASK(1, 0)
>> +#define ISP_TOP_BED_CTRL_YHS_STAT_DIS                   (0 << 0)
>> +#define ISP_TOP_BED_CTRL_YHS_STAT_EN                    BIT(0)
>> +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK              BIT(2)
>> +#define ISP_TOP_BED_CTRL_GRPH_STAT_DIS                  (0 << 2)
>> +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN                   BIT(2)
>> +#define ISP_TOP_BED_CTRL_FMETER_EN_MASK                 BIT(3)
>> +#define ISP_TOP_BED_CTRL_FMETER_DIS                     (0 << 3)
>> +#define ISP_TOP_BED_CTRL_FMETER_EN                      BIT(3)
>> +#define ISP_TOP_BED_CTRL_BSC_EN_MASK                    BIT(10)
>> +#define ISP_TOP_BED_CTRL_BSC_DIS                        (0 << 10)
>> +#define ISP_TOP_BED_CTRL_BSC_EN                         BIT(10)
>> +#define ISP_TOP_BED_CTRL_CNR2_EN_MASK                   BIT(11)
>> +#define ISP_TOP_BED_CTRL_CNR2_DIS                       (0 << 11)
>> +#define ISP_TOP_BED_CTRL_CNR2_EN                        BIT(11)
>> +#define ISP_TOP_BED_CTRL_CM1_EN_MASK                    BIT(13)
>> +#define ISP_TOP_BED_CTRL_CM1_DIS                        (0 << 13)
>> +#define ISP_TOP_BED_CTRL_CM1_EN                         BIT(13)
>> +#define ISP_TOP_BED_CTRL_CM0_EN_MASK                    BIT(14)
>> +#define ISP_TOP_BED_CTRL_CM0_DIS                        (0 << 14)
>> +#define ISP_TOP_BED_CTRL_CM0_EN                         BIT(14)
>> +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK              BIT(16)
>> +#define ISP_TOP_BED_CTRL_PST_GAMMA_DIS                  (0 << 16)
>> +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN                   BIT(16)
>> +#define ISP_TOP_BED_CTRL_LUT3D_EN_MASK                  BIT(17)
>> +#define ISP_TOP_BED_CTRL_LUT3D_DIS                      (0 << 17)
>> +#define ISP_TOP_BED_CTRL_LUT3D_EN                       BIT(17)
>> +#define ISP_TOP_BED_CTRL_CCM_EN_MASK                    BIT(18)
>> +#define ISP_TOP_BED_CTRL_CCM_DIS                        (0 << 18)
>> +#define ISP_TOP_BED_CTRL_CCM_EN                         BIT(18)
>> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK           BIT(21)
>> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS               (0 << 21)
>> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN                BIT(21)
>> +#define ISP_TOP_BED_CTRL_AMCM_EN_MASK                   BIT(25)
>> +#define ISP_TOP_BED_CTRL_AMCM_DIS                       (0 << 25)
>> +#define ISP_TOP_BED_CTRL_AMCM_EN                        BIT(25)
>> +
>> +#define ISP_TOP_3A_STAT_CRTL                            0x0424
>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK            BIT(0)
>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS                (0 << 0)
>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN                 BIT(0)
>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK           BIT(1)
>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS               (0 << 1)
>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN                BIT(1)
>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK            BIT(2)
>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS                (0 << 2)
>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN                 BIT(2)
>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT_MASK        GENMASK(6, 4)
>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT(x)          ((x) << 4)
>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT_MASK         GENMASK(9, 8)
>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT(x)           ((x) << 8)
>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT_MASK         GENMASK(13, 12)
>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(x)           ((x) << 12)
>> +
>> +#define ISP_LSWB_BLC_OFST0                              0x4028
>> +#define ISP_LSWB_BLC_OFST0_R_OFST_MASK                  GENMASK(15, 0)
>> +#define ISP_LSWB_BLC_OFST0_R_OFST(x)                    ((x) << 0)
>> +#define ISP_LSWB_BLC_OFST0_GR_OFST_MASK                 GENMASK(31, 16)
>> +#define ISP_LSWB_BLC_OFST0_GR_OFST(x)                   ((x) << 16)
>> +
>> +#define ISP_LSWB_BLC_OFST1                              0x402c
>> +#define ISP_LSWB_BLC_OFST1_GB_OFST_MASK                 GENMASK(15, 0)
>> +#define ISP_LSWB_BLC_OFST1_GB_OFST(x)                   ((x) << 0)
>> +#define ISP_LSWB_BLC_OFST1_B_OFST_MASK                  GENMASK(31, 16)
>> +#define ISP_LSWB_BLC_OFST1_B_OFST(x)                    ((x) << 16)
>> +
>> +#define ISP_LSWB_BLC_PHSOFST                            0x4034
>> +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
>> +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST(x)               ((x) << 0)
>> +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
>> +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
>> +
>> +#define ISP_LSWB_WB_GAIN0                               0x4038
>> +#define ISP_LSWB_WB_GAIN0_R_GAIN_MASK                   GENMASK(11, 0)
>> +#define ISP_LSWB_WB_GAIN0_R_GAIN(x)                     ((x) << 0)
>> +#define ISP_LSWB_WB_GAIN0_GR_GAIN_MASK                  GENMASK(27, 16)
>> +#define ISP_LSWB_WB_GAIN0_GR_GAIN(x)                    ((x) << 16)
>> +
>> +#define ISP_LSWB_WB_GAIN1                               0x403c
>> +#define ISP_LSWB_WB_GAIN1_GB_GAIN_MASK                  GENMASK(11, 0)
>> +#define ISP_LSWB_WB_GAIN1_GB_GAIN(x)                    ((x) << 0)
>> +#define ISP_LSWB_WB_GAIN1_B_GAIN_MASK                   GENMASK(27, 16)
>> +#define ISP_LSWB_WB_GAIN1_B_GAIN(x)                     ((x) << 16)
>> +
>> +#define ISP_LSWB_WB_GAIN2                               0x4040
>> +#define ISP_LSWB_WB_GAIN2_IR_GAIN_MASK                  GENMASK(11, 0)
>> +#define ISP_LSWB_WB_GAIN2_IR_GAIN(x)                    ((x) << 0)
>> +
>> +#define ISP_LSWB_WB_LIMIT0                              0x4044
>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MASK              GENMASK(15, 0)
>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R(x)                ((x) << 0)
>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX               (0x8fff << 0)
>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MASK             GENMASK(31, 16)
>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR(x)               ((x) << 16)
>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX              (0x8fff << 16)
>> +
>> +#define ISP_LSWB_WB_LIMIT1                              0x4048
>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MASK             GENMASK(15, 0)
>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB(x)               ((x) << 0)
>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX              (0x8fff << 0)
>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MASK              GENMASK(31, 16)
>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B(x)                ((x) << 16)
>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX               (0x8fff << 16)
>> +
>> +#define ISP_LSWB_WB_PHSOFST                             0x4050
>> +#define ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK              GENMASK(1, 0)
>> +#define ISP_LSWB_WB_PHSOFST_VERT_OFST(x)                ((x) << 0)
>> +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK             GENMASK(3, 2)
>> +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST(x)               ((x) << 2)
>> +
>> +#define ISP_LSWB_LNS_PHSOFST                            0x4054
>> +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
>> +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST(x)               ((x) << 0)
>> +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
>> +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
>> +
>> +#define ISP_DMS_COMMON_PARAM0                           0x5000
>> +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK         GENMASK(1, 0)
>> +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(x)          ((x) << 0)
>> +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK        GENMASK(3, 2)
>> +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(x)         ((x) << 2)
>> +
>> +#define ISP_CM0_COEF00_01                               0x6048
>> +#define ISP_CM0_COEF00_01_MTX_00_MASK                   GENMASK(12, 0)
>> +#define ISP_CM0_COEF00_01_MTX_00(x)                     ((x) << 0)
>> +#define ISP_CM0_COEF00_01_MTX_01_MASK                   GENMASK(28, 16)
>> +#define ISP_CM0_COEF00_01_MTX_01(x)                     ((x) << 16)
>> +
>> +#define ISP_CM0_COEF02_10                               0x604c
>> +#define ISP_CM0_COEF02_10_MTX_02_MASK                   GENMASK(12, 0)
>> +#define ISP_CM0_COEF02_10_MTX_02(x)                     ((x) << 0)
>> +#define ISP_CM0_COEF02_10_MTX_10_MASK                   GENMASK(28, 16)
>> +#define ISP_CM0_COEF02_10_MTX_10(x)                     ((x) << 16)
>> +
>> +#define ISP_CM0_COEF11_12                               0x6050
>> +#define ISP_CM0_COEF11_12_MTX_11_MASK                   GENMASK(12, 0)
>> +#define ISP_CM0_COEF11_12_MTX_11(x)                     ((x) << 0)
>> +#define ISP_CM0_COEF11_12_MTX_12_MASK                   GENMASK(28, 16)
>> +#define ISP_CM0_COEF11_12_MTX_12(x)                     ((x) << 16)
>> +
>> +#define ISP_CM0_COEF20_21                               0x6054
>> +#define ISP_CM0_COEF20_21_MTX_20_MASK                   GENMASK(12, 0)
>> +#define ISP_CM0_COEF20_21_MTX_20(x)                     ((x) << 0)
>> +#define ISP_CM0_COEF20_21_MTX_21_MASK                   GENMASK(28, 16)
>> +#define ISP_CM0_COEF20_21_MTX_21(x)                     ((x) << 16)
>> +
>> +#define ISP_CM0_COEF22_OUP_OFST0                        0x6058
>> +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK            GENMASK(12, 0)
>> +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22(x)              ((x) << 0)
>> +
>> +#define ISP_CCM_MTX_00_01                               0x6098
>> +#define ISP_CCM_MTX_00_01_MTX_00_MASK                   GENMASK(12, 0)
>> +#define ISP_CCM_MTX_00_01_MTX_00(x)                     ((x) << 0)
>> +#define ISP_CCM_MTX_00_01_MTX_01_MASK                   GENMASK(28, 16)
>> +#define ISP_CCM_MTX_00_01_MTX_01(x)                     ((x) << 16)
>> +
>> +#define ISP_CCM_MTX_02_03                               0x609c
>> +#define ISP_CCM_MTX_02_03_MTX_02_MASK                   GENMASK(12, 0)
>> +#define ISP_CCM_MTX_02_03_MTX_02(x)                     ((x) << 0)
>> +
>> +#define ISP_CCM_MTX_10_11                               0x60A0
>> +#define ISP_CCM_MTX_10_11_MTX_10_MASK                   GENMASK(12, 0)
>> +#define ISP_CCM_MTX_10_11_MTX_10(x)                     ((x) << 0)
>> +#define ISP_CCM_MTX_10_11_MTX_11_MASK                   GENMASK(28, 16)
>> +#define ISP_CCM_MTX_10_11_MTX_11(x)                     ((x) << 16)
>> +
>> +#define ISP_CCM_MTX_12_13                               0x60A4
>> +#define ISP_CCM_MTX_12_13_MTX_12_MASK                   GENMASK(12, 0)
>> +#define ISP_CCM_MTX_12_13_MTX_12(x)                     ((x) << 0)
>> +
>> +#define ISP_CCM_MTX_20_21                               0x60A8
>> +#define ISP_CCM_MTX_20_21_MTX_20_MASK                   GENMASK(12, 0)
>> +#define ISP_CCM_MTX_20_21_MTX_20(x)                     ((x) << 0)
>> +#define ISP_CCM_MTX_20_21_MTX_21_MASK                   GENMASK(28, 16)
>> +#define ISP_CCM_MTX_20_21_MTX_21(x)                     ((x) << 16)
>> +
>> +#define ISP_CCM_MTX_22_23_RS                            0x60Ac
>> +#define ISP_CCM_MTX_22_23_RS_MTX_22_MASK                GENMASK(12, 0)
>> +#define ISP_CCM_MTX_22_23_RS_MTX_22(x)                  ((x) << 0)
>> +
>> +#define ISP_PST_GAMMA_LUT_ADDR                          0x60cc
>> +#define ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(x)              ((x) << 7)
>> +
>> +#define ISP_PST_GAMMA_LUT_DATA                          0x60d0
>> +#define ISP_PST_GAMMA_LUT_DATA_DATA0(x)                 (((x) & GENMASK(15, 0)) << 0)
>> +#define ISP_PST_GAMMA_LUT_DATA_DATA1(x)                 (((x) & GENMASK(15, 0)) << 16)
>> +
>> +#define DISP0_TOP_TOP_CTRL                              0x8000
>> +#define DISP0_TOP_TOP_CTRL_CROP2_EN_MASK                BIT(5)
>> +#define DISP0_TOP_TOP_CTRL_CROP2_EN                     BIT(5)
>> +#define DISP0_TOP_TOP_CTRL_CROP2_DIS                    (0 << 5)
>> +
>> +#define DISP0_TOP_CRP2_START                            0x8004
>> +#define DISP0_TOP_CRP2_START_V_START_MASK               GENMASK(15, 0)
>> +#define DISP0_TOP_CRP2_START_V_START(x)                 ((x) << 0)
>> +#define DISP0_TOP_CRP2_START_H_START_MASK               GENMASK(31, 16)
>> +#define DISP0_TOP_CRP2_START_H_START(x)                 ((x) << 16)
>> +
>> +#define DISP0_TOP_CRP2_SIZE                             0x8008
>> +#define DISP0_TOP_CRP2_SIZE_V_SIZE_MASK                 GENMASK(15, 0)
>> +#define DISP0_TOP_CRP2_SIZE_V_SIZE(x)                   ((x) << 0)
>> +#define DISP0_TOP_CRP2_SIZE_H_SIZE_MASK                 GENMASK(31, 16)
>> +#define DISP0_TOP_CRP2_SIZE_H_SIZE(x)                   ((x) << 16)
>> +
>> +#define DISP0_TOP_OUT_SIZE                              0x800c
>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT_MASK       GENMASK(12, 0)
>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT(x)         ((x) << 0)
>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH_MASK        GENMASK(28, 16)
>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(x)          ((x) << 16)
>> +
>> +#define ISP_DISP0_TOP_IN_SIZE                           0x804c
>> +#define ISP_DISP0_TOP_IN_SIZE_VSIZE_MASK                GENMASK(12, 0)
>> +#define ISP_DISP0_TOP_IN_SIZE_VSIZE(x)                  ((x) << 0)
>> +#define ISP_DISP0_TOP_IN_SIZE_HSIZE_MASK                GENMASK(28, 16)
>> +#define ISP_DISP0_TOP_IN_SIZE_HSIZE(x)                  ((x) << 16)
>> +
>> +#define DISP0_PPS_SCALE_EN                              0x8200
>> +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK             GENMASK(3, 0)
>> +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM(x)               ((x) << 0)
>> +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM_MASK             GENMASK(7, 4)
>> +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM(x)               ((x) << 4)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK          GENMASK(11, 8)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(x)            ((x) << 8)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM_MASK          GENMASK(15, 12)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM(x)            ((x) << 12)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK             GENMASK(17, 16)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_RATE(x)               ((x) << 16)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK             GENMASK(19, 18)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_RATE(x)               ((x) << 18)
>> +#define DISP0_PPS_SCALE_EN_HSC_EN_MASK                  BIT(20)
>> +#define DISP0_PPS_SCALE_EN_HSC_EN(x)                    ((x) << 20)
>> +#define DISP0_PPS_SCALE_EN_HSC_DIS                      (0 << 20)
>> +#define DISP0_PPS_SCALE_EN_VSC_EN_MASK                  BIT(21)
>> +#define DISP0_PPS_SCALE_EN_VSC_EN(x)                    ((x) << 21)
>> +#define DISP0_PPS_SCALE_EN_VSC_DIS                      (0 << 21)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_EN_MASK               BIT(22)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_EN(x)                 ((x) << 22)
>> +#define DISP0_PPS_SCALE_EN_PREVSC_DIS                   (0 << 22)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_EN_MASK               BIT(23)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_EN(x)                 ((x) << 23)
>> +#define DISP0_PPS_SCALE_EN_PREHSC_DIS                   (0 << 23)
>> +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK         GENMASK(27, 24)
>> +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(x)           ((x) << 24)
>> +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK         GENMASK(31, 28)
>> +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(x)           ((x) << 28)
>> +
>> +#define DISP0_PPS_VSC_START_PHASE_STEP                  0x8224
>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK   GENMASK(23, 0)
>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(x)     ((x) << 0)
>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK   GENMASK(27, 24)
>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(x)     ((x) << 24)
>> +
>> +#define DISP0_PPS_HSC_START_PHASE_STEP                  0x8230
>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK  GENMASK(23, 0)
>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(x)    ((x) << 0)
>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK  GENMASK(27, 24)
>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(x)    ((x) << 24)
>> +
>> +#define DISP0_PPS_444TO422                              0x823c
>> +#define DISP0_PPS_444TO422_EN_MASK                      BIT(0)
>> +#define DISP0_PPS_444TO422_EN(x)                        ((x) << 0)
>> +
>> +#define ISP_SCALE0_COEF_IDX_LUMA                        0x8240
>> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_MASK     BIT(9)
>> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN       BIT(9)
>> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_DIS      (0 << 9)
>> +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE_MASK             GENMASK(12, 10)
>> +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE(x)               ((x) << 10)
>> +
>> +#define ISP_SCALE0_COEF_LUMA                            0x8244
>> +#define ISP_SCALE0_COEF_LUMA_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
>> +#define ISP_SCALE0_COEF_LUMA_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
>> +
>> +#define ISP_SCALE0_COEF_IDX_CHRO                        0x8248
>> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_MASK     BIT(9)
>> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN       BIT(9)
>> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_DIS      (0 << 9)
>> +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE_MASK             GENMASK(12, 10)
>> +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE(x)               ((x) << 10)
>> +
>> +#define ISP_SCALE0_COEF_CHRO                            0x824c
>> +#define ISP_SCALE0_COEF_CHRO_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
>> +#define ISP_SCALE0_COEF_CHRO_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
>> +
>> +#define ISP_AF_CTRL                                     0xa044
>> +#define ISP_AF_CTRL_VERT_OFST_MASK                      GENMASK(15, 14)
>> +#define ISP_AF_CTRL_VERT_OFST(x)                        ((x) << 14)
>> +#define ISP_AF_CTRL_HORIZ_OFST_MASK                     GENMASK(17, 16)
>> +#define ISP_AF_CTRL_HORIZ_OFST(x)                       ((x) << 16)
>> +
>> +#define ISP_AF_HV_SIZE                                  0xa04c
>> +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE_MASK               GENMASK(15, 0)
>> +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE(x)                 ((x) << 0)
>> +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE_MASK               GENMASK(31, 16)
>> +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE(x)                 ((x) << 16)
>> +
>> +#define ISP_AF_HV_BLKNUM                                0xa050
>> +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(5, 0)
>> +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
>> +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(21, 16)
>> +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
>> +
>> +#define ISP_AF_EN_CTRL                                  0xa054
>> +#define ISP_AF_EN_CTRL_STAT_SEL_MASK                    BIT(21)
>> +#define ISP_AF_EN_CTRL_STAT_SEL_OLD                     (0 << 21)
>> +#define ISP_AF_EN_CTRL_STAT_SEL_NEW                     BIT(21)
>> +
>> +#define ISP_AF_IDX_ADDR                                 0xa1c0
>> +#define ISP_AF_IDX_DATA                                 0xa1c4
>> +#define ISP_AF_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
>> +#define ISP_AF_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
>> +
>> +#define ISP_AE_CTRL                                     0xa448
>> +#define ISP_AE_CTRL_INPUT_2LINE_MASK                    BIT(7)
>> +#define ISP_AE_CTRL_INPUT_2LINE_EN                      BIT(7)
>> +#define ISP_AE_CTRL_INPUT_2LINE_DIS                     (0 << 7)
>> +#define ISP_AE_CTRL_LUMA_MODE_MASK                      GENMASK(9, 8)
>> +#define ISP_AE_CTRL_LUMA_MODE_CUR                       (0 << 8)
>> +#define ISP_AE_CTRL_LUMA_MODE_MAX                       BIT(8)
>> +#define ISP_AE_CTRL_LUMA_MODE_FILTER                    (2 << 8)
>> +#define ISP_AE_CTRL_VERT_OFST_MASK                      GENMASK(25, 24)
>> +#define ISP_AE_CTRL_VERT_OFST(x)                        ((x) << 24)
>> +#define ISP_AE_CTRL_HORIZ_OFST_MASK                     GENMASK(27, 26)
>> +#define ISP_AE_CTRL_HORIZ_OFST(x)                       ((x) << 26)
>> +
>> +#define ISP_AE_HV_SIZE                                  0xa464
>> +#define ISP_AE_HV_SIZE_VERT_SIZE_MASK                   GENMASK(15, 0)
>> +#define ISP_AE_HV_SIZE_VERT_SIZE(x)                     ((x) << 0)
>> +#define ISP_AE_HV_SIZE_HORIZ_SIZE_MASK                  GENMASK(31, 16)
>> +#define ISP_AE_HV_SIZE_HORIZ_SIZE(x)                    ((x) << 16)
>> +
>> +#define ISP_AE_HV_BLKNUM                                0xa468
>> +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(6, 0)
>> +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
>> +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(22, 16)
>> +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
>> +
>> +#define ISP_AE_IDX_ADDR                                 0xa600
>> +#define ISP_AE_IDX_DATA                                 0xa604
>> +#define ISP_AE_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
>> +#define ISP_AE_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
>> +
>> +#define ISP_AE_BLK_WT_ADDR                              0xa608
>> +#define ISP_AE_BLK_WT_DATA                              0xa60c
>> +#define ISP_AE_BLK_WT_DATA_IDX_DATA(i, x)               (((x) & GENMASK(3, 0)) << ((i) * 4))
>> +
>> +#define ISP_AWB_CTRL                                    0xa834
>> +#define ISP_AWB_CTRL_VERT_OFST_MASK                     GENMASK(1, 0)
>> +#define ISP_AWB_CTRL_VERT_OFST(x)                       ((x) << 0)
>> +#define ISP_AWB_CTRL_HORIZ_OFST_MASK                    GENMASK(3, 2)
>> +#define ISP_AWB_CTRL_HORIZ_OFST(x)                      ((x) << 2)
>> +
>> +#define ISP_AWB_HV_SIZE                                 0xa83c
>> +#define ISP_AWB_HV_SIZE_VERT_SIZE_MASK                  GENMASK(15, 0)
>> +#define ISP_AWB_HV_SIZE_VERT_SIZE(x)                    ((x) << 0)
>> +#define ISP_AWB_HV_SIZE_HORIZ_SIZE_MASK                 GENMASK(31, 16)
>> +#define ISP_AWB_HV_SIZE_HORIZ_SIZE(x)                   ((x) << 16)
>> +
>> +#define ISP_AWB_HV_BLKNUM                               0xa840
>> +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK            GENMASK(5, 0)
>> +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM(x)              ((x) << 0)
>> +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM_MASK           GENMASK(21, 16)
>> +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(x)             ((x) << 16)
>> +
>> +#define ISP_AWB_STAT_RG                                 0xa848
>> +#define ISP_AWB_STAT_RG_MIN_VALUE_MASK                  GENMASK(11, 0)
>> +#define ISP_AWB_STAT_RG_MIN_VALUE(x)                    ((x) << 0)
>> +#define ISP_AWB_STAT_RG_MAX_VALUE_MASK                  GENMASK(27, 16)
>> +#define ISP_AWB_STAT_RG_MAX_VALUE(x)                    ((x) << 16)
>> +
>> +#define ISP_AWB_STAT_BG                                 0xa84c
>> +#define ISP_AWB_STAT_BG_MIN_VALUE_MASK                  GENMASK(11, 0)
>> +#define ISP_AWB_STAT_BG_MIN_VALUE(x)                    ((x) << 0)
>> +#define ISP_AWB_STAT_BG_MAX_VALUE_MASK                  GENMASK(27, 16)
>> +#define ISP_AWB_STAT_BG_MAX_VALUE(x)                    ((x) << 16)
>> +
>> +#define ISP_AWB_STAT_RG_HL                              0xa850
>> +#define ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
>> +#define ISP_AWB_STAT_RG_HL_LOW_VALUE(x)                 ((x) << 0)
>> +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
>> +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE(x)                ((x) << 16)
>> +
>> +#define ISP_AWB_STAT_BG_HL                              0xa854
>> +#define ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
>> +#define ISP_AWB_STAT_BG_HL_LOW_VALUE(x)                 ((x) << 0)
>> +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
>> +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE(x)                ((x) << 16)
>> +
>> +#define ISP_AWB_STAT_CTRL2                              0xa858
>> +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK              BIT(0)
>> +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL(x)                ((x) << 0)
>> +
>> +#define ISP_AWB_IDX_ADDR                                0xaa00
>> +#define ISP_AWB_IDX_DATA                                0xaa04
>> +#define ISP_AWB_IDX_DATA_VIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 0)
>> +#define ISP_AWB_IDX_DATA_HIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 16)
>> +
>> +#define ISP_AWB_BLK_WT_ADDR                             0xaa08
>> +#define ISP_AWB_BLK_WT_DATA                             0xaa0c
>> +#define ISP_AWB_BLK_WT_DATA_IDX_DATA(i, x)              (((x) & GENMASK(3, 0)) << ((i) * 4))
>> +
>> +#define ISP_WRMIFX3_0_CH0_CTRL0                         0xc400
>> +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK             GENMASK(28, 16)
>> +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(x)               ((x) << 16)
>> +
>> +#define ISP_WRMIFX3_0_CH0_CTRL1                         0xc404
>> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
>> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS          BIT(27)
>> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS         (2 << 27)
>> +
>> +#define ISP_WRMIFX3_0_CH1_CTRL0                         0xc408
>> +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK             GENMASK(28, 16)
>> +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(x)               ((x) << 16)
>> +
>> +#define ISP_WRMIFX3_0_CH1_CTRL1                         0xc40c
>> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
>> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS         (2 << 27)
>> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_32BITS         (3 << 27)
>> +
>> +#define ISP_WRMIFX3_0_CH0_BADDR                         0xc440
>> +#define ISP_WRMIFX3_0_CH0_BASE_ADDR(x)                  ((x) >> 4)
>> +
>> +#define ISP_WRMIFX3_0_CH1_BADDR                         0xc444
>> +#define ISP_WRMIFX3_0_CH1_BASE_ADDR(x)                  ((x) >> 4)
>> +
>> +#define ISP_WRMIFX3_0_FMT_SIZE                          0xc464
>> +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE_MASK               GENMASK(15, 0)
>> +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE(x)                 ((x) << 0)
>> +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE_MASK               GENMASK(31, 16)
>> +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE(x)                 ((x) << 16)
>> +
>> +#define ISP_WRMIFX3_0_FMT_CTRL                          0xc468
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK           GENMASK(1, 0)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT           (0 << 0)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_10BIT          BIT(0)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_12BIT          (2 << 0)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT          (3 << 0)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK         BIT(2)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU           (0 << 2)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV           BIT(2)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK           GENMASK(5, 4)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1             (0 << 4)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2             BIT(4)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK            GENMASK(18, 16)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422          BIT(16)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420          (2 << 16)
>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY          (3 << 16)
>> +
>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                        0xc420
>> +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK         GENMASK(28, 16)
>> +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(x)           (((x) - 1) << 16)
>> +
>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                        0xc424
>> +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK         GENMASK(28, 16)
>> +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(x)           (((x) - 1) << 16)
>> +
>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                       0xc428
>> +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK       GENMASK(28, 16)
>> +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(x)         (((x) - 1) << 16)
>> +
>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                       0xc42c
>> +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK       GENMASK(28, 16)
>> +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(x)         (((x) - 1) << 16)
>> +
>> +#define VIU_DMAWR_BADDR0                                0xc840
>> +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
>> +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(x)          ((x) >> 4)
>> +
>> +#define VIU_DMAWR_BADDR1                                0xc844
>> +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK       GENMASK(27, 0)
>> +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(x)         ((x) >> 4)
>> +
>> +#define VIU_DMAWR_BADDR2                                0xc848
>> +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
>> +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(x)          ((x) >> 4)
>> +
>> +#define VIU_DMAWR_SIZE0                                 0xc854
>> +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK              GENMASK(15, 0)
>> +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE(x)                ((x) << 0)
>> +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK             GENMASK(31, 16)
>> +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE(x)               ((x) << 16)
>> +
>> +#define VIU_DMAWR_SIZE1                                 0xc858
>> +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK              GENMASK(15, 0)
>> +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE(x)                ((x) << 0)
>> +
>> +#endif
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
>> new file mode 100644
>> index 000000000000..b179eff4cfd6
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
>> @@ -0,0 +1,779 @@
>> +// 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"
>> +
>> +/* The normal parameters of pps module */
>> +static const int pps_lut_tap4_s11[C3_ISP_PPS_TAP4_S11_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 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 step_h_integer, step_v_integer;
>> +     int step_h_fraction, step_v_fraction;
>> +     int yuv444to422_en;
>> +
>> +     /* Calculate the integer part of horizonal scaler step */
>> +     step_h_integer = thsize / ohsize;
>> +
>> +     /* Calculate the vertical part of horizonal scaler step */
>> +     step_v_integer = 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.
>> +      */
>> +     step_h_fraction = ((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.
>> +      */
>> +     step_v_fraction = ((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_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK,
>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(step_v_fraction));
>> +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK,
>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(step_v_integer));
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK,
>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(step_h_fraction));
>> +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK,
>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(step_h_integer));
>> +}
>> +
>> +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_TAP4_S11_H_NUM; i++) {
>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>> +                          ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>> +                          ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>> +                          ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>> +                          ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[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_TAP4_S11_H_NUM; i++) {
>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>> +                          ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>> +                          ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>> +                          ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>> +                          ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[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_RESIZER_PAD_SINK);
>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     ihsize = crop->width;
>> +     ivsize = crop->height;
>> +
>> +     hsc_en = (ihsize == cmps->width) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_EN;
>> +     vsc_en = (ivsize == cmps->height) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_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.
> minor nit: seems like you should wrap comments here too


Will wrap comments.

>> +      */
>> +     preh_en = (ihsize > cmps->width * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
>> +     prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
>> +
>> +     if (rsz->id == C3_ISP_RSZ_2) {
>> +             max_hsize = C3_ISP_MAX_WIDTH;
>> +             /*
>> +              * Set vertical tap number and
>> +              * the max hsize of pre-vertical scale.
>> +              */
>> +             prevsc_flt_num = 4;
>> +             pre_vscale_max_hsize = max_hsize / 2;
>> +     } else {
>> +             max_hsize = C3_ISP_DEFAULT_WIDTH;
>> +             preh_en  = (ihsize > max_hsize) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
>> +             /*
>> +              * Set vertical tap number and
>> +              * the max hsize of pre-vertical scale.
>> +              */
>> +             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;
>> +     struct v4l2_rect *sink_crop;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>> +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>> +
>> +     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);
>> +
>> +     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_SCALER_OUT_HEIGHT_MASK,
>> +                        DISP0_TOP_OUT_SIZE_SCALER_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_SCALER_OUT_WIDTH_MASK,
>> +                        DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(src_fmt->width));
>> +
>> +     if (rsz->id == C3_ISP_RSZ_0) {
>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
>> +                                ISP_TOP_PATH_EN_DISP0_EN);
>> +     } else if (rsz->id == C3_ISP_RSZ_1) {
>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
>> +                                ISP_TOP_PATH_EN_DISP1_EN);
>> +     } else if (rsz->id == C3_ISP_RSZ_2) {
>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
>> +                                ISP_TOP_PATH_EN_DISP2_EN);
>> +     }
>> +}
>> +
>> +static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>> +{
>> +     if (rsz->id == C3_ISP_RSZ_0) {
>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
>> +                                ISP_TOP_PATH_EN_DISP0_DIS);
>> +     } else if (rsz->id == C3_ISP_RSZ_1) {
>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
>> +                                ISP_TOP_PATH_EN_DISP1_DIS);
>> +     } else if (rsz->id == C3_ISP_RSZ_2) {
>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
>> +                                ISP_TOP_PATH_EN_DISP2_DIS);
>> +     }
>> +
>> +     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 bool c3_isp_rsz_streams_ready(struct c3_isp_resizer *rsz)
>> +{
>> +     unsigned int n_links = 0;
>> +     struct media_link *link;
>> +
>> +     for_each_media_entity_data_link(&rsz->src_sd->entity, link) {
>> +             if (link->source->index == rsz->src_sd_pad &&
>> +                 link->flags == MEDIA_LNK_FL_ENABLED)
>> +                     n_links++;
>> +     }
>> +
>> +     return n_links == rsz->isp->pipe.start_count;
>> +}
>> +
>> +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);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     c3_isp_rsz_start(rsz, state);
>> +
>> +     if (!c3_isp_rsz_streams_ready(rsz))
>> +             return 0;
>> +
>> +     c3_isp_params_pre_cfg(rsz->isp);
>> +     c3_isp_stats_pre_cfg(rsz->isp);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    C3_ISP_RESIZER_PAD_SINK,
>> +                                                    &streams_mask);
> Apart from the usual comments about routing, reflowing comments and
> minor nits, this module looks ok to me. I admit I have not tried to
> fully understand the scaler programming part at the beginning, but I
> trust you know the HW better than me :)


Will remove sink_streams.

> The stats driver looks ok as well.
>
> I hope next version will be good to go, and I hope to be able to test
> this implementation soon using libcamera!
>
> Great work!
>
> Thanks
>    j
>

Thanks very much! :)

>> +     ret = v4l2_subdev_enable_streams(rsz->src_sd,
>> +                                      rsz->src_sd_pad, sink_streams);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 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);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     c3_isp_rsz_stop(rsz);
>> +
>> +     if (rsz->isp->pipe.start_count != 1)
>> +             return 0;
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    C3_ISP_RESIZER_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_disable_streams(rsz->src_sd,
>> +                                       rsz->src_sd_pad, sink_streams);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state,
>> +                               struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = C3_ISP_DEFAULT_WIDTH,
>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>> +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>> +     routes.sink_stream = 0;
>> +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>> +     routes.source_stream = 0;
>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &routes;
>> +
>> +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                  struct v4l2_subdev_state *state,
>> +                                  struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     if (code->index)
>> +             return -EINVAL;
>> +
>> +     code->code = MEDIA_BUS_FMT_YUV8_1X24;
>> +
>> +     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;
>> +
>> +     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_RESIZER_PAD_SOURCE);
>> +
>> +     sink_fmt->code = MEDIA_BUS_FMT_YUV8_1X24;
>> +     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_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;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>> +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     src_fmt->code = sink_fmt->code;
>> +     src_fmt->width = sink_cmps->width;
>> +     src_fmt->height = sink_cmps->height;
>> +
>> +     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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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 *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_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_RESIZER_PAD_SINK);
>> +     cmps->width = C3_ISP_DEFAULT_WIDTH;
>> +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
>> +     cmps->left = 0;
>> +     cmps->top = 0;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>> +     *src_fmt = *sink_fmt;
>> +
>> +     return c3_isp_rsz_init_routing(sd, state);
>> +}
>> +
>> +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_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_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)
>> +{
>> +     u32 i;
>> +     int ret;
>> +
>> +     for (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_sd_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>> +
>> +             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)
>> +{
>> +     u32 i;
>> +
>> +     for (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);
>> +     };
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
>> new file mode 100644
>> index 000000000000..1c376c800176
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
>> @@ -0,0 +1,319 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#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-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>> +{
>> +     u32 awb_dma_size = sizeof(struct c3_isp_awb_stats);
>> +     u32 ae_dma_size = sizeof(struct c3_isp_ae_stats);
>> +     u32 awb_dma_addr = stats->buff->dma_addr;
>> +     u32 af_dma_addr;
>> +     u32 ae_dma_addr;
>> +
>> +     ae_dma_addr = awb_dma_addr + awb_dma_size;
>> +     af_dma_addr = ae_dma_addr + ae_dma_size;
>> +
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR0,
>> +                        VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK,
>> +                        VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(af_dma_addr));
>> +
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR1,
>> +                        VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK,
>> +                        VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(awb_dma_addr));
>> +
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR2,
>> +                        VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK,
>> +                        VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(ae_dma_addr));
>> +}
>> +
>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>> +{
>> +     stats->buff = list_first_entry_or_null(&stats->pending,
>> +                                            struct c3_isp_buffer, list);
>> +     if (stats->buff) {
>> +             c3_isp_stats_cfg_dmawr_addr(stats);
>> +             list_del(&stats->buff->list);
>> +     }
>> +}
>> +
>> +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +     u32 dma_size;
>> +
>> +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, ISP_AF_EN_CTRL_STAT_SEL_MASK,
>> +                        ISP_AF_EN_CTRL_STAT_SEL_NEW);
>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, ISP_AE_CTRL_LUMA_MODE_MASK,
>> +                        ISP_AE_CTRL_LUMA_MODE_FILTER);
>> +
>> +     /* The unit of dma_size is 16 bytes */
>> +     dma_size = sizeof(struct c3_isp_af_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK,
>> +                        VIU_DMAWR_SIZE0_AF_STATS_SIZE(dma_size));
>> +
>> +     dma_size = sizeof(struct c3_isp_awb_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK,
>> +                        VIU_DMAWR_SIZE0_AWB_STATS_SIZE(dma_size));
>> +
>> +     dma_size = sizeof(struct c3_isp_ae_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK,
>> +                        VIU_DMAWR_SIZE1_AE_STATS_SIZE(dma_size));
>> +
>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>> +
>> +     c3_isp_stats_cfg_buff(stats);
>> +}
>> +
>> +static int c3_isp_stats_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_stats_enum_fmt(struct file *file, void *fh,
>> +                              struct v4l2_fmtdesc *f)
>> +{
>> +     struct c3_isp_stats *stats = video_drvdata(file);
>> +
>> +     if (f->index > 0 || f->type != stats->vb2_q.type)
>> +             return -EINVAL;
>> +
>> +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>> +                           struct v4l2_format *f)
>> +{
>> +     struct c3_isp_stats *stats = video_drvdata(file);
>> +
>> +     f->fmt.meta = stats->vfmt.fmt.meta;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_stats_querycap,
>> +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>> +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>> +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>> +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_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_stats_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_stats_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_stats_buffer))
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = sizeof(struct c3_isp_stats_buffer);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>> +
>> +     list_add_tail(&buf->list, &stats->pending);
>> +}
>> +
>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>> +             dev_err(stats->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
>> +
>> +     buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>> +
>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>> +
>> +     if (stats->buff) {
>> +             vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>> +             stats->buff = NULL;
>> +     }
>> +
>> +     while (!list_empty(&stats->pending)) {
>> +             struct c3_isp_buffer *buff;
>> +
>> +             buff = list_first_entry(&stats->pending,
>> +                                     struct c3_isp_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>> +     }
>> +}
>> +
>> +static const struct vb2_ops isp_stats_vb2_ops = {
>> +     .queue_setup = c3_isp_stats_vb2_queue_setup,
>> +     .buf_queue = c3_isp_stats_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>> +     .buf_init = c3_isp_stats_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>> +};
>> +
>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +     struct video_device *vdev = &stats->vdev;
>> +     struct vb2_queue *vb2_q = &stats->vb2_q;
>> +     int ret;
>> +
>> +     memset(stats, 0, sizeof(*stats));
>> +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>> +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_buffer);
>> +     stats->isp = isp;
>> +     INIT_LIST_HEAD(&stats->pending);
>> +     spin_lock_init(&stats->buff_lock);
>> +
>> +     mutex_init(&stats->lock);
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "c3-isp-stats");
>> +     vdev->fops = &isp_stats_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>> +     vdev->lock = &stats->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_RX;
>> +     video_set_drvdata(vdev, stats);
>> +
>> +     vb2_q->drv_priv = stats;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_stats_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>> +     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_buffer);
>> +     vb2_q->dev = isp->dev;
>> +     vb2_q->lock = &stats->lock;
>> +     vb2_q->min_queued_buffers = 2;
>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret)
>> +             goto err_destroy;
>> +
>> +     stats->pad.flags = MEDIA_PAD_FL_SINK;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>> +     if (ret)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret) {
>> +             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_destroy:
>> +     mutex_destroy(&stats->lock);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +
>> +     vb2_queue_release(&stats->vb2_q);
>> +     media_entity_cleanup(&stats->vdev.entity);
>> +     video_unregister_device(&stats->vdev);
>> +     mutex_destroy(&stats->lock);
>> +}
>> +
>> +void c3_isp_stats_isr(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +
>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>> +
>> +     if (stats->buff) {
>> +             stats->buff->vb.sequence = stats->isp->frm_sequence;
>> +             stats->buff->vb.vb2_buf.timestamp = ktime_get();
>> +             stats->buff->vb.field = V4L2_FIELD_NONE;
>> +             vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +     }
>> +
>> +     c3_isp_stats_cfg_buff(stats);
>> +}
>>
>> --
>> 2.47.0
>>
>>
>>
Jacopo Mondi Dec. 13, 2024, 11:34 a.m. UTC | #3
Hi Keke

On Fri, Dec 13, 2024 at 07:14:16PM +0800, Keke Li wrote:
> Hi Jacopo
>
> Thanks very much for your reply.
>
> On 2024/12/12 23:39, Jacopo Mondi wrote:
> > [ EXTERNAL EMAIL ]
> >
> > Hi Keke
> >
> > On Thu, Dec 05, 2024 at 05:04:34PM +0800, Keke Li via B4 Relay wrote:
> > > From: Keke Li <keke.li@amlogic.com>
> > >
> > > The C3 ISP supports multi-camera and muti-exposure
> > > high dynamic range (HDR). It brings together some
> > > advanced imaging technologies to provide good image quality.
> > > This driver mainly responsible for driving ISP pipeline
> > > to process raw image.
> > >
> > > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > > ---
> > >   drivers/media/platform/amlogic/c3/Kconfig          |   1 +
> > >   drivers/media/platform/amlogic/c3/Makefile         |   1 +
> > >   drivers/media/platform/amlogic/c3/isp/Kconfig      |  17 +
> > >   drivers/media/platform/amlogic/c3/isp/Makefile     |  10 +
> > >   .../media/platform/amlogic/c3/isp/c3-isp-capture.c | 790 +++++++++++++++++
> > >   .../media/platform/amlogic/c3/isp/c3-isp-common.h  | 352 ++++++++
> > >   .../media/platform/amlogic/c3/isp/c3-isp-core.c    | 553 ++++++++++++
> > >   drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c | 459 ++++++++++
> > >   .../media/platform/amlogic/c3/isp/c3-isp-params.c  | 942 +++++++++++++++++++++
> > >   .../media/platform/amlogic/c3/isp/c3-isp-regs.h    | 599 +++++++++++++
> > >   .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 779 +++++++++++++++++
> > >   .../media/platform/amlogic/c3/isp/c3-isp-stats.c   | 319 +++++++
> > >   12 files changed, 4822 insertions(+)
> > >
> > > diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig
> > > index a504a1eb22e6..d355d3a9358d 100644
> > > --- a/drivers/media/platform/amlogic/c3/Kconfig
> > > +++ b/drivers/media/platform/amlogic/c3/Kconfig
> > > @@ -1,4 +1,5 @@
> > >   # SPDX-License-Identifier: GPL-2.0-only
> > >
> > > +source "drivers/media/platform/amlogic/c3/isp/Kconfig"
> > >   source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig"
> > >   source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig"
> > > diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile
> > > index 770b2a2903ad..14f305a493d2 100644
> > > --- a/drivers/media/platform/amlogic/c3/Makefile
> > > +++ b/drivers/media/platform/amlogic/c3/Makefile
> > > @@ -1,4 +1,5 @@
> > >   # SPDX-License-Identifier: GPL-2.0-only
> > >
> > > +obj-y += isp/
> > >   obj-y += mipi-adapter/
> > >   obj-y += mipi-csi2/
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/Kconfig b/drivers/media/platform/amlogic/c3/isp/Kconfig
> > > new file mode 100644
> > > index 000000000000..de68d19ca2d1
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/Kconfig
> > > @@ -0,0 +1,17 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +config VIDEO_C3_ISP
> > > +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
> > > +     depends on ARCH_MESON || COMPILE_TEST
> > > +     depends on VIDEO_DEV
> > > +     depends on OF
> > > +     select MEDIA_CONTROLLER
> > > +     select V4L2_FWNODE
> > > +     select VIDEO_V4L2_SUBDEV_API
> > > +     select VIDEOBUF2_DMA_CONTIG
> > also
> >          select VIDEOBUF2_VMALLOC
> >
> > for the params node
> >
>
> Will add 'select VIDEOBUF2_VMALLOC'
>

Yes please, I just tried and building with aarch64 defconfig breaks
without this

> > > +     help
> > > +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
> > > +       The C3 ISP is used for processing raw images and
> > > +       outputing results to memory.
> > > +
> > > +       To compile this driver as a module choose m here.
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/Makefile b/drivers/media/platform/amlogic/c3/isp/Makefile
> > > new file mode 100644
> > > index 000000000000..b1b064170b57
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/Makefile
> > > @@ -0,0 +1,10 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +c3-isp-objs := c3-isp-dev.o \
> > > +            c3-isp-params.o \
> > > +            c3-isp-stats.o \
> > > +            c3-isp-capture.o \
> > > +            c3-isp-core.o \
> > > +            c3-isp-resizer.o
> > > +
> > > +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
> > > new file mode 100644
> > > index 000000000000..e1bbb3fa8da2
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
> > > @@ -0,0 +1,790 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/cleanup.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-event.h>
> > > +#include <media/v4l2-ioctl.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/videobuf2-dma-contig.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +static const struct c3_isp_capture_format cap_formats[] = {
> > > +     {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> > > +             .fourcc = V4L2_PIX_FMT_GREY,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> > > +             .fourcc = V4L2_PIX_FMT_NV12,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> > > +             .fourcc = V4L2_PIX_FMT_NV21,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> > > +             .fourcc = V4L2_PIX_FMT_NV16,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
> > > +             .fourcc = V4L2_PIX_FMT_NV61,
> > > +     },
> > > +};
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +/* Set the address of wrmifx3(write memory interface) */
> > > +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > > +     dma_addr_t dma_addr;
> > > +
> > > +     if (cap->buff)
> > > +             dma_addr = cap->buff->dma_addr;
> > > +     else
> > > +             dma_addr = cap->dummy_buff.dma_addr;
> > > +
> > > +     c3_isp_write(cap->isp,
> > > +                  C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
> > > +                  ISP_WRMIFX3_0_CH0_BASE_ADDR(dma_addr));
> > > +
> > > +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
> > > +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
> > > +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
> > > +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
> > > +             u32 offset = ALIGN(pix->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES) * pix->height;
> > I'm sorry but V4L2 has a requirement about 80-cols stricter than other
> > subsystem (I know..)
> >
> > Could you run
> > ./scripts/checkpatch.pl --strict --max-line-length=80
> >
> > on your patches and see how you can fix these ?
>
>
> Will check this issue and adjust max-line-length to 80.
>

Thanks, I know it's sometimes annoying

> > > +
> > > +             c3_isp_write(cap->isp,
> > > +                          C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
> > > +                          ISP_WRMIFX3_0_CH1_BASE_ADDR(dma_addr + offset));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +     u32 stride;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
> > > +
> > > +     /* Grey has 1 plane*/
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1);
> > > +
> > > +     /* Set Y only as output format */
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY);
> > > +
> > > +     /* The unit of stride is 16 bytes */
> > > +     stride = DIV_ROUND_UP(fmt->width * 1, 16);
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > > +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
> > > +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > > +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
> > > +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, bool swap)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +     u32 stride;
> > > +     u32 swap_uv;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
> > > +
> > > +     swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
> > > +                      ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
> > > +
> > > +     /* NV12 or NV21 has 2 planes*/
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
> > > +
> > > +     /* Set YUV420 as output format */
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width >> 1));
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
> > > +
> > > +     /* The unit of Y and UV strides is 16 bytes */
> > > +     stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > > +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
> > > +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> > > +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
> > > +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > > +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
> > > +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> > > +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
> > > +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, bool swap)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +     u32 stride;
> > > +     u32 swap_uv;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT);
> > > +
> > > +     swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
> > > +                      ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
> > > +
> > > +     /* NV16 or NV61 has 2 planes*/
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
> > > +
> > > +     /* Set YUV422 as output format */
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
> > > +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width));
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
> > > +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
> > > +
> > > +     /* The unit of Y stride is 16 bytes */
> > > +     stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > > +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
> > > +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
> > > +
> > > +     /* The unit of UV stride is 16 bytes */
> > > +     stride = DIV_ROUND_UP(fmt->width * 2, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> > > +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
> > > +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > > +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
> > > +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> > > +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
> > > +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
> > > +}
> > I feel like the above functions could 'easily' be unified in a single
> > one that computes the right parameters using the pixel format info.
> >
> > Feel free to expand 'struct c3_isp_capture_format' with all the info
> > you need to compute the correct register values if you think unifying
> > the above functions is helpful
> >
>
> Will try to test your suggestion.
>
> > > +
> > > +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +
> > > +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY)
> > > +             c3_isp_cap_wrmifx3_grey(cap);
> > > +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV12)
> > > +             c3_isp_cap_wrmifx3_yuv420(cap, true);
> > > +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV21)
> > > +             c3_isp_cap_wrmifx3_yuv420(cap, false);
> > > +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV16)
> > > +             c3_isp_cap_wrmifx3_yuv422(cap, true);
> > > +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV61)
> > > +             c3_isp_cap_wrmifx3_yuv422(cap, false);
> > > +
> > > +     c3_isp_write(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
> > > +                  ISP_WRMIFX3_0_FMT_SIZE_HSIZE(fmt->width) |
> > > +                  ISP_WRMIFX3_0_FMT_SIZE_VSIZE(fmt->height));
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
> > > +                        ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK,
> > > +                        ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(fmt->width));
> > > +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
> > > +                        ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK,
> > > +                        ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(fmt->height));
> > > +}
> > > +
> > > +static int c3_isp_cap_dummy_buff_create(struct c3_isp_capture *cap)
> > > +{
> > > +     struct c3_isp_dummy_buffer *dummy_buff = &cap->dummy_buff;
> > > +
> > > +     dummy_buff->size = cap->vfmt.fmt.pix.sizeimage;
> > > +
> > > +     /* The driver never access vaddr, no mapping is required */
> > > +     dummy_buff->vaddr = dma_alloc_attrs(cap->isp->dev,
> > > +                                         dummy_buff->size,
> > > +                                         &dummy_buff->dma_addr,
> > > +                                         GFP_KERNEL,
> > > +                                         DMA_ATTR_NO_KERNEL_MAPPING);
> > > +     if (!dummy_buff->vaddr)
> > > +             return -ENOMEM;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_cap_dummy_buff_destroy(struct c3_isp_capture *cap)
> > > +{
> > > +     dma_free_attrs(cap->isp->dev,
> > > +                    cap->dummy_buff.size, cap->dummy_buff.vaddr,
> > > +                    cap->dummy_buff.dma_addr, DMA_ATTR_NO_KERNEL_MAPPING);
> > > +}
> > > +
> > > +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
> > > +{
> > > +     cap->buff = list_first_entry_or_null(&cap->pending,
> > > +                                          struct c3_isp_buffer, list);
> > > +
> > > +     c3_isp_cap_wrmifx3_buff(cap);
> > > +
> > > +     if (cap->buff)
> > > +             list_del(&cap->buff->list);
> > > +}
> > > +
> > > +static void c3_isp_cap_start(struct c3_isp_capture *cap)
> > > +{
> > > +     scoped_guard(spinlock_irqsave, &cap->buff_lock)
> > > +             c3_isp_cap_cfg_buff(cap);
> > > +
> > > +     c3_isp_cap_wrmifx3_size(cap);
> > > +
> > > +     if (cap->id == C3_ISP_CAP_DEV_0) {
> > > +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_WRMIF0_EN);
> > > +     } else if (cap->id == C3_ISP_CAP_DEV_1) {
> > > +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_WRMIF1_EN);
> > > +     } else if (cap->id == C3_ISP_CAP_DEV_2) {
> > > +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_WRMIF2_EN);
> > > +     }
> > You could spare the {} for single line statements
> >
> > Could this maybe be simplified as
> >
> >          u32 val = cap->id == C3_ISP_CAP_DEV_0 ? ISP_TOP_PATH_EN_WRMIF0_EN :
> >                    cap->id == C3_ISP_CAP_DEV_1 ? ISP_TOP_PATH_EN_WRMIF1_EN :
> >                    cap->id == C3_ISP_CAP_DEV_2 ? ISP_TOP_PATH_EN_WRMIF2_EN : 0;
> >
> >          scoped_guard(spinlock_irqsave, &cap->buff_lock)
> >                  c3_isp_cap_cfg_buff(cap);
> >
> >          c3_isp_cap_wrmifx3_size(cap);
> >          c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, val, val);
> >
>
> Will use your method.
>
> > > +}
> > > +
> > > +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
> > > +{
> > > +     if (cap->id == C3_ISP_CAP_DEV_0) {
> > > +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_WRMIF0_DIS);
> > > +     } else if (cap->id == C3_ISP_CAP_DEV_1) {
> > > +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_WRMIF1_DIS);
> > > +     } else if (cap->id == C3_ISP_CAP_DEV_2) {
> > > +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_WRMIF2_DIS);
> > > +     }
> > same here
>
>
> Will use your method.
>
> > > +}
> > > +
> > > +static void c3_isp_cap_done(struct c3_isp_capture *cap)
> > > +{
> > > +     struct c3_isp_buffer *buff = cap->buff;
> > > +
> > > +     guard(spinlock_irqsave)(&cap->buff_lock);
> > > +
> > > +     if (buff) {
> > > +             buff->vb.sequence = cap->isp->frm_sequence;
> > > +             buff->vb.vb2_buf.timestamp = ktime_get();
> > > +             buff->vb.field = V4L2_FIELD_NONE;
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > > +     }
> > > +
> > > +     c3_isp_cap_cfg_buff(cap);
> > > +}
> > > +
> > > +/* V4L2 video operations */
> > > +
> > > +static const struct c3_isp_capture_format
> > > +*c3_cap_find_fmt(u32 fourcc)
> > fits in one line
>
>
> Will fit in one line.
>
> > > +{
> > > +     unsigned int i;
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> > nit: you can declare i inside the for loop
>
>
> Will use 'unsigned int i = 0' in the for loop.
>
> > > +             if (cap_formats[i].fourcc == fourcc)
> > > +                     return &cap_formats[i];
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
> > > +                        struct v4l2_pix_format *pix)
> > > +{
> > > +     const struct c3_isp_capture_format *fmt;
> > > +     const struct v4l2_format_info *info;
> > > +
> > > +     fmt = c3_cap_find_fmt(pix->pixelformat);
> > > +     if (!fmt)
> > > +             fmt = &cap_formats[0];
> > > +
> > > +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > > +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > > +     pix->pixelformat = fmt->fourcc;
> > > +     pix->field = V4L2_FIELD_NONE;
> > > +     pix->colorspace = V4L2_COLORSPACE_SRGB;
> > > +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > > +
> > > +     info = v4l2_format_info(fmt->fourcc);
> > > +     pix->bytesperline = pix->width * info->bpp[0] / info->bpp_div[0];
> > > +
> > > +     /* ISP hardware requires 16 bytes alignment */
> > > +     pix->bytesperline = ALIGN(pix->bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> > > +
> > > +     pix->sizeimage = 0;
> > > +     for (unsigned int i = 0; i < info->comp_planes; i++) {
> > > +             unsigned int hdiv = (i == 0) ? 1 : info->hdiv;
> > > +             unsigned int vdiv = (i == 0) ? 1 : info->vdiv;
> > > +             unsigned int bytesperline;
> > > +
> > > +             bytesperline = info->bpp[i] * DIV_ROUND_UP(pix->width, hdiv) / info->bpp_div[i];
> > > +
> > > +             /* ISP hardware requires 16 bytes alignment */
> > > +             bytesperline = ALIGN(bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
> > > +             pix->sizeimage += bytesperline * DIV_ROUND_UP(pix->height, vdiv);
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
> > > +                                   enum vb2_buffer_state state)
> > > +{
> > > +     struct c3_isp_buffer *buff;
> > > +
> > > +     guard(spinlock_irqsave)(&cap->buff_lock);
> > > +
> > > +     if (cap->buff) {
> > > +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
> > > +             cap->buff = NULL;
> > > +     }
> > > +
> > > +     while (!list_empty(&cap->pending)) {
> > > +             buff = list_first_entry(&cap->pending,
> > > +                                     struct c3_isp_buffer, list);
> > > +             list_del(&buff->list);
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, state);
> > > +     }
> > > +}
> > > +
> > > +static int c3_isp_cap_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_cap_enum_fmt(struct file *file, void *fh,
> > > +                            struct v4l2_fmtdesc *f)
> > > +{
> > > +     const struct c3_isp_capture_format *fmt;
> > > +     unsigned int index = 0;
> > > +     unsigned int i;
> > > +
> > > +     if (!f->mbus_code) {
> > > +             if (f->index >= ARRAY_SIZE(cap_formats))
> > > +                     return -EINVAL;
> > > +
> > > +             fmt = &cap_formats[f->index];
> > > +             f->pixelformat = fmt->fourcc;
> > > +             return 0;
> > > +     }
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> > > +             fmt = &cap_formats[i];
> > > +             if (f->mbus_code != fmt->mbus_code)
> > > +                     continue;
> > > +
> > > +             if (index++ == f->index) {
> > > +                     f->pixelformat = cap_formats[i].fourcc;
> > > +                     return 0;
> > > +             }
> > > +     }
> > > +
> > > +     return -EINVAL;
> > > +}
> > > +
> > > +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
> > > +                         struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_capture *cap = video_drvdata(file);
> > > +
> > > +     f->fmt.pix = cap->vfmt.fmt.pix;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
> > > +                         struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_capture *cap = video_drvdata(file);
> > > +
> > > +     c3_cap_try_fmt(cap, &f->fmt.pix);
> > > +     cap->vfmt = *f;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
> > > +                           struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_capture *cap = video_drvdata(file);
> > > +
> > > +     c3_cap_try_fmt(cap, &f->fmt.pix);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
> > > +                                struct v4l2_frmsizeenum *fsize)
> > > +{
> > > +     const struct c3_isp_capture_format *fmt;
> > > +
> > > +     if (fsize->index)
> > > +             return -EINVAL;
> > > +
> > > +     fmt = c3_cap_find_fmt(fsize->pixel_format);
> > > +     if (!fmt)
> > > +             return -EINVAL;
> > > +
> > > +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> > > +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
> > > +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
> > > +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
> > > +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
> > > +     fsize->stepwise.step_width = 2;
> > > +     fsize->stepwise.step_height = 2;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
> > > +     .vidioc_querycap                = c3_isp_cap_querycap,
> > > +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
> > > +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
> > > +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
> > > +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_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,
> > > +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
> > > +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> > > +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> > > +};
> > > +
> > > +static const struct v4l2_file_operations isp_cap_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_cap_link_validate(struct media_link *link)
> > > +{
> > > +     struct video_device *vdev =
> > > +             media_entity_to_video_device(link->sink->entity);
> > > +     struct v4l2_subdev *sd =
> > > +             media_entity_to_v4l2_subdev(link->source->entity);
> > > +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
> > > +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
> > > +     struct v4l2_subdev_format src_fmt = {
> > > +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> > > +             .pad = link->source->index,
> > > +     };
> > > +     const struct c3_isp_capture_format *cap_fmt =
> > > +                             c3_cap_find_fmt(pix_fmt->pixelformat);
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     if (src_fmt.format.width != pix_fmt->width ||
> > > +         src_fmt.format.height != pix_fmt->height ||
> > > +         src_fmt.format.code != cap_fmt->mbus_code) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
> > > +                     link->source->entity->name, link->source->index,
> > > +                     link->sink->entity->name, link->sink->index,
> > > +                     src_fmt.format.code, src_fmt.format.width,
> > > +                     src_fmt.format.height, cap_fmt->mbus_code,
> > > +                     pix_fmt->width, pix_fmt->height);
> > > +
> > > +             return -EPIPE;
> > > +     };
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct media_entity_operations isp_cap_entity_ops = {
> > > +     .link_validate = c3_isp_cap_link_validate,
> > > +};
> > > +
> > > +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
> > > +                               unsigned int *num_buffers,
> > > +                               unsigned int *num_planes,
> > > +                               unsigned int sizes[],
> > > +                               struct device *alloc_devs[])
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > > +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > > +
> > > +     if (*num_planes) {
> > > +             if (*num_planes != 1)
> > > +                     return -EINVAL;
> > > +
> > > +             if (sizes[0] < pix->sizeimage)
> > > +                     return -EINVAL;
> > I'm not sure this is correct. This capture device supports formats
> > with multiple planes, you should verify that *num_planes is equal to
> > the number of planes of the currently applied format and that each
> > plane has the correct size
>
>
> This capture device only supports 'V4L2_CAP_VIDEO_CAPTURE', so only need
> '*num_planes = 1'.
>
> > And, if you return here
>
>
> The reason for doing this is to pass the test of 'v4l2-compliance
> 1.29.0-5246'.
>
> >
> > > +     } else {
> > You can skip the else {}
> >
> > > +             *num_planes = 1;
> > > +             sizes[0] = pix->sizeimage;
> > And here set the number of planes to the correct number and set the
> > size of each plane ?
>
>
> The reason for doing this is to pass the test of 'v4l2-compliance
> 1.29.0-5246'.
>
> >
> > And now I realized you're using the single-planar API for buffer
> > management
> >
> >          vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> >
> > I was expecting it to rather be
> >
> >          vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> >
> > Even more so as you support multiplanar YUV formats.
> >
> > Any reason why you chose to use the single planar API ?
>
>
> I think the single-planar API is a bit simpler.
>
> Will try to use multi-planar.
>

I asked around if there are strict rules in place for this, and my
understanding is that if multi-planar formats are used the
multi-planar API are preferred. Maybe someone could clarify if I'm
mistaken here.

> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > > +
> > > +     guard(spinlock_irqsave)(&cap->buff_lock);
> > > +
> > > +     list_add_tail(&buf->list, &cap->pending);
> > > +}
> > > +
> > > +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
> > > +
> > > +     if (vb2_plane_size(vb, 0) < size) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "User buffer too small (%ld < %u)\n",
> > > +                     vb2_plane_size(vb, 0), size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     vb2_set_plane_payload(vb, 0, size);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_buffer *buf =
> > > +             container_of(v4l2_buf, struct c3_isp_buffer, vb);
> > > +
> > > +     buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
> > > +                                   unsigned int count)
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > > +     int ret;
> > > +
> > > +     ret = pm_runtime_resume_and_get(cap->isp->dev);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
> > > +     if (ret) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "Failed to start cap%u pipeline: %d\n", cap->id, ret);
> > > +             goto err_pm_put;
> > > +     }
> > You could pipeline_start() after pm_runtime_resume_and_get() and save
> > this jump.
>
>
> Will firstly use pipeline_start() and then pm_runtime_resume_and_get().
>
> > > +
> > > +     ret = c3_isp_cap_dummy_buff_create(cap);
> > > +     if (ret)
> > > +             goto err_pipeline_stop;
> > > +
> > > +     c3_isp_cap_start(cap);
> > > +
> > > +     ret = v4l2_subdev_enable_streams(&cap->rsz->sd,
> > > +                                      C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
> > > +     if (ret)
> > > +             goto err_dummy_destroy;
> > > +
> > > +     return 0;
> > > +
> > > +err_dummy_destroy:
> > > +     c3_isp_cap_dummy_buff_destroy(cap);
> > > +err_pipeline_stop:
> > > +     video_device_pipeline_stop(&cap->vdev);
> > > +err_pm_put:
> > > +     pm_runtime_put(cap->isp->dev);
> > > +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > > +
> > > +     c3_isp_cap_stop(cap);
> > > +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
> > > +
> > > +     v4l2_subdev_disable_streams(&cap->rsz->sd,
> > > +                                 C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
> > > +
> > > +     c3_isp_cap_dummy_buff_destroy(cap);
> > > +
> > > +     video_device_pipeline_stop(&cap->vdev);
> > > +     pm_runtime_put(cap->isp->dev);
> > > +}
> > > +
> > > +static const struct vb2_ops isp_video_vb2_ops = {
> > > +     .queue_setup = c3_isp_vb2_queue_setup,
> > > +     .buf_queue = c3_isp_vb2_buf_queue,
> > > +     .buf_prepare = c3_isp_vb2_buf_prepare,
> > > +     .buf_init = c3_isp_vb2_buf_init,
> > > +     .wait_prepare = vb2_ops_wait_prepare,
> > > +     .wait_finish = vb2_ops_wait_finish,
> > > +     .start_streaming = c3_isp_vb2_start_streaming,
> > > +     .stop_streaming = c3_isp_vb2_stop_streaming,
> > > +};
> > > +
> > > +static int c3_isp_register_capture(struct c3_isp_capture *cap)
> > > +{
> > > +     struct video_device *vdev = &cap->vdev;
> > > +     struct vb2_queue *vb2_q = &cap->vb2_q;
> > > +     int ret;
> > > +
> > > +     snprintf(vdev->name, sizeof(vdev->name), "c3-isp-cap%u", cap->id);
> > > +     vdev->fops = &isp_cap_v4l2_fops;
> > > +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
> > > +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
> > > +     vdev->entity.ops = &isp_cap_entity_ops;
> > > +     vdev->lock = &cap->lock;
> > > +     vdev->minor = -1;
> > > +     vdev->queue = vb2_q;
> > > +     vdev->release = video_device_release_empty;
> > > +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> > > +     vdev->vfl_dir = VFL_DIR_RX;
> > > +     video_set_drvdata(vdev, cap);
> > > +
> > > +     vb2_q->drv_priv = cap;
> > > +     vb2_q->mem_ops = &vb2_dma_contig_memops;
> > > +     vb2_q->ops = &isp_video_vb2_ops;
> > > +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > > +     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_buffer);
> > > +     vb2_q->dev = cap->isp->dev;
> > > +     vb2_q->lock = &cap->lock;
> > > +     vb2_q->min_queued_buffers = 2;
> > Now that you're using a dummy buffer you can drop this
>
>
> Will drop this line.
>
> > > +
> > > +     ret = vb2_queue_init(vb2_q);
> > > +     if (ret < 0)
> > > +             goto err_destroy;
> > > +
> > > +     cap->pad.flags = MEDIA_PAD_FL_SINK;
> > > +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
> > > +     if (ret < 0)
> > > +             goto err_queue_release;
> > > +
> > > +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > > +     if (ret < 0) {
> > > +             dev_err(cap->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_destroy:
> > > +     mutex_destroy(&cap->lock);
> > > +     return ret;
> > > +}
> > > +
> > > +int c3_isp_captures_register(struct c3_isp_device *isp)
> > > +{
> > > +     int ret;
> > > +     unsigned int i;
> > > +     struct c3_isp_capture *cap;
> > > +
> > > +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> > > +             cap = &isp->caps[i];
> > > +             memset(cap, 0, sizeof(*cap));
> > > +
> > > +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
> > > +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
> > > +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
> > > +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
> > > +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> > > +
> > > +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
> > > +
> > > +             cap->id = i;
> > > +             cap->rsz = &isp->resizers[i];
> > > +             cap->isp = isp;
> > > +             INIT_LIST_HEAD(&cap->pending);
> > > +             spin_lock_init(&cap->buff_lock);
> > > +             mutex_init(&cap->lock);
> > > +
> > > +             ret = c3_isp_register_capture(cap);
> > > +             if (ret) {
> > > +                     cap->isp = NULL;
> > > +                     mutex_destroy(&cap->lock);
> > > +                     c3_isp_captures_unregister(isp);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +void c3_isp_captures_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     unsigned int i;
> > > +     struct c3_isp_capture *cap;
> > > +
> > > +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> > > +             cap = &isp->caps[i];
> > > +
> > > +             if (!cap->isp)
> > > +                     continue;
> > > +             vb2_queue_release(&cap->vb2_q);
> > > +             media_entity_cleanup(&cap->vdev.entity);
> > > +             video_unregister_device(&cap->vdev);
> > > +             mutex_destroy(&cap->lock);
> > > +     }
> > > +}
> > > +
> > > +void c3_isp_captures_isr(struct c3_isp_device *isp)
> > > +{
> > > +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
> > > +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
> > > +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
> > > new file mode 100644
> > > index 000000000000..d2c0b9aacf36
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
> > > @@ -0,0 +1,352 @@
> > > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#ifndef __C3_ISP_COMMON_H__
> > > +#define __C3_ISP_COMMON_H__
> > > +
> > > +#include <linux/clk.h>
> > > +
> > > +#include <media/media-device.h>
> > > +#include <media/videobuf2-core.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-subdev.h>
> > > +#include <media/videobuf2-v4l2.h>
> > > +
> > > +#define C3_ISP_DRIVER_NAME            "c3-isp"
> > > +#define C3_ISP_CLOCK_NUM_MAX          3
> > > +
> > > +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
> > > +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUV8_1X24
> > > +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUV8_1X24
> > > +#define C3_ISP_DEFAULT_WIDTH          1920
> > > +#define C3_ISP_DEFAULT_HEIGHT         1080
> > > +#define C3_ISP_MAX_WIDTH              2888
> > > +#define C3_ISP_MAX_HEIGHT             2240
> > > +#define C3_ISP_MIN_WIDTH              160
> > > +#define C3_ISP_MIN_HEIGHT             120
> > > +
> > > +#define C3_ISP_DMA_SIZE_ALIGN_BYTES   16
> > > +#define C3_ISP_DISP_REG(base, id)     ((base) + (id) * 0x400)
> > > +#define C3_ISP_WRMIFX3_REG(base, id)  ((base) + (id) * 0x100)
> > > +#define C3_ISP_PPS_TAP4_S11_H_NUM     33
> > > +#define C3_ISP_PPS_LUT_CTYPE_0        0
> > > +#define C3_ISP_PPS_LUT_CTYPE_2        2
> > > +#define C3_ISP_SCALE_EN               1
> > > +#define C3_ISP_SCALE_DIS              0
> > > +
> > > +#define C3_ISP_PHASE_OFFSET_0         0
> > > +#define C3_ISP_PHASE_OFFSET_1         1
> > > +#define C3_ISP_PHASE_OFFSET_NONE      0xff
> > > +
> > > +enum c3_isp_core_pads {
> > > +     C3_ISP_CORE_PAD_SINK_VIDEO,
> > > +     C3_ISP_CORE_PAD_SINK_PARAMS,
> > > +     C3_ISP_CORE_PAD_SOURCE_STATS,
> > > +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +     C3_ISP_CORE_PAD_MAX
> > > +};
> > > +
> > > +enum c3_isp_resizer_ids {
> > > +     C3_ISP_RSZ_0,
> > > +     C3_ISP_RSZ_1,
> > > +     C3_ISP_RSZ_2,
> > > +     C3_ISP_NUM_RSZ
> > > +};
> > > +
> > > +enum c3_isp_resizer_pads {
> > > +     C3_ISP_RESIZER_PAD_SINK,
> > > +     C3_ISP_RESIZER_PAD_SOURCE,
> > > +     C3_ISP_RESIZER_PAD_MAX
> > > +};
> > > +
> > > +enum c3_isp_cap_devs {
> > > +     C3_ISP_CAP_DEV_0,
> > > +     C3_ISP_CAP_DEV_1,
> > > +     C3_ISP_CAP_DEV_2,
> > > +     C3_ISP_NUM_CAP_DEVS
> > > +};
> > > +
> > > +/*
> > > + * 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;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_mbus_format_info - MBUS format information
> > > + *
> > > + * @mbus_code: the mbus code
> > > + * @pads: bitmask detailing valid pads for this mbus_code
> > > + * @xofst: horizontal phase offset of hardware
> > > + * @yofst: vertical phase offset of hardware
> > > + */
> > > +struct c3_isp_mbus_format_info {
> > It seems to me some of these types are used only inside a single .c
> > file
> >
> > $ git grep c3_isp_mbus_format_info drivers/media/platform/amlogic/c3/isp/
> > drivers/media/platform/amlogic/c3/isp/c3-isp-common.h: * struct c3_isp_mbus_format_info - MBUS format information
> > drivers/media/platform/amlogic/c3/isp/c3-isp-common.h:struct c3_isp_mbus_format_info {
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *info;
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
> > drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
> >
> > Could they be declared there ?
>
>
> OK, it will be declared in c3-isp-core.c file.
>

Same for other files where a type doesn't need to be shared between
different .c files

> > > +     u32 mbus_code;
> > > +     u32 pads;
> > > +     u8 xofst;
> > > +     u8 yofst;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_capture_format - The image format of capture device
> > > + *
> > > + * @mbus_code: the mbus code
> > > + * @fourcc: pixel format
> > > + */
> > > +struct c3_isp_capture_format {
> > > +     u32 mbus_code;
> > > +     u32 fourcc;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_buffer - A container of vb2 buffer used by the video devices:
> > > + *                        capture and stats video devices
> > > + *
> > > + * @vb: vb2 buffer
> > > + * @dma_addr: buffer physical address
> > > + * @list: entry of the buffer in the queue
> > > + */
> > > +struct c3_isp_buffer {
> > > +     struct vb2_v4l2_buffer vb;
> > > +     dma_addr_t dma_addr;
> > > +     struct list_head list;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_params_buffer - A container of vb2 buffer used by the
> > > + *                               params video device
> > > + *
> > > + * @vb: vb2 buffer
> > > + * @cfg: scratch buffer used for caching the ISP configuration parameters
> > > + * @list: entry of the buffer in the queue
> > > + */
> > > +struct c3_isp_params_buffer {
> > > +     struct vb2_v4l2_buffer vb;
> > > +     void *cfg;
> > > +     struct list_head list;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_dummy_buffer - A buffer to write the next frame to in case
> > > + *                           there are no vb2 buffers available.
> > > + *
> > > + * @vaddr:   return value of call to dma_alloc_attrs
> > > + * @dma_addr:        dma address of the buffer
> > > + * @size:    size of the buffer
> > > + */
> > > +struct c3_isp_dummy_buffer {
> > > +     void *vaddr;
> > > +     dma_addr_t dma_addr;
> > > +     u32 size;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_core - ISP core subdev
> > > + *
> > > + * @sd: ISP sub-device
> > > + * @pads: ISP sub-device pads
> > > + * @src_sd: source sub-device
> > > + * @src_sd_pad: source sub-device pad
> > > + * @isp: pointer to c3_isp_device
> > > + */
> > > +struct c3_isp_core {
> > > +     struct v4l2_subdev sd;
> > > +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
> > > +     struct v4l2_subdev *src_sd;
> > > +     u16 src_sd_pad;
> > > +     struct c3_isp_device *isp;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_resizer - ISP resizer subdev
> > > + *
> > > + * @id: resizer id
> > > + * @sd: resizer sub-device
> > > + * @pads: resizer sub-device pads
> > > + * @src_sd: source sub-device
> > > + * @src_sd_pad: source sub-device pad
> > > + * @isp: pointer to c3_isp_device
> > > + */
> > > +struct c3_isp_resizer {
> > > +     enum c3_isp_resizer_ids id;
> > > +     struct v4l2_subdev sd;
> > > +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
> > > +     struct v4l2_subdev *src_sd;
> > > +     u32 src_sd_pad;
> > > +     struct c3_isp_device *isp;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_stats - ISP statistics device
> > > + *
> > > + * @vb2_q: vb2 buffer queue
> > > + * @vdev: video node
> > > + * @vfmt: v4l2_format of the metadata format
> > > + * @pad: media pad
> > > + * @lock: protects vb2_q, vdev
> > > + * @isp: pointer to c3_isp_device
> > > + * @buff: in use buffer
> > > + * @buff_lock: protects stats buffer
> > > + * @pending: stats buffer list head
> > > + */
> > > +struct c3_isp_stats {
> > > +     struct vb2_queue vb2_q;
> > > +     struct video_device vdev;
> > > +     struct v4l2_format vfmt;
> > > +     struct media_pad pad;
> > > +
> > > +     struct mutex lock; /* Protects vb2_q, vdev */
> > > +     struct c3_isp_device *isp;
> > > +
> > > +     struct c3_isp_buffer *buff;
> > > +     spinlock_t buff_lock; /* Protects stats buffer */
> > > +     struct list_head pending;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_params - ISP parameters device
> > > + *
> > > + * @vb2_q: vb2 buffer queue
> > > + * @vdev: video node
> > > + * @vfmt: v4l2_format of the metadata format
> > > + * @pad: media pad
> > > + * @lock: protects vb2_q, vdev
> > > + * @isp: pointer to c3_isp_device
> > > + * @buff: in use buffer
> > > + * @buff_lock: protects stats buffer
> > > + * @pending: stats buffer list head
> > > + */
> > > +struct c3_isp_params {
> > > +     struct vb2_queue vb2_q;
> > > +     struct video_device vdev;
> > > +     struct v4l2_format vfmt;
> > > +     struct media_pad pad;
> > > +
> > > +     struct mutex lock; /* Protects vb2_q, vdev */
> > > +     struct c3_isp_device *isp;
> > > +
> > > +     struct c3_isp_params_buffer *buff;
> > > +     spinlock_t buff_lock; /* Protects params buffer */
> > > +     struct list_head pending;
> > > +};
> > > +
> > > +/*
> > > + * struct c3_isp_capture - ISP capture device
> > > + *
> > > + * @id: capture device ID
> > > + * @vb2_q: vb2 buffer queue
> > > + * @vdev: video node
> > > + * @vfmt: v4l2_format of the capture format
> > > + * @pad: media pad
> > > + * @lock: protects vb2_q, vdev
> > > + * @isp: pointer to c3_isp_device
> > > + * @rsz: pointer to c3_isp_resizer
> > > + * @buff: in use buffer
> > > + * @buff_lock: protects capture buffer
> > > + * @pending: capture buffer list head
> > > + */
> > > +struct c3_isp_capture {
> > > +     enum c3_isp_cap_devs id;
> > > +     struct vb2_queue vb2_q;
> > > +     struct video_device vdev;
> > > +     struct v4l2_format vfmt;
> > > +     struct media_pad pad;
> > > +
> > > +     struct mutex lock; /* Protects vb2_q, vdev */
> > > +     struct c3_isp_device *isp;
> > > +     struct c3_isp_resizer *rsz;
> > > +
> > > +     struct c3_isp_dummy_buffer dummy_buff;
> > > +     struct c3_isp_buffer *buff;
> > > +     spinlock_t buff_lock; /* Protects stream buffer */
> > > +     struct list_head pending;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_info - ISP information
> > > + *
> > > + * @clocks: array of ISP clock names
> > > + * @clock_rates: array of ISP clock rate
> > > + * @clock_num: actual clock number
> > > + */
> > > +struct c3_isp_info {
> > > +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
> > > +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
> > > +     u32 clock_num;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_device - ISP platform device
> > > + *
> > > + * @dev: pointer to the struct device
> > > + * @base: base register address
> > > + * @clks: array of clocks
> > > + * @notifier: notifier to register on the v4l2-async API
> > > + * @v4l2_dev: v4l2_device variable
> > > + * @media_dev: media device variable
> > > + * @pipe: media pipeline
> > > + * @core: ISP core subdev
> > > + * @resizer: ISP resizer subdev
> > > + * @stats: ISP stats device
> > > + * @params: ISP params device
> > > + * @caps: array of ISP capture device
> > > + * @frm_sequence: used to record frame id
> > > + * @info: version-specific ISP information
> > > + */
> > > +struct c3_isp_device {
> > > +     struct device *dev;
> > > +     void __iomem *base;
> > > +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
> > > +
> > > +     struct v4l2_async_notifier notifier;
> > > +     struct v4l2_device v4l2_dev;
> > > +     struct media_device media_dev;
> > > +     struct media_pipeline pipe;
> > > +
> > > +     struct c3_isp_core core;
> > > +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
> > > +     struct c3_isp_stats stats;
> > > +     struct c3_isp_params params;
> > > +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
> > > +
> > > +     u32 frm_sequence;
> > > +     const struct c3_isp_info *info;
> > > +};
> > > +
> > > +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
> > > +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
> > > +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
> > > +
> > > +void c3_isp_core_queue_eof(struct c3_isp_device *isp);
> > > +int c3_isp_core_register(struct c3_isp_device *isp);
> > > +void c3_isp_core_unregister(struct c3_isp_device *isp);
> > > +int c3_isp_resizers_register(struct c3_isp_device *isp);
> > > +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
> > > +int c3_isp_captures_register(struct c3_isp_device *isp);
> > > +void c3_isp_captures_unregister(struct c3_isp_device *isp);
> > > +void c3_isp_captures_isr(struct c3_isp_device *isp);
> > > +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp);
> > > +int c3_isp_stats_register(struct c3_isp_device *isp);
> > > +void c3_isp_stats_unregister(struct c3_isp_device *isp);
> > > +void c3_isp_stats_isr(struct c3_isp_device *isp);
> > > +void c3_isp_params_pre_cfg(struct c3_isp_device *isp);
> > > +int c3_isp_params_register(struct c3_isp_device *isp);
> > > +void c3_isp_params_unregister(struct c3_isp_device *isp);
> > > +void c3_isp_params_isr(struct c3_isp_device *isp);
> > > +
> > > +#endif
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
> > > new file mode 100644
> > > index 000000000000..bf83e73dd744
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
> > > @@ -0,0 +1,553 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/media/amlogic/c3-isp-config.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-event.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +#define C3_ISP_CORE_SUBDEV_NAME        "c3-isp-core"
> > > +
> > > +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> > > +     /* RAW formats */
> > > +     {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     },
> > > +     /* YUV formats */
> > > +     {
> > > +             .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     },
> > > +};
> > > +
> > > +static const struct c3_isp_mbus_format_info
> > > +*core_find_format_by_code(u32 code, u32 pad)
> > > +{
> > > +     for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> > > +             const struct c3_isp_mbus_format_info *info =
> > > +                     &c3_isp_core_mbus_formats[i];
> > > +
> > > +             if (info->mbus_code == code && info->pads & BIT(pad))
> > > +                     return info;
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static const struct c3_isp_mbus_format_info
> > > +*core_find_format_by_index(u32 index, u32 pad)
> > > +{
> > > +     for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> > > +             const struct c3_isp_mbus_format_info *info =
> > > +                     &c3_isp_core_mbus_formats[i];
> > > +
> > > +             if (!(info->pads & BIT(pad)))
> > > +                     continue;
> > > +
> > > +             if (!index)
> > > +                     return info;
> > > +
> > > +             index--;
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static void c3_isp_core_enable(struct c3_isp_device *isp)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
> > > +                        ISP_TOP_IRQ_EN_FRAME_END_EN);
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
> > > +                        ISP_TOP_IRQ_EN_3A_DMA_ERR_EN);
> > > +
> > > +     /* Enable image data to ISP core */
> > > +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
> > > +                        ISP_TOP_PATH_SEL_CORE_MIPI_CORE);
> > > +}
> > > +
> > > +static void c3_isp_core_disable(struct c3_isp_device *isp)
> > > +{
> > > +     /* Disable image data to ISP core */
> > > +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
> > > +                        ISP_TOP_PATH_SEL_CORE_CORE_DIS);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
> > > +                        ISP_TOP_IRQ_EN_FRAME_END_DIS);
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
> > > +                        ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS);
> > > +}
> > > +
> > > +/* Set the phase offset of blc, wb and lns */
> > > +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
> > > +                               u8 xofst, u8 yofst)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK,
> > > +                        ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK,
> > > +                        ISP_LSWB_BLC_PHSOFST_VERT_OFST(yofst));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK,
> > > +                        ISP_LSWB_WB_PHSOFST_HORIZ_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK,
> > > +                        ISP_LSWB_WB_PHSOFST_VERT_OFST(yofst));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK,
> > > +                        ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK,
> > > +                        ISP_LSWB_LNS_PHSOFST_VERT_OFST(yofst));
> > > +}
> > > +
> > > +/* Set the phase offset of af, ae and awb */
> > > +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
> > > +                             u8 xofst, u8 yofst)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_HORIZ_OFST_MASK,
> > > +                        ISP_AF_CTRL_HORIZ_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_VERT_OFST_MASK,
> > > +                        ISP_AF_CTRL_VERT_OFST(yofst));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_HORIZ_OFST_MASK,
> > > +                        ISP_AE_CTRL_HORIZ_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_VERT_OFST_MASK,
> > > +                        ISP_AE_CTRL_VERT_OFST(yofst));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_HORIZ_OFST_MASK,
> > > +                        ISP_AWB_CTRL_HORIZ_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_VERT_OFST_MASK,
> > > +                        ISP_AWB_CTRL_VERT_OFST(yofst));
> > > +}
> > > +
> > > +/* Set the phase offset of demosaic */
> > > +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
> > > +                              u8 xofst, u8 yofst)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK,
> > > +                        ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(xofst));
> > > +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK,
> > > +                        ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(yofst));
> > > +}
> > > +
> > > +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
> > > +                                struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +     const struct c3_isp_mbus_format_info *isp_fmt;
> > > +
> > > +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +     isp_fmt = core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +
> > > +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
> > > +                  ISP_TOP_INPUT_SIZE_HORIZ_SIZE(fmt->width) |
> > > +                  ISP_TOP_INPUT_SIZE_VERT_SIZE(fmt->height));
> > > +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
> > > +                  ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(fmt->width) |
> > > +                  ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(fmt->height));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE,
> > > +                        ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK,
> > > +                        ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(fmt->width));
> > > +
> > > +     c3_isp_write(isp, ISP_AF_HV_SIZE,
> > > +                  ISP_AF_HV_SIZE_GLB_WIN_XSIZE(fmt->width) |
> > > +                  ISP_AF_HV_SIZE_GLB_WIN_YSIZE(fmt->height));
> > > +     c3_isp_write(isp, ISP_AE_HV_SIZE,
> > > +                  ISP_AE_HV_SIZE_HORIZ_SIZE(fmt->width) |
> > > +                  ISP_AE_HV_SIZE_VERT_SIZE(fmt->height));
> > > +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
> > > +                  ISP_AWB_HV_SIZE_HORIZ_SIZE(fmt->width) |
> > > +                  ISP_AWB_HV_SIZE_VERT_SIZE(fmt->height));
> > > +
> > > +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > > +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > > +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > > +}
> > > +
> > > +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
> > > +                                   struct v4l2_subdev_state *state,
> > > +                                   u32 pad, u64 streams_mask)
> > > +{
> > > +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     core->isp->frm_sequence = 0;
> > > +     c3_isp_core_cfg_format(core->isp, state);
> > > +     c3_isp_core_enable(core->isp);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_enable_streams(core->src_sd,
> > > +                                      core->src_sd_pad, sink_streams);
> > > +     if (ret) {
> > > +             c3_isp_core_disable(core->isp);
> > > +             return ret;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
> > > +                                    struct v4l2_subdev_state *state,
> > > +                                    u32 pad, u64 streams_mask)
> > > +{
> > > +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
> > > +                                                    &streams_mask);
> > You can drop xlate_streams if you drop routing support
>
>
> OK, will drop routing.
>
> > > +     ret = v4l2_subdev_disable_streams(core->src_sd,
> > > +                                       core->src_sd_pad, sink_streams);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     c3_isp_core_disable(core->isp);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state,
> > > +                                struct v4l2_subdev_krouting *routing)
> > > +{
> > As per the other drivers, routing support can be dropped
>
>
> Will drop routing.
>
> > > +     static const struct v4l2_mbus_framefmt format = {
> > > +             .width = C3_ISP_DEFAULT_WIDTH,
> > > +             .height = C3_ISP_DEFAULT_HEIGHT,
> > > +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
> > > +             .field = V4L2_FIELD_NONE,
> > > +             .colorspace = V4L2_COLORSPACE_SRGB,
> > > +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
> > > +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > > +             .xfer_func = V4L2_XFER_FUNC_SRGB,
> > > +     };
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_routing_validate(sd, routing,
> > > +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_subdev_route route;
> > > +     struct v4l2_subdev_krouting routing;
> > > +
> > > +     route.sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> > > +     route.sink_stream = 0;
> > > +     route.source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> > > +     route.source_stream = 0;
> > > +     route.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routing.num_routes = 1;
> > > +     routing.routes = &route;
> > > +
> > > +     return c3_isp_core_cfg_routing(sd, state, &routing);
> > > +}
> > > +
> > > +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
> > > +                                   struct v4l2_subdev_state *state,
> > > +                                   struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +     const struct c3_isp_mbus_format_info *info;
> > > +     int ret = 0;
> > > +
> > > +     switch (code->pad) {
> > > +     case C3_ISP_CORE_PAD_SINK_VIDEO:
> > > +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> > > +             info = core_find_format_by_index(code->index, code->pad);
> > > +             if (!info)
> > > +                     ret = -EINVAL;
> >                          return -EINVAL;
> >
> > > +             else
> > so you can drop the else
> >
>
> Yes, will drop 'else'
>
> > > +                     code->code = info->mbus_code;
> > > +
> > > +             break;
> > > +     case C3_ISP_CORE_PAD_SINK_PARAMS:
> > > +     case C3_ISP_CORE_PAD_SOURCE_STATS:
> > > +             if (code->index)
> > > +                     ret = -EINVAL;
> > > +             else
> > here as well
>
>
> Yes, will drop 'else'
>
> > > +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > > +
> > > +             break;
> > > +     default:
> > > +             ret = -EINVAL;
> >                  return -EINVAL;
> >
> > > +             break;
> > > +     }
> > > +
> > > +     return ret;
> >          return 0;
> >
> > > +}
> > > +
> > > +static void c3_isp_core_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 c3_isp_mbus_format_info *isp_fmt;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> > > +
> > > +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> > > +     if (!isp_fmt)
> > > +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> > > +     else
> > > +             sink_fmt->code = format->format.code;
> > > +
> > > +     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);
> > > +
> > Should you set the colorspace related information like you do in
> > init_state() ?
> >
> >          sink_fmt->field = V4L2_FIELD_NONE;
> >          sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> >          sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> >          sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> >          sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>
>
> OK , will add the information.
>
> > > +     src_fmt->width  = sink_fmt->width;
> > > +     src_fmt->height = sink_fmt->height;
> > > +
> > > +     format->format = *sink_fmt;
> > > +}
> > > +
> > > +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
> > > +                                    struct v4l2_subdev_format *format)
> > > +{
> > > +     const struct c3_isp_mbus_format_info *isp_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +
> > > +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +
> > > +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> > > +     if (!isp_fmt)
> > > +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> > > +     else
> > > +             src_fmt->code = format->format.code;
> > > +
> > > +     format->format = *src_fmt;
> > mm, this doesn't check the sizes... if the ISP cannot scale/crop the
> > sizes here should come from the sink format, don't they ? Also
> > remember to set the colorspace information
> >
>
> Will check the size.
>
> > > +}
> > > +
> > > +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
> > > +                            struct v4l2_subdev_state *state,
> > > +                            struct v4l2_subdev_format *format)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +
> > > +     if (format->pad == C3_ISP_CORE_PAD_SINK_VIDEO) {
> > > +             c3_isp_core_set_sink_fmt(state, format);
> > > +     } else if (format->pad == C3_ISP_CORE_PAD_SINK_PARAMS ||
> > > +                format->pad == C3_ISP_CORE_PAD_SOURCE_STATS) {
> > > +             fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +             format->format = *fmt;
> > or
> >
> >                  format->format =
> >                          *v4l2_subdev_state_get_format(state, format->pad);
> >
> > so you can drop *fmt and the {} or maybe turn this into a
> >          switch (format->pad) {
> >
> >          }
> >
> > Up to you
> >
>
> Will check this issue.
>
> > > +     } else if (format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO) {
> > > +             c3_isp_core_set_source_fmt(state, format);
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
> > > +                               struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > Or simply use one
> >
> >          struct v4l2_mbus_framefmt *fmt;
>
>
> Will use one '*fmt'.
>
> > > +
> > > +     /* Video sink pad */
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > > +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> > > +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > > +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> > > +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> > > +
> > > +     /* Video source pad */
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> > > +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > > +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     src_fmt->field = V4L2_FIELD_NONE;
> > > +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> > > +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> > > +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > > +
> > > +     /* Parameters pad */
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
> > > +     sink_fmt->width = 0;
> > > +     sink_fmt->height = 0;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > > +
> > > +     /* Statistics pad */
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
> > > +     src_fmt->width = 0;
> > > +     src_fmt->height = 0;
> > > +     src_fmt->field = V4L2_FIELD_NONE;
> > > +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > > +
> > > +     return c3_isp_core_init_routing(sd, state);
> > Drop routing support
> >
>
> Will drop routing.
>
> > > +}
> > > +
> > > +static int c3_isp_core_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
> > > +                                    struct v4l2_event_subscription *sub)
> > > +{
> > > +     if (sub->type != V4L2_EVENT_FRAME_SYNC)
> > > +             return -EINVAL;
> > > +
> > > +     /* V4L2_EVENT_FRAME_SYNC doesn't need id, so should set 0 */
> > > +     if (sub->id != 0)
> > > +             return -EINVAL;
> > > +
> > > +     return v4l2_event_subscribe(fh, sub, 0, NULL);
> > > +}
> > > +
> > > +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
> > > +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
> > > +     .get_fmt = v4l2_subdev_get_fmt,
> > > +     .set_fmt = c3_isp_core_set_fmt,
> > > +     .enable_streams = c3_isp_core_enable_streams,
> > > +     .disable_streams = c3_isp_core_disable_streams,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_core_ops c3_isp_core_core_ops = {
> > > +     .subscribe_event = c3_isp_core_subscribe_event,
> > > +     .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
> > > +     .core = &c3_isp_core_core_ops,
> > > +     .pad = &c3_isp_core_pad_ops,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
> > > +     .init_state = c3_isp_core_init_state,
> > > +};
> > > +
> > > +static int c3_isp_core_link_validate(struct media_link *link)
> > > +{
> > > +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
> > > +             return 0;
> > > +
> > > +     return v4l2_subdev_link_validate(link);
> > > +}
> > > +
> > > +/* Media entity operations */
> > > +static const struct media_entity_operations c3_isp_core_entity_ops = {
> > > +     .link_validate = c3_isp_core_link_validate,
> > > +};
> > > +
> > > +void c3_isp_core_queue_eof(struct c3_isp_device *isp)
> > > +{
> > This gets called in the end of frame IRQ right ?
> >
> >          if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
> >                  c3_isp_core_queue_eof(isp);
> >
> > When processing is completed.
> >
> > FRAME_SYNC should ideally be emitted as soon as the processing of a
> > frame start. Do you have other interrupt sources you can use to detect
> > "start processing of a frame" ?
> >
>
> Will check the C308L datasheet.
>
> > > +     struct v4l2_event event = {
> > > +             .type = V4L2_EVENT_FRAME_SYNC,
> > > +     };
> > > +
> > > +     event.u.frame_sync.frame_sequence = isp->frm_sequence;
> > > +     v4l2_event_queue(isp->core.sd.devnode, &event);
> > > +}
> > > +
> > > +int c3_isp_core_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_core *core = &isp->core;
> > > +     struct v4l2_subdev *sd = &core->sd;
> > > +     int ret;
> > > +
> > > +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
> > > +     sd->owner = THIS_MODULE;
> > > +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> > > +     sd->internal_ops = &c3_isp_core_internal_ops;
> > > +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
> > > +
> > > +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> > > +     sd->entity.ops = &c3_isp_core_entity_ops;
> > > +
> > > +     core->isp = isp;
> > > +     sd->dev = isp->dev;
> > > +     v4l2_set_subdevdata(sd, core);
> > > +
> > > +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> > > +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
> > > +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
> > > +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_init_finalize(sd);
> > > +     if (ret)
> > > +             goto err_entity_cleanup;
> > > +
> > > +     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_entity_cleanup:
> > > +     media_entity_cleanup(&sd->entity);
> > > +     return ret;
> > > +}
> > > +
> > > +void c3_isp_core_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_core *core = &isp->core;
> > > +     struct v4l2_subdev *sd = &core->sd;
> > > +
> > > +     v4l2_device_unregister_subdev(sd);
> > > +     v4l2_subdev_cleanup(sd);
> > > +     media_entity_cleanup(&sd->entity);
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
> > > new file mode 100644
> > > index 000000000000..0c6795ab9925
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
> > > @@ -0,0 +1,459 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/device.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-common.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-mc.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
> > > +{
> > > +     return readl(isp->base + reg);
> > > +}
> > > +
> > > +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
> > > +{
> > > +     writel(val, isp->base + reg);
> > > +}
> > > +
> > > +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
> > > +{
> > > +     u32 orig, tmp;
> > > +
> > > +     orig = c3_isp_read(isp, reg);
> > > +
> > > +     tmp = orig & ~mask;
> > > +     tmp |= val & mask;
> > > +
> > > +     if (tmp != orig)
> > > +             c3_isp_write(isp, reg, tmp);
> > > +}
> > > +
> > > +/* PM runtime suspend */
> > > +static int c3_isp_runtime_suspend(struct device *dev)
> > > +{
> > > +     struct c3_isp_device *isp = dev_get_drvdata(dev);
> > > +
> > > +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +/* PM runtime resume */
> > > +static int c3_isp_runtime_resume(struct device *dev)
> > > +{
> > > +     struct c3_isp_device *isp = dev_get_drvdata(dev);
> > > +
> > > +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
> > > +}
> > > +
> > > +static const struct dev_pm_ops c3_isp_pm_ops = {
> > > +     SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > > +                         pm_runtime_force_resume)
> > > +     RUNTIME_PM_OPS(c3_isp_runtime_suspend,
> > > +                    c3_isp_runtime_resume, NULL)
> > > +};
> > > +
> > > +/* IRQ handling */
> > > +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
> > > +{
> > > +     struct c3_isp_device *isp = dev;
> > > +     u32 status;
> > > +
> > > +     /* Get irq status and clear irq status */
> > > +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
> > > +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
> > > +
> > > +     if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
> > > +             c3_isp_core_queue_eof(isp);
> > > +             c3_isp_stats_isr(isp);
> > > +             c3_isp_params_isr(isp);
> > > +             c3_isp_captures_isr(isp);
> > > +             isp->frm_sequence++;
> > > +     }
> > > +
> > > +     if (status & ISP_TOP_IRQ_EN_3A_DMA_ERR_EN)
> > > +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
> > > +
> > > +     return IRQ_HANDLED;
> > > +}
> > > +
> > > +/* Subdev notifier register */
> > > +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
> > > +                            struct v4l2_subdev *sd,
> > > +                            struct v4l2_async_connection *asc)
> > > +{
> > > +     struct c3_isp_device *isp =
> > > +             container_of(notifier, struct c3_isp_device, notifier);
> > > +     struct c3_isp_core *core = &isp->core;
> > > +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
> > > +     int ret;
> > > +
> > > +     ret = media_entity_get_fwnode_pad(&sd->entity,
> > > +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
> > > +     if (ret < 0) {
> > > +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
> > > +             return ret;
> > > +     }
> > > +
> > > +     core->src_sd = sd;
> > > +     core->src_sd_pad = ret;
> > > +
> > > +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> > > +                                            MEDIA_LNK_FL_IMMUTABLE);
> > > +}
> > > +
> > > +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
> > > +{
> > > +     struct c3_isp_device *isp =
> > > +             container_of(notifier, struct c3_isp_device, notifier);
> > > +     int ret;
> > > +
> > > +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
> > > +     if (ret < 0) {
> > > +             dev_err(isp->dev,
> > > +                     "Failed to register subdev nodes: %d\n", ret);
> > > +             return ret;
> > > +     }
> > > +
> > > +     dev_info(isp->dev, "notify complete\n");
> > Possibly drop this
>
>
> Will drop this line.
>
> > > +
> > > +     return media_device_register(&isp->media_dev);
> > Any reason you're registering the media device so late when the async
> > chain completes instead of doing so in probe ? I think it's fine doing
> > this here or during probe (after registering the v4l2_dev the video
> > devices. If you do it here and the remote subdev doesn't probe you
> > won't have any media graph. If you do it in probe() and the remote
> > subdev fails to probe, the media graph will register but be unusable.
> > Not sure what's best, maybe an incomplete graph is easier to debug
> > than no graph at all, but I won't push for this. Up to you :)
> >
>
> Will move it to probe()
>
> > > +}
> > I'm not against using notify_complete() but I suspect you have a
> > single remote async subdev, right ? (iow you could have done this in
> > _bound ? )
>
>
> Yes,  will do this in _bound.
>

Thanks for both. Please check if my suggestion fits your needs

> > > +
> > > +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
> > > +{
> > > +     struct c3_isp_device *isp =
> > > +             container_of(asc->notifier, struct c3_isp_device, notifier);
> > > +
> > > +     media_device_unregister(&isp->media_dev);
> > > +}
> > > +
> > > +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
> > > +     .bound = c3_isp_notify_bound,
> > > +     .complete = c3_isp_notify_complete,
> > > +     .destroy = c3_isp_notify_destroy,
> > > +};
> > > +
> > > +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct v4l2_async_connection *asc;
> > > +     struct fwnode_handle *ep;
> > > +     int ret;
> > > +
> > > +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
> > > +
> > > +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
> > > +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
> > > +     if (!ep)
> > > +             return -ENOTCONN;
> > > +
> > > +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> > > +                                           struct v4l2_async_connection);
> > > +     fwnode_handle_put(ep);
> > > +
> > > +     if (IS_ERR(asc))
> > > +             return PTR_ERR(asc);
> > > +
> > > +     isp->notifier.ops = &c3_isp_notify_ops;
> > > +     ret = v4l2_async_nf_register(&isp->notifier);
> > > +     if (ret)
> > > +             v4l2_async_nf_cleanup(&isp->notifier);
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     v4l2_async_nf_unregister(&isp->notifier);
> > > +     v4l2_async_nf_cleanup(&isp->notifier);
> > > +}
> > > +
> > > +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct media_device *media_dev = &isp->media_dev;
> > > +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
> > > +     int ret;
> > > +
> > > +     /* Initialize media device */
> > > +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
> > > +             sizeof(media_dev->model));
> > > +     media_dev->dev = isp->dev;
> > > +
> > > +     media_device_init(media_dev);
> > > +
> > > +     /* Initialize v4l2 device */
> > > +     v4l2_dev->mdev = media_dev;
> > > +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
> > > +             sizeof(v4l2_dev->name));
> > > +
> > > +     ret = v4l2_device_register(isp->dev, v4l2_dev);
> > > +     if (ret) {
> > > +             media_device_cleanup(media_dev);
> > > +             dev_err(isp->dev,
> > > +                     "Failed to register V4L2 device: %d\n", ret);
> > > +     }
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     v4l2_device_unregister(&isp->v4l2_dev);
> > > +     media_device_cleanup(&isp->media_dev);
> > > +}
> > > +
> > > +static void c3_isp_remove_links(struct c3_isp_device *isp)
> > > +{
> > > +     unsigned int i;
> > > +
> > > +     media_entity_remove_links(&isp->core.sd.entity);
> > > +
> > > +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
> > > +             media_entity_remove_links(&isp->resizers[i].sd.entity);
> > > +
> > > +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
> > > +             media_entity_remove_links(&isp->caps[i].vdev.entity);
> > > +}
> > > +
> > > +static int c3_isp_create_links(struct c3_isp_device *isp)
> > > +{
> > > +     unsigned int i;
> > > +     int ret;
> > > +
> > > +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
> > > +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
> > > +                                         C3_ISP_RESIZER_PAD_SOURCE,
> > > +                                         &isp->caps[i].vdev.entity,
> > > +                                         0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> > > +             if (ret) {
> > > +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
> > > +                     goto err_remove_links;
> > > +             }
> > > +
> > > +             ret = media_create_pad_link(&isp->core.sd.entity,
> > > +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +                                         &isp->resizers[i].sd.entity,
> > > +                                         C3_ISP_RESIZER_PAD_SINK,
> > > +                                         MEDIA_LNK_FL_ENABLED);
> > > +             if (ret) {
> > > +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
> > > +                     goto err_remove_links;
> > > +             }
> > > +     }
> > > +
> > > +     ret = media_create_pad_link(&isp->core.sd.entity,
> > > +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
> > > +                                 &isp->stats.vdev.entity,
> > > +                                 0, MEDIA_LNK_FL_ENABLED);
> > > +     if (ret) {
> > > +             dev_err(isp->dev, "Failed to link core and stats\n");
> > > +             goto err_remove_links;
> > > +     }
> > > +
> > > +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
> > > +                                 &isp->core.sd.entity,
> > > +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
> > > +                                 MEDIA_LNK_FL_ENABLED);
> > > +     if (ret) {
> > > +             dev_err(isp->dev, "Failed to link params and core\n");
> > > +             goto err_remove_links;
> > > +     }
> > > +
> > > +     return 0;
> > > +
> > > +err_remove_links:
> > > +     c3_isp_remove_links(isp);
> > > +     return ret;
> > > +}
> > > +
> > > +static int c3_isp_videos_register(struct c3_isp_device *isp)
> > > +{
> > > +     int ret;
> > > +
> > > +     ret = c3_isp_captures_register(isp);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = c3_isp_stats_register(isp);
> > > +     if (ret)
> > > +             goto err_captures_unregister;
> > > +
> > > +     ret = c3_isp_params_register(isp);
> > > +     if (ret)
> > > +             goto err_stats_unregister;
> > > +
> > > +     ret = c3_isp_create_links(isp);
> > > +     if (ret)
> > > +             goto err_params_unregister;
> > > +
> > > +     return 0;
> > > +
> > > +err_params_unregister:
> > > +     c3_isp_params_unregister(isp);
> > > +err_stats_unregister:
> > > +     c3_isp_stats_unregister(isp);
> > > +err_captures_unregister:
> > > +     c3_isp_captures_unregister(isp);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     c3_isp_remove_links(isp);
> > > +     c3_isp_params_unregister(isp);
> > > +     c3_isp_stats_unregister(isp);
> > > +     c3_isp_captures_unregister(isp);
> > > +}
> > > +
> > > +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
> > > +{
> > > +     const struct c3_isp_info *info = isp->info;
> > > +     int ret;
> > > +     u32 i;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++)
> > > +             isp->clks[i].id = info->clocks[i];
> > > +
> > > +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++) {
> > > +             if (!info->clock_rates[i])
> > > +                     continue;
> > > +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
> > same comment as per the other drivers
>
>
> OK!
>
> > > +             if (ret) {
> > > +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
> > > +                             info->clock_rates[i]);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_probe(struct platform_device *pdev)
> > > +{
> > > +     struct device *dev = &pdev->dev;
> > > +     struct c3_isp_device *isp;
> > > +     int irq;
> > > +     int ret;
> > > +
> > > +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
> > > +     if (!isp)
> > > +             return -ENOMEM;
> > > +
> > > +     isp->info = of_device_get_match_data(dev);
> > > +     isp->dev = dev;
> > > +
> > > +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
> > > +     if (IS_ERR(isp->base))
> > > +             return dev_err_probe(dev, PTR_ERR(isp->base),
> > > +                                  "Failed to ioremap resource\n");
> > > +
> > > +     irq = platform_get_irq(pdev, 0);
> > > +     if (irq < 0)
> > > +             return irq;
> > > +
> > > +     ret = c3_isp_cfg_clocks(isp);
> > > +     if (ret)
> > > +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> > > +
> > > +     platform_set_drvdata(pdev, isp);
> > > +
> > > +     pm_runtime_enable(dev);
> > > +
> > > +     ret = c3_isp_v4l2_register(isp);
> > > +     if (ret)
> > > +             goto err_runtime_disable;
> > > +
> > > +     ret = c3_isp_core_register(isp);
> > > +     if (ret)
> > > +             goto err_v4l2_unregister;
> > > +
> > > +     ret = c3_isp_resizers_register(isp);
> > > +     if (ret)
> > > +             goto err_core_unregister;
> > > +
> > > +     ret = c3_isp_async_nf_register(isp);
> > > +     if (ret)
> > > +             goto err_resizers_unregister;
> > > +
> > > +     ret = devm_request_irq(dev, irq,
> > > +                            c3_isp_irq_handler, IRQF_SHARED,
> > > +                            dev_driver_string(dev), isp);
> > > +     if (ret)
> > > +             goto err_nf_unregister;
> > > +
> > > +     ret = c3_isp_videos_register(isp);
> > > +     if (ret)
> > > +             goto err_nf_unregister;
> > > +
> > > +     return 0;
> > > +
> > > +err_nf_unregister:
> > > +     c3_isp_async_nf_unregister(isp);
> > > +err_resizers_unregister:
> > > +     c3_isp_resizers_unregister(isp);
> > > +err_core_unregister:
> > > +     c3_isp_core_unregister(isp);
> > > +err_v4l2_unregister:
> > > +     c3_isp_v4l2_unregister(isp);
> > > +err_runtime_disable:
> > > +     pm_runtime_disable(dev);
> > > +     return ret;
> > > +};
> > > +
> > > +static void c3_isp_remove(struct platform_device *pdev)
> > > +{
> > > +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
> > > +
> > > +     c3_isp_videos_unregister(isp);
> > > +     c3_isp_async_nf_unregister(isp);
> > > +     c3_isp_core_unregister(isp);
> > > +     c3_isp_resizers_unregister(isp);
> > > +     c3_isp_v4l2_unregister(isp);
> > > +     pm_runtime_disable(isp->dev);
> > > +};
> > > +
> > > +static const struct c3_isp_info isp_info = {
> > > +     .clocks = {"vapb", "isp0"},
> > > +     .clock_rates = {0, 400000000},
> > > +     .clock_num = 2
> > > +};
> > > +
> > > +static const struct of_device_id c3_isp_of_match[] = {
> > > +     { .compatible = "amlogic,c3-isp",
> > > +       .data = &isp_info },
> > > +     { },
> > > +};
> > > +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
> > > +
> > > +static struct platform_driver c3_isp_driver = {
> > > +     .probe = c3_isp_probe,
> > > +     .remove = c3_isp_remove,
> > > +     .driver = {
> > > +             .name = "c3-isp",
> > > +             .of_match_table = c3_isp_of_match,
> > > +             .pm = pm_ptr(&c3_isp_pm_ops),
> > > +     },
> > > +};
> > > +
> > > +module_platform_driver(c3_isp_driver);
> > > +
> > > +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> > > +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
> > > +MODULE_LICENSE("GPL");
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
> > > new file mode 100644
> > > index 000000000000..dc33a57dcd59
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
> > > @@ -0,0 +1,942 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#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-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 pointer 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. The data member at the end allows a pointer to an address
> > > + * within the data member of :c:type:`c3_isp_params_cfg` to initialise a
> > > + * union variable.
> > > + *
> > > + * @header:          Pointer to the shared header struct embedded as the
> > > + *                   first member of all the possible other members (except
> > > + *                   @data). 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_PST_CCM
> > > + * @csc:             For header->type == C3_ISP_PARAMS_BLOCK_PST_CSC
> > > + * @blc:             For header->type == C3_ISP_PARAMS_BLOCK_PST_BLC
> > > + * @data:            Allows easy initialisation of a union variable with a
> > > + *                   pointer into a u8 array.
> > > + */
> > > +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;
> > > +     u8 *data;
> > Why do you need data here ?
> >
> > Oh, I've see in c3_isp_params_cfg_blocks() now :)
> >
> > The consequences of this is that all handlers take an
> >
> > typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
> >                                       union c3_isp_params_block block);
> >
> > by value and not by pointer.
> >
> > I know it's equivalent since the union only stores pointers but it
> > might be confusing... I don't know, some parts of me like it, some
> > doesn't and would prefer something like union
> > rkisp1_ext_params_config. Clever hack btw ;) If you prefer this style
> > I'm fine with you keeping it even if seeing a parameter passed by
> > value to the handlers makes me cringe a bit
> >
>
> Will check this issue.
>

Up to you, maybe a matter of tastes

> > > +};
> > > +
> > > +typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
> > > +                                  union c3_isp_params_block block);
> > > +
> > > +struct c3_isp_params_handler {
> > > +     size_t size;
> > > +     c3_isp_block_handler handler;
> > > +};
> > > +
> > > +#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,
> > > +                                     union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_awb_gains *awb_gains = block.awb_gains;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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));
> > > +
> > > +     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_cfg_awb_weight(struct c3_isp_device *isp,
> > > +                                      struct c3_isp_params_awb_config *awb_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 = awb_cfg->horiz_zones_num * awb_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_IDX_DATA(0, awb_cfg->zone_weight[base + 0]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(1, awb_cfg->zone_weight[base + 1]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(2, awb_cfg->zone_weight[base + 2]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(3, awb_cfg->zone_weight[base + 3]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(4, awb_cfg->zone_weight[base + 4]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(5, awb_cfg->zone_weight[base + 5]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(6, awb_cfg->zone_weight[base + 6]) |
> > > +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(7, awb_cfg->zone_weight[base + 7]);
> > > +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
> > > +     }
> > > +
> > > +     if (zones_num % 8) {
> > > +             data = 0;
> > > +             base = i * 8;
> > > +
> > > +             for (i = 0; i < zones_num % 8; i++)
> > > +                     data |= ISP_AWB_BLK_WT_DATA_IDX_DATA(i, awb_cfg->zone_weight[base + i]);
> > > +
> > > +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_awb_cood(struct c3_isp_device *isp,
> > > +                                    struct c3_isp_params_awb_config *awb_cfg)
> > > +{
> > > +     unsigned int max_point_num;
> > > +
> > > +     /* The number of points is one more than the number of edges */
> > > +     max_point_num = max(awb_cfg->horiz_zones_num, awb_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(awb_cfg->horiz_cood[i]) |
> > > +                          ISP_AWB_IDX_DATA_VIDX_DATA(awb_cfg->vert_cood[i]));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp,
> > > +                                      union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_awb_config *awb_cfg = block.awb_cfg;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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_STAT_POINT_MASK,
> > > +                        ISP_TOP_3A_STAT_CRTL_AWB_STAT_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_HORIZ_BLKS_NUM_MASK,
> > > +                        ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(awb_cfg->horiz_zones_num));
> > > +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK,
> > > +                        ISP_AWB_HV_BLKNUM_VERT_BLKS_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_cfg_awb_weight(isp, awb_cfg);
> > > +     c3_isp_params_cfg_awb_cood(isp, awb_cfg);
> > > +
> > > +     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_cfg_ae_weight(struct c3_isp_device *isp,
> > > +                                     struct c3_isp_params_ae_config *ae_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 = ae_cfg->horiz_zones_num * ae_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_IDX_DATA(0, ae_cfg->zone_weight[base + 0]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(1, ae_cfg->zone_weight[base + 1]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(2, ae_cfg->zone_weight[base + 2]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(3, ae_cfg->zone_weight[base + 3]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(4, ae_cfg->zone_weight[base + 4]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(5, ae_cfg->zone_weight[base + 5]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(6, ae_cfg->zone_weight[base + 6]) |
> > > +                    ISP_AE_BLK_WT_DATA_IDX_DATA(7, ae_cfg->zone_weight[base + 7]);
> > > +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
> > > +     }
> > > +
> > > +     /* Write the last weights data */
> > > +     if (zones_num % 8) {
> > > +             data = 0;
> > > +             base = i * 8;
> > > +
> > > +             for (i = 0; i < zones_num % 8; i++)
> > > +                     data |= ISP_AE_BLK_WT_DATA_IDX_DATA(i, ae_cfg->zone_weight[base + i]);
> > > +
> > > +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_ae_cood(struct c3_isp_device *isp,
> > > +                                   struct c3_isp_params_ae_config *ae_cfg)
> > > +{
> > > +     unsigned int max_point_num;
> > > +
> > > +     /* The number of points is one more than the number of edges */
> > > +     max_point_num = max(ae_cfg->horiz_zones_num, ae_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(ae_cfg->horiz_cood[i]) |
> > > +                          ISP_AE_IDX_DATA_VIDX_DATA(ae_cfg->vert_cood[i]));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp,
> > > +                                     union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_ae_config *ae_cfg = block.ae_cfg;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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_STAT_POINT_MASK,
> > > +                        ISP_TOP_3A_STAT_CRTL_AE_STAT_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_BLK_HORIZ_NUM_MASK,
> > > +                        ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(ae_cfg->horiz_zones_num));
> > > +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK,
> > > +                        ISP_AE_HV_BLKNUM_BLK_VERT_NUM(ae_cfg->vert_zones_num));
> > > +
> > > +     c3_isp_params_cfg_ae_weight(isp, ae_cfg);
> > > +     c3_isp_params_cfg_ae_cood(isp, ae_cfg);
> > > +
> > > +     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_cfg_af_cood(struct c3_isp_device *isp,
> > > +                                   struct c3_isp_params_af_config *af_cfg)
> > > +{
> > > +     unsigned int max_point_num;
> > > +
> > > +     /* The number of points is one more than the number of edges */
> > > +     max_point_num = max(af_cfg->horiz_zones_num, af_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(af_cfg->horiz_cood[i]) |
> > > +                          ISP_AF_IDX_DATA_VIDX_DATA(af_cfg->vert_cood[i]));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp,
> > > +                                     union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_af_config *af_cfg = block.af_cfg;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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_STAT_POINT_MASK,
> > > +                        ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(af_cfg->tap_point));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK,
> > > +                        ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(af_cfg->horiz_zones_num));
> > > +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK,
> > > +                        ISP_AF_HV_BLKNUM_BLK_VERT_NUM(af_cfg->vert_zones_num));
> > > +
> > > +     c3_isp_params_cfg_af_cood(isp, af_cfg);
> > > +
> > > +     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,
> > > +                                     union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_pst_gamma *gamma = block.pst_gamma;
> > > +     unsigned int base;
> > > +     unsigned int i;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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(gamma->lut) / 2; i++) {
> > > +                     base = i * 2;
> > > +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> > > +                                  ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]) |
> > > +                                  ISP_PST_GAMMA_LUT_DATA_DATA1(gamma->lut[base + 1]));
> > > +             }
> > > +
> > > +             /* Write the last one */
> > > +             if (ARRAY_SIZE(gamma->lut) % 2) {
> > > +                     base = i * 2;
> > > +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> > > +                                  ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]));
> > > +             }
> > > +     }
> > > +
> > > +     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,
> > > +                               union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_ccm *ccm = block.ccm;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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]));
> > > +
> > > +     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,
> > > +                               union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_csc *csc = block.csc;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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]));
> > > +
> > > +     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,
> > > +                               union c3_isp_params_block block)
> > > +{
> > > +     struct c3_isp_params_blc *blc = block.blc;
> > > +
> > > +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
> > > +             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));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_BLC_EN_MASK,
> > > +                        ISP_TOP_BEO_CTRL_BLC_EN);
> > > +}
> > > +
> > > +static const struct c3_isp_params_handler c3_isp_params_handlers[] = {
> > > +     [C3_ISP_PARAMS_BLOCK_AWB_GAINS] = {
> > > +             .size = sizeof(struct c3_isp_params_awb_gains),
> > > +             .handler = c3_isp_params_cfg_awb_gains,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = {
> > > +             .size = sizeof(struct c3_isp_params_awb_config),
> > > +             .handler = c3_isp_params_cfg_awb_config,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_AE_CONFIG] = {
> > > +             .size = sizeof(struct c3_isp_params_ae_config),
> > > +             .handler = c3_isp_params_cfg_ae_config,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_AF_CONFIG] = {
> > > +             .size = sizeof(struct c3_isp_params_af_config),
> > > +             .handler = c3_isp_params_cfg_af_config,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_PST_GAMMA] = {
> > > +             .size = sizeof(struct c3_isp_params_pst_gamma),
> > > +             .handler = c3_isp_params_cfg_pst_gamma,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_CCM] = {
> > > +             .size = sizeof(struct c3_isp_params_ccm),
> > > +             .handler = c3_isp_params_cfg_ccm,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_CSC] = {
> > > +             .size = sizeof(struct c3_isp_params_csc),
> > > +             .handler = c3_isp_params_cfg_csc,
> > > +     },
> > > +     [C3_ISP_PARAMS_BLOCK_BLC] = {
> > > +             .size = sizeof(struct c3_isp_params_blc),
> > > +             .handler = c3_isp_params_cfg_blc,
> > > +     },
> > > +};
> > > +
> > > +static enum vb2_buffer_state
> > > +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
> > > +{
> > > +     struct c3_isp_params_cfg *config = params->buff->cfg;
> > > +     size_t block_offset = 0;
> > > +     size_t max_offset = 0;
> > > +
> > > +     if (WARN_ON(!config))
> > > +             return VB2_BUF_STATE_ERROR;
> > > +
> > > +     /* Ensure config->data has a full struct c3_isp_params_block_header */
> > > +     max_offset = config->data_size - sizeof(struct c3_isp_params_block_header);
> > I might have missed why you subtract this. Hasn't the package been
> > validated in c3_isp_params_vb2_buf_prepare() ?
>
>
> Yes, the package has been validated.
>
> Will check this issue.
>
> > > +
> > > +     /* Walk the list of parameter blocks and process them */
> > > +     while (block_offset <= max_offset) {
> > > +             const struct c3_isp_params_handler *block_handler;
> > > +             union c3_isp_params_block block;
> > > +
> > > +             block = (union c3_isp_params_block)
> > > +                      &config->data[block_offset];
> > > +
> > > +             block_handler = &c3_isp_params_handlers[block.header->type];
> > > +             block_handler->handler(params->isp, block);
> > > +
> > > +             block_offset += block.header->size;
> > > +     }
> > > +
> > > +     return VB2_BUF_STATE_DONE;
> > > +}
> > > +
> > > +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)(&params->buff_lock);
> > > +
> > > +     /* Only use the first buffer to initialize ISP */
> > > +     params->buff = list_first_entry_or_null(&params->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 here
> >
>
> What is the reason to return 0?

Ah sorry, I wasn't clear, I didn't mean to replace return -EINVAL but to

     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;

up to you

>
> I think need to consider the test of 'v4l2-compliance 1.29.0-5246'.
>
> > > +     } else {
> > and you can spare the } else {
>
>
> Will check here.
>
> > > +             *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)(&params->buff_lock);
> > > +
> > > +     list_add_tail(&buf->list, &params->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 *cfg = buf->cfg;
> > > +     struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0);
> > > +     size_t payload_size = vb2_get_plane_payload(vb, 0);
> > > +     size_t header_size = offsetof(struct c3_isp_params_cfg, data);
> > > +     size_t block_offset = 0;
> > > +     size_t cfg_size;
> > > +
> > > +     /* Payload size can't be greater than the destination buffer size */
> > > +     if (payload_size > params->vfmt.fmt.meta.buffersize) {
> > > +             dev_dbg(params->isp->dev,
> > > +                     "Payload size is too large: %zu\n", payload_size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     /* Payload size can't be smaller than the header size */
> > > +     if (payload_size < header_size) {
> > > +             dev_dbg(params->isp->dev,
> > > +                     "Payload size is too small: %zu\n", payload_size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     /*
> > > +      * Use the internal scratch buffer to avoid userspace modifying
> > > +      * the buffer content while the driver is processing it.
> > > +      */
> > > +     memcpy(cfg, usr_cfg, payload_size);
> > > +
> > > +     /* Only v0 is supported at the moment */
> > > +     if (cfg->version != C3_ISP_PARAMS_BUFFER_V0) {
> > > +             dev_dbg(params->isp->dev,
> > > +                     "Unsupported params buffer version: %u\n", cfg->version);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     /* Validate the size reported in the parameter buffer header */
> > > +     cfg_size = header_size + cfg->data_size;
> > > +     if (cfg_size != payload_size) {
> > > +             dev_dbg(params->isp->dev,
> > > +                     "Data size %zu and payload size %zu are different\n",
> > > +                     cfg_size, payload_size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     /* Walk the list of parameter blocks and validate them */
> > > +     cfg_size = cfg->data_size;
> > > +     while (cfg_size >= sizeof(struct c3_isp_params_block_header)) {
> > > +             const struct c3_isp_params_block_header *block;
> > > +             const struct c3_isp_params_handler *handler;
> > > +
> > > +             block = (struct c3_isp_params_block_header *)
> > > +                     &cfg->data[block_offset];
> > > +
> > > +             if (block->type >= ARRAY_SIZE(c3_isp_params_handlers)) {
> > > +                     dev_dbg(params->isp->dev,
> > > +                             "Invalid params block type\n");
> > > +                     return -EINVAL;
> > > +             }
> > > +
> > > +             if (block->size > cfg_size) {
> > > +                     dev_dbg(params->isp->dev,
> > > +                             "Block size is greater than cfg size\n");
> > > +                     return -EINVAL;
> > > +             }
> > > +
> > > +             handler = &c3_isp_params_handlers[block->type];
> > > +             if (block->size != handler->size) {
> > > +                     dev_dbg(params->isp->dev,
> > > +                             "Invalid params block size\n");
> > > +                     return -EINVAL;
> > > +             }
> > > +
> > > +             block_offset += block->size;
> > > +             cfg_size -= block->size;
> > > +     }
> > > +
> > > +     if (cfg_size) {
> > > +             dev_dbg(params->isp->dev,
> > > +                     "Unexpected data after the params buffer end\n");
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +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)(&params->buff_lock);
> > > +
> > > +     while (!list_empty(&params->pending)) {
> > > +             buff = list_first_entry(&params->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,
> > > +     .wait_prepare = vb2_ops_wait_prepare,
> > > +     .wait_finish = vb2_ops_wait_finish,
> > > +     .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 = &params->vdev;
> > > +     struct vb2_queue *vb2_q = &params->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(&params->pending);
> > > +     spin_lock_init(&params->buff_lock);
> > > +     mutex_init(&params->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 = &params->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_buffer);
> > > +     vb2_q->dev = isp->dev;
> > > +     vb2_q->lock = &params->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, &params->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(&params->lock);
> > > +     return ret;
> > > +}
> > > +
> > > +void c3_isp_params_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_params *params = &isp->params;
> > > +
> > > +     vb2_queue_release(&params->vb2_q);
> > > +     media_entity_cleanup(&params->vdev.entity);
> > > +     video_unregister_device(&params->vdev);
> > > +     mutex_destroy(&params->lock);
> > > +}
> > > +
> > > +void c3_isp_params_isr(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_params *params = &isp->params;
> > > +     enum vb2_buffer_state state;
> > > +
> > > +     guard(spinlock_irqsave)(&params->buff_lock);
> > > +
> > > +     params->buff = list_first_entry_or_null(&params->pending,
> > > +                                             struct c3_isp_params_buffer, list);
> > > +     if (!params->buff)
> > > +             return;
> > > +
> > > +     list_del(&params->buff->list);
> > > +
> > > +     state = 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(&params->buff->vb.vb2_buf, state);
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
> > > new file mode 100644
> > > index 000000000000..c4d3b8c96479
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
> > > @@ -0,0 +1,599 @@
> > > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#ifndef __C3_ISP_REGS_H__
> > > +#define __C3_ISP_REGS_H__
> > > +
> > > +#define ISP_TOP_INPUT_SIZE                              0x0000
> > > +#define ISP_TOP_INPUT_SIZE_VERT_SIZE_MASK               GENMASK(15, 0)
> > > +#define ISP_TOP_INPUT_SIZE_VERT_SIZE(x)                 ((x) << 0)
> > > +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE_MASK              GENMASK(31, 16)
> > > +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE(x)                ((x) << 16)
> > > +
> > > +#define ISP_TOP_FRM_SIZE                                0x0004
> > > +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE_MASK            GENMASK(15, 0)
> > > +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(x)              ((x) << 0)
> > > +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE_MASK           GENMASK(31, 16)
> > > +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(x)             ((x) << 16)
> > > +
> > > +#define ISP_TOP_HOLD_SIZE                               0x0008
> > > +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK          GENMASK(31, 16)
> > > +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(x)            ((x) << 16)
> > > +
> > > +#define ISP_TOP_PATH_EN                                 0x0010
> > > +#define ISP_TOP_PATH_EN_DISP0_EN_MASK                   BIT(0)
> > > +#define ISP_TOP_PATH_EN_DISP0_EN                        BIT(0)
> > > +#define ISP_TOP_PATH_EN_DISP0_DIS                       (0 << 0)
> > > +#define ISP_TOP_PATH_EN_DISP1_EN_MASK                   BIT(1)
> > > +#define ISP_TOP_PATH_EN_DISP1_EN                        BIT(1)
> > > +#define ISP_TOP_PATH_EN_DISP1_DIS                       (0 << 1)
> > > +#define ISP_TOP_PATH_EN_DISP2_EN_MASK                   BIT(2)
> > > +#define ISP_TOP_PATH_EN_DISP2_EN                        BIT(2)
> > > +#define ISP_TOP_PATH_EN_DISP2_DIS                       (0 << 2)
> > > +#define ISP_TOP_PATH_EN_WRMIF0_EN_MASK                  BIT(8)
> > > +#define ISP_TOP_PATH_EN_WRMIF0_EN                       BIT(8)
> > > +#define ISP_TOP_PATH_EN_WRMIF0_DIS                      (0 << 8)
> > > +#define ISP_TOP_PATH_EN_WRMIF1_EN_MASK                  BIT(9)
> > > +#define ISP_TOP_PATH_EN_WRMIF1_EN                       BIT(9)
> > > +#define ISP_TOP_PATH_EN_WRMIF1_DIS                      (0 << 9)
> > > +#define ISP_TOP_PATH_EN_WRMIF2_EN_MASK                  BIT(10)
> > > +#define ISP_TOP_PATH_EN_WRMIF2_EN                       BIT(10)
> > > +#define ISP_TOP_PATH_EN_WRMIF2_DIS                      (0 << 10)
> > > +
> > > +#define ISP_TOP_PATH_SEL                                0x0014
> > > +#define ISP_TOP_PATH_SEL_CORE_MASK                      GENMASK(18, 16)
> > > +#define ISP_TOP_PATH_SEL_CORE_CORE_DIS                  (0 << 16)
> > > +#define ISP_TOP_PATH_SEL_CORE_MIPI_CORE                 BIT(16)
> > > +
> > > +#define ISP_TOP_IRQ_EN                                  0x0080
> > > +#define ISP_TOP_IRQ_EN_FRAME_END_MASK                   BIT(0)
> > > +#define ISP_TOP_IRQ_EN_FRAME_END_EN                     BIT(0)
> > > +#define ISP_TOP_IRQ_EN_FRAME_END_DIS                    (0 << 0)
> > > +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK                  BIT(5)
> > > +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_EN                    BIT(5)
> > > +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS                   (0 << 5)
> > > +
> > > +#define ISP_TOP_IRQ_CLR                                 0x0084
> > > +#define ISP_TOP_RO_IRQ_STAT                             0x01c4
> > > +
> > > +#define ISP_TOP_MODE_CTRL                               0x0400
> > > +#define ISP_TOP_FEO_CTRL0                               0x040c
> > > +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK             BIT(8)
> > > +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS                 (0 << 8)
> > > +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN                  BIT(8)
> > > +
> > > +#define ISP_TOP_FEO_CTRL1_0                             0x0410
> > > +#define ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK                 BIT(3)
> > > +#define ISP_TOP_FEO_CTRL1_0_DPC_DIS                     (0 << 3)
> > > +#define ISP_TOP_FEO_CTRL1_0_DPC_EN                      BIT(3)
> > > +#define ISP_TOP_FEO_CTRL1_0_OG_EN_MASK                  BIT(5)
> > > +#define ISP_TOP_FEO_CTRL1_0_OG_DIS                      (0 << 5)
> > > +#define ISP_TOP_FEO_CTRL1_0_OG_EN                       BIT(5)
> > > +
> > > +#define ISP_TOP_FED_CTRL                                0x0418
> > > +#define ISP_TOP_FED_CTRL_PDPC_EN_MASK                   BIT(1)
> > > +#define ISP_TOP_FED_CTRL_PDPC_DIS                       (0 << 1)
> > > +#define ISP_TOP_FED_CTRL_PDPC_EN                        BIT(1)
> > > +#define ISP_TOP_FED_CTRL_RAWCNR_EN_MASK                 GENMASK(6, 5)
> > > +#define ISP_TOP_FED_CTRL_RAWCNR_DIS                     (0 << 5)
> > > +#define ISP_TOP_FED_CTRL_RAWCNR_EN                      BIT(5)
> > > +#define ISP_TOP_FED_CTRL_SNR1_EN_MASK                   BIT(9)
> > > +#define ISP_TOP_FED_CTRL_SNR1_DIS                       (0 << 9)
> > > +#define ISP_TOP_FED_CTRL_SNR1_EN                        BIT(9)
> > > +#define ISP_TOP_FED_CTRL_TNR0_EN_MASK                   BIT(11)
> > > +#define ISP_TOP_FED_CTRL_TNR0_DIS                       (0 << 11)
> > > +#define ISP_TOP_FED_CTRL_TNR0_EN                        BIT(11)
> > > +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK               BIT(12)
> > > +#define ISP_TOP_FED_CTRL_CUBIC_CS_DIS                   (0 << 12)
> > > +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN                    BIT(12)
> > > +#define ISP_TOP_FED_CTRL_SQRT_EN_MASK                   BIT(14)
> > > +#define ISP_TOP_FED_CTRL_SQRT_DIS                       (0 << 14)
> > > +#define ISP_TOP_FED_CTRL_SQRT_EN                        BIT(14)
> > > +#define ISP_TOP_FED_CTRL_DGAIN_EN_MASK                  BIT(16)
> > > +#define ISP_TOP_FED_CTRL_DGAIN_DIS                      (0 << 16)
> > > +#define ISP_TOP_FED_CTRL_DGAIN_EN                       BIT(16)
> > > +
> > > +#define ISP_TOP_BEO_CTRL                                0x041c
> > > +#define ISP_TOP_BEO_CTRL_WB_EN_MASK                     BIT(6)
> > > +#define ISP_TOP_BEO_CTRL_WB_DIS                         (0 << 6)
> > > +#define ISP_TOP_BEO_CTRL_WB_EN                          BIT(6)
> > > +#define ISP_TOP_BEO_CTRL_BLC_EN_MASK                    BIT(7)
> > > +#define ISP_TOP_BEO_CTRL_BLC_DIS                        (0 << 7)
> > > +#define ISP_TOP_BEO_CTRL_BLC_EN                         BIT(7)
> > > +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK              BIT(8)
> > > +#define ISP_TOP_BEO_CTRL_INV_DGAIN_DIS                  (0 << 8)
> > > +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN                   BIT(8)
> > > +#define ISP_TOP_BEO_CTRL_EOTF_EN_MASK                   BIT(9)
> > > +#define ISP_TOP_BEO_CTRL_EOTF_DIS                       (0 << 9)
> > > +#define ISP_TOP_BEO_CTRL_EOTF_EN                        BIT(9)
> > > +
> > > +#define ISP_TOP_BED_CTRL                                0x0420
> > > +#define ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK               GENMASK(1, 0)
> > > +#define ISP_TOP_BED_CTRL_YHS_STAT_DIS                   (0 << 0)
> > > +#define ISP_TOP_BED_CTRL_YHS_STAT_EN                    BIT(0)
> > > +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK              BIT(2)
> > > +#define ISP_TOP_BED_CTRL_GRPH_STAT_DIS                  (0 << 2)
> > > +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN                   BIT(2)
> > > +#define ISP_TOP_BED_CTRL_FMETER_EN_MASK                 BIT(3)
> > > +#define ISP_TOP_BED_CTRL_FMETER_DIS                     (0 << 3)
> > > +#define ISP_TOP_BED_CTRL_FMETER_EN                      BIT(3)
> > > +#define ISP_TOP_BED_CTRL_BSC_EN_MASK                    BIT(10)
> > > +#define ISP_TOP_BED_CTRL_BSC_DIS                        (0 << 10)
> > > +#define ISP_TOP_BED_CTRL_BSC_EN                         BIT(10)
> > > +#define ISP_TOP_BED_CTRL_CNR2_EN_MASK                   BIT(11)
> > > +#define ISP_TOP_BED_CTRL_CNR2_DIS                       (0 << 11)
> > > +#define ISP_TOP_BED_CTRL_CNR2_EN                        BIT(11)
> > > +#define ISP_TOP_BED_CTRL_CM1_EN_MASK                    BIT(13)
> > > +#define ISP_TOP_BED_CTRL_CM1_DIS                        (0 << 13)
> > > +#define ISP_TOP_BED_CTRL_CM1_EN                         BIT(13)
> > > +#define ISP_TOP_BED_CTRL_CM0_EN_MASK                    BIT(14)
> > > +#define ISP_TOP_BED_CTRL_CM0_DIS                        (0 << 14)
> > > +#define ISP_TOP_BED_CTRL_CM0_EN                         BIT(14)
> > > +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK              BIT(16)
> > > +#define ISP_TOP_BED_CTRL_PST_GAMMA_DIS                  (0 << 16)
> > > +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN                   BIT(16)
> > > +#define ISP_TOP_BED_CTRL_LUT3D_EN_MASK                  BIT(17)
> > > +#define ISP_TOP_BED_CTRL_LUT3D_DIS                      (0 << 17)
> > > +#define ISP_TOP_BED_CTRL_LUT3D_EN                       BIT(17)
> > > +#define ISP_TOP_BED_CTRL_CCM_EN_MASK                    BIT(18)
> > > +#define ISP_TOP_BED_CTRL_CCM_DIS                        (0 << 18)
> > > +#define ISP_TOP_BED_CTRL_CCM_EN                         BIT(18)
> > > +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK           BIT(21)
> > > +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS               (0 << 21)
> > > +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN                BIT(21)
> > > +#define ISP_TOP_BED_CTRL_AMCM_EN_MASK                   BIT(25)
> > > +#define ISP_TOP_BED_CTRL_AMCM_DIS                       (0 << 25)
> > > +#define ISP_TOP_BED_CTRL_AMCM_EN                        BIT(25)
> > > +
> > > +#define ISP_TOP_3A_STAT_CRTL                            0x0424
> > > +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK            BIT(0)
> > > +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS                (0 << 0)
> > > +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN                 BIT(0)
> > > +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK           BIT(1)
> > > +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS               (0 << 1)
> > > +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN                BIT(1)
> > > +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK            BIT(2)
> > > +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS                (0 << 2)
> > > +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN                 BIT(2)
> > > +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT_MASK        GENMASK(6, 4)
> > > +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT(x)          ((x) << 4)
> > > +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT_MASK         GENMASK(9, 8)
> > > +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT(x)           ((x) << 8)
> > > +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT_MASK         GENMASK(13, 12)
> > > +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(x)           ((x) << 12)
> > > +
> > > +#define ISP_LSWB_BLC_OFST0                              0x4028
> > > +#define ISP_LSWB_BLC_OFST0_R_OFST_MASK                  GENMASK(15, 0)
> > > +#define ISP_LSWB_BLC_OFST0_R_OFST(x)                    ((x) << 0)
> > > +#define ISP_LSWB_BLC_OFST0_GR_OFST_MASK                 GENMASK(31, 16)
> > > +#define ISP_LSWB_BLC_OFST0_GR_OFST(x)                   ((x) << 16)
> > > +
> > > +#define ISP_LSWB_BLC_OFST1                              0x402c
> > > +#define ISP_LSWB_BLC_OFST1_GB_OFST_MASK                 GENMASK(15, 0)
> > > +#define ISP_LSWB_BLC_OFST1_GB_OFST(x)                   ((x) << 0)
> > > +#define ISP_LSWB_BLC_OFST1_B_OFST_MASK                  GENMASK(31, 16)
> > > +#define ISP_LSWB_BLC_OFST1_B_OFST(x)                    ((x) << 16)
> > > +
> > > +#define ISP_LSWB_BLC_PHSOFST                            0x4034
> > > +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
> > > +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST(x)               ((x) << 0)
> > > +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
> > > +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
> > > +
> > > +#define ISP_LSWB_WB_GAIN0                               0x4038
> > > +#define ISP_LSWB_WB_GAIN0_R_GAIN_MASK                   GENMASK(11, 0)
> > > +#define ISP_LSWB_WB_GAIN0_R_GAIN(x)                     ((x) << 0)
> > > +#define ISP_LSWB_WB_GAIN0_GR_GAIN_MASK                  GENMASK(27, 16)
> > > +#define ISP_LSWB_WB_GAIN0_GR_GAIN(x)                    ((x) << 16)
> > > +
> > > +#define ISP_LSWB_WB_GAIN1                               0x403c
> > > +#define ISP_LSWB_WB_GAIN1_GB_GAIN_MASK                  GENMASK(11, 0)
> > > +#define ISP_LSWB_WB_GAIN1_GB_GAIN(x)                    ((x) << 0)
> > > +#define ISP_LSWB_WB_GAIN1_B_GAIN_MASK                   GENMASK(27, 16)
> > > +#define ISP_LSWB_WB_GAIN1_B_GAIN(x)                     ((x) << 16)
> > > +
> > > +#define ISP_LSWB_WB_GAIN2                               0x4040
> > > +#define ISP_LSWB_WB_GAIN2_IR_GAIN_MASK                  GENMASK(11, 0)
> > > +#define ISP_LSWB_WB_GAIN2_IR_GAIN(x)                    ((x) << 0)
> > > +
> > > +#define ISP_LSWB_WB_LIMIT0                              0x4044
> > > +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MASK              GENMASK(15, 0)
> > > +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R(x)                ((x) << 0)
> > > +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX               (0x8fff << 0)
> > > +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MASK             GENMASK(31, 16)
> > > +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR(x)               ((x) << 16)
> > > +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX              (0x8fff << 16)
> > > +
> > > +#define ISP_LSWB_WB_LIMIT1                              0x4048
> > > +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MASK             GENMASK(15, 0)
> > > +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB(x)               ((x) << 0)
> > > +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX              (0x8fff << 0)
> > > +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MASK              GENMASK(31, 16)
> > > +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B(x)                ((x) << 16)
> > > +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX               (0x8fff << 16)
> > > +
> > > +#define ISP_LSWB_WB_PHSOFST                             0x4050
> > > +#define ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK              GENMASK(1, 0)
> > > +#define ISP_LSWB_WB_PHSOFST_VERT_OFST(x)                ((x) << 0)
> > > +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK             GENMASK(3, 2)
> > > +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST(x)               ((x) << 2)
> > > +
> > > +#define ISP_LSWB_LNS_PHSOFST                            0x4054
> > > +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
> > > +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST(x)               ((x) << 0)
> > > +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
> > > +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
> > > +
> > > +#define ISP_DMS_COMMON_PARAM0                           0x5000
> > > +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK         GENMASK(1, 0)
> > > +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(x)          ((x) << 0)
> > > +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK        GENMASK(3, 2)
> > > +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(x)         ((x) << 2)
> > > +
> > > +#define ISP_CM0_COEF00_01                               0x6048
> > > +#define ISP_CM0_COEF00_01_MTX_00_MASK                   GENMASK(12, 0)
> > > +#define ISP_CM0_COEF00_01_MTX_00(x)                     ((x) << 0)
> > > +#define ISP_CM0_COEF00_01_MTX_01_MASK                   GENMASK(28, 16)
> > > +#define ISP_CM0_COEF00_01_MTX_01(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CM0_COEF02_10                               0x604c
> > > +#define ISP_CM0_COEF02_10_MTX_02_MASK                   GENMASK(12, 0)
> > > +#define ISP_CM0_COEF02_10_MTX_02(x)                     ((x) << 0)
> > > +#define ISP_CM0_COEF02_10_MTX_10_MASK                   GENMASK(28, 16)
> > > +#define ISP_CM0_COEF02_10_MTX_10(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CM0_COEF11_12                               0x6050
> > > +#define ISP_CM0_COEF11_12_MTX_11_MASK                   GENMASK(12, 0)
> > > +#define ISP_CM0_COEF11_12_MTX_11(x)                     ((x) << 0)
> > > +#define ISP_CM0_COEF11_12_MTX_12_MASK                   GENMASK(28, 16)
> > > +#define ISP_CM0_COEF11_12_MTX_12(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CM0_COEF20_21                               0x6054
> > > +#define ISP_CM0_COEF20_21_MTX_20_MASK                   GENMASK(12, 0)
> > > +#define ISP_CM0_COEF20_21_MTX_20(x)                     ((x) << 0)
> > > +#define ISP_CM0_COEF20_21_MTX_21_MASK                   GENMASK(28, 16)
> > > +#define ISP_CM0_COEF20_21_MTX_21(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CM0_COEF22_OUP_OFST0                        0x6058
> > > +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK            GENMASK(12, 0)
> > > +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22(x)              ((x) << 0)
> > > +
> > > +#define ISP_CCM_MTX_00_01                               0x6098
> > > +#define ISP_CCM_MTX_00_01_MTX_00_MASK                   GENMASK(12, 0)
> > > +#define ISP_CCM_MTX_00_01_MTX_00(x)                     ((x) << 0)
> > > +#define ISP_CCM_MTX_00_01_MTX_01_MASK                   GENMASK(28, 16)
> > > +#define ISP_CCM_MTX_00_01_MTX_01(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CCM_MTX_02_03                               0x609c
> > > +#define ISP_CCM_MTX_02_03_MTX_02_MASK                   GENMASK(12, 0)
> > > +#define ISP_CCM_MTX_02_03_MTX_02(x)                     ((x) << 0)
> > > +
> > > +#define ISP_CCM_MTX_10_11                               0x60A0
> > > +#define ISP_CCM_MTX_10_11_MTX_10_MASK                   GENMASK(12, 0)
> > > +#define ISP_CCM_MTX_10_11_MTX_10(x)                     ((x) << 0)
> > > +#define ISP_CCM_MTX_10_11_MTX_11_MASK                   GENMASK(28, 16)
> > > +#define ISP_CCM_MTX_10_11_MTX_11(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CCM_MTX_12_13                               0x60A4
> > > +#define ISP_CCM_MTX_12_13_MTX_12_MASK                   GENMASK(12, 0)
> > > +#define ISP_CCM_MTX_12_13_MTX_12(x)                     ((x) << 0)
> > > +
> > > +#define ISP_CCM_MTX_20_21                               0x60A8
> > > +#define ISP_CCM_MTX_20_21_MTX_20_MASK                   GENMASK(12, 0)
> > > +#define ISP_CCM_MTX_20_21_MTX_20(x)                     ((x) << 0)
> > > +#define ISP_CCM_MTX_20_21_MTX_21_MASK                   GENMASK(28, 16)
> > > +#define ISP_CCM_MTX_20_21_MTX_21(x)                     ((x) << 16)
> > > +
> > > +#define ISP_CCM_MTX_22_23_RS                            0x60Ac
> > > +#define ISP_CCM_MTX_22_23_RS_MTX_22_MASK                GENMASK(12, 0)
> > > +#define ISP_CCM_MTX_22_23_RS_MTX_22(x)                  ((x) << 0)
> > > +
> > > +#define ISP_PST_GAMMA_LUT_ADDR                          0x60cc
> > > +#define ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(x)              ((x) << 7)
> > > +
> > > +#define ISP_PST_GAMMA_LUT_DATA                          0x60d0
> > > +#define ISP_PST_GAMMA_LUT_DATA_DATA0(x)                 (((x) & GENMASK(15, 0)) << 0)
> > > +#define ISP_PST_GAMMA_LUT_DATA_DATA1(x)                 (((x) & GENMASK(15, 0)) << 16)
> > > +
> > > +#define DISP0_TOP_TOP_CTRL                              0x8000
> > > +#define DISP0_TOP_TOP_CTRL_CROP2_EN_MASK                BIT(5)
> > > +#define DISP0_TOP_TOP_CTRL_CROP2_EN                     BIT(5)
> > > +#define DISP0_TOP_TOP_CTRL_CROP2_DIS                    (0 << 5)
> > > +
> > > +#define DISP0_TOP_CRP2_START                            0x8004
> > > +#define DISP0_TOP_CRP2_START_V_START_MASK               GENMASK(15, 0)
> > > +#define DISP0_TOP_CRP2_START_V_START(x)                 ((x) << 0)
> > > +#define DISP0_TOP_CRP2_START_H_START_MASK               GENMASK(31, 16)
> > > +#define DISP0_TOP_CRP2_START_H_START(x)                 ((x) << 16)
> > > +
> > > +#define DISP0_TOP_CRP2_SIZE                             0x8008
> > > +#define DISP0_TOP_CRP2_SIZE_V_SIZE_MASK                 GENMASK(15, 0)
> > > +#define DISP0_TOP_CRP2_SIZE_V_SIZE(x)                   ((x) << 0)
> > > +#define DISP0_TOP_CRP2_SIZE_H_SIZE_MASK                 GENMASK(31, 16)
> > > +#define DISP0_TOP_CRP2_SIZE_H_SIZE(x)                   ((x) << 16)
> > > +
> > > +#define DISP0_TOP_OUT_SIZE                              0x800c
> > > +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT_MASK       GENMASK(12, 0)
> > > +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT(x)         ((x) << 0)
> > > +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH_MASK        GENMASK(28, 16)
> > > +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(x)          ((x) << 16)
> > > +
> > > +#define ISP_DISP0_TOP_IN_SIZE                           0x804c
> > > +#define ISP_DISP0_TOP_IN_SIZE_VSIZE_MASK                GENMASK(12, 0)
> > > +#define ISP_DISP0_TOP_IN_SIZE_VSIZE(x)                  ((x) << 0)
> > > +#define ISP_DISP0_TOP_IN_SIZE_HSIZE_MASK                GENMASK(28, 16)
> > > +#define ISP_DISP0_TOP_IN_SIZE_HSIZE(x)                  ((x) << 16)
> > > +
> > > +#define DISP0_PPS_SCALE_EN                              0x8200
> > > +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK             GENMASK(3, 0)
> > > +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM(x)               ((x) << 0)
> > > +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM_MASK             GENMASK(7, 4)
> > > +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM(x)               ((x) << 4)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK          GENMASK(11, 8)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(x)            ((x) << 8)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM_MASK          GENMASK(15, 12)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM(x)            ((x) << 12)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK             GENMASK(17, 16)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_RATE(x)               ((x) << 16)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK             GENMASK(19, 18)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_RATE(x)               ((x) << 18)
> > > +#define DISP0_PPS_SCALE_EN_HSC_EN_MASK                  BIT(20)
> > > +#define DISP0_PPS_SCALE_EN_HSC_EN(x)                    ((x) << 20)
> > > +#define DISP0_PPS_SCALE_EN_HSC_DIS                      (0 << 20)
> > > +#define DISP0_PPS_SCALE_EN_VSC_EN_MASK                  BIT(21)
> > > +#define DISP0_PPS_SCALE_EN_VSC_EN(x)                    ((x) << 21)
> > > +#define DISP0_PPS_SCALE_EN_VSC_DIS                      (0 << 21)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_EN_MASK               BIT(22)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_EN(x)                 ((x) << 22)
> > > +#define DISP0_PPS_SCALE_EN_PREVSC_DIS                   (0 << 22)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_EN_MASK               BIT(23)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_EN(x)                 ((x) << 23)
> > > +#define DISP0_PPS_SCALE_EN_PREHSC_DIS                   (0 << 23)
> > > +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK         GENMASK(27, 24)
> > > +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(x)           ((x) << 24)
> > > +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK         GENMASK(31, 28)
> > > +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(x)           ((x) << 28)
> > > +
> > > +#define DISP0_PPS_VSC_START_PHASE_STEP                  0x8224
> > > +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK   GENMASK(23, 0)
> > > +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(x)     ((x) << 0)
> > > +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK   GENMASK(27, 24)
> > > +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(x)     ((x) << 24)
> > > +
> > > +#define DISP0_PPS_HSC_START_PHASE_STEP                  0x8230
> > > +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK  GENMASK(23, 0)
> > > +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(x)    ((x) << 0)
> > > +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK  GENMASK(27, 24)
> > > +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(x)    ((x) << 24)
> > > +
> > > +#define DISP0_PPS_444TO422                              0x823c
> > > +#define DISP0_PPS_444TO422_EN_MASK                      BIT(0)
> > > +#define DISP0_PPS_444TO422_EN(x)                        ((x) << 0)
> > > +
> > > +#define ISP_SCALE0_COEF_IDX_LUMA                        0x8240
> > > +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_MASK     BIT(9)
> > > +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN       BIT(9)
> > > +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_DIS      (0 << 9)
> > > +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE_MASK             GENMASK(12, 10)
> > > +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE(x)               ((x) << 10)
> > > +
> > > +#define ISP_SCALE0_COEF_LUMA                            0x8244
> > > +#define ISP_SCALE0_COEF_LUMA_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
> > > +#define ISP_SCALE0_COEF_LUMA_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
> > > +
> > > +#define ISP_SCALE0_COEF_IDX_CHRO                        0x8248
> > > +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_MASK     BIT(9)
> > > +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN       BIT(9)
> > > +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_DIS      (0 << 9)
> > > +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE_MASK             GENMASK(12, 10)
> > > +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE(x)               ((x) << 10)
> > > +
> > > +#define ISP_SCALE0_COEF_CHRO                            0x824c
> > > +#define ISP_SCALE0_COEF_CHRO_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
> > > +#define ISP_SCALE0_COEF_CHRO_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
> > > +
> > > +#define ISP_AF_CTRL                                     0xa044
> > > +#define ISP_AF_CTRL_VERT_OFST_MASK                      GENMASK(15, 14)
> > > +#define ISP_AF_CTRL_VERT_OFST(x)                        ((x) << 14)
> > > +#define ISP_AF_CTRL_HORIZ_OFST_MASK                     GENMASK(17, 16)
> > > +#define ISP_AF_CTRL_HORIZ_OFST(x)                       ((x) << 16)
> > > +
> > > +#define ISP_AF_HV_SIZE                                  0xa04c
> > > +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE_MASK               GENMASK(15, 0)
> > > +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE(x)                 ((x) << 0)
> > > +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE_MASK               GENMASK(31, 16)
> > > +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE(x)                 ((x) << 16)
> > > +
> > > +#define ISP_AF_HV_BLKNUM                                0xa050
> > > +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(5, 0)
> > > +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
> > > +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(21, 16)
> > > +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
> > > +
> > > +#define ISP_AF_EN_CTRL                                  0xa054
> > > +#define ISP_AF_EN_CTRL_STAT_SEL_MASK                    BIT(21)
> > > +#define ISP_AF_EN_CTRL_STAT_SEL_OLD                     (0 << 21)
> > > +#define ISP_AF_EN_CTRL_STAT_SEL_NEW                     BIT(21)
> > > +
> > > +#define ISP_AF_IDX_ADDR                                 0xa1c0
> > > +#define ISP_AF_IDX_DATA                                 0xa1c4
> > > +#define ISP_AF_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
> > > +#define ISP_AF_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
> > > +
> > > +#define ISP_AE_CTRL                                     0xa448
> > > +#define ISP_AE_CTRL_INPUT_2LINE_MASK                    BIT(7)
> > > +#define ISP_AE_CTRL_INPUT_2LINE_EN                      BIT(7)
> > > +#define ISP_AE_CTRL_INPUT_2LINE_DIS                     (0 << 7)
> > > +#define ISP_AE_CTRL_LUMA_MODE_MASK                      GENMASK(9, 8)
> > > +#define ISP_AE_CTRL_LUMA_MODE_CUR                       (0 << 8)
> > > +#define ISP_AE_CTRL_LUMA_MODE_MAX                       BIT(8)
> > > +#define ISP_AE_CTRL_LUMA_MODE_FILTER                    (2 << 8)
> > > +#define ISP_AE_CTRL_VERT_OFST_MASK                      GENMASK(25, 24)
> > > +#define ISP_AE_CTRL_VERT_OFST(x)                        ((x) << 24)
> > > +#define ISP_AE_CTRL_HORIZ_OFST_MASK                     GENMASK(27, 26)
> > > +#define ISP_AE_CTRL_HORIZ_OFST(x)                       ((x) << 26)
> > > +
> > > +#define ISP_AE_HV_SIZE                                  0xa464
> > > +#define ISP_AE_HV_SIZE_VERT_SIZE_MASK                   GENMASK(15, 0)
> > > +#define ISP_AE_HV_SIZE_VERT_SIZE(x)                     ((x) << 0)
> > > +#define ISP_AE_HV_SIZE_HORIZ_SIZE_MASK                  GENMASK(31, 16)
> > > +#define ISP_AE_HV_SIZE_HORIZ_SIZE(x)                    ((x) << 16)
> > > +
> > > +#define ISP_AE_HV_BLKNUM                                0xa468
> > > +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(6, 0)
> > > +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
> > > +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(22, 16)
> > > +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
> > > +
> > > +#define ISP_AE_IDX_ADDR                                 0xa600
> > > +#define ISP_AE_IDX_DATA                                 0xa604
> > > +#define ISP_AE_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
> > > +#define ISP_AE_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
> > > +
> > > +#define ISP_AE_BLK_WT_ADDR                              0xa608
> > > +#define ISP_AE_BLK_WT_DATA                              0xa60c
> > > +#define ISP_AE_BLK_WT_DATA_IDX_DATA(i, x)               (((x) & GENMASK(3, 0)) << ((i) * 4))
> > > +
> > > +#define ISP_AWB_CTRL                                    0xa834
> > > +#define ISP_AWB_CTRL_VERT_OFST_MASK                     GENMASK(1, 0)
> > > +#define ISP_AWB_CTRL_VERT_OFST(x)                       ((x) << 0)
> > > +#define ISP_AWB_CTRL_HORIZ_OFST_MASK                    GENMASK(3, 2)
> > > +#define ISP_AWB_CTRL_HORIZ_OFST(x)                      ((x) << 2)
> > > +
> > > +#define ISP_AWB_HV_SIZE                                 0xa83c
> > > +#define ISP_AWB_HV_SIZE_VERT_SIZE_MASK                  GENMASK(15, 0)
> > > +#define ISP_AWB_HV_SIZE_VERT_SIZE(x)                    ((x) << 0)
> > > +#define ISP_AWB_HV_SIZE_HORIZ_SIZE_MASK                 GENMASK(31, 16)
> > > +#define ISP_AWB_HV_SIZE_HORIZ_SIZE(x)                   ((x) << 16)
> > > +
> > > +#define ISP_AWB_HV_BLKNUM                               0xa840
> > > +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK            GENMASK(5, 0)
> > > +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM(x)              ((x) << 0)
> > > +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM_MASK           GENMASK(21, 16)
> > > +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(x)             ((x) << 16)
> > > +
> > > +#define ISP_AWB_STAT_RG                                 0xa848
> > > +#define ISP_AWB_STAT_RG_MIN_VALUE_MASK                  GENMASK(11, 0)
> > > +#define ISP_AWB_STAT_RG_MIN_VALUE(x)                    ((x) << 0)
> > > +#define ISP_AWB_STAT_RG_MAX_VALUE_MASK                  GENMASK(27, 16)
> > > +#define ISP_AWB_STAT_RG_MAX_VALUE(x)                    ((x) << 16)
> > > +
> > > +#define ISP_AWB_STAT_BG                                 0xa84c
> > > +#define ISP_AWB_STAT_BG_MIN_VALUE_MASK                  GENMASK(11, 0)
> > > +#define ISP_AWB_STAT_BG_MIN_VALUE(x)                    ((x) << 0)
> > > +#define ISP_AWB_STAT_BG_MAX_VALUE_MASK                  GENMASK(27, 16)
> > > +#define ISP_AWB_STAT_BG_MAX_VALUE(x)                    ((x) << 16)
> > > +
> > > +#define ISP_AWB_STAT_RG_HL                              0xa850
> > > +#define ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
> > > +#define ISP_AWB_STAT_RG_HL_LOW_VALUE(x)                 ((x) << 0)
> > > +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
> > > +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE(x)                ((x) << 16)
> > > +
> > > +#define ISP_AWB_STAT_BG_HL                              0xa854
> > > +#define ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
> > > +#define ISP_AWB_STAT_BG_HL_LOW_VALUE(x)                 ((x) << 0)
> > > +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
> > > +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE(x)                ((x) << 16)
> > > +
> > > +#define ISP_AWB_STAT_CTRL2                              0xa858
> > > +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK              BIT(0)
> > > +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL(x)                ((x) << 0)
> > > +
> > > +#define ISP_AWB_IDX_ADDR                                0xaa00
> > > +#define ISP_AWB_IDX_DATA                                0xaa04
> > > +#define ISP_AWB_IDX_DATA_VIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 0)
> > > +#define ISP_AWB_IDX_DATA_HIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 16)
> > > +
> > > +#define ISP_AWB_BLK_WT_ADDR                             0xaa08
> > > +#define ISP_AWB_BLK_WT_DATA                             0xaa0c
> > > +#define ISP_AWB_BLK_WT_DATA_IDX_DATA(i, x)              (((x) & GENMASK(3, 0)) << ((i) * 4))
> > > +
> > > +#define ISP_WRMIFX3_0_CH0_CTRL0                         0xc400
> > > +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK             GENMASK(28, 16)
> > > +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(x)               ((x) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_CH0_CTRL1                         0xc404
> > > +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
> > > +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS          BIT(27)
> > > +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS         (2 << 27)
> > > +
> > > +#define ISP_WRMIFX3_0_CH1_CTRL0                         0xc408
> > > +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK             GENMASK(28, 16)
> > > +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(x)               ((x) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_CH1_CTRL1                         0xc40c
> > > +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
> > > +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS         (2 << 27)
> > > +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_32BITS         (3 << 27)
> > > +
> > > +#define ISP_WRMIFX3_0_CH0_BADDR                         0xc440
> > > +#define ISP_WRMIFX3_0_CH0_BASE_ADDR(x)                  ((x) >> 4)
> > > +
> > > +#define ISP_WRMIFX3_0_CH1_BADDR                         0xc444
> > > +#define ISP_WRMIFX3_0_CH1_BASE_ADDR(x)                  ((x) >> 4)
> > > +
> > > +#define ISP_WRMIFX3_0_FMT_SIZE                          0xc464
> > > +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE_MASK               GENMASK(15, 0)
> > > +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE(x)                 ((x) << 0)
> > > +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE_MASK               GENMASK(31, 16)
> > > +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE(x)                 ((x) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_FMT_CTRL                          0xc468
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK           GENMASK(1, 0)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT           (0 << 0)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_10BIT          BIT(0)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_12BIT          (2 << 0)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT          (3 << 0)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK         BIT(2)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU           (0 << 2)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV           BIT(2)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK           GENMASK(5, 4)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1             (0 << 4)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2             BIT(4)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK            GENMASK(18, 16)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422          BIT(16)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420          (2 << 16)
> > > +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY          (3 << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_H                        0xc420
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK         GENMASK(28, 16)
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(x)           (((x) - 1) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_V                        0xc424
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK         GENMASK(28, 16)
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(x)           (((x) - 1) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_H                       0xc428
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK       GENMASK(28, 16)
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(x)         (((x) - 1) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_V                       0xc42c
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK       GENMASK(28, 16)
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(x)         (((x) - 1) << 16)
> > > +
> > > +#define VIU_DMAWR_BADDR0                                0xc840
> > > +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
> > > +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(x)          ((x) >> 4)
> > > +
> > > +#define VIU_DMAWR_BADDR1                                0xc844
> > > +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK       GENMASK(27, 0)
> > > +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(x)         ((x) >> 4)
> > > +
> > > +#define VIU_DMAWR_BADDR2                                0xc848
> > > +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
> > > +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(x)          ((x) >> 4)
> > > +
> > > +#define VIU_DMAWR_SIZE0                                 0xc854
> > > +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK              GENMASK(15, 0)
> > > +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE(x)                ((x) << 0)
> > > +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK             GENMASK(31, 16)
> > > +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE(x)               ((x) << 16)
> > > +
> > > +#define VIU_DMAWR_SIZE1                                 0xc858
> > > +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK              GENMASK(15, 0)
> > > +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE(x)                ((x) << 0)
> > > +
> > > +#endif
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
> > > new file mode 100644
> > > index 000000000000..b179eff4cfd6
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
> > > @@ -0,0 +1,779 @@
> > > +// 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"
> > > +
> > > +/* The normal parameters of pps module */
> > > +static const int pps_lut_tap4_s11[C3_ISP_PPS_TAP4_S11_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 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 step_h_integer, step_v_integer;
> > > +     int step_h_fraction, step_v_fraction;
> > > +     int yuv444to422_en;
> > > +
> > > +     /* Calculate the integer part of horizonal scaler step */
> > > +     step_h_integer = thsize / ohsize;
> > > +
> > > +     /* Calculate the vertical part of horizonal scaler step */
> > > +     step_v_integer = 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.
> > > +      */
> > > +     step_h_fraction = ((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.
> > > +      */
> > > +     step_v_fraction = ((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_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> > > +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK,
> > > +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(step_v_fraction));
> > > +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> > > +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK,
> > > +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(step_v_integer));
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> > > +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK,
> > > +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(step_h_fraction));
> > > +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> > > +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK,
> > > +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(step_h_integer));
> > > +}
> > > +
> > > +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_TAP4_S11_H_NUM; i++) {
> > > +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> > > +                          ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
> > > +                          ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
> > > +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> > > +                          ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
> > > +                          ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[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_TAP4_S11_H_NUM; i++) {
> > > +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> > > +                          ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
> > > +                          ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
> > > +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> > > +                          ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
> > > +                          ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[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_RESIZER_PAD_SINK);
> > > +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > > +
> > > +     ihsize = crop->width;
> > > +     ivsize = crop->height;
> > > +
> > > +     hsc_en = (ihsize == cmps->width) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_EN;
> > > +     vsc_en = (ivsize == cmps->height) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_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.
> > minor nit: seems like you should wrap comments here too
>
>
> Will wrap comments.
>
> > > +      */
> > > +     preh_en = (ihsize > cmps->width * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
> > > +     prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
> > > +
> > > +     if (rsz->id == C3_ISP_RSZ_2) {
> > > +             max_hsize = C3_ISP_MAX_WIDTH;
> > > +             /*
> > > +              * Set vertical tap number and
> > > +              * the max hsize of pre-vertical scale.
> > > +              */
> > > +             prevsc_flt_num = 4;
> > > +             pre_vscale_max_hsize = max_hsize / 2;
> > > +     } else {
> > > +             max_hsize = C3_ISP_DEFAULT_WIDTH;
> > > +             preh_en  = (ihsize > max_hsize) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
> > > +             /*
> > > +              * Set vertical tap number and
> > > +              * the max hsize of pre-vertical scale.
> > > +              */
> > > +             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;
> > > +     struct v4l2_rect *sink_crop;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> > > +
> > > +     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);
> > > +
> > > +     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_SCALER_OUT_HEIGHT_MASK,
> > > +                        DISP0_TOP_OUT_SIZE_SCALER_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_SCALER_OUT_WIDTH_MASK,
> > > +                        DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(src_fmt->width));
> > > +
> > > +     if (rsz->id == C3_ISP_RSZ_0) {
> > > +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_DISP0_EN);
> > > +     } else if (rsz->id == C3_ISP_RSZ_1) {
> > > +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_DISP1_EN);
> > > +     } else if (rsz->id == C3_ISP_RSZ_2) {
> > > +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_DISP2_EN);
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
> > > +{
> > > +     if (rsz->id == C3_ISP_RSZ_0) {
> > > +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_DISP0_DIS);
> > > +     } else if (rsz->id == C3_ISP_RSZ_1) {
> > > +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_DISP1_DIS);
> > > +     } else if (rsz->id == C3_ISP_RSZ_2) {
> > > +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
> > > +                                ISP_TOP_PATH_EN_DISP2_DIS);
> > > +     }
> > > +
> > > +     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 bool c3_isp_rsz_streams_ready(struct c3_isp_resizer *rsz)
> > > +{
> > > +     unsigned int n_links = 0;
> > > +     struct media_link *link;
> > > +
> > > +     for_each_media_entity_data_link(&rsz->src_sd->entity, link) {
> > > +             if (link->source->index == rsz->src_sd_pad &&
> > > +                 link->flags == MEDIA_LNK_FL_ENABLED)
> > > +                     n_links++;
> > > +     }
> > > +
> > > +     return n_links == rsz->isp->pipe.start_count;
> > > +}
> > > +
> > > +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);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     c3_isp_rsz_start(rsz, state);
> > > +
> > > +     if (!c3_isp_rsz_streams_ready(rsz))
> > > +             return 0;
> > > +
> > > +     c3_isp_params_pre_cfg(rsz->isp);
> > > +     c3_isp_stats_pre_cfg(rsz->isp);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    C3_ISP_RESIZER_PAD_SINK,
> > > +                                                    &streams_mask);
> > Apart from the usual comments about routing, reflowing comments and
> > minor nits, this module looks ok to me. I admit I have not tried to
> > fully understand the scaler programming part at the beginning, but I
> > trust you know the HW better than me :)
>
>
> Will remove sink_streams.
>
> > The stats driver looks ok as well.
> >
> > I hope next version will be good to go, and I hope to be able to test
> > this implementation soon using libcamera!
> >
> > Great work!
> >
> > Thanks
> >    j
> >
>
> Thanks very much! :)
>

Thanks, let's try to land this!

> > > +     ret = v4l2_subdev_enable_streams(rsz->src_sd,
> > > +                                      rsz->src_sd_pad, sink_streams);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 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);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     c3_isp_rsz_stop(rsz);
> > > +
> > > +     if (rsz->isp->pipe.start_count != 1)
> > > +             return 0;
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    C3_ISP_RESIZER_PAD_SINK,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_disable_streams(rsz->src_sd,
> > > +                                       rsz->src_sd_pad, sink_streams);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
> > > +                               struct v4l2_subdev_state *state,
> > > +                               struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     static const struct v4l2_mbus_framefmt format = {
> > > +             .width = C3_ISP_DEFAULT_WIDTH,
> > > +             .height = C3_ISP_DEFAULT_HEIGHT,
> > > +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
> > > +             .field = V4L2_FIELD_NONE,
> > > +             .colorspace = V4L2_COLORSPACE_SRGB,
> > > +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
> > > +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > > +             .xfer_func = V4L2_XFER_FUNC_SRGB,
> > > +     };
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_subdev_route routes;
> > > +     struct v4l2_subdev_krouting routing;
> > > +
> > > +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
> > > +     routes.sink_stream = 0;
> > > +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
> > > +     routes.source_stream = 0;
> > > +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routing.num_routes = 1;
> > > +     routing.routes = &routes;
> > > +
> > > +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
> > > +}
> > > +
> > > +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
> > > +                                  struct v4l2_subdev_state *state,
> > > +                                  struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +     if (code->index)
> > > +             return -EINVAL;
> > > +
> > > +     code->code = MEDIA_BUS_FMT_YUV8_1X24;
> > > +
> > > +     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;
> > > +
> > > +     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_RESIZER_PAD_SOURCE);
> > > +
> > > +     sink_fmt->code = MEDIA_BUS_FMT_YUV8_1X24;
> > > +     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_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;
> > > +
> > > +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > > +
> > > +     src_fmt->code = sink_fmt->code;
> > > +     src_fmt->width = sink_cmps->width;
> > > +     src_fmt->height = sink_cmps->height;
> > > +
> > > +     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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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 *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +     struct v4l2_rect *crop;
> > > +     struct v4l2_rect *cmps;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > > +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > > +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> > > +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > > +
> > > +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_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_RESIZER_PAD_SINK);
> > > +     cmps->width = C3_ISP_DEFAULT_WIDTH;
> > > +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     cmps->left = 0;
> > > +     cmps->top = 0;
> > > +
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> > > +     *src_fmt = *sink_fmt;
> > > +
> > > +     return c3_isp_rsz_init_routing(sd, state);
> > > +}
> > > +
> > > +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_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > > +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_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)
> > > +{
> > > +     u32 i;
> > > +     int ret;
> > > +
> > > +     for (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_sd_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> > > +
> > > +             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)
> > > +{
> > > +     u32 i;
> > > +
> > > +     for (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);
> > > +     };
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
> > > new file mode 100644
> > > index 000000000000..1c376c800176
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
> > > @@ -0,0 +1,319 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#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-mc.h>
> > > +#include <media/videobuf2-dma-contig.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
> > > +{
> > > +     u32 awb_dma_size = sizeof(struct c3_isp_awb_stats);
> > > +     u32 ae_dma_size = sizeof(struct c3_isp_ae_stats);
> > > +     u32 awb_dma_addr = stats->buff->dma_addr;
> > > +     u32 af_dma_addr;
> > > +     u32 ae_dma_addr;
> > > +
> > > +     ae_dma_addr = awb_dma_addr + awb_dma_size;
> > > +     af_dma_addr = ae_dma_addr + ae_dma_size;
> > > +
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR0,
> > > +                        VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK,
> > > +                        VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(af_dma_addr));
> > > +
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR1,
> > > +                        VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK,
> > > +                        VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(awb_dma_addr));
> > > +
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR2,
> > > +                        VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK,
> > > +                        VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(ae_dma_addr));
> > > +}
> > > +
> > > +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
> > > +{
> > > +     stats->buff = list_first_entry_or_null(&stats->pending,
> > > +                                            struct c3_isp_buffer, list);
> > > +     if (stats->buff) {
> > > +             c3_isp_stats_cfg_dmawr_addr(stats);
> > > +             list_del(&stats->buff->list);
> > > +     }
> > > +}
> > > +
> > > +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +     u32 dma_size;
> > > +
> > > +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, ISP_AF_EN_CTRL_STAT_SEL_MASK,
> > > +                        ISP_AF_EN_CTRL_STAT_SEL_NEW);
> > > +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, ISP_AE_CTRL_LUMA_MODE_MASK,
> > > +                        ISP_AE_CTRL_LUMA_MODE_FILTER);
> > > +
> > > +     /* The unit of dma_size is 16 bytes */
> > > +     dma_size = sizeof(struct c3_isp_af_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK,
> > > +                        VIU_DMAWR_SIZE0_AF_STATS_SIZE(dma_size));
> > > +
> > > +     dma_size = sizeof(struct c3_isp_awb_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK,
> > > +                        VIU_DMAWR_SIZE0_AWB_STATS_SIZE(dma_size));
> > > +
> > > +     dma_size = sizeof(struct c3_isp_ae_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK,
> > > +                        VIU_DMAWR_SIZE1_AE_STATS_SIZE(dma_size));
> > > +
> > > +     guard(spinlock_irqsave)(&stats->buff_lock);
> > > +
> > > +     c3_isp_stats_cfg_buff(stats);
> > > +}
> > > +
> > > +static int c3_isp_stats_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_stats_enum_fmt(struct file *file, void *fh,
> > > +                              struct v4l2_fmtdesc *f)
> > > +{
> > > +     struct c3_isp_stats *stats = video_drvdata(file);
> > > +
> > > +     if (f->index > 0 || f->type != stats->vb2_q.type)
> > > +             return -EINVAL;
> > > +
> > > +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
> > > +                           struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_stats *stats = video_drvdata(file);
> > > +
> > > +     f->fmt.meta = stats->vfmt.fmt.meta;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
> > > +     .vidioc_querycap                = c3_isp_stats_querycap,
> > > +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
> > > +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
> > > +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
> > > +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_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_stats_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_stats_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_stats_buffer))
> > > +                     return -EINVAL;
> > > +     } else {
> > > +             *num_planes = 1;
> > > +             sizes[0] = sizeof(struct c3_isp_stats_buffer);
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > > +
> > > +     guard(spinlock_irqsave)(&stats->buff_lock);
> > > +
> > > +     list_add_tail(&buf->list, &stats->pending);
> > > +}
> > > +
> > > +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
> > > +{
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
> > > +
> > > +     if (vb2_plane_size(vb, 0) < size) {
> > > +             dev_err(stats->isp->dev,
> > > +                     "User buffer too small (%ld < %u)\n",
> > > +                     vb2_plane_size(vb, 0), size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     vb2_set_plane_payload(vb, 0, size);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
> > > +
> > > +     buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
> > > +{
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> > > +
> > > +     guard(spinlock_irqsave)(&stats->buff_lock);
> > > +
> > > +     if (stats->buff) {
> > > +             vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> > > +             stats->buff = NULL;
> > > +     }
> > > +
> > > +     while (!list_empty(&stats->pending)) {
> > > +             struct c3_isp_buffer *buff;
> > > +
> > > +             buff = list_first_entry(&stats->pending,
> > > +                                     struct c3_isp_buffer, list);
> > > +             list_del(&buff->list);
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> > > +     }
> > > +}
> > > +
> > > +static const struct vb2_ops isp_stats_vb2_ops = {
> > > +     .queue_setup = c3_isp_stats_vb2_queue_setup,
> > > +     .buf_queue = c3_isp_stats_vb2_buf_queue,
> > > +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
> > > +     .buf_init = c3_isp_stats_vb2_buf_init,
> > > +     .wait_prepare = vb2_ops_wait_prepare,
> > > +     .wait_finish = vb2_ops_wait_finish,
> > > +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
> > > +};
> > > +
> > > +int c3_isp_stats_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +     struct video_device *vdev = &stats->vdev;
> > > +     struct vb2_queue *vb2_q = &stats->vb2_q;
> > > +     int ret;
> > > +
> > > +     memset(stats, 0, sizeof(*stats));
> > > +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
> > > +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_buffer);
> > > +     stats->isp = isp;
> > > +     INIT_LIST_HEAD(&stats->pending);
> > > +     spin_lock_init(&stats->buff_lock);
> > > +
> > > +     mutex_init(&stats->lock);
> > > +
> > > +     snprintf(vdev->name, sizeof(vdev->name), "c3-isp-stats");
> > > +     vdev->fops = &isp_stats_v4l2_fops;
> > > +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
> > > +     vdev->v4l2_dev = &isp->v4l2_dev;
> > > +     vdev->lock = &stats->lock;
> > > +     vdev->minor = -1;
> > > +     vdev->queue = vb2_q;
> > > +     vdev->release = video_device_release_empty;
> > > +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
> > > +     vdev->vfl_dir = VFL_DIR_RX;
> > > +     video_set_drvdata(vdev, stats);
> > > +
> > > +     vb2_q->drv_priv = stats;
> > > +     vb2_q->mem_ops = &vb2_dma_contig_memops;
> > > +     vb2_q->ops = &isp_stats_vb2_ops;
> > > +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
> > > +     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_buffer);
> > > +     vb2_q->dev = isp->dev;
> > > +     vb2_q->lock = &stats->lock;
> > > +     vb2_q->min_queued_buffers = 2;
> > > +
> > > +     ret = vb2_queue_init(vb2_q);
> > > +     if (ret)
> > > +             goto err_destroy;
> > > +
> > > +     stats->pad.flags = MEDIA_PAD_FL_SINK;
> > > +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
> > > +     if (ret)
> > > +             goto err_queue_release;
> > > +
> > > +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > > +     if (ret) {
> > > +             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_destroy:
> > > +     mutex_destroy(&stats->lock);
> > > +     return ret;
> > > +}
> > > +
> > > +void c3_isp_stats_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +
> > > +     vb2_queue_release(&stats->vb2_q);
> > > +     media_entity_cleanup(&stats->vdev.entity);
> > > +     video_unregister_device(&stats->vdev);
> > > +     mutex_destroy(&stats->lock);
> > > +}
> > > +
> > > +void c3_isp_stats_isr(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +
> > > +     guard(spinlock_irqsave)(&stats->buff_lock);
> > > +
> > > +     if (stats->buff) {
> > > +             stats->buff->vb.sequence = stats->isp->frm_sequence;
> > > +             stats->buff->vb.vb2_buf.timestamp = ktime_get();
> > > +             stats->buff->vb.field = V4L2_FIELD_NONE;
> > > +             vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > > +     }
> > > +
> > > +     c3_isp_stats_cfg_buff(stats);
> > > +}
> > >
> > > --
> > > 2.47.0
> > >
> > >
> > >
>
Keke Li Dec. 13, 2024, 11:51 a.m. UTC | #4
Hi Jacopo

Thanks very much for your reply.

On 2024/12/13 19:34, Jacopo Mondi wrote:
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
> On Fri, Dec 13, 2024 at 07:14:16PM +0800, Keke Li wrote:
>> Hi Jacopo
>>
>> Thanks very much for your reply.
>>
>> On 2024/12/12 23:39, Jacopo Mondi wrote:
>>> [ EXTERNAL EMAIL ]
>>>
>>> Hi Keke
>>>
>>> On Thu, Dec 05, 2024 at 05:04:34PM +0800, Keke Li via B4 Relay wrote:
>>>> From: Keke Li <keke.li@amlogic.com>
>>>>
>>>> The C3 ISP supports multi-camera and muti-exposure
>>>> high dynamic range (HDR). It brings together some
>>>> advanced imaging technologies to provide good image quality.
>>>> This driver mainly responsible for driving ISP pipeline
>>>> to process raw image.
>>>>
>>>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>>>> ---
>>>>    drivers/media/platform/amlogic/c3/Kconfig          |   1 +
>>>>    drivers/media/platform/amlogic/c3/Makefile         |   1 +
>>>>    drivers/media/platform/amlogic/c3/isp/Kconfig      |  17 +
>>>>    drivers/media/platform/amlogic/c3/isp/Makefile     |  10 +
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-capture.c | 790 +++++++++++++++++
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-common.h  | 352 ++++++++
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-core.c    | 553 ++++++++++++
>>>>    drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c | 459 ++++++++++
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-params.c  | 942 +++++++++++++++++++++
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-regs.h    | 599 +++++++++++++
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 779 +++++++++++++++++
>>>>    .../media/platform/amlogic/c3/isp/c3-isp-stats.c   | 319 +++++++
>>>>    12 files changed, 4822 insertions(+)
>>>>
>>>> diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig
>>>> index a504a1eb22e6..d355d3a9358d 100644
>>>> --- a/drivers/media/platform/amlogic/c3/Kconfig
>>>> +++ b/drivers/media/platform/amlogic/c3/Kconfig
>>>> @@ -1,4 +1,5 @@
>>>>    # SPDX-License-Identifier: GPL-2.0-only
>>>>
>>>> +source "drivers/media/platform/amlogic/c3/isp/Kconfig"
>>>>    source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig"
>>>>    source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig"
>>>> diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile
>>>> index 770b2a2903ad..14f305a493d2 100644
>>>> --- a/drivers/media/platform/amlogic/c3/Makefile
>>>> +++ b/drivers/media/platform/amlogic/c3/Makefile
>>>> @@ -1,4 +1,5 @@
>>>>    # SPDX-License-Identifier: GPL-2.0-only
>>>>
>>>> +obj-y += isp/
>>>>    obj-y += mipi-adapter/
>>>>    obj-y += mipi-csi2/
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/Kconfig b/drivers/media/platform/amlogic/c3/isp/Kconfig
>>>> new file mode 100644
>>>> index 000000000000..de68d19ca2d1
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/Kconfig
>>>> @@ -0,0 +1,17 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +config VIDEO_C3_ISP
>>>> +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
>>>> +     depends on ARCH_MESON || COMPILE_TEST
>>>> +     depends on VIDEO_DEV
>>>> +     depends on OF
>>>> +     select MEDIA_CONTROLLER
>>>> +     select V4L2_FWNODE
>>>> +     select VIDEO_V4L2_SUBDEV_API
>>>> +     select VIDEOBUF2_DMA_CONTIG
>>> also
>>>           select VIDEOBUF2_VMALLOC
>>>
>>> for the params node
>>>
>> Will add 'select VIDEOBUF2_VMALLOC'
>>
> Yes please, I just tried and building with aarch64 defconfig breaks
> without this


OK

>>>> +     help
>>>> +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
>>>> +       The C3 ISP is used for processing raw images and
>>>> +       outputing results to memory.
>>>> +
>>>> +       To compile this driver as a module choose m here.
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/Makefile b/drivers/media/platform/amlogic/c3/isp/Makefile
>>>> new file mode 100644
>>>> index 000000000000..b1b064170b57
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/Makefile
>>>> @@ -0,0 +1,10 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +c3-isp-objs := c3-isp-dev.o \
>>>> +            c3-isp-params.o \
>>>> +            c3-isp-stats.o \
>>>> +            c3-isp-capture.o \
>>>> +            c3-isp-core.o \
>>>> +            c3-isp-resizer.o
>>>> +
>>>> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
>>>> new file mode 100644
>>>> index 000000000000..e1bbb3fa8da2
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
>>>> @@ -0,0 +1,790 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/cleanup.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-event.h>
>>>> +#include <media/v4l2-ioctl.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +#include <media/videobuf2-dma-contig.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +static const struct c3_isp_capture_format cap_formats[] = {
>>>> +     {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>>>> +             .fourcc = V4L2_PIX_FMT_GREY,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>>>> +             .fourcc = V4L2_PIX_FMT_NV12,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>>>> +             .fourcc = V4L2_PIX_FMT_NV21,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>>>> +             .fourcc = V4L2_PIX_FMT_NV16,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
>>>> +             .fourcc = V4L2_PIX_FMT_NV61,
>>>> +     },
>>>> +};
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +/* Set the address of wrmifx3(write memory interface) */
>>>> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>>> +     dma_addr_t dma_addr;
>>>> +
>>>> +     if (cap->buff)
>>>> +             dma_addr = cap->buff->dma_addr;
>>>> +     else
>>>> +             dma_addr = cap->dummy_buff.dma_addr;
>>>> +
>>>> +     c3_isp_write(cap->isp,
>>>> +                  C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
>>>> +                  ISP_WRMIFX3_0_CH0_BASE_ADDR(dma_addr));
>>>> +
>>>> +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
>>>> +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
>>>> +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
>>>> +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
>>>> +             u32 offset = ALIGN(pix->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES) * pix->height;
>>> I'm sorry but V4L2 has a requirement about 80-cols stricter than other
>>> subsystem (I know..)
>>>
>>> Could you run
>>> ./scripts/checkpatch.pl --strict --max-line-length=80
>>>
>>> on your patches and see how you can fix these ?
>>
>> Will check this issue and adjust max-line-length to 80.
>>
> Thanks, I know it's sometimes annoying
>>>> +
>>>> +             c3_isp_write(cap->isp,
>>>> +                          C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
>>>> +                          ISP_WRMIFX3_0_CH1_BASE_ADDR(dma_addr + offset));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +     u32 stride;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
>>>> +
>>>> +     /* Grey has 1 plane*/
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1);
>>>> +
>>>> +     /* Set Y only as output format */
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY);
>>>> +
>>>> +     /* The unit of stride is 16 bytes */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 1, 16);
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, bool swap)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +     u32 stride;
>>>> +     u32 swap_uv;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
>>>> +
>>>> +     swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
>>>> +                      ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
>>>> +
>>>> +     /* NV12 or NV21 has 2 planes*/
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
>>>> +
>>>> +     /* Set YUV420 as output format */
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width >> 1));
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
>>>> +
>>>> +     /* The unit of Y and UV strides is 16 bytes */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, bool swap)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +     u32 stride;
>>>> +     u32 swap_uv;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT);
>>>> +
>>>> +     swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
>>>> +                      ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
>>>> +
>>>> +     /* NV16 or NV61 has 2 planes*/
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
>>>> +
>>>> +     /* Set YUV422 as output format */
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
>>>> +                        ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width));
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
>>>> +                        ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
>>>> +
>>>> +     /* The unit of Y stride is 16 bytes */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
>>>> +
>>>> +     /* The unit of UV stride is 16 bytes */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 2, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
>>>> +                        ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
>>>> +}
>>> I feel like the above functions could 'easily' be unified in a single
>>> one that computes the right parameters using the pixel format info.
>>>
>>> Feel free to expand 'struct c3_isp_capture_format' with all the info
>>> you need to compute the correct register values if you think unifying
>>> the above functions is helpful
>>>
>> Will try to test your suggestion.
>>
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +
>>>> +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY)
>>>> +             c3_isp_cap_wrmifx3_grey(cap);
>>>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV12)
>>>> +             c3_isp_cap_wrmifx3_yuv420(cap, true);
>>>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV21)
>>>> +             c3_isp_cap_wrmifx3_yuv420(cap, false);
>>>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV16)
>>>> +             c3_isp_cap_wrmifx3_yuv422(cap, true);
>>>> +     else if (fmt->pixelformat == V4L2_PIX_FMT_NV61)
>>>> +             c3_isp_cap_wrmifx3_yuv422(cap, false);
>>>> +
>>>> +     c3_isp_write(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
>>>> +                  ISP_WRMIFX3_0_FMT_SIZE_HSIZE(fmt->width) |
>>>> +                  ISP_WRMIFX3_0_FMT_SIZE_VSIZE(fmt->height));
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
>>>> +                        ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK,
>>>> +                        ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(fmt->width));
>>>> +     c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
>>>> +                        ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK,
>>>> +                        ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(fmt->height));
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_dummy_buff_create(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct c3_isp_dummy_buffer *dummy_buff = &cap->dummy_buff;
>>>> +
>>>> +     dummy_buff->size = cap->vfmt.fmt.pix.sizeimage;
>>>> +
>>>> +     /* The driver never access vaddr, no mapping is required */
>>>> +     dummy_buff->vaddr = dma_alloc_attrs(cap->isp->dev,
>>>> +                                         dummy_buff->size,
>>>> +                                         &dummy_buff->dma_addr,
>>>> +                                         GFP_KERNEL,
>>>> +                                         DMA_ATTR_NO_KERNEL_MAPPING);
>>>> +     if (!dummy_buff->vaddr)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_dummy_buff_destroy(struct c3_isp_capture *cap)
>>>> +{
>>>> +     dma_free_attrs(cap->isp->dev,
>>>> +                    cap->dummy_buff.size, cap->dummy_buff.vaddr,
>>>> +                    cap->dummy_buff.dma_addr, DMA_ATTR_NO_KERNEL_MAPPING);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
>>>> +{
>>>> +     cap->buff = list_first_entry_or_null(&cap->pending,
>>>> +                                          struct c3_isp_buffer, list);
>>>> +
>>>> +     c3_isp_cap_wrmifx3_buff(cap);
>>>> +
>>>> +     if (cap->buff)
>>>> +             list_del(&cap->buff->list);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
>>>> +{
>>>> +     scoped_guard(spinlock_irqsave, &cap->buff_lock)
>>>> +             c3_isp_cap_cfg_buff(cap);
>>>> +
>>>> +     c3_isp_cap_wrmifx3_size(cap);
>>>> +
>>>> +     if (cap->id == C3_ISP_CAP_DEV_0) {
>>>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_WRMIF0_EN);
>>>> +     } else if (cap->id == C3_ISP_CAP_DEV_1) {
>>>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_WRMIF1_EN);
>>>> +     } else if (cap->id == C3_ISP_CAP_DEV_2) {
>>>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_WRMIF2_EN);
>>>> +     }
>>> You could spare the {} for single line statements
>>>
>>> Could this maybe be simplified as
>>>
>>>           u32 val = cap->id == C3_ISP_CAP_DEV_0 ? ISP_TOP_PATH_EN_WRMIF0_EN :
>>>                     cap->id == C3_ISP_CAP_DEV_1 ? ISP_TOP_PATH_EN_WRMIF1_EN :
>>>                     cap->id == C3_ISP_CAP_DEV_2 ? ISP_TOP_PATH_EN_WRMIF2_EN : 0;
>>>
>>>           scoped_guard(spinlock_irqsave, &cap->buff_lock)
>>>                   c3_isp_cap_cfg_buff(cap);
>>>
>>>           c3_isp_cap_wrmifx3_size(cap);
>>>           c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, val, val);
>>>
>> Will use your method.
>>
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
>>>> +{
>>>> +     if (cap->id == C3_ISP_CAP_DEV_0) {
>>>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_WRMIF0_DIS);
>>>> +     } else if (cap->id == C3_ISP_CAP_DEV_1) {
>>>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_WRMIF1_DIS);
>>>> +     } else if (cap->id == C3_ISP_CAP_DEV_2) {
>>>> +             c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_WRMIF2_DIS);
>>>> +     }
>>> same here
>>
>> Will use your method.
>>
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_done(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct c3_isp_buffer *buff = cap->buff;
>>>> +
>>>> +     guard(spinlock_irqsave)(&cap->buff_lock);
>>>> +
>>>> +     if (buff) {
>>>> +             buff->vb.sequence = cap->isp->frm_sequence;
>>>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>>>> +             buff->vb.field = V4L2_FIELD_NONE;
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>>> +     }
>>>> +
>>>> +     c3_isp_cap_cfg_buff(cap);
>>>> +}
>>>> +
>>>> +/* V4L2 video operations */
>>>> +
>>>> +static const struct c3_isp_capture_format
>>>> +*c3_cap_find_fmt(u32 fourcc)
>>> fits in one line
>>
>> Will fit in one line.
>>
>>>> +{
>>>> +     unsigned int i;
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>>> nit: you can declare i inside the for loop
>>
>> Will use 'unsigned int i = 0' in the for loop.
>>
>>>> +             if (cap_formats[i].fourcc == fourcc)
>>>> +                     return &cap_formats[i];
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
>>>> +                        struct v4l2_pix_format *pix)
>>>> +{
>>>> +     const struct c3_isp_capture_format *fmt;
>>>> +     const struct v4l2_format_info *info;
>>>> +
>>>> +     fmt = c3_cap_find_fmt(pix->pixelformat);
>>>> +     if (!fmt)
>>>> +             fmt = &cap_formats[0];
>>>> +
>>>> +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>>> +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>>> +     pix->pixelformat = fmt->fourcc;
>>>> +     pix->field = V4L2_FIELD_NONE;
>>>> +     pix->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>>> +
>>>> +     info = v4l2_format_info(fmt->fourcc);
>>>> +     pix->bytesperline = pix->width * info->bpp[0] / info->bpp_div[0];
>>>> +
>>>> +     /* ISP hardware requires 16 bytes alignment */
>>>> +     pix->bytesperline = ALIGN(pix->bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>>>> +
>>>> +     pix->sizeimage = 0;
>>>> +     for (unsigned int i = 0; i < info->comp_planes; i++) {
>>>> +             unsigned int hdiv = (i == 0) ? 1 : info->hdiv;
>>>> +             unsigned int vdiv = (i == 0) ? 1 : info->vdiv;
>>>> +             unsigned int bytesperline;
>>>> +
>>>> +             bytesperline = info->bpp[i] * DIV_ROUND_UP(pix->width, hdiv) / info->bpp_div[i];
>>>> +
>>>> +             /* ISP hardware requires 16 bytes alignment */
>>>> +             bytesperline = ALIGN(bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
>>>> +             pix->sizeimage += bytesperline * DIV_ROUND_UP(pix->height, vdiv);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
>>>> +                                   enum vb2_buffer_state state)
>>>> +{
>>>> +     struct c3_isp_buffer *buff;
>>>> +
>>>> +     guard(spinlock_irqsave)(&cap->buff_lock);
>>>> +
>>>> +     if (cap->buff) {
>>>> +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
>>>> +             cap->buff = NULL;
>>>> +     }
>>>> +
>>>> +     while (!list_empty(&cap->pending)) {
>>>> +             buff = list_first_entry(&cap->pending,
>>>> +                                     struct c3_isp_buffer, list);
>>>> +             list_del(&buff->list);
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>>>> +     }
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_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_cap_enum_fmt(struct file *file, void *fh,
>>>> +                            struct v4l2_fmtdesc *f)
>>>> +{
>>>> +     const struct c3_isp_capture_format *fmt;
>>>> +     unsigned int index = 0;
>>>> +     unsigned int i;
>>>> +
>>>> +     if (!f->mbus_code) {
>>>> +             if (f->index >= ARRAY_SIZE(cap_formats))
>>>> +                     return -EINVAL;
>>>> +
>>>> +             fmt = &cap_formats[f->index];
>>>> +             f->pixelformat = fmt->fourcc;
>>>> +             return 0;
>>>> +     }
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>>>> +             fmt = &cap_formats[i];
>>>> +             if (f->mbus_code != fmt->mbus_code)
>>>> +                     continue;
>>>> +
>>>> +             if (index++ == f->index) {
>>>> +                     f->pixelformat = cap_formats[i].fourcc;
>>>> +                     return 0;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return -EINVAL;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
>>>> +                         struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_capture *cap = video_drvdata(file);
>>>> +
>>>> +     f->fmt.pix = cap->vfmt.fmt.pix;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
>>>> +                         struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_capture *cap = video_drvdata(file);
>>>> +
>>>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>>>> +     cap->vfmt = *f;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
>>>> +                           struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_capture *cap = video_drvdata(file);
>>>> +
>>>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
>>>> +                                struct v4l2_frmsizeenum *fsize)
>>>> +{
>>>> +     const struct c3_isp_capture_format *fmt;
>>>> +
>>>> +     if (fsize->index)
>>>> +             return -EINVAL;
>>>> +
>>>> +     fmt = c3_cap_find_fmt(fsize->pixel_format);
>>>> +     if (!fmt)
>>>> +             return -EINVAL;
>>>> +
>>>> +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>>>> +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
>>>> +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
>>>> +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
>>>> +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
>>>> +     fsize->stepwise.step_width = 2;
>>>> +     fsize->stepwise.step_height = 2;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
>>>> +     .vidioc_querycap                = c3_isp_cap_querycap,
>>>> +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
>>>> +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
>>>> +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
>>>> +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_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,
>>>> +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
>>>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>>> +};
>>>> +
>>>> +static const struct v4l2_file_operations isp_cap_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_cap_link_validate(struct media_link *link)
>>>> +{
>>>> +     struct video_device *vdev =
>>>> +             media_entity_to_video_device(link->sink->entity);
>>>> +     struct v4l2_subdev *sd =
>>>> +             media_entity_to_v4l2_subdev(link->source->entity);
>>>> +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
>>>> +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
>>>> +     struct v4l2_subdev_format src_fmt = {
>>>> +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>>>> +             .pad = link->source->index,
>>>> +     };
>>>> +     const struct c3_isp_capture_format *cap_fmt =
>>>> +                             c3_cap_find_fmt(pix_fmt->pixelformat);
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     if (src_fmt.format.width != pix_fmt->width ||
>>>> +         src_fmt.format.height != pix_fmt->height ||
>>>> +         src_fmt.format.code != cap_fmt->mbus_code) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
>>>> +                     link->source->entity->name, link->source->index,
>>>> +                     link->sink->entity->name, link->sink->index,
>>>> +                     src_fmt.format.code, src_fmt.format.width,
>>>> +                     src_fmt.format.height, cap_fmt->mbus_code,
>>>> +                     pix_fmt->width, pix_fmt->height);
>>>> +
>>>> +             return -EPIPE;
>>>> +     };
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct media_entity_operations isp_cap_entity_ops = {
>>>> +     .link_validate = c3_isp_cap_link_validate,
>>>> +};
>>>> +
>>>> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
>>>> +                               unsigned int *num_buffers,
>>>> +                               unsigned int *num_planes,
>>>> +                               unsigned int sizes[],
>>>> +                               struct device *alloc_devs[])
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>>> +
>>>> +     if (*num_planes) {
>>>> +             if (*num_planes != 1)
>>>> +                     return -EINVAL;
>>>> +
>>>> +             if (sizes[0] < pix->sizeimage)
>>>> +                     return -EINVAL;
>>> I'm not sure this is correct. This capture device supports formats
>>> with multiple planes, you should verify that *num_planes is equal to
>>> the number of planes of the currently applied format and that each
>>> plane has the correct size
>>
>> This capture device only supports 'V4L2_CAP_VIDEO_CAPTURE', so only need
>> '*num_planes = 1'.
>>
>>> And, if you return here
>>
>> The reason for doing this is to pass the test of 'v4l2-compliance
>> 1.29.0-5246'.
>>
>>>> +     } else {
>>> You can skip the else {}
>>>
>>>> +             *num_planes = 1;
>>>> +             sizes[0] = pix->sizeimage;
>>> And here set the number of planes to the correct number and set the
>>> size of each plane ?
>>
>> The reason for doing this is to pass the test of 'v4l2-compliance
>> 1.29.0-5246'.
>>
>>> And now I realized you're using the single-planar API for buffer
>>> management
>>>
>>>           vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>>>
>>> I was expecting it to rather be
>>>
>>>           vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
>>>
>>> Even more so as you support multiplanar YUV formats.
>>>
>>> Any reason why you chose to use the single planar API ?
>>
>> I think the single-planar API is a bit simpler.
>>
>> Will try to use multi-planar.
>>
> I asked around if there are strict rules in place for this, and my
> understanding is that if multi-planar formats are used the
> multi-planar API are preferred. Maybe someone could clarify if I'm
> mistaken here.
>

Will use multi-planar.

>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>>> +
>>>> +     guard(spinlock_irqsave)(&cap->buff_lock);
>>>> +
>>>> +     list_add_tail(&buf->list, &cap->pending);
>>>> +}
>>>> +
>>>> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
>>>> +
>>>> +     if (vb2_plane_size(vb, 0) < size) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "User buffer too small (%ld < %u)\n",
>>>> +                     vb2_plane_size(vb, 0), size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     vb2_set_plane_payload(vb, 0, size);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_buffer *buf =
>>>> +             container_of(v4l2_buf, struct c3_isp_buffer, vb);
>>>> +
>>>> +     buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
>>>> +                                   unsigned int count)
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>>> +     int ret;
>>>> +
>>>> +     ret = pm_runtime_resume_and_get(cap->isp->dev);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
>>>> +     if (ret) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "Failed to start cap%u pipeline: %d\n", cap->id, ret);
>>>> +             goto err_pm_put;
>>>> +     }
>>> You could pipeline_start() after pm_runtime_resume_and_get() and save
>>> this jump.
>>
>> Will firstly use pipeline_start() and then pm_runtime_resume_and_get().
>>
>>>> +
>>>> +     ret = c3_isp_cap_dummy_buff_create(cap);
>>>> +     if (ret)
>>>> +             goto err_pipeline_stop;
>>>> +
>>>> +     c3_isp_cap_start(cap);
>>>> +
>>>> +     ret = v4l2_subdev_enable_streams(&cap->rsz->sd,
>>>> +                                      C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
>>>> +     if (ret)
>>>> +             goto err_dummy_destroy;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_dummy_destroy:
>>>> +     c3_isp_cap_dummy_buff_destroy(cap);
>>>> +err_pipeline_stop:
>>>> +     video_device_pipeline_stop(&cap->vdev);
>>>> +err_pm_put:
>>>> +     pm_runtime_put(cap->isp->dev);
>>>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>>> +
>>>> +     c3_isp_cap_stop(cap);
>>>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
>>>> +
>>>> +     v4l2_subdev_disable_streams(&cap->rsz->sd,
>>>> +                                 C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
>>>> +
>>>> +     c3_isp_cap_dummy_buff_destroy(cap);
>>>> +
>>>> +     video_device_pipeline_stop(&cap->vdev);
>>>> +     pm_runtime_put(cap->isp->dev);
>>>> +}
>>>> +
>>>> +static const struct vb2_ops isp_video_vb2_ops = {
>>>> +     .queue_setup = c3_isp_vb2_queue_setup,
>>>> +     .buf_queue = c3_isp_vb2_buf_queue,
>>>> +     .buf_prepare = c3_isp_vb2_buf_prepare,
>>>> +     .buf_init = c3_isp_vb2_buf_init,
>>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>>> +     .wait_finish = vb2_ops_wait_finish,
>>>> +     .start_streaming = c3_isp_vb2_start_streaming,
>>>> +     .stop_streaming = c3_isp_vb2_stop_streaming,
>>>> +};
>>>> +
>>>> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct video_device *vdev = &cap->vdev;
>>>> +     struct vb2_queue *vb2_q = &cap->vb2_q;
>>>> +     int ret;
>>>> +
>>>> +     snprintf(vdev->name, sizeof(vdev->name), "c3-isp-cap%u", cap->id);
>>>> +     vdev->fops = &isp_cap_v4l2_fops;
>>>> +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
>>>> +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
>>>> +     vdev->entity.ops = &isp_cap_entity_ops;
>>>> +     vdev->lock = &cap->lock;
>>>> +     vdev->minor = -1;
>>>> +     vdev->queue = vb2_q;
>>>> +     vdev->release = video_device_release_empty;
>>>> +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>>>> +     vdev->vfl_dir = VFL_DIR_RX;
>>>> +     video_set_drvdata(vdev, cap);
>>>> +
>>>> +     vb2_q->drv_priv = cap;
>>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>>> +     vb2_q->ops = &isp_video_vb2_ops;
>>>> +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>>>> +     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_buffer);
>>>> +     vb2_q->dev = cap->isp->dev;
>>>> +     vb2_q->lock = &cap->lock;
>>>> +     vb2_q->min_queued_buffers = 2;
>>> Now that you're using a dummy buffer you can drop this
>>
>> Will drop this line.
>>
>>>> +
>>>> +     ret = vb2_queue_init(vb2_q);
>>>> +     if (ret < 0)
>>>> +             goto err_destroy;
>>>> +
>>>> +     cap->pad.flags = MEDIA_PAD_FL_SINK;
>>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
>>>> +     if (ret < 0)
>>>> +             goto err_queue_release;
>>>> +
>>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>> +     if (ret < 0) {
>>>> +             dev_err(cap->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_destroy:
>>>> +     mutex_destroy(&cap->lock);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     int ret;
>>>> +     unsigned int i;
>>>> +     struct c3_isp_capture *cap;
>>>> +
>>>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>>> +             cap = &isp->caps[i];
>>>> +             memset(cap, 0, sizeof(*cap));
>>>> +
>>>> +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>>>> +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>>>> +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>>>> +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>>>> +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>>>> +
>>>> +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>>>> +
>>>> +             cap->id = i;
>>>> +             cap->rsz = &isp->resizers[i];
>>>> +             cap->isp = isp;
>>>> +             INIT_LIST_HEAD(&cap->pending);
>>>> +             spin_lock_init(&cap->buff_lock);
>>>> +             mutex_init(&cap->lock);
>>>> +
>>>> +             ret = c3_isp_register_capture(cap);
>>>> +             if (ret) {
>>>> +                     cap->isp = NULL;
>>>> +                     mutex_destroy(&cap->lock);
>>>> +                     c3_isp_captures_unregister(isp);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     unsigned int i;
>>>> +     struct c3_isp_capture *cap;
>>>> +
>>>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>>> +             cap = &isp->caps[i];
>>>> +
>>>> +             if (!cap->isp)
>>>> +                     continue;
>>>> +             vb2_queue_release(&cap->vb2_q);
>>>> +             media_entity_cleanup(&cap->vdev.entity);
>>>> +             video_unregister_device(&cap->vdev);
>>>> +             mutex_destroy(&cap->lock);
>>>> +     }
>>>> +}
>>>> +
>>>> +void c3_isp_captures_isr(struct c3_isp_device *isp)
>>>> +{
>>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
>>>> new file mode 100644
>>>> index 000000000000..d2c0b9aacf36
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
>>>> @@ -0,0 +1,352 @@
>>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#ifndef __C3_ISP_COMMON_H__
>>>> +#define __C3_ISP_COMMON_H__
>>>> +
>>>> +#include <linux/clk.h>
>>>> +
>>>> +#include <media/media-device.h>
>>>> +#include <media/videobuf2-core.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +#include <media/videobuf2-v4l2.h>
>>>> +
>>>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>>>> +#define C3_ISP_CLOCK_NUM_MAX          3
>>>> +
>>>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
>>>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUV8_1X24
>>>> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUV8_1X24
>>>> +#define C3_ISP_DEFAULT_WIDTH          1920
>>>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>>>> +#define C3_ISP_MAX_WIDTH              2888
>>>> +#define C3_ISP_MAX_HEIGHT             2240
>>>> +#define C3_ISP_MIN_WIDTH              160
>>>> +#define C3_ISP_MIN_HEIGHT             120
>>>> +
>>>> +#define C3_ISP_DMA_SIZE_ALIGN_BYTES   16
>>>> +#define C3_ISP_DISP_REG(base, id)     ((base) + (id) * 0x400)
>>>> +#define C3_ISP_WRMIFX3_REG(base, id)  ((base) + (id) * 0x100)
>>>> +#define C3_ISP_PPS_TAP4_S11_H_NUM     33
>>>> +#define C3_ISP_PPS_LUT_CTYPE_0        0
>>>> +#define C3_ISP_PPS_LUT_CTYPE_2        2
>>>> +#define C3_ISP_SCALE_EN               1
>>>> +#define C3_ISP_SCALE_DIS              0
>>>> +
>>>> +#define C3_ISP_PHASE_OFFSET_0         0
>>>> +#define C3_ISP_PHASE_OFFSET_1         1
>>>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>>>> +
>>>> +enum c3_isp_core_pads {
>>>> +     C3_ISP_CORE_PAD_SINK_VIDEO,
>>>> +     C3_ISP_CORE_PAD_SINK_PARAMS,
>>>> +     C3_ISP_CORE_PAD_SOURCE_STATS,
>>>> +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +     C3_ISP_CORE_PAD_MAX
>>>> +};
>>>> +
>>>> +enum c3_isp_resizer_ids {
>>>> +     C3_ISP_RSZ_0,
>>>> +     C3_ISP_RSZ_1,
>>>> +     C3_ISP_RSZ_2,
>>>> +     C3_ISP_NUM_RSZ
>>>> +};
>>>> +
>>>> +enum c3_isp_resizer_pads {
>>>> +     C3_ISP_RESIZER_PAD_SINK,
>>>> +     C3_ISP_RESIZER_PAD_SOURCE,
>>>> +     C3_ISP_RESIZER_PAD_MAX
>>>> +};
>>>> +
>>>> +enum c3_isp_cap_devs {
>>>> +     C3_ISP_CAP_DEV_0,
>>>> +     C3_ISP_CAP_DEV_1,
>>>> +     C3_ISP_CAP_DEV_2,
>>>> +     C3_ISP_NUM_CAP_DEVS
>>>> +};
>>>> +
>>>> +/*
>>>> + * 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;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_mbus_format_info - MBUS format information
>>>> + *
>>>> + * @mbus_code: the mbus code
>>>> + * @pads: bitmask detailing valid pads for this mbus_code
>>>> + * @xofst: horizontal phase offset of hardware
>>>> + * @yofst: vertical phase offset of hardware
>>>> + */
>>>> +struct c3_isp_mbus_format_info {
>>> It seems to me some of these types are used only inside a single .c
>>> file
>>>
>>> $ git grep c3_isp_mbus_format_info drivers/media/platform/amlogic/c3/isp/
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-common.h: * struct c3_isp_mbus_format_info - MBUS format information
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-common.h:struct c3_isp_mbus_format_info {
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:static const struct c3_isp_mbus_format_info
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:            const struct c3_isp_mbus_format_info *info =
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *info;
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
>>> drivers/media/platform/amlogic/c3/isp/c3-isp-core.c:    const struct c3_isp_mbus_format_info *isp_fmt;
>>>
>>> Could they be declared there ?
>>
>> OK, it will be declared in c3-isp-core.c file.
>>
> Same for other files where a type doesn't need to be shared between
> different .c files


OK.

>>>> +     u32 mbus_code;
>>>> +     u32 pads;
>>>> +     u8 xofst;
>>>> +     u8 yofst;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_capture_format - The image format of capture device
>>>> + *
>>>> + * @mbus_code: the mbus code
>>>> + * @fourcc: pixel format
>>>> + */
>>>> +struct c3_isp_capture_format {
>>>> +     u32 mbus_code;
>>>> +     u32 fourcc;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_buffer - A container of vb2 buffer used by the video devices:
>>>> + *                        capture and stats video devices
>>>> + *
>>>> + * @vb: vb2 buffer
>>>> + * @dma_addr: buffer physical address
>>>> + * @list: entry of the buffer in the queue
>>>> + */
>>>> +struct c3_isp_buffer {
>>>> +     struct vb2_v4l2_buffer vb;
>>>> +     dma_addr_t dma_addr;
>>>> +     struct list_head list;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_params_buffer - A container of vb2 buffer used by the
>>>> + *                               params video device
>>>> + *
>>>> + * @vb: vb2 buffer
>>>> + * @cfg: scratch buffer used for caching the ISP configuration parameters
>>>> + * @list: entry of the buffer in the queue
>>>> + */
>>>> +struct c3_isp_params_buffer {
>>>> +     struct vb2_v4l2_buffer vb;
>>>> +     void *cfg;
>>>> +     struct list_head list;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_dummy_buffer - A buffer to write the next frame to in case
>>>> + *                           there are no vb2 buffers available.
>>>> + *
>>>> + * @vaddr:   return value of call to dma_alloc_attrs
>>>> + * @dma_addr:        dma address of the buffer
>>>> + * @size:    size of the buffer
>>>> + */
>>>> +struct c3_isp_dummy_buffer {
>>>> +     void *vaddr;
>>>> +     dma_addr_t dma_addr;
>>>> +     u32 size;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_core - ISP core subdev
>>>> + *
>>>> + * @sd: ISP sub-device
>>>> + * @pads: ISP sub-device pads
>>>> + * @src_sd: source sub-device
>>>> + * @src_sd_pad: source sub-device pad
>>>> + * @isp: pointer to c3_isp_device
>>>> + */
>>>> +struct c3_isp_core {
>>>> +     struct v4l2_subdev sd;
>>>> +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>>>> +     struct v4l2_subdev *src_sd;
>>>> +     u16 src_sd_pad;
>>>> +     struct c3_isp_device *isp;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_resizer - ISP resizer subdev
>>>> + *
>>>> + * @id: resizer id
>>>> + * @sd: resizer sub-device
>>>> + * @pads: resizer sub-device pads
>>>> + * @src_sd: source sub-device
>>>> + * @src_sd_pad: source sub-device pad
>>>> + * @isp: pointer to c3_isp_device
>>>> + */
>>>> +struct c3_isp_resizer {
>>>> +     enum c3_isp_resizer_ids id;
>>>> +     struct v4l2_subdev sd;
>>>> +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>>>> +     struct v4l2_subdev *src_sd;
>>>> +     u32 src_sd_pad;
>>>> +     struct c3_isp_device *isp;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_stats - ISP statistics device
>>>> + *
>>>> + * @vb2_q: vb2 buffer queue
>>>> + * @vdev: video node
>>>> + * @vfmt: v4l2_format of the metadata format
>>>> + * @pad: media pad
>>>> + * @lock: protects vb2_q, vdev
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @buff: in use buffer
>>>> + * @buff_lock: protects stats buffer
>>>> + * @pending: stats buffer list head
>>>> + */
>>>> +struct c3_isp_stats {
>>>> +     struct vb2_queue vb2_q;
>>>> +     struct video_device vdev;
>>>> +     struct v4l2_format vfmt;
>>>> +     struct media_pad pad;
>>>> +
>>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>>> +     struct c3_isp_device *isp;
>>>> +
>>>> +     struct c3_isp_buffer *buff;
>>>> +     spinlock_t buff_lock; /* Protects stats buffer */
>>>> +     struct list_head pending;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_params - ISP parameters device
>>>> + *
>>>> + * @vb2_q: vb2 buffer queue
>>>> + * @vdev: video node
>>>> + * @vfmt: v4l2_format of the metadata format
>>>> + * @pad: media pad
>>>> + * @lock: protects vb2_q, vdev
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @buff: in use buffer
>>>> + * @buff_lock: protects stats buffer
>>>> + * @pending: stats buffer list head
>>>> + */
>>>> +struct c3_isp_params {
>>>> +     struct vb2_queue vb2_q;
>>>> +     struct video_device vdev;
>>>> +     struct v4l2_format vfmt;
>>>> +     struct media_pad pad;
>>>> +
>>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>>> +     struct c3_isp_device *isp;
>>>> +
>>>> +     struct c3_isp_params_buffer *buff;
>>>> +     spinlock_t buff_lock; /* Protects params buffer */
>>>> +     struct list_head pending;
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct c3_isp_capture - ISP capture device
>>>> + *
>>>> + * @id: capture device ID
>>>> + * @vb2_q: vb2 buffer queue
>>>> + * @vdev: video node
>>>> + * @vfmt: v4l2_format of the capture format
>>>> + * @pad: media pad
>>>> + * @lock: protects vb2_q, vdev
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @rsz: pointer to c3_isp_resizer
>>>> + * @buff: in use buffer
>>>> + * @buff_lock: protects capture buffer
>>>> + * @pending: capture buffer list head
>>>> + */
>>>> +struct c3_isp_capture {
>>>> +     enum c3_isp_cap_devs id;
>>>> +     struct vb2_queue vb2_q;
>>>> +     struct video_device vdev;
>>>> +     struct v4l2_format vfmt;
>>>> +     struct media_pad pad;
>>>> +
>>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>>> +     struct c3_isp_device *isp;
>>>> +     struct c3_isp_resizer *rsz;
>>>> +
>>>> +     struct c3_isp_dummy_buffer dummy_buff;
>>>> +     struct c3_isp_buffer *buff;
>>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>>> +     struct list_head pending;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_info - ISP information
>>>> + *
>>>> + * @clocks: array of ISP clock names
>>>> + * @clock_rates: array of ISP clock rate
>>>> + * @clock_num: actual clock number
>>>> + */
>>>> +struct c3_isp_info {
>>>> +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
>>>> +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>>>> +     u32 clock_num;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_device - ISP platform device
>>>> + *
>>>> + * @dev: pointer to the struct device
>>>> + * @base: base register address
>>>> + * @clks: array of clocks
>>>> + * @notifier: notifier to register on the v4l2-async API
>>>> + * @v4l2_dev: v4l2_device variable
>>>> + * @media_dev: media device variable
>>>> + * @pipe: media pipeline
>>>> + * @core: ISP core subdev
>>>> + * @resizer: ISP resizer subdev
>>>> + * @stats: ISP stats device
>>>> + * @params: ISP params device
>>>> + * @caps: array of ISP capture device
>>>> + * @frm_sequence: used to record frame id
>>>> + * @info: version-specific ISP information
>>>> + */
>>>> +struct c3_isp_device {
>>>> +     struct device *dev;
>>>> +     void __iomem *base;
>>>> +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>>>> +
>>>> +     struct v4l2_async_notifier notifier;
>>>> +     struct v4l2_device v4l2_dev;
>>>> +     struct media_device media_dev;
>>>> +     struct media_pipeline pipe;
>>>> +
>>>> +     struct c3_isp_core core;
>>>> +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>>>> +     struct c3_isp_stats stats;
>>>> +     struct c3_isp_params params;
>>>> +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>>>> +
>>>> +     u32 frm_sequence;
>>>> +     const struct c3_isp_info *info;
>>>> +};
>>>> +
>>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
>>>> +
>>>> +void c3_isp_core_queue_eof(struct c3_isp_device *isp);
>>>> +int c3_isp_core_register(struct c3_isp_device *isp);
>>>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>>>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>>>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>>>> +void c3_isp_captures_isr(struct c3_isp_device *isp);
>>>> +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp);
>>>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>>>> +void c3_isp_stats_isr(struct c3_isp_device *isp);
>>>> +void c3_isp_params_pre_cfg(struct c3_isp_device *isp);
>>>> +int c3_isp_params_register(struct c3_isp_device *isp);
>>>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>>>> +void c3_isp_params_isr(struct c3_isp_device *isp);
>>>> +
>>>> +#endif
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
>>>> new file mode 100644
>>>> index 000000000000..bf83e73dd744
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
>>>> @@ -0,0 +1,553 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/media/amlogic/c3-isp-config.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-event.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +#define C3_ISP_CORE_SUBDEV_NAME        "c3-isp-core"
>>>> +
>>>> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>>>> +     /* RAW formats */
>>>> +     {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     },
>>>> +     /* YUV formats */
>>>> +     {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     },
>>>> +};
>>>> +
>>>> +static const struct c3_isp_mbus_format_info
>>>> +*core_find_format_by_code(u32 code, u32 pad)
>>>> +{
>>>> +     for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>>> +             const struct c3_isp_mbus_format_info *info =
>>>> +                     &c3_isp_core_mbus_formats[i];
>>>> +
>>>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>>>> +                     return info;
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static const struct c3_isp_mbus_format_info
>>>> +*core_find_format_by_index(u32 index, u32 pad)
>>>> +{
>>>> +     for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>>> +             const struct c3_isp_mbus_format_info *info =
>>>> +                     &c3_isp_core_mbus_formats[i];
>>>> +
>>>> +             if (!(info->pads & BIT(pad)))
>>>> +                     continue;
>>>> +
>>>> +             if (!index)
>>>> +                     return info;
>>>> +
>>>> +             index--;
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
>>>> +                        ISP_TOP_IRQ_EN_FRAME_END_EN);
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
>>>> +                        ISP_TOP_IRQ_EN_3A_DMA_ERR_EN);
>>>> +
>>>> +     /* Enable image data to ISP core */
>>>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
>>>> +                        ISP_TOP_PATH_SEL_CORE_MIPI_CORE);
>>>> +}
>>>> +
>>>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>>>> +{
>>>> +     /* Disable image data to ISP core */
>>>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
>>>> +                        ISP_TOP_PATH_SEL_CORE_CORE_DIS);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
>>>> +                        ISP_TOP_IRQ_EN_FRAME_END_DIS);
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
>>>> +                        ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS);
>>>> +}
>>>> +
>>>> +/* Set the phase offset of blc, wb and lns */
>>>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>>>> +                               u8 xofst, u8 yofst)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK,
>>>> +                        ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK,
>>>> +                        ISP_LSWB_BLC_PHSOFST_VERT_OFST(yofst));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK,
>>>> +                        ISP_LSWB_WB_PHSOFST_HORIZ_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK,
>>>> +                        ISP_LSWB_WB_PHSOFST_VERT_OFST(yofst));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK,
>>>> +                        ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK,
>>>> +                        ISP_LSWB_LNS_PHSOFST_VERT_OFST(yofst));
>>>> +}
>>>> +
>>>> +/* Set the phase offset of af, ae and awb */
>>>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>>>> +                             u8 xofst, u8 yofst)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_HORIZ_OFST_MASK,
>>>> +                        ISP_AF_CTRL_HORIZ_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_VERT_OFST_MASK,
>>>> +                        ISP_AF_CTRL_VERT_OFST(yofst));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_HORIZ_OFST_MASK,
>>>> +                        ISP_AE_CTRL_HORIZ_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_VERT_OFST_MASK,
>>>> +                        ISP_AE_CTRL_VERT_OFST(yofst));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_HORIZ_OFST_MASK,
>>>> +                        ISP_AWB_CTRL_HORIZ_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_VERT_OFST_MASK,
>>>> +                        ISP_AWB_CTRL_VERT_OFST(yofst));
>>>> +}
>>>> +
>>>> +/* Set the phase offset of demosaic */
>>>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>>>> +                              u8 xofst, u8 yofst)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK,
>>>> +                        ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(xofst));
>>>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK,
>>>> +                        ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(yofst));
>>>> +}
>>>> +
>>>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>>>> +                                struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>>> +
>>>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +     isp_fmt = core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +
>>>> +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>>>> +                  ISP_TOP_INPUT_SIZE_HORIZ_SIZE(fmt->width) |
>>>> +                  ISP_TOP_INPUT_SIZE_VERT_SIZE(fmt->height));
>>>> +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>>>> +                  ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(fmt->width) |
>>>> +                  ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(fmt->height));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE,
>>>> +                        ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK,
>>>> +                        ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(fmt->width));
>>>> +
>>>> +     c3_isp_write(isp, ISP_AF_HV_SIZE,
>>>> +                  ISP_AF_HV_SIZE_GLB_WIN_XSIZE(fmt->width) |
>>>> +                  ISP_AF_HV_SIZE_GLB_WIN_YSIZE(fmt->height));
>>>> +     c3_isp_write(isp, ISP_AE_HV_SIZE,
>>>> +                  ISP_AE_HV_SIZE_HORIZ_SIZE(fmt->width) |
>>>> +                  ISP_AE_HV_SIZE_VERT_SIZE(fmt->height));
>>>> +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
>>>> +                  ISP_AWB_HV_SIZE_HORIZ_SIZE(fmt->width) |
>>>> +                  ISP_AWB_HV_SIZE_VERT_SIZE(fmt->height));
>>>> +
>>>> +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>>> +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>>> +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>>> +}
>>>> +
>>>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>>>> +                                   struct v4l2_subdev_state *state,
>>>> +                                   u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     core->isp->frm_sequence = 0;
>>>> +     c3_isp_core_cfg_format(core->isp, state);
>>>> +     c3_isp_core_enable(core->isp);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_enable_streams(core->src_sd,
>>>> +                                      core->src_sd_pad, sink_streams);
>>>> +     if (ret) {
>>>> +             c3_isp_core_disable(core->isp);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>>>> +                                    struct v4l2_subdev_state *state,
>>>> +                                    u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>>>> +                                                    &streams_mask);
>>> You can drop xlate_streams if you drop routing support
>>
>> OK, will drop routing.
>>
>>>> +     ret = v4l2_subdev_disable_streams(core->src_sd,
>>>> +                                       core->src_sd_pad, sink_streams);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     c3_isp_core_disable(core->isp);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state,
>>>> +                                struct v4l2_subdev_krouting *routing)
>>>> +{
>>> As per the other drivers, routing support can be dropped
>>
>> Will drop routing.
>>
>>>> +     static const struct v4l2_mbus_framefmt format = {
>>>> +             .width = C3_ISP_DEFAULT_WIDTH,
>>>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>>>> +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>>>> +             .field = V4L2_FIELD_NONE,
>>>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>>>> +     };
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>>>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_subdev_route route;
>>>> +     struct v4l2_subdev_krouting routing;
>>>> +
>>>> +     route.sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>>> +     route.sink_stream = 0;
>>>> +     route.source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>>>> +     route.source_stream = 0;
>>>> +     route.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routing.num_routes = 1;
>>>> +     routing.routes = &route;
>>>> +
>>>> +     return c3_isp_core_cfg_routing(sd, state, &routing);
>>>> +}
>>>> +
>>>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                   struct v4l2_subdev_state *state,
>>>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +     const struct c3_isp_mbus_format_info *info;
>>>> +     int ret = 0;
>>>> +
>>>> +     switch (code->pad) {
>>>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>>>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>>> +             info = core_find_format_by_index(code->index, code->pad);
>>>> +             if (!info)
>>>> +                     ret = -EINVAL;
>>>                           return -EINVAL;
>>>
>>>> +             else
>>> so you can drop the else
>>>
>> Yes, will drop 'else'
>>
>>>> +                     code->code = info->mbus_code;
>>>> +
>>>> +             break;
>>>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>>>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>>>> +             if (code->index)
>>>> +                     ret = -EINVAL;
>>>> +             else
>>> here as well
>>
>> Yes, will drop 'else'
>>
>>>> +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>>> +
>>>> +             break;
>>>> +     default:
>>>> +             ret = -EINVAL;
>>>                   return -EINVAL;
>>>
>>>> +             break;
>>>> +     }
>>>> +
>>>> +     return ret;
>>>           return 0;
>>>
>>>> +}
>>>> +
>>>> +static void c3_isp_core_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 c3_isp_mbus_format_info *isp_fmt;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>>>> +
>>>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>>> +     if (!isp_fmt)
>>>> +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>>> +     else
>>>> +             sink_fmt->code = format->format.code;
>>>> +
>>>> +     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);
>>>> +
>>> Should you set the colorspace related information like you do in
>>> init_state() ?
>>>
>>>           sink_fmt->field = V4L2_FIELD_NONE;
>>>           sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>           sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>>>           sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>           sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>>
>> OK , will add the information.
>>
>>>> +     src_fmt->width  = sink_fmt->width;
>>>> +     src_fmt->height = sink_fmt->height;
>>>> +
>>>> +     format->format = *sink_fmt;
>>>> +}
>>>> +
>>>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>>>> +                                    struct v4l2_subdev_format *format)
>>>> +{
>>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +
>>>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>>> +     if (!isp_fmt)
>>>> +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>>> +     else
>>>> +             src_fmt->code = format->format.code;
>>>> +
>>>> +     format->format = *src_fmt;
>>> mm, this doesn't check the sizes... if the ISP cannot scale/crop the
>>> sizes here should come from the sink format, don't they ? Also
>>> remember to set the colorspace information
>>>
>> Will check the size.
>>
>>>> +}
>>>> +
>>>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>>>> +                            struct v4l2_subdev_state *state,
>>>> +                            struct v4l2_subdev_format *format)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +     if (format->pad == C3_ISP_CORE_PAD_SINK_VIDEO) {
>>>> +             c3_isp_core_set_sink_fmt(state, format);
>>>> +     } else if (format->pad == C3_ISP_CORE_PAD_SINK_PARAMS ||
>>>> +                format->pad == C3_ISP_CORE_PAD_SOURCE_STATS) {
>>>> +             fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +             format->format = *fmt;
>>> or
>>>
>>>                   format->format =
>>>                           *v4l2_subdev_state_get_format(state, format->pad);
>>>
>>> so you can drop *fmt and the {} or maybe turn this into a
>>>           switch (format->pad) {
>>>
>>>           }
>>>
>>> Up to you
>>>
>> Will check this issue.
>>
>>>> +     } else if (format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO) {
>>>> +             c3_isp_core_set_source_fmt(state, format);
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>>>> +                               struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>> Or simply use one
>>>
>>>           struct v4l2_mbus_framefmt *fmt;
>>
>> Will use one '*fmt'.
>>
>>>> +
>>>> +     /* Video sink pad */
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>>>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>>>> +
>>>> +     /* Video source pad */
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>>>> +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     src_fmt->field = V4L2_FIELD_NONE;
>>>> +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>>> +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>>> +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>>> +
>>>> +     /* Parameters pad */
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
>>>> +     sink_fmt->width = 0;
>>>> +     sink_fmt->height = 0;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>>> +
>>>> +     /* Statistics pad */
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
>>>> +     src_fmt->width = 0;
>>>> +     src_fmt->height = 0;
>>>> +     src_fmt->field = V4L2_FIELD_NONE;
>>>> +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>>> +
>>>> +     return c3_isp_core_init_routing(sd, state);
>>> Drop routing support
>>>
>> Will drop routing.
>>
>>>> +}
>>>> +
>>>> +static int c3_isp_core_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
>>>> +                                    struct v4l2_event_subscription *sub)
>>>> +{
>>>> +     if (sub->type != V4L2_EVENT_FRAME_SYNC)
>>>> +             return -EINVAL;
>>>> +
>>>> +     /* V4L2_EVENT_FRAME_SYNC doesn't need id, so should set 0 */
>>>> +     if (sub->id != 0)
>>>> +             return -EINVAL;
>>>> +
>>>> +     return v4l2_event_subscribe(fh, sub, 0, NULL);
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>>>> +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
>>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>>> +     .set_fmt = c3_isp_core_set_fmt,
>>>> +     .enable_streams = c3_isp_core_enable_streams,
>>>> +     .disable_streams = c3_isp_core_disable_streams,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_core_ops c3_isp_core_core_ops = {
>>>> +     .subscribe_event = c3_isp_core_subscribe_event,
>>>> +     .unsubscribe_event = v4l2_event_subdev_unsubscribe,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>>>> +     .core = &c3_isp_core_core_ops,
>>>> +     .pad = &c3_isp_core_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
>>>> +     .init_state = c3_isp_core_init_state,
>>>> +};
>>>> +
>>>> +static int c3_isp_core_link_validate(struct media_link *link)
>>>> +{
>>>> +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>>>> +             return 0;
>>>> +
>>>> +     return v4l2_subdev_link_validate(link);
>>>> +}
>>>> +
>>>> +/* Media entity operations */
>>>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>>>> +     .link_validate = c3_isp_core_link_validate,
>>>> +};
>>>> +
>>>> +void c3_isp_core_queue_eof(struct c3_isp_device *isp)
>>>> +{
>>> This gets called in the end of frame IRQ right ?
>>>
>>>           if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
>>>                   c3_isp_core_queue_eof(isp);
>>>
>>> When processing is completed.
>>>
>>> FRAME_SYNC should ideally be emitted as soon as the processing of a
>>> frame start. Do you have other interrupt sources you can use to detect
>>> "start processing of a frame" ?
>>>
>> Will check the C308L datasheet.
>>
>>>> +     struct v4l2_event event = {
>>>> +             .type = V4L2_EVENT_FRAME_SYNC,
>>>> +     };
>>>> +
>>>> +     event.u.frame_sync.frame_sequence = isp->frm_sequence;
>>>> +     v4l2_event_queue(isp->core.sd.devnode, &event);
>>>> +}
>>>> +
>>>> +int c3_isp_core_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_core *core = &isp->core;
>>>> +     struct v4l2_subdev *sd = &core->sd;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>>>> +     sd->owner = THIS_MODULE;
>>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
>>>> +     sd->internal_ops = &c3_isp_core_internal_ops;
>>>> +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
>>>> +
>>>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>>> +     sd->entity.ops = &c3_isp_core_entity_ops;
>>>> +
>>>> +     core->isp = isp;
>>>> +     sd->dev = isp->dev;
>>>> +     v4l2_set_subdevdata(sd, core);
>>>> +
>>>> +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>>>> +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>>>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_init_finalize(sd);
>>>> +     if (ret)
>>>> +             goto err_entity_cleanup;
>>>> +
>>>> +     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_entity_cleanup:
>>>> +     media_entity_cleanup(&sd->entity);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_core *core = &isp->core;
>>>> +     struct v4l2_subdev *sd = &core->sd;
>>>> +
>>>> +     v4l2_device_unregister_subdev(sd);
>>>> +     v4l2_subdev_cleanup(sd);
>>>> +     media_entity_cleanup(&sd->entity);
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
>>>> new file mode 100644
>>>> index 000000000000..0c6795ab9925
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
>>>> @@ -0,0 +1,459 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-common.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>>>> +{
>>>> +     return readl(isp->base + reg);
>>>> +}
>>>> +
>>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>>>> +{
>>>> +     writel(val, isp->base + reg);
>>>> +}
>>>> +
>>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
>>>> +{
>>>> +     u32 orig, tmp;
>>>> +
>>>> +     orig = c3_isp_read(isp, reg);
>>>> +
>>>> +     tmp = orig & ~mask;
>>>> +     tmp |= val & mask;
>>>> +
>>>> +     if (tmp != orig)
>>>> +             c3_isp_write(isp, reg, tmp);
>>>> +}
>>>> +
>>>> +/* PM runtime suspend */
>>>> +static int c3_isp_runtime_suspend(struct device *dev)
>>>> +{
>>>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>>>> +
>>>> +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +/* PM runtime resume */
>>>> +static int c3_isp_runtime_resume(struct device *dev)
>>>> +{
>>>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>>>> +
>>>> +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>>>> +     SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>> +                         pm_runtime_force_resume)
>>>> +     RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>>>> +                    c3_isp_runtime_resume, NULL)
>>>> +};
>>>> +
>>>> +/* IRQ handling */
>>>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>>>> +{
>>>> +     struct c3_isp_device *isp = dev;
>>>> +     u32 status;
>>>> +
>>>> +     /* Get irq status and clear irq status */
>>>> +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>>>> +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>>>> +
>>>> +     if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
>>>> +             c3_isp_core_queue_eof(isp);
>>>> +             c3_isp_stats_isr(isp);
>>>> +             c3_isp_params_isr(isp);
>>>> +             c3_isp_captures_isr(isp);
>>>> +             isp->frm_sequence++;
>>>> +     }
>>>> +
>>>> +     if (status & ISP_TOP_IRQ_EN_3A_DMA_ERR_EN)
>>>> +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>>>> +
>>>> +     return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +/* Subdev notifier register */
>>>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>>>> +                            struct v4l2_subdev *sd,
>>>> +                            struct v4l2_async_connection *asc)
>>>> +{
>>>> +     struct c3_isp_device *isp =
>>>> +             container_of(notifier, struct c3_isp_device, notifier);
>>>> +     struct c3_isp_core *core = &isp->core;
>>>> +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>>>> +     int ret;
>>>> +
>>>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>>>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>>> +     if (ret < 0) {
>>>> +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     core->src_sd = sd;
>>>> +     core->src_sd_pad = ret;
>>>> +
>>>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>>>> +}
>>>> +
>>>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>>>> +{
>>>> +     struct c3_isp_device *isp =
>>>> +             container_of(notifier, struct c3_isp_device, notifier);
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>>>> +     if (ret < 0) {
>>>> +             dev_err(isp->dev,
>>>> +                     "Failed to register subdev nodes: %d\n", ret);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     dev_info(isp->dev, "notify complete\n");
>>> Possibly drop this
>>
>> Will drop this line.
>>
>>>> +
>>>> +     return media_device_register(&isp->media_dev);
>>> Any reason you're registering the media device so late when the async
>>> chain completes instead of doing so in probe ? I think it's fine doing
>>> this here or during probe (after registering the v4l2_dev the video
>>> devices. If you do it here and the remote subdev doesn't probe you
>>> won't have any media graph. If you do it in probe() and the remote
>>> subdev fails to probe, the media graph will register but be unusable.
>>> Not sure what's best, maybe an incomplete graph is easier to debug
>>> than no graph at all, but I won't push for this. Up to you :)
>>>
>> Will move it to probe()
>>
>>>> +}
>>> I'm not against using notify_complete() but I suspect you have a
>>> single remote async subdev, right ? (iow you could have done this in
>>> _bound ? )
>>
>> Yes,  will do this in _bound.
>>
> Thanks for both. Please check if my suggestion fits your needs


OK.

>>>> +
>>>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>>>> +{
>>>> +     struct c3_isp_device *isp =
>>>> +             container_of(asc->notifier, struct c3_isp_device, notifier);
>>>> +
>>>> +     media_device_unregister(&isp->media_dev);
>>>> +}
>>>> +
>>>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
>>>> +     .bound = c3_isp_notify_bound,
>>>> +     .complete = c3_isp_notify_complete,
>>>> +     .destroy = c3_isp_notify_destroy,
>>>> +};
>>>> +
>>>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct v4l2_async_connection *asc;
>>>> +     struct fwnode_handle *ep;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>>>> +
>>>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>>>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>>>> +     if (!ep)
>>>> +             return -ENOTCONN;
>>>> +
>>>> +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>>>> +                                           struct v4l2_async_connection);
>>>> +     fwnode_handle_put(ep);
>>>> +
>>>> +     if (IS_ERR(asc))
>>>> +             return PTR_ERR(asc);
>>>> +
>>>> +     isp->notifier.ops = &c3_isp_notify_ops;
>>>> +     ret = v4l2_async_nf_register(&isp->notifier);
>>>> +     if (ret)
>>>> +             v4l2_async_nf_cleanup(&isp->notifier);
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     v4l2_async_nf_unregister(&isp->notifier);
>>>> +     v4l2_async_nf_cleanup(&isp->notifier);
>>>> +}
>>>> +
>>>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct media_device *media_dev = &isp->media_dev;
>>>> +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>>>> +     int ret;
>>>> +
>>>> +     /* Initialize media device */
>>>> +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>>>> +             sizeof(media_dev->model));
>>>> +     media_dev->dev = isp->dev;
>>>> +
>>>> +     media_device_init(media_dev);
>>>> +
>>>> +     /* Initialize v4l2 device */
>>>> +     v4l2_dev->mdev = media_dev;
>>>> +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>>>> +             sizeof(v4l2_dev->name));
>>>> +
>>>> +     ret = v4l2_device_register(isp->dev, v4l2_dev);
>>>> +     if (ret) {
>>>> +             media_device_cleanup(media_dev);
>>>> +             dev_err(isp->dev,
>>>> +                     "Failed to register V4L2 device: %d\n", ret);
>>>> +     }
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     v4l2_device_unregister(&isp->v4l2_dev);
>>>> +     media_device_cleanup(&isp->media_dev);
>>>> +}
>>>> +
>>>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>>>> +{
>>>> +     unsigned int i;
>>>> +
>>>> +     media_entity_remove_links(&isp->core.sd.entity);
>>>> +
>>>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>>>> +             media_entity_remove_links(&isp->resizers[i].sd.entity);
>>>> +
>>>> +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>>>> +             media_entity_remove_links(&isp->caps[i].vdev.entity);
>>>> +}
>>>> +
>>>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>>>> +{
>>>> +     unsigned int i;
>>>> +     int ret;
>>>> +
>>>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>>>> +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>>>> +                                         C3_ISP_RESIZER_PAD_SOURCE,
>>>> +                                         &isp->caps[i].vdev.entity,
>>>> +                                         0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
>>>> +             if (ret) {
>>>> +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
>>>> +                     goto err_remove_links;
>>>> +             }
>>>> +
>>>> +             ret = media_create_pad_link(&isp->core.sd.entity,
>>>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +                                         &isp->resizers[i].sd.entity,
>>>> +                                         C3_ISP_RESIZER_PAD_SINK,
>>>> +                                         MEDIA_LNK_FL_ENABLED);
>>>> +             if (ret) {
>>>> +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
>>>> +                     goto err_remove_links;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     ret = media_create_pad_link(&isp->core.sd.entity,
>>>> +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
>>>> +                                 &isp->stats.vdev.entity,
>>>> +                                 0, MEDIA_LNK_FL_ENABLED);
>>>> +     if (ret) {
>>>> +             dev_err(isp->dev, "Failed to link core and stats\n");
>>>> +             goto err_remove_links;
>>>> +     }
>>>> +
>>>> +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>>>> +                                 &isp->core.sd.entity,
>>>> +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
>>>> +                                 MEDIA_LNK_FL_ENABLED);
>>>> +     if (ret) {
>>>> +             dev_err(isp->dev, "Failed to link params and core\n");
>>>> +             goto err_remove_links;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_remove_links:
>>>> +     c3_isp_remove_links(isp);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     int ret;
>>>> +
>>>> +     ret = c3_isp_captures_register(isp);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = c3_isp_stats_register(isp);
>>>> +     if (ret)
>>>> +             goto err_captures_unregister;
>>>> +
>>>> +     ret = c3_isp_params_register(isp);
>>>> +     if (ret)
>>>> +             goto err_stats_unregister;
>>>> +
>>>> +     ret = c3_isp_create_links(isp);
>>>> +     if (ret)
>>>> +             goto err_params_unregister;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_params_unregister:
>>>> +     c3_isp_params_unregister(isp);
>>>> +err_stats_unregister:
>>>> +     c3_isp_stats_unregister(isp);
>>>> +err_captures_unregister:
>>>> +     c3_isp_captures_unregister(isp);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     c3_isp_remove_links(isp);
>>>> +     c3_isp_params_unregister(isp);
>>>> +     c3_isp_stats_unregister(isp);
>>>> +     c3_isp_captures_unregister(isp);
>>>> +}
>>>> +
>>>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>>>> +{
>>>> +     const struct c3_isp_info *info = isp->info;
>>>> +     int ret;
>>>> +     u32 i;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++)
>>>> +             isp->clks[i].id = info->clocks[i];
>>>> +
>>>> +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++) {
>>>> +             if (!info->clock_rates[i])
>>>> +                     continue;
>>>> +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
>>> same comment as per the other drivers
>>
>> OK!
>>
>>>> +             if (ret) {
>>>> +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>>> +                             info->clock_rates[i]);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_probe(struct platform_device *pdev)
>>>> +{
>>>> +     struct device *dev = &pdev->dev;
>>>> +     struct c3_isp_device *isp;
>>>> +     int irq;
>>>> +     int ret;
>>>> +
>>>> +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>>>> +     if (!isp)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     isp->info = of_device_get_match_data(dev);
>>>> +     isp->dev = dev;
>>>> +
>>>> +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>>>> +     if (IS_ERR(isp->base))
>>>> +             return dev_err_probe(dev, PTR_ERR(isp->base),
>>>> +                                  "Failed to ioremap resource\n");
>>>> +
>>>> +     irq = platform_get_irq(pdev, 0);
>>>> +     if (irq < 0)
>>>> +             return irq;
>>>> +
>>>> +     ret = c3_isp_cfg_clocks(isp);
>>>> +     if (ret)
>>>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>>> +
>>>> +     platform_set_drvdata(pdev, isp);
>>>> +
>>>> +     pm_runtime_enable(dev);
>>>> +
>>>> +     ret = c3_isp_v4l2_register(isp);
>>>> +     if (ret)
>>>> +             goto err_runtime_disable;
>>>> +
>>>> +     ret = c3_isp_core_register(isp);
>>>> +     if (ret)
>>>> +             goto err_v4l2_unregister;
>>>> +
>>>> +     ret = c3_isp_resizers_register(isp);
>>>> +     if (ret)
>>>> +             goto err_core_unregister;
>>>> +
>>>> +     ret = c3_isp_async_nf_register(isp);
>>>> +     if (ret)
>>>> +             goto err_resizers_unregister;
>>>> +
>>>> +     ret = devm_request_irq(dev, irq,
>>>> +                            c3_isp_irq_handler, IRQF_SHARED,
>>>> +                            dev_driver_string(dev), isp);
>>>> +     if (ret)
>>>> +             goto err_nf_unregister;
>>>> +
>>>> +     ret = c3_isp_videos_register(isp);
>>>> +     if (ret)
>>>> +             goto err_nf_unregister;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_nf_unregister:
>>>> +     c3_isp_async_nf_unregister(isp);
>>>> +err_resizers_unregister:
>>>> +     c3_isp_resizers_unregister(isp);
>>>> +err_core_unregister:
>>>> +     c3_isp_core_unregister(isp);
>>>> +err_v4l2_unregister:
>>>> +     c3_isp_v4l2_unregister(isp);
>>>> +err_runtime_disable:
>>>> +     pm_runtime_disable(dev);
>>>> +     return ret;
>>>> +};
>>>> +
>>>> +static void c3_isp_remove(struct platform_device *pdev)
>>>> +{
>>>> +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
>>>> +
>>>> +     c3_isp_videos_unregister(isp);
>>>> +     c3_isp_async_nf_unregister(isp);
>>>> +     c3_isp_core_unregister(isp);
>>>> +     c3_isp_resizers_unregister(isp);
>>>> +     c3_isp_v4l2_unregister(isp);
>>>> +     pm_runtime_disable(isp->dev);
>>>> +};
>>>> +
>>>> +static const struct c3_isp_info isp_info = {
>>>> +     .clocks = {"vapb", "isp0"},
>>>> +     .clock_rates = {0, 400000000},
>>>> +     .clock_num = 2
>>>> +};
>>>> +
>>>> +static const struct of_device_id c3_isp_of_match[] = {
>>>> +     { .compatible = "amlogic,c3-isp",
>>>> +       .data = &isp_info },
>>>> +     { },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>>>> +
>>>> +static struct platform_driver c3_isp_driver = {
>>>> +     .probe = c3_isp_probe,
>>>> +     .remove = c3_isp_remove,
>>>> +     .driver = {
>>>> +             .name = "c3-isp",
>>>> +             .of_match_table = c3_isp_of_match,
>>>> +             .pm = pm_ptr(&c3_isp_pm_ops),
>>>> +     },
>>>> +};
>>>> +
>>>> +module_platform_driver(c3_isp_driver);
>>>> +
>>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>>>> +MODULE_LICENSE("GPL");
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
>>>> new file mode 100644
>>>> index 000000000000..dc33a57dcd59
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
>>>> @@ -0,0 +1,942 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#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-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 pointer 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. The data member at the end allows a pointer to an address
>>>> + * within the data member of :c:type:`c3_isp_params_cfg` to initialise a
>>>> + * union variable.
>>>> + *
>>>> + * @header:          Pointer to the shared header struct embedded as the
>>>> + *                   first member of all the possible other members (except
>>>> + *                   @data). 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_PST_CCM
>>>> + * @csc:             For header->type == C3_ISP_PARAMS_BLOCK_PST_CSC
>>>> + * @blc:             For header->type == C3_ISP_PARAMS_BLOCK_PST_BLC
>>>> + * @data:            Allows easy initialisation of a union variable with a
>>>> + *                   pointer into a u8 array.
>>>> + */
>>>> +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;
>>>> +     u8 *data;
>>> Why do you need data here ?
>>>
>>> Oh, I've see in c3_isp_params_cfg_blocks() now :)
>>>
>>> The consequences of this is that all handlers take an
>>>
>>> typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
>>>                                        union c3_isp_params_block block);
>>>
>>> by value and not by pointer.
>>>
>>> I know it's equivalent since the union only stores pointers but it
>>> might be confusing... I don't know, some parts of me like it, some
>>> doesn't and would prefer something like union
>>> rkisp1_ext_params_config. Clever hack btw ;) If you prefer this style
>>> I'm fine with you keeping it even if seeing a parameter passed by
>>> value to the handlers makes me cringe a bit
>>>
>> Will check this issue.
>>
> Up to you, maybe a matter of tastes
>
>>>> +};
>>>> +
>>>> +typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
>>>> +                                  union c3_isp_params_block block);
>>>> +
>>>> +struct c3_isp_params_handler {
>>>> +     size_t size;
>>>> +     c3_isp_block_handler handler;
>>>> +};
>>>> +
>>>> +#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,
>>>> +                                     union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_awb_gains *awb_gains = block.awb_gains;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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));
>>>> +
>>>> +     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_cfg_awb_weight(struct c3_isp_device *isp,
>>>> +                                      struct c3_isp_params_awb_config *awb_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 = awb_cfg->horiz_zones_num * awb_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_IDX_DATA(0, awb_cfg->zone_weight[base + 0]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(1, awb_cfg->zone_weight[base + 1]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(2, awb_cfg->zone_weight[base + 2]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(3, awb_cfg->zone_weight[base + 3]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(4, awb_cfg->zone_weight[base + 4]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(5, awb_cfg->zone_weight[base + 5]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(6, awb_cfg->zone_weight[base + 6]) |
>>>> +                    ISP_AWB_BLK_WT_DATA_IDX_DATA(7, awb_cfg->zone_weight[base + 7]);
>>>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
>>>> +     }
>>>> +
>>>> +     if (zones_num % 8) {
>>>> +             data = 0;
>>>> +             base = i * 8;
>>>> +
>>>> +             for (i = 0; i < zones_num % 8; i++)
>>>> +                     data |= ISP_AWB_BLK_WT_DATA_IDX_DATA(i, awb_cfg->zone_weight[base + i]);
>>>> +
>>>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_awb_cood(struct c3_isp_device *isp,
>>>> +                                    struct c3_isp_params_awb_config *awb_cfg)
>>>> +{
>>>> +     unsigned int max_point_num;
>>>> +
>>>> +     /* The number of points is one more than the number of edges */
>>>> +     max_point_num = max(awb_cfg->horiz_zones_num, awb_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(awb_cfg->horiz_cood[i]) |
>>>> +                          ISP_AWB_IDX_DATA_VIDX_DATA(awb_cfg->vert_cood[i]));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp,
>>>> +                                      union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_awb_config *awb_cfg = block.awb_cfg;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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_STAT_POINT_MASK,
>>>> +                        ISP_TOP_3A_STAT_CRTL_AWB_STAT_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_HORIZ_BLKS_NUM_MASK,
>>>> +                        ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(awb_cfg->horiz_zones_num));
>>>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK,
>>>> +                        ISP_AWB_HV_BLKNUM_VERT_BLKS_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_cfg_awb_weight(isp, awb_cfg);
>>>> +     c3_isp_params_cfg_awb_cood(isp, awb_cfg);
>>>> +
>>>> +     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_cfg_ae_weight(struct c3_isp_device *isp,
>>>> +                                     struct c3_isp_params_ae_config *ae_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 = ae_cfg->horiz_zones_num * ae_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_IDX_DATA(0, ae_cfg->zone_weight[base + 0]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(1, ae_cfg->zone_weight[base + 1]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(2, ae_cfg->zone_weight[base + 2]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(3, ae_cfg->zone_weight[base + 3]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(4, ae_cfg->zone_weight[base + 4]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(5, ae_cfg->zone_weight[base + 5]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(6, ae_cfg->zone_weight[base + 6]) |
>>>> +                    ISP_AE_BLK_WT_DATA_IDX_DATA(7, ae_cfg->zone_weight[base + 7]);
>>>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
>>>> +     }
>>>> +
>>>> +     /* Write the last weights data */
>>>> +     if (zones_num % 8) {
>>>> +             data = 0;
>>>> +             base = i * 8;
>>>> +
>>>> +             for (i = 0; i < zones_num % 8; i++)
>>>> +                     data |= ISP_AE_BLK_WT_DATA_IDX_DATA(i, ae_cfg->zone_weight[base + i]);
>>>> +
>>>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_ae_cood(struct c3_isp_device *isp,
>>>> +                                   struct c3_isp_params_ae_config *ae_cfg)
>>>> +{
>>>> +     unsigned int max_point_num;
>>>> +
>>>> +     /* The number of points is one more than the number of edges */
>>>> +     max_point_num = max(ae_cfg->horiz_zones_num, ae_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(ae_cfg->horiz_cood[i]) |
>>>> +                          ISP_AE_IDX_DATA_VIDX_DATA(ae_cfg->vert_cood[i]));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp,
>>>> +                                     union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_ae_config *ae_cfg = block.ae_cfg;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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_STAT_POINT_MASK,
>>>> +                        ISP_TOP_3A_STAT_CRTL_AE_STAT_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_BLK_HORIZ_NUM_MASK,
>>>> +                        ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(ae_cfg->horiz_zones_num));
>>>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK,
>>>> +                        ISP_AE_HV_BLKNUM_BLK_VERT_NUM(ae_cfg->vert_zones_num));
>>>> +
>>>> +     c3_isp_params_cfg_ae_weight(isp, ae_cfg);
>>>> +     c3_isp_params_cfg_ae_cood(isp, ae_cfg);
>>>> +
>>>> +     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_cfg_af_cood(struct c3_isp_device *isp,
>>>> +                                   struct c3_isp_params_af_config *af_cfg)
>>>> +{
>>>> +     unsigned int max_point_num;
>>>> +
>>>> +     /* The number of points is one more than the number of edges */
>>>> +     max_point_num = max(af_cfg->horiz_zones_num, af_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(af_cfg->horiz_cood[i]) |
>>>> +                          ISP_AF_IDX_DATA_VIDX_DATA(af_cfg->vert_cood[i]));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp,
>>>> +                                     union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_af_config *af_cfg = block.af_cfg;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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_STAT_POINT_MASK,
>>>> +                        ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(af_cfg->tap_point));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK,
>>>> +                        ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(af_cfg->horiz_zones_num));
>>>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK,
>>>> +                        ISP_AF_HV_BLKNUM_BLK_VERT_NUM(af_cfg->vert_zones_num));
>>>> +
>>>> +     c3_isp_params_cfg_af_cood(isp, af_cfg);
>>>> +
>>>> +     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,
>>>> +                                     union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_pst_gamma *gamma = block.pst_gamma;
>>>> +     unsigned int base;
>>>> +     unsigned int i;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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(gamma->lut) / 2; i++) {
>>>> +                     base = i * 2;
>>>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>>> +                                  ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]) |
>>>> +                                  ISP_PST_GAMMA_LUT_DATA_DATA1(gamma->lut[base + 1]));
>>>> +             }
>>>> +
>>>> +             /* Write the last one */
>>>> +             if (ARRAY_SIZE(gamma->lut) % 2) {
>>>> +                     base = i * 2;
>>>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>>> +                                  ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]));
>>>> +             }
>>>> +     }
>>>> +
>>>> +     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,
>>>> +                               union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_ccm *ccm = block.ccm;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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]));
>>>> +
>>>> +     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,
>>>> +                               union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_csc *csc = block.csc;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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]));
>>>> +
>>>> +     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,
>>>> +                               union c3_isp_params_block block)
>>>> +{
>>>> +     struct c3_isp_params_blc *blc = block.blc;
>>>> +
>>>> +     if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
>>>> +             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));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_BLC_EN_MASK,
>>>> +                        ISP_TOP_BEO_CTRL_BLC_EN);
>>>> +}
>>>> +
>>>> +static const struct c3_isp_params_handler c3_isp_params_handlers[] = {
>>>> +     [C3_ISP_PARAMS_BLOCK_AWB_GAINS] = {
>>>> +             .size = sizeof(struct c3_isp_params_awb_gains),
>>>> +             .handler = c3_isp_params_cfg_awb_gains,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = {
>>>> +             .size = sizeof(struct c3_isp_params_awb_config),
>>>> +             .handler = c3_isp_params_cfg_awb_config,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_AE_CONFIG] = {
>>>> +             .size = sizeof(struct c3_isp_params_ae_config),
>>>> +             .handler = c3_isp_params_cfg_ae_config,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_AF_CONFIG] = {
>>>> +             .size = sizeof(struct c3_isp_params_af_config),
>>>> +             .handler = c3_isp_params_cfg_af_config,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_PST_GAMMA] = {
>>>> +             .size = sizeof(struct c3_isp_params_pst_gamma),
>>>> +             .handler = c3_isp_params_cfg_pst_gamma,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_CCM] = {
>>>> +             .size = sizeof(struct c3_isp_params_ccm),
>>>> +             .handler = c3_isp_params_cfg_ccm,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_CSC] = {
>>>> +             .size = sizeof(struct c3_isp_params_csc),
>>>> +             .handler = c3_isp_params_cfg_csc,
>>>> +     },
>>>> +     [C3_ISP_PARAMS_BLOCK_BLC] = {
>>>> +             .size = sizeof(struct c3_isp_params_blc),
>>>> +             .handler = c3_isp_params_cfg_blc,
>>>> +     },
>>>> +};
>>>> +
>>>> +static enum vb2_buffer_state
>>>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>>>> +{
>>>> +     struct c3_isp_params_cfg *config = params->buff->cfg;
>>>> +     size_t block_offset = 0;
>>>> +     size_t max_offset = 0;
>>>> +
>>>> +     if (WARN_ON(!config))
>>>> +             return VB2_BUF_STATE_ERROR;
>>>> +
>>>> +     /* Ensure config->data has a full struct c3_isp_params_block_header */
>>>> +     max_offset = config->data_size - sizeof(struct c3_isp_params_block_header);
>>> I might have missed why you subtract this. Hasn't the package been
>>> validated in c3_isp_params_vb2_buf_prepare() ?
>>
>> Yes, the package has been validated.
>>
>> Will check this issue.
>>
>>>> +
>>>> +     /* Walk the list of parameter blocks and process them */
>>>> +     while (block_offset <= max_offset) {
>>>> +             const struct c3_isp_params_handler *block_handler;
>>>> +             union c3_isp_params_block block;
>>>> +
>>>> +             block = (union c3_isp_params_block)
>>>> +                      &config->data[block_offset];
>>>> +
>>>> +             block_handler = &c3_isp_params_handlers[block.header->type];
>>>> +             block_handler->handler(params->isp, block);
>>>> +
>>>> +             block_offset += block.header->size;
>>>> +     }
>>>> +
>>>> +     return VB2_BUF_STATE_DONE;
>>>> +}
>>>> +
>>>> +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)(&params->buff_lock);
>>>> +
>>>> +     /* Only use the first buffer to initialize ISP */
>>>> +     params->buff = list_first_entry_or_null(&params->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 here
>>>
>> What is the reason to return 0?
> Ah sorry, I wasn't clear, I didn't mean to replace return -EINVAL but to
>
>       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;
>
> up to you
>

OK, I see

>> I think need to consider the test of 'v4l2-compliance 1.29.0-5246'.
>>
>>>> +     } else {
>>> and you can spare the } else {
>>
>> Will check here.
>>
>>>> +             *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)(&params->buff_lock);
>>>> +
>>>> +     list_add_tail(&buf->list, &params->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 *cfg = buf->cfg;
>>>> +     struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0);
>>>> +     size_t payload_size = vb2_get_plane_payload(vb, 0);
>>>> +     size_t header_size = offsetof(struct c3_isp_params_cfg, data);
>>>> +     size_t block_offset = 0;
>>>> +     size_t cfg_size;
>>>> +
>>>> +     /* Payload size can't be greater than the destination buffer size */
>>>> +     if (payload_size > params->vfmt.fmt.meta.buffersize) {
>>>> +             dev_dbg(params->isp->dev,
>>>> +                     "Payload size is too large: %zu\n", payload_size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     /* Payload size can't be smaller than the header size */
>>>> +     if (payload_size < header_size) {
>>>> +             dev_dbg(params->isp->dev,
>>>> +                     "Payload size is too small: %zu\n", payload_size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     /*
>>>> +      * Use the internal scratch buffer to avoid userspace modifying
>>>> +      * the buffer content while the driver is processing it.
>>>> +      */
>>>> +     memcpy(cfg, usr_cfg, payload_size);
>>>> +
>>>> +     /* Only v0 is supported at the moment */
>>>> +     if (cfg->version != C3_ISP_PARAMS_BUFFER_V0) {
>>>> +             dev_dbg(params->isp->dev,
>>>> +                     "Unsupported params buffer version: %u\n", cfg->version);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     /* Validate the size reported in the parameter buffer header */
>>>> +     cfg_size = header_size + cfg->data_size;
>>>> +     if (cfg_size != payload_size) {
>>>> +             dev_dbg(params->isp->dev,
>>>> +                     "Data size %zu and payload size %zu are different\n",
>>>> +                     cfg_size, payload_size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     /* Walk the list of parameter blocks and validate them */
>>>> +     cfg_size = cfg->data_size;
>>>> +     while (cfg_size >= sizeof(struct c3_isp_params_block_header)) {
>>>> +             const struct c3_isp_params_block_header *block;
>>>> +             const struct c3_isp_params_handler *handler;
>>>> +
>>>> +             block = (struct c3_isp_params_block_header *)
>>>> +                     &cfg->data[block_offset];
>>>> +
>>>> +             if (block->type >= ARRAY_SIZE(c3_isp_params_handlers)) {
>>>> +                     dev_dbg(params->isp->dev,
>>>> +                             "Invalid params block type\n");
>>>> +                     return -EINVAL;
>>>> +             }
>>>> +
>>>> +             if (block->size > cfg_size) {
>>>> +                     dev_dbg(params->isp->dev,
>>>> +                             "Block size is greater than cfg size\n");
>>>> +                     return -EINVAL;
>>>> +             }
>>>> +
>>>> +             handler = &c3_isp_params_handlers[block->type];
>>>> +             if (block->size != handler->size) {
>>>> +                     dev_dbg(params->isp->dev,
>>>> +                             "Invalid params block size\n");
>>>> +                     return -EINVAL;
>>>> +             }
>>>> +
>>>> +             block_offset += block->size;
>>>> +             cfg_size -= block->size;
>>>> +     }
>>>> +
>>>> +     if (cfg_size) {
>>>> +             dev_dbg(params->isp->dev,
>>>> +                     "Unexpected data after the params buffer end\n");
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +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)(&params->buff_lock);
>>>> +
>>>> +     while (!list_empty(&params->pending)) {
>>>> +             buff = list_first_entry(&params->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,
>>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>>> +     .wait_finish = vb2_ops_wait_finish,
>>>> +     .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 = &params->vdev;
>>>> +     struct vb2_queue *vb2_q = &params->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(&params->pending);
>>>> +     spin_lock_init(&params->buff_lock);
>>>> +     mutex_init(&params->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 = &params->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_buffer);
>>>> +     vb2_q->dev = isp->dev;
>>>> +     vb2_q->lock = &params->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, &params->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(&params->lock);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_params *params = &isp->params;
>>>> +
>>>> +     vb2_queue_release(&params->vb2_q);
>>>> +     media_entity_cleanup(&params->vdev.entity);
>>>> +     video_unregister_device(&params->vdev);
>>>> +     mutex_destroy(&params->lock);
>>>> +}
>>>> +
>>>> +void c3_isp_params_isr(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_params *params = &isp->params;
>>>> +     enum vb2_buffer_state state;
>>>> +
>>>> +     guard(spinlock_irqsave)(&params->buff_lock);
>>>> +
>>>> +     params->buff = list_first_entry_or_null(&params->pending,
>>>> +                                             struct c3_isp_params_buffer, list);
>>>> +     if (!params->buff)
>>>> +             return;
>>>> +
>>>> +     list_del(&params->buff->list);
>>>> +
>>>> +     state = 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(&params->buff->vb.vb2_buf, state);
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
>>>> new file mode 100644
>>>> index 000000000000..c4d3b8c96479
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
>>>> @@ -0,0 +1,599 @@
>>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#ifndef __C3_ISP_REGS_H__
>>>> +#define __C3_ISP_REGS_H__
>>>> +
>>>> +#define ISP_TOP_INPUT_SIZE                              0x0000
>>>> +#define ISP_TOP_INPUT_SIZE_VERT_SIZE_MASK               GENMASK(15, 0)
>>>> +#define ISP_TOP_INPUT_SIZE_VERT_SIZE(x)                 ((x) << 0)
>>>> +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE_MASK              GENMASK(31, 16)
>>>> +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE(x)                ((x) << 16)
>>>> +
>>>> +#define ISP_TOP_FRM_SIZE                                0x0004
>>>> +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE_MASK            GENMASK(15, 0)
>>>> +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(x)              ((x) << 0)
>>>> +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE_MASK           GENMASK(31, 16)
>>>> +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(x)             ((x) << 16)
>>>> +
>>>> +#define ISP_TOP_HOLD_SIZE                               0x0008
>>>> +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK          GENMASK(31, 16)
>>>> +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(x)            ((x) << 16)
>>>> +
>>>> +#define ISP_TOP_PATH_EN                                 0x0010
>>>> +#define ISP_TOP_PATH_EN_DISP0_EN_MASK                   BIT(0)
>>>> +#define ISP_TOP_PATH_EN_DISP0_EN                        BIT(0)
>>>> +#define ISP_TOP_PATH_EN_DISP0_DIS                       (0 << 0)
>>>> +#define ISP_TOP_PATH_EN_DISP1_EN_MASK                   BIT(1)
>>>> +#define ISP_TOP_PATH_EN_DISP1_EN                        BIT(1)
>>>> +#define ISP_TOP_PATH_EN_DISP1_DIS                       (0 << 1)
>>>> +#define ISP_TOP_PATH_EN_DISP2_EN_MASK                   BIT(2)
>>>> +#define ISP_TOP_PATH_EN_DISP2_EN                        BIT(2)
>>>> +#define ISP_TOP_PATH_EN_DISP2_DIS                       (0 << 2)
>>>> +#define ISP_TOP_PATH_EN_WRMIF0_EN_MASK                  BIT(8)
>>>> +#define ISP_TOP_PATH_EN_WRMIF0_EN                       BIT(8)
>>>> +#define ISP_TOP_PATH_EN_WRMIF0_DIS                      (0 << 8)
>>>> +#define ISP_TOP_PATH_EN_WRMIF1_EN_MASK                  BIT(9)
>>>> +#define ISP_TOP_PATH_EN_WRMIF1_EN                       BIT(9)
>>>> +#define ISP_TOP_PATH_EN_WRMIF1_DIS                      (0 << 9)
>>>> +#define ISP_TOP_PATH_EN_WRMIF2_EN_MASK                  BIT(10)
>>>> +#define ISP_TOP_PATH_EN_WRMIF2_EN                       BIT(10)
>>>> +#define ISP_TOP_PATH_EN_WRMIF2_DIS                      (0 << 10)
>>>> +
>>>> +#define ISP_TOP_PATH_SEL                                0x0014
>>>> +#define ISP_TOP_PATH_SEL_CORE_MASK                      GENMASK(18, 16)
>>>> +#define ISP_TOP_PATH_SEL_CORE_CORE_DIS                  (0 << 16)
>>>> +#define ISP_TOP_PATH_SEL_CORE_MIPI_CORE                 BIT(16)
>>>> +
>>>> +#define ISP_TOP_IRQ_EN                                  0x0080
>>>> +#define ISP_TOP_IRQ_EN_FRAME_END_MASK                   BIT(0)
>>>> +#define ISP_TOP_IRQ_EN_FRAME_END_EN                     BIT(0)
>>>> +#define ISP_TOP_IRQ_EN_FRAME_END_DIS                    (0 << 0)
>>>> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK                  BIT(5)
>>>> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_EN                    BIT(5)
>>>> +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS                   (0 << 5)
>>>> +
>>>> +#define ISP_TOP_IRQ_CLR                                 0x0084
>>>> +#define ISP_TOP_RO_IRQ_STAT                             0x01c4
>>>> +
>>>> +#define ISP_TOP_MODE_CTRL                               0x0400
>>>> +#define ISP_TOP_FEO_CTRL0                               0x040c
>>>> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK             BIT(8)
>>>> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS                 (0 << 8)
>>>> +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN                  BIT(8)
>>>> +
>>>> +#define ISP_TOP_FEO_CTRL1_0                             0x0410
>>>> +#define ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK                 BIT(3)
>>>> +#define ISP_TOP_FEO_CTRL1_0_DPC_DIS                     (0 << 3)
>>>> +#define ISP_TOP_FEO_CTRL1_0_DPC_EN                      BIT(3)
>>>> +#define ISP_TOP_FEO_CTRL1_0_OG_EN_MASK                  BIT(5)
>>>> +#define ISP_TOP_FEO_CTRL1_0_OG_DIS                      (0 << 5)
>>>> +#define ISP_TOP_FEO_CTRL1_0_OG_EN                       BIT(5)
>>>> +
>>>> +#define ISP_TOP_FED_CTRL                                0x0418
>>>> +#define ISP_TOP_FED_CTRL_PDPC_EN_MASK                   BIT(1)
>>>> +#define ISP_TOP_FED_CTRL_PDPC_DIS                       (0 << 1)
>>>> +#define ISP_TOP_FED_CTRL_PDPC_EN                        BIT(1)
>>>> +#define ISP_TOP_FED_CTRL_RAWCNR_EN_MASK                 GENMASK(6, 5)
>>>> +#define ISP_TOP_FED_CTRL_RAWCNR_DIS                     (0 << 5)
>>>> +#define ISP_TOP_FED_CTRL_RAWCNR_EN                      BIT(5)
>>>> +#define ISP_TOP_FED_CTRL_SNR1_EN_MASK                   BIT(9)
>>>> +#define ISP_TOP_FED_CTRL_SNR1_DIS                       (0 << 9)
>>>> +#define ISP_TOP_FED_CTRL_SNR1_EN                        BIT(9)
>>>> +#define ISP_TOP_FED_CTRL_TNR0_EN_MASK                   BIT(11)
>>>> +#define ISP_TOP_FED_CTRL_TNR0_DIS                       (0 << 11)
>>>> +#define ISP_TOP_FED_CTRL_TNR0_EN                        BIT(11)
>>>> +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK               BIT(12)
>>>> +#define ISP_TOP_FED_CTRL_CUBIC_CS_DIS                   (0 << 12)
>>>> +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN                    BIT(12)
>>>> +#define ISP_TOP_FED_CTRL_SQRT_EN_MASK                   BIT(14)
>>>> +#define ISP_TOP_FED_CTRL_SQRT_DIS                       (0 << 14)
>>>> +#define ISP_TOP_FED_CTRL_SQRT_EN                        BIT(14)
>>>> +#define ISP_TOP_FED_CTRL_DGAIN_EN_MASK                  BIT(16)
>>>> +#define ISP_TOP_FED_CTRL_DGAIN_DIS                      (0 << 16)
>>>> +#define ISP_TOP_FED_CTRL_DGAIN_EN                       BIT(16)
>>>> +
>>>> +#define ISP_TOP_BEO_CTRL                                0x041c
>>>> +#define ISP_TOP_BEO_CTRL_WB_EN_MASK                     BIT(6)
>>>> +#define ISP_TOP_BEO_CTRL_WB_DIS                         (0 << 6)
>>>> +#define ISP_TOP_BEO_CTRL_WB_EN                          BIT(6)
>>>> +#define ISP_TOP_BEO_CTRL_BLC_EN_MASK                    BIT(7)
>>>> +#define ISP_TOP_BEO_CTRL_BLC_DIS                        (0 << 7)
>>>> +#define ISP_TOP_BEO_CTRL_BLC_EN                         BIT(7)
>>>> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK              BIT(8)
>>>> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_DIS                  (0 << 8)
>>>> +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN                   BIT(8)
>>>> +#define ISP_TOP_BEO_CTRL_EOTF_EN_MASK                   BIT(9)
>>>> +#define ISP_TOP_BEO_CTRL_EOTF_DIS                       (0 << 9)
>>>> +#define ISP_TOP_BEO_CTRL_EOTF_EN                        BIT(9)
>>>> +
>>>> +#define ISP_TOP_BED_CTRL                                0x0420
>>>> +#define ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK               GENMASK(1, 0)
>>>> +#define ISP_TOP_BED_CTRL_YHS_STAT_DIS                   (0 << 0)
>>>> +#define ISP_TOP_BED_CTRL_YHS_STAT_EN                    BIT(0)
>>>> +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK              BIT(2)
>>>> +#define ISP_TOP_BED_CTRL_GRPH_STAT_DIS                  (0 << 2)
>>>> +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN                   BIT(2)
>>>> +#define ISP_TOP_BED_CTRL_FMETER_EN_MASK                 BIT(3)
>>>> +#define ISP_TOP_BED_CTRL_FMETER_DIS                     (0 << 3)
>>>> +#define ISP_TOP_BED_CTRL_FMETER_EN                      BIT(3)
>>>> +#define ISP_TOP_BED_CTRL_BSC_EN_MASK                    BIT(10)
>>>> +#define ISP_TOP_BED_CTRL_BSC_DIS                        (0 << 10)
>>>> +#define ISP_TOP_BED_CTRL_BSC_EN                         BIT(10)
>>>> +#define ISP_TOP_BED_CTRL_CNR2_EN_MASK                   BIT(11)
>>>> +#define ISP_TOP_BED_CTRL_CNR2_DIS                       (0 << 11)
>>>> +#define ISP_TOP_BED_CTRL_CNR2_EN                        BIT(11)
>>>> +#define ISP_TOP_BED_CTRL_CM1_EN_MASK                    BIT(13)
>>>> +#define ISP_TOP_BED_CTRL_CM1_DIS                        (0 << 13)
>>>> +#define ISP_TOP_BED_CTRL_CM1_EN                         BIT(13)
>>>> +#define ISP_TOP_BED_CTRL_CM0_EN_MASK                    BIT(14)
>>>> +#define ISP_TOP_BED_CTRL_CM0_DIS                        (0 << 14)
>>>> +#define ISP_TOP_BED_CTRL_CM0_EN                         BIT(14)
>>>> +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK              BIT(16)
>>>> +#define ISP_TOP_BED_CTRL_PST_GAMMA_DIS                  (0 << 16)
>>>> +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN                   BIT(16)
>>>> +#define ISP_TOP_BED_CTRL_LUT3D_EN_MASK                  BIT(17)
>>>> +#define ISP_TOP_BED_CTRL_LUT3D_DIS                      (0 << 17)
>>>> +#define ISP_TOP_BED_CTRL_LUT3D_EN                       BIT(17)
>>>> +#define ISP_TOP_BED_CTRL_CCM_EN_MASK                    BIT(18)
>>>> +#define ISP_TOP_BED_CTRL_CCM_DIS                        (0 << 18)
>>>> +#define ISP_TOP_BED_CTRL_CCM_EN                         BIT(18)
>>>> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK           BIT(21)
>>>> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS               (0 << 21)
>>>> +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN                BIT(21)
>>>> +#define ISP_TOP_BED_CTRL_AMCM_EN_MASK                   BIT(25)
>>>> +#define ISP_TOP_BED_CTRL_AMCM_DIS                       (0 << 25)
>>>> +#define ISP_TOP_BED_CTRL_AMCM_EN                        BIT(25)
>>>> +
>>>> +#define ISP_TOP_3A_STAT_CRTL                            0x0424
>>>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK            BIT(0)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS                (0 << 0)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN                 BIT(0)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK           BIT(1)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS               (0 << 1)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN                BIT(1)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK            BIT(2)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS                (0 << 2)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN                 BIT(2)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT_MASK        GENMASK(6, 4)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT(x)          ((x) << 4)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT_MASK         GENMASK(9, 8)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT(x)           ((x) << 8)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT_MASK         GENMASK(13, 12)
>>>> +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(x)           ((x) << 12)
>>>> +
>>>> +#define ISP_LSWB_BLC_OFST0                              0x4028
>>>> +#define ISP_LSWB_BLC_OFST0_R_OFST_MASK                  GENMASK(15, 0)
>>>> +#define ISP_LSWB_BLC_OFST0_R_OFST(x)                    ((x) << 0)
>>>> +#define ISP_LSWB_BLC_OFST0_GR_OFST_MASK                 GENMASK(31, 16)
>>>> +#define ISP_LSWB_BLC_OFST0_GR_OFST(x)                   ((x) << 16)
>>>> +
>>>> +#define ISP_LSWB_BLC_OFST1                              0x402c
>>>> +#define ISP_LSWB_BLC_OFST1_GB_OFST_MASK                 GENMASK(15, 0)
>>>> +#define ISP_LSWB_BLC_OFST1_GB_OFST(x)                   ((x) << 0)
>>>> +#define ISP_LSWB_BLC_OFST1_B_OFST_MASK                  GENMASK(31, 16)
>>>> +#define ISP_LSWB_BLC_OFST1_B_OFST(x)                    ((x) << 16)
>>>> +
>>>> +#define ISP_LSWB_BLC_PHSOFST                            0x4034
>>>> +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
>>>> +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST(x)               ((x) << 0)
>>>> +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
>>>> +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
>>>> +
>>>> +#define ISP_LSWB_WB_GAIN0                               0x4038
>>>> +#define ISP_LSWB_WB_GAIN0_R_GAIN_MASK                   GENMASK(11, 0)
>>>> +#define ISP_LSWB_WB_GAIN0_R_GAIN(x)                     ((x) << 0)
>>>> +#define ISP_LSWB_WB_GAIN0_GR_GAIN_MASK                  GENMASK(27, 16)
>>>> +#define ISP_LSWB_WB_GAIN0_GR_GAIN(x)                    ((x) << 16)
>>>> +
>>>> +#define ISP_LSWB_WB_GAIN1                               0x403c
>>>> +#define ISP_LSWB_WB_GAIN1_GB_GAIN_MASK                  GENMASK(11, 0)
>>>> +#define ISP_LSWB_WB_GAIN1_GB_GAIN(x)                    ((x) << 0)
>>>> +#define ISP_LSWB_WB_GAIN1_B_GAIN_MASK                   GENMASK(27, 16)
>>>> +#define ISP_LSWB_WB_GAIN1_B_GAIN(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_LSWB_WB_GAIN2                               0x4040
>>>> +#define ISP_LSWB_WB_GAIN2_IR_GAIN_MASK                  GENMASK(11, 0)
>>>> +#define ISP_LSWB_WB_GAIN2_IR_GAIN(x)                    ((x) << 0)
>>>> +
>>>> +#define ISP_LSWB_WB_LIMIT0                              0x4044
>>>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MASK              GENMASK(15, 0)
>>>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R(x)                ((x) << 0)
>>>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX               (0x8fff << 0)
>>>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MASK             GENMASK(31, 16)
>>>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR(x)               ((x) << 16)
>>>> +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX              (0x8fff << 16)
>>>> +
>>>> +#define ISP_LSWB_WB_LIMIT1                              0x4048
>>>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MASK             GENMASK(15, 0)
>>>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB(x)               ((x) << 0)
>>>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX              (0x8fff << 0)
>>>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MASK              GENMASK(31, 16)
>>>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B(x)                ((x) << 16)
>>>> +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX               (0x8fff << 16)
>>>> +
>>>> +#define ISP_LSWB_WB_PHSOFST                             0x4050
>>>> +#define ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK              GENMASK(1, 0)
>>>> +#define ISP_LSWB_WB_PHSOFST_VERT_OFST(x)                ((x) << 0)
>>>> +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK             GENMASK(3, 2)
>>>> +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST(x)               ((x) << 2)
>>>> +
>>>> +#define ISP_LSWB_LNS_PHSOFST                            0x4054
>>>> +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
>>>> +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST(x)               ((x) << 0)
>>>> +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
>>>> +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
>>>> +
>>>> +#define ISP_DMS_COMMON_PARAM0                           0x5000
>>>> +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK         GENMASK(1, 0)
>>>> +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(x)          ((x) << 0)
>>>> +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK        GENMASK(3, 2)
>>>> +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(x)         ((x) << 2)
>>>> +
>>>> +#define ISP_CM0_COEF00_01                               0x6048
>>>> +#define ISP_CM0_COEF00_01_MTX_00_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CM0_COEF00_01_MTX_00(x)                     ((x) << 0)
>>>> +#define ISP_CM0_COEF00_01_MTX_01_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CM0_COEF00_01_MTX_01(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CM0_COEF02_10                               0x604c
>>>> +#define ISP_CM0_COEF02_10_MTX_02_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CM0_COEF02_10_MTX_02(x)                     ((x) << 0)
>>>> +#define ISP_CM0_COEF02_10_MTX_10_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CM0_COEF02_10_MTX_10(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CM0_COEF11_12                               0x6050
>>>> +#define ISP_CM0_COEF11_12_MTX_11_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CM0_COEF11_12_MTX_11(x)                     ((x) << 0)
>>>> +#define ISP_CM0_COEF11_12_MTX_12_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CM0_COEF11_12_MTX_12(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CM0_COEF20_21                               0x6054
>>>> +#define ISP_CM0_COEF20_21_MTX_20_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CM0_COEF20_21_MTX_20(x)                     ((x) << 0)
>>>> +#define ISP_CM0_COEF20_21_MTX_21_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CM0_COEF20_21_MTX_21(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CM0_COEF22_OUP_OFST0                        0x6058
>>>> +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK            GENMASK(12, 0)
>>>> +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22(x)              ((x) << 0)
>>>> +
>>>> +#define ISP_CCM_MTX_00_01                               0x6098
>>>> +#define ISP_CCM_MTX_00_01_MTX_00_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CCM_MTX_00_01_MTX_00(x)                     ((x) << 0)
>>>> +#define ISP_CCM_MTX_00_01_MTX_01_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CCM_MTX_00_01_MTX_01(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CCM_MTX_02_03                               0x609c
>>>> +#define ISP_CCM_MTX_02_03_MTX_02_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CCM_MTX_02_03_MTX_02(x)                     ((x) << 0)
>>>> +
>>>> +#define ISP_CCM_MTX_10_11                               0x60A0
>>>> +#define ISP_CCM_MTX_10_11_MTX_10_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CCM_MTX_10_11_MTX_10(x)                     ((x) << 0)
>>>> +#define ISP_CCM_MTX_10_11_MTX_11_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CCM_MTX_10_11_MTX_11(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CCM_MTX_12_13                               0x60A4
>>>> +#define ISP_CCM_MTX_12_13_MTX_12_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CCM_MTX_12_13_MTX_12(x)                     ((x) << 0)
>>>> +
>>>> +#define ISP_CCM_MTX_20_21                               0x60A8
>>>> +#define ISP_CCM_MTX_20_21_MTX_20_MASK                   GENMASK(12, 0)
>>>> +#define ISP_CCM_MTX_20_21_MTX_20(x)                     ((x) << 0)
>>>> +#define ISP_CCM_MTX_20_21_MTX_21_MASK                   GENMASK(28, 16)
>>>> +#define ISP_CCM_MTX_20_21_MTX_21(x)                     ((x) << 16)
>>>> +
>>>> +#define ISP_CCM_MTX_22_23_RS                            0x60Ac
>>>> +#define ISP_CCM_MTX_22_23_RS_MTX_22_MASK                GENMASK(12, 0)
>>>> +#define ISP_CCM_MTX_22_23_RS_MTX_22(x)                  ((x) << 0)
>>>> +
>>>> +#define ISP_PST_GAMMA_LUT_ADDR                          0x60cc
>>>> +#define ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(x)              ((x) << 7)
>>>> +
>>>> +#define ISP_PST_GAMMA_LUT_DATA                          0x60d0
>>>> +#define ISP_PST_GAMMA_LUT_DATA_DATA0(x)                 (((x) & GENMASK(15, 0)) << 0)
>>>> +#define ISP_PST_GAMMA_LUT_DATA_DATA1(x)                 (((x) & GENMASK(15, 0)) << 16)
>>>> +
>>>> +#define DISP0_TOP_TOP_CTRL                              0x8000
>>>> +#define DISP0_TOP_TOP_CTRL_CROP2_EN_MASK                BIT(5)
>>>> +#define DISP0_TOP_TOP_CTRL_CROP2_EN                     BIT(5)
>>>> +#define DISP0_TOP_TOP_CTRL_CROP2_DIS                    (0 << 5)
>>>> +
>>>> +#define DISP0_TOP_CRP2_START                            0x8004
>>>> +#define DISP0_TOP_CRP2_START_V_START_MASK               GENMASK(15, 0)
>>>> +#define DISP0_TOP_CRP2_START_V_START(x)                 ((x) << 0)
>>>> +#define DISP0_TOP_CRP2_START_H_START_MASK               GENMASK(31, 16)
>>>> +#define DISP0_TOP_CRP2_START_H_START(x)                 ((x) << 16)
>>>> +
>>>> +#define DISP0_TOP_CRP2_SIZE                             0x8008
>>>> +#define DISP0_TOP_CRP2_SIZE_V_SIZE_MASK                 GENMASK(15, 0)
>>>> +#define DISP0_TOP_CRP2_SIZE_V_SIZE(x)                   ((x) << 0)
>>>> +#define DISP0_TOP_CRP2_SIZE_H_SIZE_MASK                 GENMASK(31, 16)
>>>> +#define DISP0_TOP_CRP2_SIZE_H_SIZE(x)                   ((x) << 16)
>>>> +
>>>> +#define DISP0_TOP_OUT_SIZE                              0x800c
>>>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT_MASK       GENMASK(12, 0)
>>>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT(x)         ((x) << 0)
>>>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH_MASK        GENMASK(28, 16)
>>>> +#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(x)          ((x) << 16)
>>>> +
>>>> +#define ISP_DISP0_TOP_IN_SIZE                           0x804c
>>>> +#define ISP_DISP0_TOP_IN_SIZE_VSIZE_MASK                GENMASK(12, 0)
>>>> +#define ISP_DISP0_TOP_IN_SIZE_VSIZE(x)                  ((x) << 0)
>>>> +#define ISP_DISP0_TOP_IN_SIZE_HSIZE_MASK                GENMASK(28, 16)
>>>> +#define ISP_DISP0_TOP_IN_SIZE_HSIZE(x)                  ((x) << 16)
>>>> +
>>>> +#define DISP0_PPS_SCALE_EN                              0x8200
>>>> +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK             GENMASK(3, 0)
>>>> +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM(x)               ((x) << 0)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM_MASK             GENMASK(7, 4)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM(x)               ((x) << 4)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK          GENMASK(11, 8)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(x)            ((x) << 8)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM_MASK          GENMASK(15, 12)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM(x)            ((x) << 12)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK             GENMASK(17, 16)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_RATE(x)               ((x) << 16)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK             GENMASK(19, 18)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_RATE(x)               ((x) << 18)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_EN_MASK                  BIT(20)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_EN(x)                    ((x) << 20)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_DIS                      (0 << 20)
>>>> +#define DISP0_PPS_SCALE_EN_VSC_EN_MASK                  BIT(21)
>>>> +#define DISP0_PPS_SCALE_EN_VSC_EN(x)                    ((x) << 21)
>>>> +#define DISP0_PPS_SCALE_EN_VSC_DIS                      (0 << 21)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_EN_MASK               BIT(22)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_EN(x)                 ((x) << 22)
>>>> +#define DISP0_PPS_SCALE_EN_PREVSC_DIS                   (0 << 22)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_EN_MASK               BIT(23)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_EN(x)                 ((x) << 23)
>>>> +#define DISP0_PPS_SCALE_EN_PREHSC_DIS                   (0 << 23)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK         GENMASK(27, 24)
>>>> +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(x)           ((x) << 24)
>>>> +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK         GENMASK(31, 28)
>>>> +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(x)           ((x) << 28)
>>>> +
>>>> +#define DISP0_PPS_VSC_START_PHASE_STEP                  0x8224
>>>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK   GENMASK(23, 0)
>>>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(x)     ((x) << 0)
>>>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK   GENMASK(27, 24)
>>>> +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(x)     ((x) << 24)
>>>> +
>>>> +#define DISP0_PPS_HSC_START_PHASE_STEP                  0x8230
>>>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK  GENMASK(23, 0)
>>>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(x)    ((x) << 0)
>>>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK  GENMASK(27, 24)
>>>> +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(x)    ((x) << 24)
>>>> +
>>>> +#define DISP0_PPS_444TO422                              0x823c
>>>> +#define DISP0_PPS_444TO422_EN_MASK                      BIT(0)
>>>> +#define DISP0_PPS_444TO422_EN(x)                        ((x) << 0)
>>>> +
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA                        0x8240
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_MASK     BIT(9)
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN       BIT(9)
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_DIS      (0 << 9)
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE_MASK             GENMASK(12, 10)
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE(x)               ((x) << 10)
>>>> +
>>>> +#define ISP_SCALE0_COEF_LUMA                            0x8244
>>>> +#define ISP_SCALE0_COEF_LUMA_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
>>>> +#define ISP_SCALE0_COEF_LUMA_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
>>>> +
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO                        0x8248
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_MASK     BIT(9)
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN       BIT(9)
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_DIS      (0 << 9)
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE_MASK             GENMASK(12, 10)
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE(x)               ((x) << 10)
>>>> +
>>>> +#define ISP_SCALE0_COEF_CHRO                            0x824c
>>>> +#define ISP_SCALE0_COEF_CHRO_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
>>>> +#define ISP_SCALE0_COEF_CHRO_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
>>>> +
>>>> +#define ISP_AF_CTRL                                     0xa044
>>>> +#define ISP_AF_CTRL_VERT_OFST_MASK                      GENMASK(15, 14)
>>>> +#define ISP_AF_CTRL_VERT_OFST(x)                        ((x) << 14)
>>>> +#define ISP_AF_CTRL_HORIZ_OFST_MASK                     GENMASK(17, 16)
>>>> +#define ISP_AF_CTRL_HORIZ_OFST(x)                       ((x) << 16)
>>>> +
>>>> +#define ISP_AF_HV_SIZE                                  0xa04c
>>>> +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE_MASK               GENMASK(15, 0)
>>>> +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE(x)                 ((x) << 0)
>>>> +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE_MASK               GENMASK(31, 16)
>>>> +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE(x)                 ((x) << 16)
>>>> +
>>>> +#define ISP_AF_HV_BLKNUM                                0xa050
>>>> +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(5, 0)
>>>> +#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
>>>> +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(21, 16)
>>>> +#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
>>>> +
>>>> +#define ISP_AF_EN_CTRL                                  0xa054
>>>> +#define ISP_AF_EN_CTRL_STAT_SEL_MASK                    BIT(21)
>>>> +#define ISP_AF_EN_CTRL_STAT_SEL_OLD                     (0 << 21)
>>>> +#define ISP_AF_EN_CTRL_STAT_SEL_NEW                     BIT(21)
>>>> +
>>>> +#define ISP_AF_IDX_ADDR                                 0xa1c0
>>>> +#define ISP_AF_IDX_DATA                                 0xa1c4
>>>> +#define ISP_AF_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
>>>> +#define ISP_AF_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
>>>> +
>>>> +#define ISP_AE_CTRL                                     0xa448
>>>> +#define ISP_AE_CTRL_INPUT_2LINE_MASK                    BIT(7)
>>>> +#define ISP_AE_CTRL_INPUT_2LINE_EN                      BIT(7)
>>>> +#define ISP_AE_CTRL_INPUT_2LINE_DIS                     (0 << 7)
>>>> +#define ISP_AE_CTRL_LUMA_MODE_MASK                      GENMASK(9, 8)
>>>> +#define ISP_AE_CTRL_LUMA_MODE_CUR                       (0 << 8)
>>>> +#define ISP_AE_CTRL_LUMA_MODE_MAX                       BIT(8)
>>>> +#define ISP_AE_CTRL_LUMA_MODE_FILTER                    (2 << 8)
>>>> +#define ISP_AE_CTRL_VERT_OFST_MASK                      GENMASK(25, 24)
>>>> +#define ISP_AE_CTRL_VERT_OFST(x)                        ((x) << 24)
>>>> +#define ISP_AE_CTRL_HORIZ_OFST_MASK                     GENMASK(27, 26)
>>>> +#define ISP_AE_CTRL_HORIZ_OFST(x)                       ((x) << 26)
>>>> +
>>>> +#define ISP_AE_HV_SIZE                                  0xa464
>>>> +#define ISP_AE_HV_SIZE_VERT_SIZE_MASK                   GENMASK(15, 0)
>>>> +#define ISP_AE_HV_SIZE_VERT_SIZE(x)                     ((x) << 0)
>>>> +#define ISP_AE_HV_SIZE_HORIZ_SIZE_MASK                  GENMASK(31, 16)
>>>> +#define ISP_AE_HV_SIZE_HORIZ_SIZE(x)                    ((x) << 16)
>>>> +
>>>> +#define ISP_AE_HV_BLKNUM                                0xa468
>>>> +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(6, 0)
>>>> +#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
>>>> +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(22, 16)
>>>> +#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
>>>> +
>>>> +#define ISP_AE_IDX_ADDR                                 0xa600
>>>> +#define ISP_AE_IDX_DATA                                 0xa604
>>>> +#define ISP_AE_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
>>>> +#define ISP_AE_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
>>>> +
>>>> +#define ISP_AE_BLK_WT_ADDR                              0xa608
>>>> +#define ISP_AE_BLK_WT_DATA                              0xa60c
>>>> +#define ISP_AE_BLK_WT_DATA_IDX_DATA(i, x)               (((x) & GENMASK(3, 0)) << ((i) * 4))
>>>> +
>>>> +#define ISP_AWB_CTRL                                    0xa834
>>>> +#define ISP_AWB_CTRL_VERT_OFST_MASK                     GENMASK(1, 0)
>>>> +#define ISP_AWB_CTRL_VERT_OFST(x)                       ((x) << 0)
>>>> +#define ISP_AWB_CTRL_HORIZ_OFST_MASK                    GENMASK(3, 2)
>>>> +#define ISP_AWB_CTRL_HORIZ_OFST(x)                      ((x) << 2)
>>>> +
>>>> +#define ISP_AWB_HV_SIZE                                 0xa83c
>>>> +#define ISP_AWB_HV_SIZE_VERT_SIZE_MASK                  GENMASK(15, 0)
>>>> +#define ISP_AWB_HV_SIZE_VERT_SIZE(x)                    ((x) << 0)
>>>> +#define ISP_AWB_HV_SIZE_HORIZ_SIZE_MASK                 GENMASK(31, 16)
>>>> +#define ISP_AWB_HV_SIZE_HORIZ_SIZE(x)                   ((x) << 16)
>>>> +
>>>> +#define ISP_AWB_HV_BLKNUM                               0xa840
>>>> +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK            GENMASK(5, 0)
>>>> +#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM(x)              ((x) << 0)
>>>> +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM_MASK           GENMASK(21, 16)
>>>> +#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(x)             ((x) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_RG                                 0xa848
>>>> +#define ISP_AWB_STAT_RG_MIN_VALUE_MASK                  GENMASK(11, 0)
>>>> +#define ISP_AWB_STAT_RG_MIN_VALUE(x)                    ((x) << 0)
>>>> +#define ISP_AWB_STAT_RG_MAX_VALUE_MASK                  GENMASK(27, 16)
>>>> +#define ISP_AWB_STAT_RG_MAX_VALUE(x)                    ((x) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_BG                                 0xa84c
>>>> +#define ISP_AWB_STAT_BG_MIN_VALUE_MASK                  GENMASK(11, 0)
>>>> +#define ISP_AWB_STAT_BG_MIN_VALUE(x)                    ((x) << 0)
>>>> +#define ISP_AWB_STAT_BG_MAX_VALUE_MASK                  GENMASK(27, 16)
>>>> +#define ISP_AWB_STAT_BG_MAX_VALUE(x)                    ((x) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_RG_HL                              0xa850
>>>> +#define ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
>>>> +#define ISP_AWB_STAT_RG_HL_LOW_VALUE(x)                 ((x) << 0)
>>>> +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
>>>> +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE(x)                ((x) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_BG_HL                              0xa854
>>>> +#define ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
>>>> +#define ISP_AWB_STAT_BG_HL_LOW_VALUE(x)                 ((x) << 0)
>>>> +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
>>>> +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE(x)                ((x) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_CTRL2                              0xa858
>>>> +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK              BIT(0)
>>>> +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL(x)                ((x) << 0)
>>>> +
>>>> +#define ISP_AWB_IDX_ADDR                                0xaa00
>>>> +#define ISP_AWB_IDX_DATA                                0xaa04
>>>> +#define ISP_AWB_IDX_DATA_VIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 0)
>>>> +#define ISP_AWB_IDX_DATA_HIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 16)
>>>> +
>>>> +#define ISP_AWB_BLK_WT_ADDR                             0xaa08
>>>> +#define ISP_AWB_BLK_WT_DATA                             0xaa0c
>>>> +#define ISP_AWB_BLK_WT_DATA_IDX_DATA(i, x)              (((x) & GENMASK(3, 0)) << ((i) * 4))
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL0                         0xc400
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK             GENMASK(28, 16)
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(x)               ((x) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL1                         0xc404
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS          BIT(27)
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS         (2 << 27)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL0                         0xc408
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK             GENMASK(28, 16)
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(x)               ((x) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL1                         0xc40c
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS         (2 << 27)
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_32BITS         (3 << 27)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH0_BADDR                         0xc440
>>>> +#define ISP_WRMIFX3_0_CH0_BASE_ADDR(x)                  ((x) >> 4)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH1_BADDR                         0xc444
>>>> +#define ISP_WRMIFX3_0_CH1_BASE_ADDR(x)                  ((x) >> 4)
>>>> +
>>>> +#define ISP_WRMIFX3_0_FMT_SIZE                          0xc464
>>>> +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE_MASK               GENMASK(15, 0)
>>>> +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE(x)                 ((x) << 0)
>>>> +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE_MASK               GENMASK(31, 16)
>>>> +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE(x)                 ((x) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL                          0xc468
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK           GENMASK(1, 0)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT           (0 << 0)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_10BIT          BIT(0)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_12BIT          (2 << 0)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT          (3 << 0)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK         BIT(2)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU           (0 << 2)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV           BIT(2)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK           GENMASK(5, 4)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1             (0 << 4)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2             BIT(4)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK            GENMASK(18, 16)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422          BIT(16)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420          (2 << 16)
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY          (3 << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                        0xc420
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK         GENMASK(28, 16)
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(x)           (((x) - 1) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                        0xc424
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK         GENMASK(28, 16)
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(x)           (((x) - 1) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                       0xc428
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK       GENMASK(28, 16)
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(x)         (((x) - 1) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                       0xc42c
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK       GENMASK(28, 16)
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(x)         (((x) - 1) << 16)
>>>> +
>>>> +#define VIU_DMAWR_BADDR0                                0xc840
>>>> +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
>>>> +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(x)          ((x) >> 4)
>>>> +
>>>> +#define VIU_DMAWR_BADDR1                                0xc844
>>>> +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK       GENMASK(27, 0)
>>>> +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(x)         ((x) >> 4)
>>>> +
>>>> +#define VIU_DMAWR_BADDR2                                0xc848
>>>> +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
>>>> +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(x)          ((x) >> 4)
>>>> +
>>>> +#define VIU_DMAWR_SIZE0                                 0xc854
>>>> +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK              GENMASK(15, 0)
>>>> +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE(x)                ((x) << 0)
>>>> +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK             GENMASK(31, 16)
>>>> +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE(x)               ((x) << 16)
>>>> +
>>>> +#define VIU_DMAWR_SIZE1                                 0xc858
>>>> +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK              GENMASK(15, 0)
>>>> +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE(x)                ((x) << 0)
>>>> +
>>>> +#endif
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
>>>> new file mode 100644
>>>> index 000000000000..b179eff4cfd6
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
>>>> @@ -0,0 +1,779 @@
>>>> +// 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"
>>>> +
>>>> +/* The normal parameters of pps module */
>>>> +static const int pps_lut_tap4_s11[C3_ISP_PPS_TAP4_S11_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 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 step_h_integer, step_v_integer;
>>>> +     int step_h_fraction, step_v_fraction;
>>>> +     int yuv444to422_en;
>>>> +
>>>> +     /* Calculate the integer part of horizonal scaler step */
>>>> +     step_h_integer = thsize / ohsize;
>>>> +
>>>> +     /* Calculate the vertical part of horizonal scaler step */
>>>> +     step_v_integer = 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.
>>>> +      */
>>>> +     step_h_fraction = ((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.
>>>> +      */
>>>> +     step_v_fraction = ((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_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK,
>>>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(step_v_fraction));
>>>> +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK,
>>>> +                        DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(step_v_integer));
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK,
>>>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(step_h_fraction));
>>>> +     c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK,
>>>> +                        DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(step_h_integer));
>>>> +}
>>>> +
>>>> +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_TAP4_S11_H_NUM; i++) {
>>>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>>> +                          ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>>>> +                          ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>>>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>>> +                          ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>>>> +                          ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[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_TAP4_S11_H_NUM; i++) {
>>>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>>> +                          ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>>>> +                          ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>>>> +             c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>>> +                          ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>>>> +                          ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[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_RESIZER_PAD_SINK);
>>>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +
>>>> +     ihsize = crop->width;
>>>> +     ivsize = crop->height;
>>>> +
>>>> +     hsc_en = (ihsize == cmps->width) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_EN;
>>>> +     vsc_en = (ivsize == cmps->height) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_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.
>>> minor nit: seems like you should wrap comments here too
>>
>> Will wrap comments.
>>
>>>> +      */
>>>> +     preh_en = (ihsize > cmps->width * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
>>>> +     prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
>>>> +
>>>> +     if (rsz->id == C3_ISP_RSZ_2) {
>>>> +             max_hsize = C3_ISP_MAX_WIDTH;
>>>> +             /*
>>>> +              * Set vertical tap number and
>>>> +              * the max hsize of pre-vertical scale.
>>>> +              */
>>>> +             prevsc_flt_num = 4;
>>>> +             pre_vscale_max_hsize = max_hsize / 2;
>>>> +     } else {
>>>> +             max_hsize = C3_ISP_DEFAULT_WIDTH;
>>>> +             preh_en  = (ihsize > max_hsize) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
>>>> +             /*
>>>> +              * Set vertical tap number and
>>>> +              * the max hsize of pre-vertical scale.
>>>> +              */
>>>> +             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;
>>>> +     struct v4l2_rect *sink_crop;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>>> +
>>>> +     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);
>>>> +
>>>> +     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_SCALER_OUT_HEIGHT_MASK,
>>>> +                        DISP0_TOP_OUT_SIZE_SCALER_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_SCALER_OUT_WIDTH_MASK,
>>>> +                        DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(src_fmt->width));
>>>> +
>>>> +     if (rsz->id == C3_ISP_RSZ_0) {
>>>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_DISP0_EN);
>>>> +     } else if (rsz->id == C3_ISP_RSZ_1) {
>>>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_DISP1_EN);
>>>> +     } else if (rsz->id == C3_ISP_RSZ_2) {
>>>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_DISP2_EN);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     if (rsz->id == C3_ISP_RSZ_0) {
>>>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_DISP0_DIS);
>>>> +     } else if (rsz->id == C3_ISP_RSZ_1) {
>>>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_DISP1_DIS);
>>>> +     } else if (rsz->id == C3_ISP_RSZ_2) {
>>>> +             c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
>>>> +                                ISP_TOP_PATH_EN_DISP2_DIS);
>>>> +     }
>>>> +
>>>> +     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 bool c3_isp_rsz_streams_ready(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     unsigned int n_links = 0;
>>>> +     struct media_link *link;
>>>> +
>>>> +     for_each_media_entity_data_link(&rsz->src_sd->entity, link) {
>>>> +             if (link->source->index == rsz->src_sd_pad &&
>>>> +                 link->flags == MEDIA_LNK_FL_ENABLED)
>>>> +                     n_links++;
>>>> +     }
>>>> +
>>>> +     return n_links == rsz->isp->pipe.start_count;
>>>> +}
>>>> +
>>>> +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);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     c3_isp_rsz_start(rsz, state);
>>>> +
>>>> +     if (!c3_isp_rsz_streams_ready(rsz))
>>>> +             return 0;
>>>> +
>>>> +     c3_isp_params_pre_cfg(rsz->isp);
>>>> +     c3_isp_stats_pre_cfg(rsz->isp);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    C3_ISP_RESIZER_PAD_SINK,
>>>> +                                                    &streams_mask);
>>> Apart from the usual comments about routing, reflowing comments and
>>> minor nits, this module looks ok to me. I admit I have not tried to
>>> fully understand the scaler programming part at the beginning, but I
>>> trust you know the HW better than me :)
>>
>> Will remove sink_streams.
>>
>>> The stats driver looks ok as well.
>>>
>>> I hope next version will be good to go, and I hope to be able to test
>>> this implementation soon using libcamera!
>>>
>>> Great work!
>>>
>>> Thanks
>>>     j
>>>
>> Thanks very much! :)
>>
> Thanks, let's try to land this!
>

OK, thanks.

>>>> +     ret = v4l2_subdev_enable_streams(rsz->src_sd,
>>>> +                                      rsz->src_sd_pad, sink_streams);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 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);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     c3_isp_rsz_stop(rsz);
>>>> +
>>>> +     if (rsz->isp->pipe.start_count != 1)
>>>> +             return 0;
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    C3_ISP_RESIZER_PAD_SINK,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_disable_streams(rsz->src_sd,
>>>> +                                       rsz->src_sd_pad, sink_streams);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>>>> +                               struct v4l2_subdev_state *state,
>>>> +                               struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     static const struct v4l2_mbus_framefmt format = {
>>>> +             .width = C3_ISP_DEFAULT_WIDTH,
>>>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>>>> +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
>>>> +             .field = V4L2_FIELD_NONE,
>>>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>>>> +     };
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_subdev_route routes;
>>>> +     struct v4l2_subdev_krouting routing;
>>>> +
>>>> +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>>>> +     routes.sink_stream = 0;
>>>> +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>>>> +     routes.source_stream = 0;
>>>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routing.num_routes = 1;
>>>> +     routing.routes = &routes;
>>>> +
>>>> +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                  struct v4l2_subdev_state *state,
>>>> +                                  struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +     if (code->index)
>>>> +             return -EINVAL;
>>>> +
>>>> +     code->code = MEDIA_BUS_FMT_YUV8_1X24;
>>>> +
>>>> +     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;
>>>> +
>>>> +     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_RESIZER_PAD_SOURCE);
>>>> +
>>>> +     sink_fmt->code = MEDIA_BUS_FMT_YUV8_1X24;
>>>> +     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_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;
>>>> +
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +
>>>> +     src_fmt->code = sink_fmt->code;
>>>> +     src_fmt->width = sink_cmps->width;
>>>> +     src_fmt->height = sink_cmps->height;
>>>> +
>>>> +     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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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 *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +     struct v4l2_rect *crop;
>>>> +     struct v4l2_rect *cmps;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>>> +
>>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_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_RESIZER_PAD_SINK);
>>>> +     cmps->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     cmps->left = 0;
>>>> +     cmps->top = 0;
>>>> +
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>>> +     *src_fmt = *sink_fmt;
>>>> +
>>>> +     return c3_isp_rsz_init_routing(sd, state);
>>>> +}
>>>> +
>>>> +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_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_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)
>>>> +{
>>>> +     u32 i;
>>>> +     int ret;
>>>> +
>>>> +     for (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_sd_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>>>> +
>>>> +             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)
>>>> +{
>>>> +     u32 i;
>>>> +
>>>> +     for (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);
>>>> +     };
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
>>>> new file mode 100644
>>>> index 000000000000..1c376c800176
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
>>>> @@ -0,0 +1,319 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#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-mc.h>
>>>> +#include <media/videobuf2-dma-contig.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>>>> +{
>>>> +     u32 awb_dma_size = sizeof(struct c3_isp_awb_stats);
>>>> +     u32 ae_dma_size = sizeof(struct c3_isp_ae_stats);
>>>> +     u32 awb_dma_addr = stats->buff->dma_addr;
>>>> +     u32 af_dma_addr;
>>>> +     u32 ae_dma_addr;
>>>> +
>>>> +     ae_dma_addr = awb_dma_addr + awb_dma_size;
>>>> +     af_dma_addr = ae_dma_addr + ae_dma_size;
>>>> +
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR0,
>>>> +                        VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK,
>>>> +                        VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(af_dma_addr));
>>>> +
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR1,
>>>> +                        VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK,
>>>> +                        VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(awb_dma_addr));
>>>> +
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR2,
>>>> +                        VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK,
>>>> +                        VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(ae_dma_addr));
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>>>> +{
>>>> +     stats->buff = list_first_entry_or_null(&stats->pending,
>>>> +                                            struct c3_isp_buffer, list);
>>>> +     if (stats->buff) {
>>>> +             c3_isp_stats_cfg_dmawr_addr(stats);
>>>> +             list_del(&stats->buff->list);
>>>> +     }
>>>> +}
>>>> +
>>>> +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +     u32 dma_size;
>>>> +
>>>> +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, ISP_AF_EN_CTRL_STAT_SEL_MASK,
>>>> +                        ISP_AF_EN_CTRL_STAT_SEL_NEW);
>>>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, ISP_AE_CTRL_LUMA_MODE_MASK,
>>>> +                        ISP_AE_CTRL_LUMA_MODE_FILTER);
>>>> +
>>>> +     /* The unit of dma_size is 16 bytes */
>>>> +     dma_size = sizeof(struct c3_isp_af_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK,
>>>> +                        VIU_DMAWR_SIZE0_AF_STATS_SIZE(dma_size));
>>>> +
>>>> +     dma_size = sizeof(struct c3_isp_awb_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK,
>>>> +                        VIU_DMAWR_SIZE0_AWB_STATS_SIZE(dma_size));
>>>> +
>>>> +     dma_size = sizeof(struct c3_isp_ae_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK,
>>>> +                        VIU_DMAWR_SIZE1_AE_STATS_SIZE(dma_size));
>>>> +
>>>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>>>> +
>>>> +     c3_isp_stats_cfg_buff(stats);
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_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_stats_enum_fmt(struct file *file, void *fh,
>>>> +                              struct v4l2_fmtdesc *f)
>>>> +{
>>>> +     struct c3_isp_stats *stats = video_drvdata(file);
>>>> +
>>>> +     if (f->index > 0 || f->type != stats->vb2_q.type)
>>>> +             return -EINVAL;
>>>> +
>>>> +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>>>> +                           struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_stats *stats = video_drvdata(file);
>>>> +
>>>> +     f->fmt.meta = stats->vfmt.fmt.meta;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>>>> +     .vidioc_querycap                = c3_isp_stats_querycap,
>>>> +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>>>> +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>>> +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>>> +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_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_stats_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_stats_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_stats_buffer))
>>>> +                     return -EINVAL;
>>>> +     } else {
>>>> +             *num_planes = 1;
>>>> +             sizes[0] = sizeof(struct c3_isp_stats_buffer);
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>>> +
>>>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>>>> +
>>>> +     list_add_tail(&buf->list, &stats->pending);
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
>>>> +
>>>> +     if (vb2_plane_size(vb, 0) < size) {
>>>> +             dev_err(stats->isp->dev,
>>>> +                     "User buffer too small (%ld < %u)\n",
>>>> +                     vb2_plane_size(vb, 0), size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     vb2_set_plane_payload(vb, 0, size);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_buffer, vb);
>>>> +
>>>> +     buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>>>> +{
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>>> +
>>>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>>>> +
>>>> +     if (stats->buff) {
>>>> +             vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>>>> +             stats->buff = NULL;
>>>> +     }
>>>> +
>>>> +     while (!list_empty(&stats->pending)) {
>>>> +             struct c3_isp_buffer *buff;
>>>> +
>>>> +             buff = list_first_entry(&stats->pending,
>>>> +                                     struct c3_isp_buffer, list);
>>>> +             list_del(&buff->list);
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>>>> +     }
>>>> +}
>>>> +
>>>> +static const struct vb2_ops isp_stats_vb2_ops = {
>>>> +     .queue_setup = c3_isp_stats_vb2_queue_setup,
>>>> +     .buf_queue = c3_isp_stats_vb2_buf_queue,
>>>> +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>>>> +     .buf_init = c3_isp_stats_vb2_buf_init,
>>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>>> +     .wait_finish = vb2_ops_wait_finish,
>>>> +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>>>> +};
>>>> +
>>>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +     struct video_device *vdev = &stats->vdev;
>>>> +     struct vb2_queue *vb2_q = &stats->vb2_q;
>>>> +     int ret;
>>>> +
>>>> +     memset(stats, 0, sizeof(*stats));
>>>> +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>>>> +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_buffer);
>>>> +     stats->isp = isp;
>>>> +     INIT_LIST_HEAD(&stats->pending);
>>>> +     spin_lock_init(&stats->buff_lock);
>>>> +
>>>> +     mutex_init(&stats->lock);
>>>> +
>>>> +     snprintf(vdev->name, sizeof(vdev->name), "c3-isp-stats");
>>>> +     vdev->fops = &isp_stats_v4l2_fops;
>>>> +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>>>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>>>> +     vdev->lock = &stats->lock;
>>>> +     vdev->minor = -1;
>>>> +     vdev->queue = vb2_q;
>>>> +     vdev->release = video_device_release_empty;
>>>> +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>>>> +     vdev->vfl_dir = VFL_DIR_RX;
>>>> +     video_set_drvdata(vdev, stats);
>>>> +
>>>> +     vb2_q->drv_priv = stats;
>>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>>> +     vb2_q->ops = &isp_stats_vb2_ops;
>>>> +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>>>> +     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_buffer);
>>>> +     vb2_q->dev = isp->dev;
>>>> +     vb2_q->lock = &stats->lock;
>>>> +     vb2_q->min_queued_buffers = 2;
>>>> +
>>>> +     ret = vb2_queue_init(vb2_q);
>>>> +     if (ret)
>>>> +             goto err_destroy;
>>>> +
>>>> +     stats->pad.flags = MEDIA_PAD_FL_SINK;
>>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>>>> +     if (ret)
>>>> +             goto err_queue_release;
>>>> +
>>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>> +     if (ret) {
>>>> +             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_destroy:
>>>> +     mutex_destroy(&stats->lock);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +
>>>> +     vb2_queue_release(&stats->vb2_q);
>>>> +     media_entity_cleanup(&stats->vdev.entity);
>>>> +     video_unregister_device(&stats->vdev);
>>>> +     mutex_destroy(&stats->lock);
>>>> +}
>>>> +
>>>> +void c3_isp_stats_isr(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +
>>>> +     guard(spinlock_irqsave)(&stats->buff_lock);
>>>> +
>>>> +     if (stats->buff) {
>>>> +             stats->buff->vb.sequence = stats->isp->frm_sequence;
>>>> +             stats->buff->vb.vb2_buf.timestamp = ktime_get();
>>>> +             stats->buff->vb.field = V4L2_FIELD_NONE;
>>>> +             vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>>> +     }
>>>> +
>>>> +     c3_isp_stats_cfg_buff(stats);
>>>> +}
>>>>
>>>> --
>>>> 2.47.0
>>>>
>>>>
>>>>
diff mbox series

Patch

diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig
index a504a1eb22e6..d355d3a9358d 100644
--- a/drivers/media/platform/amlogic/c3/Kconfig
+++ b/drivers/media/platform/amlogic/c3/Kconfig
@@ -1,4 +1,5 @@ 
 # SPDX-License-Identifier: GPL-2.0-only
 
+source "drivers/media/platform/amlogic/c3/isp/Kconfig"
 source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig"
 source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig"
diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile
index 770b2a2903ad..14f305a493d2 100644
--- a/drivers/media/platform/amlogic/c3/Makefile
+++ b/drivers/media/platform/amlogic/c3/Makefile
@@ -1,4 +1,5 @@ 
 # SPDX-License-Identifier: GPL-2.0-only
 
+obj-y += isp/
 obj-y += mipi-adapter/
 obj-y += mipi-csi2/
diff --git a/drivers/media/platform/amlogic/c3/isp/Kconfig b/drivers/media/platform/amlogic/c3/isp/Kconfig
new file mode 100644
index 000000000000..de68d19ca2d1
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/Kconfig
@@ -0,0 +1,17 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_C3_ISP
+	tristate "Amlogic C3 Image Signal Processor (ISP) driver"
+	depends on ARCH_MESON || COMPILE_TEST
+	depends on VIDEO_DEV
+	depends on OF
+	select MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	select VIDEOBUF2_DMA_CONTIG
+	help
+	  Video4Linux2 driver for Amlogic C3 ISP pipeline.
+	  The C3 ISP is used for processing raw images and
+	  outputing results to memory.
+
+	  To compile this driver as a module choose m here.
diff --git a/drivers/media/platform/amlogic/c3/isp/Makefile b/drivers/media/platform/amlogic/c3/isp/Makefile
new file mode 100644
index 000000000000..b1b064170b57
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/Makefile
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+c3-isp-objs := c3-isp-dev.o \
+	       c3-isp-params.o \
+	       c3-isp-stats.o \
+	       c3-isp-capture.o \
+	       c3-isp-core.o \
+	       c3-isp-resizer.o
+
+obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
new file mode 100644
index 000000000000..e1bbb3fa8da2
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c
@@ -0,0 +1,790 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/cleanup.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+static const struct c3_isp_capture_format cap_formats[] = {
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+		.fourcc = V4L2_PIX_FMT_GREY,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+		.fourcc = V4L2_PIX_FMT_NV12,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+		.fourcc = V4L2_PIX_FMT_NV21,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+		.fourcc = V4L2_PIX_FMT_NV16,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+		.fourcc = V4L2_PIX_FMT_NV61,
+	},
+};
+
+/* Hardware configuration */
+
+/* Set the address of wrmifx3(write memory interface) */
+static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
+	dma_addr_t dma_addr;
+
+	if (cap->buff)
+		dma_addr = cap->buff->dma_addr;
+	else
+		dma_addr = cap->dummy_buff.dma_addr;
+
+	c3_isp_write(cap->isp,
+		     C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
+		     ISP_WRMIFX3_0_CH0_BASE_ADDR(dma_addr));
+
+	if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
+	    pix->pixelformat == V4L2_PIX_FMT_NV21 ||
+	    pix->pixelformat == V4L2_PIX_FMT_NV16 ||
+	    pix->pixelformat == V4L2_PIX_FMT_NV61) {
+		u32 offset = ALIGN(pix->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES) * pix->height;
+
+		c3_isp_write(cap->isp,
+			     C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
+			     ISP_WRMIFX3_0_CH1_BASE_ADDR(dma_addr + offset));
+	}
+}
+
+static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+	u32 stride;
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
+
+	/* Grey has 1 plane*/
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1);
+
+	/* Set Y only as output format */
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY);
+
+	/* The unit of stride is 16 bytes */
+	stride = DIV_ROUND_UP(fmt->width * 1, 16);
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
+			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
+			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
+			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
+			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
+}
+
+static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, bool swap)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+	u32 stride;
+	u32 swap_uv;
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT);
+
+	swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
+			 ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
+
+	/* NV12 or NV21 has 2 planes*/
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
+
+	/* Set YUV420 as output format */
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420);
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
+			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
+			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width >> 1));
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
+			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
+			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
+
+	/* The unit of Y and UV strides is 16 bytes */
+	stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
+			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
+			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
+			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
+			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
+			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
+			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
+			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
+			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
+}
+
+static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, bool swap)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+	u32 stride;
+	u32 swap_uv;
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT);
+
+	swap_uv = swap ? ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV :
+			 ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU;
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, swap_uv);
+
+	/* NV16 or NV61 has 2 planes*/
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2);
+
+	/* Set YUV422 as output format */
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK,
+			   ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422);
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
+			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK,
+			   ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(fmt->width));
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
+			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK,
+			   ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(fmt->height >> 1));
+
+	/* The unit of Y stride is 16 bytes */
+	stride = DIV_ROUND_UP(fmt->width * 1, C3_ISP_DMA_SIZE_ALIGN_BYTES);
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
+			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK,
+			   ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride));
+
+	/* The unit of UV stride is 16 bytes */
+	stride = DIV_ROUND_UP(fmt->width * 2, C3_ISP_DMA_SIZE_ALIGN_BYTES);
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
+			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK,
+			   ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride));
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
+			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK,
+			   ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS);
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
+			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK,
+			   ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS);
+}
+
+static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+
+	if (fmt->pixelformat == V4L2_PIX_FMT_GREY)
+		c3_isp_cap_wrmifx3_grey(cap);
+	else if (fmt->pixelformat == V4L2_PIX_FMT_NV12)
+		c3_isp_cap_wrmifx3_yuv420(cap, true);
+	else if (fmt->pixelformat == V4L2_PIX_FMT_NV21)
+		c3_isp_cap_wrmifx3_yuv420(cap, false);
+	else if (fmt->pixelformat == V4L2_PIX_FMT_NV16)
+		c3_isp_cap_wrmifx3_yuv422(cap, true);
+	else if (fmt->pixelformat == V4L2_PIX_FMT_NV61)
+		c3_isp_cap_wrmifx3_yuv422(cap, false);
+
+	c3_isp_write(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
+		     ISP_WRMIFX3_0_FMT_SIZE_HSIZE(fmt->width) |
+		     ISP_WRMIFX3_0_FMT_SIZE_VSIZE(fmt->height));
+
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
+			   ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK,
+			   ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(fmt->width));
+	c3_isp_update_bits(cap->isp, C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
+			   ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK,
+			   ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(fmt->height));
+}
+
+static int c3_isp_cap_dummy_buff_create(struct c3_isp_capture *cap)
+{
+	struct c3_isp_dummy_buffer *dummy_buff = &cap->dummy_buff;
+
+	dummy_buff->size = cap->vfmt.fmt.pix.sizeimage;
+
+	/* The driver never access vaddr, no mapping is required */
+	dummy_buff->vaddr = dma_alloc_attrs(cap->isp->dev,
+					    dummy_buff->size,
+					    &dummy_buff->dma_addr,
+					    GFP_KERNEL,
+					    DMA_ATTR_NO_KERNEL_MAPPING);
+	if (!dummy_buff->vaddr)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void c3_isp_cap_dummy_buff_destroy(struct c3_isp_capture *cap)
+{
+	dma_free_attrs(cap->isp->dev,
+		       cap->dummy_buff.size, cap->dummy_buff.vaddr,
+		       cap->dummy_buff.dma_addr, DMA_ATTR_NO_KERNEL_MAPPING);
+}
+
+static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
+{
+	cap->buff = list_first_entry_or_null(&cap->pending,
+					     struct c3_isp_buffer, list);
+
+	c3_isp_cap_wrmifx3_buff(cap);
+
+	if (cap->buff)
+		list_del(&cap->buff->list);
+}
+
+static void c3_isp_cap_start(struct c3_isp_capture *cap)
+{
+	scoped_guard(spinlock_irqsave, &cap->buff_lock)
+		c3_isp_cap_cfg_buff(cap);
+
+	c3_isp_cap_wrmifx3_size(cap);
+
+	if (cap->id == C3_ISP_CAP_DEV_0) {
+		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
+				   ISP_TOP_PATH_EN_WRMIF0_EN);
+	} else if (cap->id == C3_ISP_CAP_DEV_1) {
+		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
+				   ISP_TOP_PATH_EN_WRMIF1_EN);
+	} else if (cap->id == C3_ISP_CAP_DEV_2) {
+		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
+				   ISP_TOP_PATH_EN_WRMIF2_EN);
+	}
+}
+
+static void c3_isp_cap_stop(struct c3_isp_capture *cap)
+{
+	if (cap->id == C3_ISP_CAP_DEV_0) {
+		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF0_EN_MASK,
+				   ISP_TOP_PATH_EN_WRMIF0_DIS);
+	} else if (cap->id == C3_ISP_CAP_DEV_1) {
+		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF1_EN_MASK,
+				   ISP_TOP_PATH_EN_WRMIF1_DIS);
+	} else if (cap->id == C3_ISP_CAP_DEV_2) {
+		c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_WRMIF2_EN_MASK,
+				   ISP_TOP_PATH_EN_WRMIF2_DIS);
+	}
+}
+
+static void c3_isp_cap_done(struct c3_isp_capture *cap)
+{
+	struct c3_isp_buffer *buff = cap->buff;
+
+	guard(spinlock_irqsave)(&cap->buff_lock);
+
+	if (buff) {
+		buff->vb.sequence = cap->isp->frm_sequence;
+		buff->vb.vb2_buf.timestamp = ktime_get();
+		buff->vb.field = V4L2_FIELD_NONE;
+		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+
+	c3_isp_cap_cfg_buff(cap);
+}
+
+/* V4L2 video operations */
+
+static const struct c3_isp_capture_format
+*c3_cap_find_fmt(u32 fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
+		if (cap_formats[i].fourcc == fourcc)
+			return &cap_formats[i];
+	}
+
+	return NULL;
+}
+
+static void c3_cap_try_fmt(struct c3_isp_capture *cap,
+			   struct v4l2_pix_format *pix)
+{
+	const struct c3_isp_capture_format *fmt;
+	const struct v4l2_format_info *info;
+
+	fmt = c3_cap_find_fmt(pix->pixelformat);
+	if (!fmt)
+		fmt = &cap_formats[0];
+
+	pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
+	pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
+	pix->pixelformat = fmt->fourcc;
+	pix->field = V4L2_FIELD_NONE;
+	pix->colorspace = V4L2_COLORSPACE_SRGB;
+	pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+	info = v4l2_format_info(fmt->fourcc);
+	pix->bytesperline = pix->width * info->bpp[0] / info->bpp_div[0];
+
+	/* ISP hardware requires 16 bytes alignment */
+	pix->bytesperline = ALIGN(pix->bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
+
+	pix->sizeimage = 0;
+	for (unsigned int i = 0; i < info->comp_planes; i++) {
+		unsigned int hdiv = (i == 0) ? 1 : info->hdiv;
+		unsigned int vdiv = (i == 0) ? 1 : info->vdiv;
+		unsigned int bytesperline;
+
+		bytesperline = info->bpp[i] * DIV_ROUND_UP(pix->width, hdiv) / info->bpp_div[i];
+
+		/* ISP hardware requires 16 bytes alignment */
+		bytesperline = ALIGN(bytesperline, C3_ISP_DMA_SIZE_ALIGN_BYTES);
+		pix->sizeimage += bytesperline * DIV_ROUND_UP(pix->height, vdiv);
+	}
+}
+
+static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
+				      enum vb2_buffer_state state)
+{
+	struct c3_isp_buffer *buff;
+
+	guard(spinlock_irqsave)(&cap->buff_lock);
+
+	if (cap->buff) {
+		vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
+		cap->buff = NULL;
+	}
+
+	while (!list_empty(&cap->pending)) {
+		buff = list_first_entry(&cap->pending,
+					struct c3_isp_buffer, list);
+		list_del(&buff->list);
+		vb2_buffer_done(&buff->vb.vb2_buf, state);
+	}
+}
+
+static int c3_isp_cap_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_cap_enum_fmt(struct file *file, void *fh,
+			       struct v4l2_fmtdesc *f)
+{
+	const struct c3_isp_capture_format *fmt;
+	unsigned int index = 0;
+	unsigned int i;
+
+	if (!f->mbus_code) {
+		if (f->index >= ARRAY_SIZE(cap_formats))
+			return -EINVAL;
+
+		fmt = &cap_formats[f->index];
+		f->pixelformat = fmt->fourcc;
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
+		fmt = &cap_formats[i];
+		if (f->mbus_code != fmt->mbus_code)
+			continue;
+
+		if (index++ == f->index) {
+			f->pixelformat = cap_formats[i].fourcc;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int c3_isp_cap_g_fmt(struct file *file, void *fh,
+			    struct v4l2_format *f)
+{
+	struct c3_isp_capture *cap = video_drvdata(file);
+
+	f->fmt.pix = cap->vfmt.fmt.pix;
+
+	return 0;
+}
+
+static int c3_isp_cap_s_fmt(struct file *file, void *fh,
+			    struct v4l2_format *f)
+{
+	struct c3_isp_capture *cap = video_drvdata(file);
+
+	c3_cap_try_fmt(cap, &f->fmt.pix);
+	cap->vfmt = *f;
+
+	return 0;
+}
+
+static int c3_isp_cap_try_fmt(struct file *file, void *fh,
+			      struct v4l2_format *f)
+{
+	struct c3_isp_capture *cap = video_drvdata(file);
+
+	c3_cap_try_fmt(cap, &f->fmt.pix);
+
+	return 0;
+}
+
+static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
+				   struct v4l2_frmsizeenum *fsize)
+{
+	const struct c3_isp_capture_format *fmt;
+
+	if (fsize->index)
+		return -EINVAL;
+
+	fmt = c3_cap_find_fmt(fsize->pixel_format);
+	if (!fmt)
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
+	fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
+	fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
+	fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
+	fsize->stepwise.step_width = 2;
+	fsize->stepwise.step_height = 2;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
+	.vidioc_querycap		= c3_isp_cap_querycap,
+	.vidioc_enum_fmt_vid_cap	= c3_isp_cap_enum_fmt,
+	.vidioc_g_fmt_vid_cap		= c3_isp_cap_g_fmt,
+	.vidioc_s_fmt_vid_cap		= c3_isp_cap_s_fmt,
+	.vidioc_try_fmt_vid_cap		= c3_isp_cap_try_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,
+	.vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
+	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations isp_cap_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_cap_link_validate(struct media_link *link)
+{
+	struct video_device *vdev =
+		media_entity_to_video_device(link->sink->entity);
+	struct v4l2_subdev *sd =
+		media_entity_to_v4l2_subdev(link->source->entity);
+	struct c3_isp_capture *cap = video_get_drvdata(vdev);
+	struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
+	struct v4l2_subdev_format src_fmt = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.pad = link->source->index,
+	};
+	const struct c3_isp_capture_format *cap_fmt =
+				c3_cap_find_fmt(pix_fmt->pixelformat);
+	int ret;
+
+	ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
+	if (ret)
+		return ret;
+
+	if (src_fmt.format.width != pix_fmt->width ||
+	    src_fmt.format.height != pix_fmt->height ||
+	    src_fmt.format.code != cap_fmt->mbus_code) {
+		dev_err(cap->isp->dev,
+			"link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
+			link->source->entity->name, link->source->index,
+			link->sink->entity->name, link->sink->index,
+			src_fmt.format.code, src_fmt.format.width,
+			src_fmt.format.height, cap_fmt->mbus_code,
+			pix_fmt->width, pix_fmt->height);
+
+		return -EPIPE;
+	};
+
+	return 0;
+}
+
+static const struct media_entity_operations isp_cap_entity_ops = {
+	.link_validate = c3_isp_cap_link_validate,
+};
+
+static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
+				  unsigned int *num_buffers,
+				  unsigned int *num_planes,
+				  unsigned int sizes[],
+				  struct device *alloc_devs[])
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
+	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
+
+	if (*num_planes) {
+		if (*num_planes != 1)
+			return -EINVAL;
+
+		if (sizes[0] < pix->sizeimage)
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = pix->sizeimage;
+	}
+
+	return 0;
+}
+
+static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_buffer, vb);
+	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
+
+	guard(spinlock_irqsave)(&cap->buff_lock);
+
+	list_add_tail(&buf->list, &cap->pending);
+}
+
+static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned int size = cap->vfmt.fmt.pix.sizeimage;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(cap->isp->dev,
+			"User buffer too small (%ld < %u)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_buffer *buf =
+		container_of(v4l2_buf, struct c3_isp_buffer, vb);
+
+	buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+	return 0;
+}
+
+static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
+				      unsigned int count)
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
+	int ret;
+
+	ret = pm_runtime_resume_and_get(cap->isp->dev);
+	if (ret)
+		return ret;
+
+	ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
+	if (ret) {
+		dev_err(cap->isp->dev,
+			"Failed to start cap%u pipeline: %d\n", cap->id, ret);
+		goto err_pm_put;
+	}
+
+	ret = c3_isp_cap_dummy_buff_create(cap);
+	if (ret)
+		goto err_pipeline_stop;
+
+	c3_isp_cap_start(cap);
+
+	ret = v4l2_subdev_enable_streams(&cap->rsz->sd,
+					 C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
+	if (ret)
+		goto err_dummy_destroy;
+
+	return 0;
+
+err_dummy_destroy:
+	c3_isp_cap_dummy_buff_destroy(cap);
+err_pipeline_stop:
+	video_device_pipeline_stop(&cap->vdev);
+err_pm_put:
+	pm_runtime_put(cap->isp->dev);
+	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
+
+	c3_isp_cap_stop(cap);
+	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
+
+	v4l2_subdev_disable_streams(&cap->rsz->sd,
+				    C3_ISP_RESIZER_PAD_SOURCE, BIT(0));
+
+	c3_isp_cap_dummy_buff_destroy(cap);
+
+	video_device_pipeline_stop(&cap->vdev);
+	pm_runtime_put(cap->isp->dev);
+}
+
+static const struct vb2_ops isp_video_vb2_ops = {
+	.queue_setup = c3_isp_vb2_queue_setup,
+	.buf_queue = c3_isp_vb2_buf_queue,
+	.buf_prepare = c3_isp_vb2_buf_prepare,
+	.buf_init = c3_isp_vb2_buf_init,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = c3_isp_vb2_start_streaming,
+	.stop_streaming = c3_isp_vb2_stop_streaming,
+};
+
+static int c3_isp_register_capture(struct c3_isp_capture *cap)
+{
+	struct video_device *vdev = &cap->vdev;
+	struct vb2_queue *vb2_q = &cap->vb2_q;
+	int ret;
+
+	snprintf(vdev->name, sizeof(vdev->name), "c3-isp-cap%u", cap->id);
+	vdev->fops = &isp_cap_v4l2_fops;
+	vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
+	vdev->v4l2_dev = &cap->isp->v4l2_dev;
+	vdev->entity.ops = &isp_cap_entity_ops;
+	vdev->lock = &cap->lock;
+	vdev->minor = -1;
+	vdev->queue = vb2_q;
+	vdev->release = video_device_release_empty;
+	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_RX;
+	video_set_drvdata(vdev, cap);
+
+	vb2_q->drv_priv = cap;
+	vb2_q->mem_ops = &vb2_dma_contig_memops;
+	vb2_q->ops = &isp_video_vb2_ops;
+	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	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_buffer);
+	vb2_q->dev = cap->isp->dev;
+	vb2_q->lock = &cap->lock;
+	vb2_q->min_queued_buffers = 2;
+
+	ret = vb2_queue_init(vb2_q);
+	if (ret < 0)
+		goto err_destroy;
+
+	cap->pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
+	if (ret < 0)
+		goto err_queue_release;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret < 0) {
+		dev_err(cap->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_destroy:
+	mutex_destroy(&cap->lock);
+	return ret;
+}
+
+int c3_isp_captures_register(struct c3_isp_device *isp)
+{
+	int ret;
+	unsigned int i;
+	struct c3_isp_capture *cap;
+
+	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
+		cap = &isp->caps[i];
+		memset(cap, 0, sizeof(*cap));
+
+		cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
+		cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
+		cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
+		cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
+		cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+
+		c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
+
+		cap->id = i;
+		cap->rsz = &isp->resizers[i];
+		cap->isp = isp;
+		INIT_LIST_HEAD(&cap->pending);
+		spin_lock_init(&cap->buff_lock);
+		mutex_init(&cap->lock);
+
+		ret = c3_isp_register_capture(cap);
+		if (ret) {
+			cap->isp = NULL;
+			mutex_destroy(&cap->lock);
+			c3_isp_captures_unregister(isp);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+void c3_isp_captures_unregister(struct c3_isp_device *isp)
+{
+	unsigned int i;
+	struct c3_isp_capture *cap;
+
+	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
+		cap = &isp->caps[i];
+
+		if (!cap->isp)
+			continue;
+		vb2_queue_release(&cap->vb2_q);
+		media_entity_cleanup(&cap->vdev.entity);
+		video_unregister_device(&cap->vdev);
+		mutex_destroy(&cap->lock);
+	}
+}
+
+void c3_isp_captures_isr(struct c3_isp_device *isp)
+{
+	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
+	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
+	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
+}
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
new file mode 100644
index 000000000000..d2c0b9aacf36
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h
@@ -0,0 +1,352 @@ 
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __C3_ISP_COMMON_H__
+#define __C3_ISP_COMMON_H__
+
+#include <linux/clk.h>
+
+#include <media/media-device.h>
+#include <media/videobuf2-core.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-v4l2.h>
+
+#define C3_ISP_DRIVER_NAME            "c3-isp"
+#define C3_ISP_CLOCK_NUM_MAX          3
+
+#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
+#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUV8_1X24
+#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUV8_1X24
+#define C3_ISP_DEFAULT_WIDTH          1920
+#define C3_ISP_DEFAULT_HEIGHT         1080
+#define C3_ISP_MAX_WIDTH              2888
+#define C3_ISP_MAX_HEIGHT             2240
+#define C3_ISP_MIN_WIDTH              160
+#define C3_ISP_MIN_HEIGHT             120
+
+#define C3_ISP_DMA_SIZE_ALIGN_BYTES   16
+#define C3_ISP_DISP_REG(base, id)     ((base) + (id) * 0x400)
+#define C3_ISP_WRMIFX3_REG(base, id)  ((base) + (id) * 0x100)
+#define C3_ISP_PPS_TAP4_S11_H_NUM     33
+#define C3_ISP_PPS_LUT_CTYPE_0        0
+#define C3_ISP_PPS_LUT_CTYPE_2        2
+#define C3_ISP_SCALE_EN               1
+#define C3_ISP_SCALE_DIS              0
+
+#define C3_ISP_PHASE_OFFSET_0         0
+#define C3_ISP_PHASE_OFFSET_1         1
+#define C3_ISP_PHASE_OFFSET_NONE      0xff
+
+enum c3_isp_core_pads {
+	C3_ISP_CORE_PAD_SINK_VIDEO,
+	C3_ISP_CORE_PAD_SINK_PARAMS,
+	C3_ISP_CORE_PAD_SOURCE_STATS,
+	C3_ISP_CORE_PAD_SOURCE_VIDEO,
+	C3_ISP_CORE_PAD_MAX
+};
+
+enum c3_isp_resizer_ids {
+	C3_ISP_RSZ_0,
+	C3_ISP_RSZ_1,
+	C3_ISP_RSZ_2,
+	C3_ISP_NUM_RSZ
+};
+
+enum c3_isp_resizer_pads {
+	C3_ISP_RESIZER_PAD_SINK,
+	C3_ISP_RESIZER_PAD_SOURCE,
+	C3_ISP_RESIZER_PAD_MAX
+};
+
+enum c3_isp_cap_devs {
+	C3_ISP_CAP_DEV_0,
+	C3_ISP_CAP_DEV_1,
+	C3_ISP_CAP_DEV_2,
+	C3_ISP_NUM_CAP_DEVS
+};
+
+/*
+ * 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;
+};
+
+/*
+ * struct c3_isp_mbus_format_info - MBUS format information
+ *
+ * @mbus_code: the mbus code
+ * @pads: bitmask detailing valid pads for this mbus_code
+ * @xofst: horizontal phase offset of hardware
+ * @yofst: vertical phase offset of hardware
+ */
+struct c3_isp_mbus_format_info {
+	u32 mbus_code;
+	u32 pads;
+	u8 xofst;
+	u8 yofst;
+};
+
+/*
+ * struct c3_isp_capture_format - The image format of capture device
+ *
+ * @mbus_code: the mbus code
+ * @fourcc: pixel format
+ */
+struct c3_isp_capture_format {
+	u32 mbus_code;
+	u32 fourcc;
+};
+
+/*
+ * struct c3_isp_buffer - A container of vb2 buffer used by the video devices:
+ *                        capture and stats video devices
+ *
+ * @vb: vb2 buffer
+ * @dma_addr: buffer physical address
+ * @list: entry of the buffer in the queue
+ */
+struct c3_isp_buffer {
+	struct vb2_v4l2_buffer vb;
+	dma_addr_t dma_addr;
+	struct list_head list;
+};
+
+/*
+ * struct c3_isp_params_buffer - A container of vb2 buffer used by the
+ *                               params video device
+ *
+ * @vb: vb2 buffer
+ * @cfg: scratch buffer used for caching the ISP configuration parameters
+ * @list: entry of the buffer in the queue
+ */
+struct c3_isp_params_buffer {
+	struct vb2_v4l2_buffer vb;
+	void *cfg;
+	struct list_head list;
+};
+
+/*
+ * struct c3_isp_dummy_buffer - A buffer to write the next frame to in case
+ *				there are no vb2 buffers available.
+ *
+ * @vaddr:	return value of call to dma_alloc_attrs
+ * @dma_addr:	dma address of the buffer
+ * @size:	size of the buffer
+ */
+struct c3_isp_dummy_buffer {
+	void *vaddr;
+	dma_addr_t dma_addr;
+	u32 size;
+};
+
+/*
+ * struct c3_isp_core - ISP core subdev
+ *
+ * @sd: ISP sub-device
+ * @pads: ISP sub-device pads
+ * @src_sd: source sub-device
+ * @src_sd_pad: source sub-device pad
+ * @isp: pointer to c3_isp_device
+ */
+struct c3_isp_core {
+	struct v4l2_subdev sd;
+	struct media_pad pads[C3_ISP_CORE_PAD_MAX];
+	struct v4l2_subdev *src_sd;
+	u16 src_sd_pad;
+	struct c3_isp_device *isp;
+};
+
+/*
+ * struct c3_isp_resizer - ISP resizer subdev
+ *
+ * @id: resizer id
+ * @sd: resizer sub-device
+ * @pads: resizer sub-device pads
+ * @src_sd: source sub-device
+ * @src_sd_pad: source sub-device pad
+ * @isp: pointer to c3_isp_device
+ */
+struct c3_isp_resizer {
+	enum c3_isp_resizer_ids id;
+	struct v4l2_subdev sd;
+	struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
+	struct v4l2_subdev *src_sd;
+	u32 src_sd_pad;
+	struct c3_isp_device *isp;
+};
+
+/*
+ * struct c3_isp_stats - ISP statistics device
+ *
+ * @vb2_q: vb2 buffer queue
+ * @vdev: video node
+ * @vfmt: v4l2_format of the metadata format
+ * @pad: media pad
+ * @lock: protects vb2_q, vdev
+ * @isp: pointer to c3_isp_device
+ * @buff: in use buffer
+ * @buff_lock: protects stats buffer
+ * @pending: stats buffer list head
+ */
+struct c3_isp_stats {
+	struct vb2_queue vb2_q;
+	struct video_device vdev;
+	struct v4l2_format vfmt;
+	struct media_pad pad;
+
+	struct mutex lock; /* Protects vb2_q, vdev */
+	struct c3_isp_device *isp;
+
+	struct c3_isp_buffer *buff;
+	spinlock_t buff_lock; /* Protects stats buffer */
+	struct list_head pending;
+};
+
+/*
+ * struct c3_isp_params - ISP parameters device
+ *
+ * @vb2_q: vb2 buffer queue
+ * @vdev: video node
+ * @vfmt: v4l2_format of the metadata format
+ * @pad: media pad
+ * @lock: protects vb2_q, vdev
+ * @isp: pointer to c3_isp_device
+ * @buff: in use buffer
+ * @buff_lock: protects stats buffer
+ * @pending: stats buffer list head
+ */
+struct c3_isp_params {
+	struct vb2_queue vb2_q;
+	struct video_device vdev;
+	struct v4l2_format vfmt;
+	struct media_pad pad;
+
+	struct mutex lock; /* Protects vb2_q, vdev */
+	struct c3_isp_device *isp;
+
+	struct c3_isp_params_buffer *buff;
+	spinlock_t buff_lock; /* Protects params buffer */
+	struct list_head pending;
+};
+
+/*
+ * struct c3_isp_capture - ISP capture device
+ *
+ * @id: capture device ID
+ * @vb2_q: vb2 buffer queue
+ * @vdev: video node
+ * @vfmt: v4l2_format of the capture format
+ * @pad: media pad
+ * @lock: protects vb2_q, vdev
+ * @isp: pointer to c3_isp_device
+ * @rsz: pointer to c3_isp_resizer
+ * @buff: in use buffer
+ * @buff_lock: protects capture buffer
+ * @pending: capture buffer list head
+ */
+struct c3_isp_capture {
+	enum c3_isp_cap_devs id;
+	struct vb2_queue vb2_q;
+	struct video_device vdev;
+	struct v4l2_format vfmt;
+	struct media_pad pad;
+
+	struct mutex lock; /* Protects vb2_q, vdev */
+	struct c3_isp_device *isp;
+	struct c3_isp_resizer *rsz;
+
+	struct c3_isp_dummy_buffer dummy_buff;
+	struct c3_isp_buffer *buff;
+	spinlock_t buff_lock; /* Protects stream buffer */
+	struct list_head pending;
+};
+
+/**
+ * struct c3_isp_info - ISP information
+ *
+ * @clocks: array of ISP clock names
+ * @clock_rates: array of ISP clock rate
+ * @clock_num: actual clock number
+ */
+struct c3_isp_info {
+	char *clocks[C3_ISP_CLOCK_NUM_MAX];
+	u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
+	u32 clock_num;
+};
+
+/**
+ * struct c3_isp_device - ISP platform device
+ *
+ * @dev: pointer to the struct device
+ * @base: base register address
+ * @clks: array of clocks
+ * @notifier: notifier to register on the v4l2-async API
+ * @v4l2_dev: v4l2_device variable
+ * @media_dev: media device variable
+ * @pipe: media pipeline
+ * @core: ISP core subdev
+ * @resizer: ISP resizer subdev
+ * @stats: ISP stats device
+ * @params: ISP params device
+ * @caps: array of ISP capture device
+ * @frm_sequence: used to record frame id
+ * @info: version-specific ISP information
+ */
+struct c3_isp_device {
+	struct device *dev;
+	void __iomem *base;
+	struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
+
+	struct v4l2_async_notifier notifier;
+	struct v4l2_device v4l2_dev;
+	struct media_device media_dev;
+	struct media_pipeline pipe;
+
+	struct c3_isp_core core;
+	struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
+	struct c3_isp_stats stats;
+	struct c3_isp_params params;
+	struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
+
+	u32 frm_sequence;
+	const struct c3_isp_info *info;
+};
+
+u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
+void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
+void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
+
+void c3_isp_core_queue_eof(struct c3_isp_device *isp);
+int c3_isp_core_register(struct c3_isp_device *isp);
+void c3_isp_core_unregister(struct c3_isp_device *isp);
+int c3_isp_resizers_register(struct c3_isp_device *isp);
+void c3_isp_resizers_unregister(struct c3_isp_device *isp);
+int c3_isp_captures_register(struct c3_isp_device *isp);
+void c3_isp_captures_unregister(struct c3_isp_device *isp);
+void c3_isp_captures_isr(struct c3_isp_device *isp);
+void c3_isp_stats_pre_cfg(struct c3_isp_device *isp);
+int c3_isp_stats_register(struct c3_isp_device *isp);
+void c3_isp_stats_unregister(struct c3_isp_device *isp);
+void c3_isp_stats_isr(struct c3_isp_device *isp);
+void c3_isp_params_pre_cfg(struct c3_isp_device *isp);
+int c3_isp_params_register(struct c3_isp_device *isp);
+void c3_isp_params_unregister(struct c3_isp_device *isp);
+void c3_isp_params_isr(struct c3_isp_device *isp);
+
+#endif
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
new file mode 100644
index 000000000000..bf83e73dd744
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c
@@ -0,0 +1,553 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/media/amlogic/c3-isp-config.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-event.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+#define C3_ISP_CORE_SUBDEV_NAME        "c3-isp-core"
+
+static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
+	/* RAW formats */
+	{
+		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SBGGR12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGBRG12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGRBG12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SRGGB12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	},
+	/* YUV formats */
+	{
+		.mbus_code	= MEDIA_BUS_FMT_YUV8_1X24,
+		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	},
+};
+
+static const struct c3_isp_mbus_format_info
+*core_find_format_by_code(u32 code, u32 pad)
+{
+	for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
+		const struct c3_isp_mbus_format_info *info =
+			&c3_isp_core_mbus_formats[i];
+
+		if (info->mbus_code == code && info->pads & BIT(pad))
+			return info;
+	}
+
+	return NULL;
+}
+
+static const struct c3_isp_mbus_format_info
+*core_find_format_by_index(u32 index, u32 pad)
+{
+	for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
+		const struct c3_isp_mbus_format_info *info =
+			&c3_isp_core_mbus_formats[i];
+
+		if (!(info->pads & BIT(pad)))
+			continue;
+
+		if (!index)
+			return info;
+
+		index--;
+	}
+
+	return NULL;
+}
+
+static void c3_isp_core_enable(struct c3_isp_device *isp)
+{
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
+			   ISP_TOP_IRQ_EN_FRAME_END_EN);
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
+			   ISP_TOP_IRQ_EN_3A_DMA_ERR_EN);
+
+	/* Enable image data to ISP core */
+	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
+			   ISP_TOP_PATH_SEL_CORE_MIPI_CORE);
+}
+
+static void c3_isp_core_disable(struct c3_isp_device *isp)
+{
+	/* Disable image data to ISP core */
+	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK,
+			   ISP_TOP_PATH_SEL_CORE_CORE_DIS);
+
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRAME_END_MASK,
+			   ISP_TOP_IRQ_EN_FRAME_END_DIS);
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK,
+			   ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS);
+}
+
+/* Set the phase offset of blc, wb and lns */
+static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
+				  u8 xofst, u8 yofst)
+{
+	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK,
+			   ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK,
+			   ISP_LSWB_BLC_PHSOFST_VERT_OFST(yofst));
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK,
+			   ISP_LSWB_WB_PHSOFST_HORIZ_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK,
+			   ISP_LSWB_WB_PHSOFST_VERT_OFST(yofst));
+
+	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK,
+			   ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK,
+			   ISP_LSWB_LNS_PHSOFST_VERT_OFST(yofst));
+}
+
+/* Set the phase offset of af, ae and awb */
+static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
+				u8 xofst, u8 yofst)
+{
+	c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_HORIZ_OFST_MASK,
+			   ISP_AF_CTRL_HORIZ_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_VERT_OFST_MASK,
+			   ISP_AF_CTRL_VERT_OFST(yofst));
+
+	c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_HORIZ_OFST_MASK,
+			   ISP_AE_CTRL_HORIZ_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_VERT_OFST_MASK,
+			   ISP_AE_CTRL_VERT_OFST(yofst));
+
+	c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_HORIZ_OFST_MASK,
+			   ISP_AWB_CTRL_HORIZ_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_VERT_OFST_MASK,
+			   ISP_AWB_CTRL_VERT_OFST(yofst));
+}
+
+/* Set the phase offset of demosaic */
+static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
+				 u8 xofst, u8 yofst)
+{
+	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK,
+			   ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(xofst));
+	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK,
+			   ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(yofst));
+}
+
+static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt;
+	const struct c3_isp_mbus_format_info *isp_fmt;
+
+	fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
+	isp_fmt = core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
+
+	c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
+		     ISP_TOP_INPUT_SIZE_HORIZ_SIZE(fmt->width) |
+		     ISP_TOP_INPUT_SIZE_VERT_SIZE(fmt->height));
+	c3_isp_write(isp, ISP_TOP_FRM_SIZE,
+		     ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(fmt->width) |
+		     ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(fmt->height));
+
+	c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE,
+			   ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK,
+			   ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(fmt->width));
+
+	c3_isp_write(isp, ISP_AF_HV_SIZE,
+		     ISP_AF_HV_SIZE_GLB_WIN_XSIZE(fmt->width) |
+		     ISP_AF_HV_SIZE_GLB_WIN_YSIZE(fmt->height));
+	c3_isp_write(isp, ISP_AE_HV_SIZE,
+		     ISP_AE_HV_SIZE_HORIZ_SIZE(fmt->width) |
+		     ISP_AE_HV_SIZE_VERT_SIZE(fmt->height));
+	c3_isp_write(isp, ISP_AWB_HV_SIZE,
+		     ISP_AWB_HV_SIZE_HORIZ_SIZE(fmt->width) |
+		     ISP_AWB_HV_SIZE_VERT_SIZE(fmt->height));
+
+	c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
+	c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
+	c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
+}
+
+static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state,
+				      u32 pad, u64 streams_mask)
+{
+	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	core->isp->frm_sequence = 0;
+	c3_isp_core_cfg_format(core->isp, state);
+	c3_isp_core_enable(core->isp);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       C3_ISP_CORE_PAD_SINK_VIDEO,
+						       &streams_mask);
+	ret = v4l2_subdev_enable_streams(core->src_sd,
+					 core->src_sd_pad, sink_streams);
+	if (ret) {
+		c3_isp_core_disable(core->isp);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       u32 pad, u64 streams_mask)
+{
+	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       C3_ISP_CORE_PAD_SINK_VIDEO,
+						       &streams_mask);
+	ret = v4l2_subdev_disable_streams(core->src_sd,
+					  core->src_sd_pad, sink_streams);
+	if (ret)
+		return ret;
+
+	c3_isp_core_disable(core->isp);
+
+	return 0;
+}
+
+static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = C3_ISP_DEFAULT_WIDTH,
+		.height = C3_ISP_DEFAULT_HEIGHT,
+		.code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route route;
+	struct v4l2_subdev_krouting routing;
+
+	route.sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
+	route.sink_stream = 0;
+	route.source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
+	route.source_stream = 0;
+	route.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routing.num_routes = 1;
+	routing.routes = &route;
+
+	return c3_isp_core_cfg_routing(sd, state, &routing);
+}
+
+static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state,
+				      struct v4l2_subdev_mbus_code_enum *code)
+{
+	const struct c3_isp_mbus_format_info *info;
+	int ret = 0;
+
+	switch (code->pad) {
+	case C3_ISP_CORE_PAD_SINK_VIDEO:
+	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
+		info = core_find_format_by_index(code->index, code->pad);
+		if (!info)
+			ret = -EINVAL;
+		else
+			code->code = info->mbus_code;
+
+		break;
+	case C3_ISP_CORE_PAD_SINK_PARAMS:
+	case C3_ISP_CORE_PAD_SOURCE_STATS:
+		if (code->index)
+			ret = -EINVAL;
+		else
+			code->code = MEDIA_BUS_FMT_METADATA_FIXED;
+
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static void c3_isp_core_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 c3_isp_mbus_format_info *isp_fmt;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
+
+	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
+	if (!isp_fmt)
+		sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
+	else
+		sink_fmt->code = format->format.code;
+
+	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);
+
+	src_fmt->width  = sink_fmt->width;
+	src_fmt->height = sink_fmt->height;
+
+	format->format = *sink_fmt;
+}
+
+static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
+				       struct v4l2_subdev_format *format)
+{
+	const struct c3_isp_mbus_format_info *isp_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+
+	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
+
+	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
+	if (!isp_fmt)
+		src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
+	else
+		src_fmt->code = format->format.code;
+
+	format->format = *src_fmt;
+}
+
+static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state,
+			       struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	if (format->pad == C3_ISP_CORE_PAD_SINK_VIDEO) {
+		c3_isp_core_set_sink_fmt(state, format);
+	} else if (format->pad == C3_ISP_CORE_PAD_SINK_PARAMS ||
+		   format->pad == C3_ISP_CORE_PAD_SOURCE_STATS) {
+		fmt = v4l2_subdev_state_get_format(state, format->pad);
+		format->format = *fmt;
+	} else if (format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO) {
+		c3_isp_core_set_source_fmt(state, format);
+	}
+
+	return 0;
+}
+
+static int c3_isp_core_init_state(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+
+	/* Video sink pad */
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
+	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
+	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
+	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
+	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
+	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+
+	/* Video source pad */
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
+	src_fmt->width = C3_ISP_DEFAULT_WIDTH;
+	src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
+	src_fmt->field = V4L2_FIELD_NONE;
+	src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
+	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+	src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
+	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+	/* Parameters pad */
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
+	sink_fmt->width = 0;
+	sink_fmt->height = 0;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
+
+	/* Statistics pad */
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
+	src_fmt->width = 0;
+	src_fmt->height = 0;
+	src_fmt->field = V4L2_FIELD_NONE;
+	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
+
+	return c3_isp_core_init_routing(sd, state);
+}
+
+static int c3_isp_core_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+				       struct v4l2_event_subscription *sub)
+{
+	if (sub->type != V4L2_EVENT_FRAME_SYNC)
+		return -EINVAL;
+
+	/* V4L2_EVENT_FRAME_SYNC doesn't need id, so should set 0 */
+	if (sub->id != 0)
+		return -EINVAL;
+
+	return v4l2_event_subscribe(fh, sub, 0, NULL);
+}
+
+static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
+	.enum_mbus_code = c3_isp_core_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = c3_isp_core_set_fmt,
+	.enable_streams = c3_isp_core_enable_streams,
+	.disable_streams = c3_isp_core_disable_streams,
+};
+
+static const struct v4l2_subdev_core_ops c3_isp_core_core_ops = {
+	.subscribe_event = c3_isp_core_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
+	.core = &c3_isp_core_core_ops,
+	.pad = &c3_isp_core_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
+	.init_state = c3_isp_core_init_state,
+};
+
+static int c3_isp_core_link_validate(struct media_link *link)
+{
+	if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
+		return 0;
+
+	return v4l2_subdev_link_validate(link);
+}
+
+/* Media entity operations */
+static const struct media_entity_operations c3_isp_core_entity_ops = {
+	.link_validate = c3_isp_core_link_validate,
+};
+
+void c3_isp_core_queue_eof(struct c3_isp_device *isp)
+{
+	struct v4l2_event event = {
+		.type = V4L2_EVENT_FRAME_SYNC,
+	};
+
+	event.u.frame_sync.frame_sequence = isp->frm_sequence;
+	v4l2_event_queue(isp->core.sd.devnode, &event);
+}
+
+int c3_isp_core_register(struct c3_isp_device *isp)
+{
+	struct c3_isp_core *core = &isp->core;
+	struct v4l2_subdev *sd = &core->sd;
+	int ret;
+
+	v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
+	sd->owner = THIS_MODULE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+	sd->internal_ops = &c3_isp_core_internal_ops;
+	snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
+
+	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+	sd->entity.ops = &c3_isp_core_entity_ops;
+
+	core->isp = isp;
+	sd->dev = isp->dev;
+	v4l2_set_subdevdata(sd, core);
+
+	core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
+	core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
+	core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
+	core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	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_entity_cleanup:
+	media_entity_cleanup(&sd->entity);
+	return ret;
+}
+
+void c3_isp_core_unregister(struct c3_isp_device *isp)
+{
+	struct c3_isp_core *core = &isp->core;
+	struct v4l2_subdev *sd = &core->sd;
+
+	v4l2_device_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
+	media_entity_cleanup(&sd->entity);
+}
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
new file mode 100644
index 000000000000..0c6795ab9925
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
@@ -0,0 +1,459 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
+{
+	return readl(isp->base + reg);
+}
+
+void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
+{
+	writel(val, isp->base + reg);
+}
+
+void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
+{
+	u32 orig, tmp;
+
+	orig = c3_isp_read(isp, reg);
+
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+
+	if (tmp != orig)
+		c3_isp_write(isp, reg, tmp);
+}
+
+/* PM runtime suspend */
+static int c3_isp_runtime_suspend(struct device *dev)
+{
+	struct c3_isp_device *isp = dev_get_drvdata(dev);
+
+	clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
+
+	return 0;
+}
+
+/* PM runtime resume */
+static int c3_isp_runtime_resume(struct device *dev)
+{
+	struct c3_isp_device *isp = dev_get_drvdata(dev);
+
+	return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
+}
+
+static const struct dev_pm_ops c3_isp_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+			    pm_runtime_force_resume)
+	RUNTIME_PM_OPS(c3_isp_runtime_suspend,
+		       c3_isp_runtime_resume, NULL)
+};
+
+/* IRQ handling */
+static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
+{
+	struct c3_isp_device *isp = dev;
+	u32 status;
+
+	/* Get irq status and clear irq status */
+	status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
+	c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
+
+	if (status & ISP_TOP_IRQ_EN_FRAME_END_EN) {
+		c3_isp_core_queue_eof(isp);
+		c3_isp_stats_isr(isp);
+		c3_isp_params_isr(isp);
+		c3_isp_captures_isr(isp);
+		isp->frm_sequence++;
+	}
+
+	if (status & ISP_TOP_IRQ_EN_3A_DMA_ERR_EN)
+		dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
+
+	return IRQ_HANDLED;
+}
+
+/* Subdev notifier register */
+static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
+			       struct v4l2_subdev *sd,
+			       struct v4l2_async_connection *asc)
+{
+	struct c3_isp_device *isp =
+		container_of(notifier, struct c3_isp_device, notifier);
+	struct c3_isp_core *core = &isp->core;
+	struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
+	int ret;
+
+	ret = media_entity_get_fwnode_pad(&sd->entity,
+					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
+		return ret;
+	}
+
+	core->src_sd = sd;
+	core->src_sd_pad = ret;
+
+	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
+					       MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
+{
+	struct c3_isp_device *isp =
+		container_of(notifier, struct c3_isp_device, notifier);
+	int ret;
+
+	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
+	if (ret < 0) {
+		dev_err(isp->dev,
+			"Failed to register subdev nodes: %d\n", ret);
+		return ret;
+	}
+
+	dev_info(isp->dev, "notify complete\n");
+
+	return media_device_register(&isp->media_dev);
+}
+
+static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
+{
+	struct c3_isp_device *isp =
+		container_of(asc->notifier, struct c3_isp_device, notifier);
+
+	media_device_unregister(&isp->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
+	.bound = c3_isp_notify_bound,
+	.complete = c3_isp_notify_complete,
+	.destroy = c3_isp_notify_destroy,
+};
+
+static int c3_isp_async_nf_register(struct c3_isp_device *isp)
+{
+	struct v4l2_async_connection *asc;
+	struct fwnode_handle *ep;
+	int ret;
+
+	v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
+					     FWNODE_GRAPH_ENDPOINT_NEXT);
+	if (!ep)
+		return -ENOTCONN;
+
+	asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
+					      struct v4l2_async_connection);
+	fwnode_handle_put(ep);
+
+	if (IS_ERR(asc))
+		return PTR_ERR(asc);
+
+	isp->notifier.ops = &c3_isp_notify_ops;
+	ret = v4l2_async_nf_register(&isp->notifier);
+	if (ret)
+		v4l2_async_nf_cleanup(&isp->notifier);
+
+	return ret;
+}
+
+static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
+{
+	v4l2_async_nf_unregister(&isp->notifier);
+	v4l2_async_nf_cleanup(&isp->notifier);
+}
+
+static int c3_isp_v4l2_register(struct c3_isp_device *isp)
+{
+	struct media_device *media_dev = &isp->media_dev;
+	struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
+	int ret;
+
+	/* Initialize media device */
+	strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
+		sizeof(media_dev->model));
+	media_dev->dev = isp->dev;
+
+	media_device_init(media_dev);
+
+	/* Initialize v4l2 device */
+	v4l2_dev->mdev = media_dev;
+	strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
+		sizeof(v4l2_dev->name));
+
+	ret = v4l2_device_register(isp->dev, v4l2_dev);
+	if (ret) {
+		media_device_cleanup(media_dev);
+		dev_err(isp->dev,
+			"Failed to register V4L2 device: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
+{
+	v4l2_device_unregister(&isp->v4l2_dev);
+	media_device_cleanup(&isp->media_dev);
+}
+
+static void c3_isp_remove_links(struct c3_isp_device *isp)
+{
+	unsigned int i;
+
+	media_entity_remove_links(&isp->core.sd.entity);
+
+	for (i = 0; i < C3_ISP_NUM_RSZ; i++)
+		media_entity_remove_links(&isp->resizers[i].sd.entity);
+
+	for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
+		media_entity_remove_links(&isp->caps[i].vdev.entity);
+}
+
+static int c3_isp_create_links(struct c3_isp_device *isp)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
+		ret = media_create_pad_link(&isp->resizers[i].sd.entity,
+					    C3_ISP_RESIZER_PAD_SOURCE,
+					    &isp->caps[i].vdev.entity,
+					    0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+		if (ret) {
+			dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
+			goto err_remove_links;
+		}
+
+		ret = media_create_pad_link(&isp->core.sd.entity,
+					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
+					    &isp->resizers[i].sd.entity,
+					    C3_ISP_RESIZER_PAD_SINK,
+					    MEDIA_LNK_FL_ENABLED);
+		if (ret) {
+			dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
+			goto err_remove_links;
+		}
+	}
+
+	ret = media_create_pad_link(&isp->core.sd.entity,
+				    C3_ISP_CORE_PAD_SOURCE_STATS,
+				    &isp->stats.vdev.entity,
+				    0, MEDIA_LNK_FL_ENABLED);
+	if (ret) {
+		dev_err(isp->dev, "Failed to link core and stats\n");
+		goto err_remove_links;
+	}
+
+	ret = media_create_pad_link(&isp->params.vdev.entity, 0,
+				    &isp->core.sd.entity,
+				    C3_ISP_CORE_PAD_SINK_PARAMS,
+				    MEDIA_LNK_FL_ENABLED);
+	if (ret) {
+		dev_err(isp->dev, "Failed to link params and core\n");
+		goto err_remove_links;
+	}
+
+	return 0;
+
+err_remove_links:
+	c3_isp_remove_links(isp);
+	return ret;
+}
+
+static int c3_isp_videos_register(struct c3_isp_device *isp)
+{
+	int ret;
+
+	ret = c3_isp_captures_register(isp);
+	if (ret)
+		return ret;
+
+	ret = c3_isp_stats_register(isp);
+	if (ret)
+		goto err_captures_unregister;
+
+	ret = c3_isp_params_register(isp);
+	if (ret)
+		goto err_stats_unregister;
+
+	ret = c3_isp_create_links(isp);
+	if (ret)
+		goto err_params_unregister;
+
+	return 0;
+
+err_params_unregister:
+	c3_isp_params_unregister(isp);
+err_stats_unregister:
+	c3_isp_stats_unregister(isp);
+err_captures_unregister:
+	c3_isp_captures_unregister(isp);
+	return ret;
+}
+
+static void c3_isp_videos_unregister(struct c3_isp_device *isp)
+{
+	c3_isp_remove_links(isp);
+	c3_isp_params_unregister(isp);
+	c3_isp_stats_unregister(isp);
+	c3_isp_captures_unregister(isp);
+}
+
+static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
+{
+	const struct c3_isp_info *info = isp->info;
+	int ret;
+	u32 i;
+
+	for (i = 0; i < info->clock_num; i++)
+		isp->clks[i].id = info->clocks[i];
+
+	ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < info->clock_num; i++) {
+		if (!info->clock_rates[i])
+			continue;
+		ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
+		if (ret) {
+			dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
+				info->clock_rates[i]);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int c3_isp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct c3_isp_device *isp;
+	int irq;
+	int ret;
+
+	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
+	if (!isp)
+		return -ENOMEM;
+
+	isp->info = of_device_get_match_data(dev);
+	isp->dev = dev;
+
+	isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
+	if (IS_ERR(isp->base))
+		return dev_err_probe(dev, PTR_ERR(isp->base),
+				     "Failed to ioremap resource\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = c3_isp_cfg_clocks(isp);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
+
+	platform_set_drvdata(pdev, isp);
+
+	pm_runtime_enable(dev);
+
+	ret = c3_isp_v4l2_register(isp);
+	if (ret)
+		goto err_runtime_disable;
+
+	ret = c3_isp_core_register(isp);
+	if (ret)
+		goto err_v4l2_unregister;
+
+	ret = c3_isp_resizers_register(isp);
+	if (ret)
+		goto err_core_unregister;
+
+	ret = c3_isp_async_nf_register(isp);
+	if (ret)
+		goto err_resizers_unregister;
+
+	ret = devm_request_irq(dev, irq,
+			       c3_isp_irq_handler, IRQF_SHARED,
+			       dev_driver_string(dev), isp);
+	if (ret)
+		goto err_nf_unregister;
+
+	ret = c3_isp_videos_register(isp);
+	if (ret)
+		goto err_nf_unregister;
+
+	return 0;
+
+err_nf_unregister:
+	c3_isp_async_nf_unregister(isp);
+err_resizers_unregister:
+	c3_isp_resizers_unregister(isp);
+err_core_unregister:
+	c3_isp_core_unregister(isp);
+err_v4l2_unregister:
+	c3_isp_v4l2_unregister(isp);
+err_runtime_disable:
+	pm_runtime_disable(dev);
+	return ret;
+};
+
+static void c3_isp_remove(struct platform_device *pdev)
+{
+	struct c3_isp_device *isp = platform_get_drvdata(pdev);
+
+	c3_isp_videos_unregister(isp);
+	c3_isp_async_nf_unregister(isp);
+	c3_isp_core_unregister(isp);
+	c3_isp_resizers_unregister(isp);
+	c3_isp_v4l2_unregister(isp);
+	pm_runtime_disable(isp->dev);
+};
+
+static const struct c3_isp_info isp_info = {
+	.clocks = {"vapb", "isp0"},
+	.clock_rates = {0, 400000000},
+	.clock_num = 2
+};
+
+static const struct of_device_id c3_isp_of_match[] = {
+	{ .compatible = "amlogic,c3-isp",
+	  .data = &isp_info },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, c3_isp_of_match);
+
+static struct platform_driver c3_isp_driver = {
+	.probe = c3_isp_probe,
+	.remove = c3_isp_remove,
+	.driver = {
+		.name = "c3-isp",
+		.of_match_table = c3_isp_of_match,
+		.pm = pm_ptr(&c3_isp_pm_ops),
+	},
+};
+
+module_platform_driver(c3_isp_driver);
+
+MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
+MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
new file mode 100644
index 000000000000..dc33a57dcd59
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c
@@ -0,0 +1,942 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#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-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 pointer 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. The data member at the end allows a pointer to an address
+ * within the data member of :c:type:`c3_isp_params_cfg` to initialise a
+ * union variable.
+ *
+ * @header:		Pointer to the shared header struct embedded as the
+ *			first member of all the possible other members (except
+ *			@data). 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_PST_CCM
+ * @csc:		For header->type == C3_ISP_PARAMS_BLOCK_PST_CSC
+ * @blc:		For header->type == C3_ISP_PARAMS_BLOCK_PST_BLC
+ * @data:		Allows easy initialisation of a union variable with a
+ *			pointer into a u8 array.
+ */
+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;
+	u8 *data;
+};
+
+typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp,
+				     union c3_isp_params_block block);
+
+struct c3_isp_params_handler {
+	size_t size;
+	c3_isp_block_handler handler;
+};
+
+#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,
+					union c3_isp_params_block block)
+{
+	struct c3_isp_params_awb_gains *awb_gains = block.awb_gains;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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));
+
+	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_cfg_awb_weight(struct c3_isp_device *isp,
+					 struct c3_isp_params_awb_config *awb_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 = awb_cfg->horiz_zones_num * awb_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_IDX_DATA(0, awb_cfg->zone_weight[base + 0]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(1, awb_cfg->zone_weight[base + 1]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(2, awb_cfg->zone_weight[base + 2]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(3, awb_cfg->zone_weight[base + 3]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(4, awb_cfg->zone_weight[base + 4]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(5, awb_cfg->zone_weight[base + 5]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(6, awb_cfg->zone_weight[base + 6]) |
+		       ISP_AWB_BLK_WT_DATA_IDX_DATA(7, awb_cfg->zone_weight[base + 7]);
+		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
+	}
+
+	if (zones_num % 8) {
+		data = 0;
+		base = i * 8;
+
+		for (i = 0; i < zones_num % 8; i++)
+			data |= ISP_AWB_BLK_WT_DATA_IDX_DATA(i, awb_cfg->zone_weight[base + i]);
+
+		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data);
+	}
+}
+
+static void c3_isp_params_cfg_awb_cood(struct c3_isp_device *isp,
+				       struct c3_isp_params_awb_config *awb_cfg)
+{
+	unsigned int max_point_num;
+
+	/* The number of points is one more than the number of edges */
+	max_point_num = max(awb_cfg->horiz_zones_num, awb_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(awb_cfg->horiz_cood[i]) |
+			     ISP_AWB_IDX_DATA_VIDX_DATA(awb_cfg->vert_cood[i]));
+	}
+}
+
+static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp,
+					 union c3_isp_params_block block)
+{
+	struct c3_isp_params_awb_config *awb_cfg = block.awb_cfg;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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_STAT_POINT_MASK,
+			   ISP_TOP_3A_STAT_CRTL_AWB_STAT_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_HORIZ_BLKS_NUM_MASK,
+			   ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(awb_cfg->horiz_zones_num));
+	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK,
+			   ISP_AWB_HV_BLKNUM_VERT_BLKS_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_cfg_awb_weight(isp, awb_cfg);
+	c3_isp_params_cfg_awb_cood(isp, awb_cfg);
+
+	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_cfg_ae_weight(struct c3_isp_device *isp,
+					struct c3_isp_params_ae_config *ae_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 = ae_cfg->horiz_zones_num * ae_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_IDX_DATA(0, ae_cfg->zone_weight[base + 0]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(1, ae_cfg->zone_weight[base + 1]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(2, ae_cfg->zone_weight[base + 2]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(3, ae_cfg->zone_weight[base + 3]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(4, ae_cfg->zone_weight[base + 4]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(5, ae_cfg->zone_weight[base + 5]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(6, ae_cfg->zone_weight[base + 6]) |
+		       ISP_AE_BLK_WT_DATA_IDX_DATA(7, ae_cfg->zone_weight[base + 7]);
+		c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
+	}
+
+	/* Write the last weights data */
+	if (zones_num % 8) {
+		data = 0;
+		base = i * 8;
+
+		for (i = 0; i < zones_num % 8; i++)
+			data |= ISP_AE_BLK_WT_DATA_IDX_DATA(i, ae_cfg->zone_weight[base + i]);
+
+		c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data);
+	}
+}
+
+static void c3_isp_params_cfg_ae_cood(struct c3_isp_device *isp,
+				      struct c3_isp_params_ae_config *ae_cfg)
+{
+	unsigned int max_point_num;
+
+	/* The number of points is one more than the number of edges */
+	max_point_num = max(ae_cfg->horiz_zones_num, ae_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(ae_cfg->horiz_cood[i]) |
+			     ISP_AE_IDX_DATA_VIDX_DATA(ae_cfg->vert_cood[i]));
+	}
+}
+
+static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp,
+					union c3_isp_params_block block)
+{
+	struct c3_isp_params_ae_config *ae_cfg = block.ae_cfg;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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_STAT_POINT_MASK,
+			   ISP_TOP_3A_STAT_CRTL_AE_STAT_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_BLK_HORIZ_NUM_MASK,
+			   ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(ae_cfg->horiz_zones_num));
+	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK,
+			   ISP_AE_HV_BLKNUM_BLK_VERT_NUM(ae_cfg->vert_zones_num));
+
+	c3_isp_params_cfg_ae_weight(isp, ae_cfg);
+	c3_isp_params_cfg_ae_cood(isp, ae_cfg);
+
+	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_cfg_af_cood(struct c3_isp_device *isp,
+				      struct c3_isp_params_af_config *af_cfg)
+{
+	unsigned int max_point_num;
+
+	/* The number of points is one more than the number of edges */
+	max_point_num = max(af_cfg->horiz_zones_num, af_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(af_cfg->horiz_cood[i]) |
+			     ISP_AF_IDX_DATA_VIDX_DATA(af_cfg->vert_cood[i]));
+	}
+}
+
+static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp,
+					union c3_isp_params_block block)
+{
+	struct c3_isp_params_af_config *af_cfg = block.af_cfg;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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_STAT_POINT_MASK,
+			   ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(af_cfg->tap_point));
+
+	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK,
+			   ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(af_cfg->horiz_zones_num));
+	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK,
+			   ISP_AF_HV_BLKNUM_BLK_VERT_NUM(af_cfg->vert_zones_num));
+
+	c3_isp_params_cfg_af_cood(isp, af_cfg);
+
+	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,
+					union c3_isp_params_block block)
+{
+	struct c3_isp_params_pst_gamma *gamma = block.pst_gamma;
+	unsigned int base;
+	unsigned int i;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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(gamma->lut) / 2; i++) {
+			base = i * 2;
+			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
+				     ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]) |
+				     ISP_PST_GAMMA_LUT_DATA_DATA1(gamma->lut[base + 1]));
+		}
+
+		/* Write the last one */
+		if (ARRAY_SIZE(gamma->lut) % 2) {
+			base = i * 2;
+			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
+				     ISP_PST_GAMMA_LUT_DATA_DATA0(gamma->lut[base]));
+		}
+	}
+
+	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,
+				  union c3_isp_params_block block)
+{
+	struct c3_isp_params_ccm *ccm = block.ccm;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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]));
+
+	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,
+				  union c3_isp_params_block block)
+{
+	struct c3_isp_params_csc *csc = block.csc;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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]));
+
+	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,
+				  union c3_isp_params_block block)
+{
+	struct c3_isp_params_blc *blc = block.blc;
+
+	if (block.header->flags & C3_ISP_PARAMS_BLOCK_FL_DISABLED) {
+		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));
+
+	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_BLC_EN_MASK,
+			   ISP_TOP_BEO_CTRL_BLC_EN);
+}
+
+static const struct c3_isp_params_handler c3_isp_params_handlers[] = {
+	[C3_ISP_PARAMS_BLOCK_AWB_GAINS] = {
+		.size = sizeof(struct c3_isp_params_awb_gains),
+		.handler = c3_isp_params_cfg_awb_gains,
+	},
+	[C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = {
+		.size = sizeof(struct c3_isp_params_awb_config),
+		.handler = c3_isp_params_cfg_awb_config,
+	},
+	[C3_ISP_PARAMS_BLOCK_AE_CONFIG] = {
+		.size = sizeof(struct c3_isp_params_ae_config),
+		.handler = c3_isp_params_cfg_ae_config,
+	},
+	[C3_ISP_PARAMS_BLOCK_AF_CONFIG] = {
+		.size = sizeof(struct c3_isp_params_af_config),
+		.handler = c3_isp_params_cfg_af_config,
+	},
+	[C3_ISP_PARAMS_BLOCK_PST_GAMMA] = {
+		.size = sizeof(struct c3_isp_params_pst_gamma),
+		.handler = c3_isp_params_cfg_pst_gamma,
+	},
+	[C3_ISP_PARAMS_BLOCK_CCM] = {
+		.size = sizeof(struct c3_isp_params_ccm),
+		.handler = c3_isp_params_cfg_ccm,
+	},
+	[C3_ISP_PARAMS_BLOCK_CSC] = {
+		.size = sizeof(struct c3_isp_params_csc),
+		.handler = c3_isp_params_cfg_csc,
+	},
+	[C3_ISP_PARAMS_BLOCK_BLC] = {
+		.size = sizeof(struct c3_isp_params_blc),
+		.handler = c3_isp_params_cfg_blc,
+	},
+};
+
+static enum vb2_buffer_state
+c3_isp_params_cfg_blocks(struct c3_isp_params *params)
+{
+	struct c3_isp_params_cfg *config = params->buff->cfg;
+	size_t block_offset = 0;
+	size_t max_offset = 0;
+
+	if (WARN_ON(!config))
+		return VB2_BUF_STATE_ERROR;
+
+	/* Ensure config->data has a full struct c3_isp_params_block_header */
+	max_offset = config->data_size - sizeof(struct c3_isp_params_block_header);
+
+	/* Walk the list of parameter blocks and process them */
+	while (block_offset <= max_offset) {
+		const struct c3_isp_params_handler *block_handler;
+		union c3_isp_params_block block;
+
+		block = (union c3_isp_params_block)
+			 &config->data[block_offset];
+
+		block_handler = &c3_isp_params_handlers[block.header->type];
+		block_handler->handler(params->isp, block);
+
+		block_offset += block.header->size;
+	}
+
+	return VB2_BUF_STATE_DONE;
+}
+
+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)(&params->buff_lock);
+
+	/* Only use the first buffer to initialize ISP */
+	params->buff = list_first_entry_or_null(&params->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;
+	} else {
+		*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)(&params->buff_lock);
+
+	list_add_tail(&buf->list, &params->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 *cfg = buf->cfg;
+	struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0);
+	size_t payload_size = vb2_get_plane_payload(vb, 0);
+	size_t header_size = offsetof(struct c3_isp_params_cfg, data);
+	size_t block_offset = 0;
+	size_t cfg_size;
+
+	/* Payload size can't be greater than the destination buffer size */
+	if (payload_size > params->vfmt.fmt.meta.buffersize) {
+		dev_dbg(params->isp->dev,
+			"Payload size is too large: %zu\n", payload_size);
+		return -EINVAL;
+	}
+
+	/* Payload size can't be smaller than the header size */
+	if (payload_size < header_size) {
+		dev_dbg(params->isp->dev,
+			"Payload size is too small: %zu\n", payload_size);
+		return -EINVAL;
+	}
+
+	/*
+	 * Use the internal scratch buffer to avoid userspace modifying
+	 * the buffer content while the driver is processing it.
+	 */
+	memcpy(cfg, usr_cfg, payload_size);
+
+	/* Only v0 is supported at the moment */
+	if (cfg->version != C3_ISP_PARAMS_BUFFER_V0) {
+		dev_dbg(params->isp->dev,
+			"Unsupported params buffer version: %u\n", cfg->version);
+		return -EINVAL;
+	}
+
+	/* Validate the size reported in the parameter buffer header */
+	cfg_size = header_size + cfg->data_size;
+	if (cfg_size != payload_size) {
+		dev_dbg(params->isp->dev,
+			"Data size %zu and payload size %zu are different\n",
+			cfg_size, payload_size);
+		return -EINVAL;
+	}
+
+	/* Walk the list of parameter blocks and validate them */
+	cfg_size = cfg->data_size;
+	while (cfg_size >= sizeof(struct c3_isp_params_block_header)) {
+		const struct c3_isp_params_block_header *block;
+		const struct c3_isp_params_handler *handler;
+
+		block = (struct c3_isp_params_block_header *)
+			&cfg->data[block_offset];
+
+		if (block->type >= ARRAY_SIZE(c3_isp_params_handlers)) {
+			dev_dbg(params->isp->dev,
+				"Invalid params block type\n");
+			return -EINVAL;
+		}
+
+		if (block->size > cfg_size) {
+			dev_dbg(params->isp->dev,
+				"Block size is greater than cfg size\n");
+			return -EINVAL;
+		}
+
+		handler = &c3_isp_params_handlers[block->type];
+		if (block->size != handler->size) {
+			dev_dbg(params->isp->dev,
+				"Invalid params block size\n");
+			return -EINVAL;
+		}
+
+		block_offset += block->size;
+		cfg_size -= block->size;
+	}
+
+	if (cfg_size) {
+		dev_dbg(params->isp->dev,
+			"Unexpected data after the params buffer end\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+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)(&params->buff_lock);
+
+	while (!list_empty(&params->pending)) {
+		buff = list_first_entry(&params->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,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.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 = &params->vdev;
+	struct vb2_queue *vb2_q = &params->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(&params->pending);
+	spin_lock_init(&params->buff_lock);
+	mutex_init(&params->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 = &params->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_buffer);
+	vb2_q->dev = isp->dev;
+	vb2_q->lock = &params->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, &params->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(&params->lock);
+	return ret;
+}
+
+void c3_isp_params_unregister(struct c3_isp_device *isp)
+{
+	struct c3_isp_params *params = &isp->params;
+
+	vb2_queue_release(&params->vb2_q);
+	media_entity_cleanup(&params->vdev.entity);
+	video_unregister_device(&params->vdev);
+	mutex_destroy(&params->lock);
+}
+
+void c3_isp_params_isr(struct c3_isp_device *isp)
+{
+	struct c3_isp_params *params = &isp->params;
+	enum vb2_buffer_state state;
+
+	guard(spinlock_irqsave)(&params->buff_lock);
+
+	params->buff = list_first_entry_or_null(&params->pending,
+						struct c3_isp_params_buffer, list);
+	if (!params->buff)
+		return;
+
+	list_del(&params->buff->list);
+
+	state = 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(&params->buff->vb.vb2_buf, state);
+}
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
new file mode 100644
index 000000000000..c4d3b8c96479
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h
@@ -0,0 +1,599 @@ 
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __C3_ISP_REGS_H__
+#define __C3_ISP_REGS_H__
+
+#define ISP_TOP_INPUT_SIZE                              0x0000
+#define ISP_TOP_INPUT_SIZE_VERT_SIZE_MASK               GENMASK(15, 0)
+#define ISP_TOP_INPUT_SIZE_VERT_SIZE(x)                 ((x) << 0)
+#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE_MASK              GENMASK(31, 16)
+#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE(x)                ((x) << 16)
+
+#define ISP_TOP_FRM_SIZE                                0x0004
+#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE_MASK            GENMASK(15, 0)
+#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(x)              ((x) << 0)
+#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE_MASK           GENMASK(31, 16)
+#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(x)             ((x) << 16)
+
+#define ISP_TOP_HOLD_SIZE                               0x0008
+#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK          GENMASK(31, 16)
+#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(x)            ((x) << 16)
+
+#define ISP_TOP_PATH_EN                                 0x0010
+#define ISP_TOP_PATH_EN_DISP0_EN_MASK                   BIT(0)
+#define ISP_TOP_PATH_EN_DISP0_EN                        BIT(0)
+#define ISP_TOP_PATH_EN_DISP0_DIS                       (0 << 0)
+#define ISP_TOP_PATH_EN_DISP1_EN_MASK                   BIT(1)
+#define ISP_TOP_PATH_EN_DISP1_EN                        BIT(1)
+#define ISP_TOP_PATH_EN_DISP1_DIS                       (0 << 1)
+#define ISP_TOP_PATH_EN_DISP2_EN_MASK                   BIT(2)
+#define ISP_TOP_PATH_EN_DISP2_EN                        BIT(2)
+#define ISP_TOP_PATH_EN_DISP2_DIS                       (0 << 2)
+#define ISP_TOP_PATH_EN_WRMIF0_EN_MASK                  BIT(8)
+#define ISP_TOP_PATH_EN_WRMIF0_EN                       BIT(8)
+#define ISP_TOP_PATH_EN_WRMIF0_DIS                      (0 << 8)
+#define ISP_TOP_PATH_EN_WRMIF1_EN_MASK                  BIT(9)
+#define ISP_TOP_PATH_EN_WRMIF1_EN                       BIT(9)
+#define ISP_TOP_PATH_EN_WRMIF1_DIS                      (0 << 9)
+#define ISP_TOP_PATH_EN_WRMIF2_EN_MASK                  BIT(10)
+#define ISP_TOP_PATH_EN_WRMIF2_EN                       BIT(10)
+#define ISP_TOP_PATH_EN_WRMIF2_DIS                      (0 << 10)
+
+#define ISP_TOP_PATH_SEL                                0x0014
+#define ISP_TOP_PATH_SEL_CORE_MASK                      GENMASK(18, 16)
+#define ISP_TOP_PATH_SEL_CORE_CORE_DIS                  (0 << 16)
+#define ISP_TOP_PATH_SEL_CORE_MIPI_CORE                 BIT(16)
+
+#define ISP_TOP_IRQ_EN                                  0x0080
+#define ISP_TOP_IRQ_EN_FRAME_END_MASK                   BIT(0)
+#define ISP_TOP_IRQ_EN_FRAME_END_EN                     BIT(0)
+#define ISP_TOP_IRQ_EN_FRAME_END_DIS                    (0 << 0)
+#define ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK                  BIT(5)
+#define ISP_TOP_IRQ_EN_3A_DMA_ERR_EN                    BIT(5)
+#define ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS                   (0 << 5)
+
+#define ISP_TOP_IRQ_CLR                                 0x0084
+#define ISP_TOP_RO_IRQ_STAT                             0x01c4
+
+#define ISP_TOP_MODE_CTRL                               0x0400
+#define ISP_TOP_FEO_CTRL0                               0x040c
+#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK             BIT(8)
+#define ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS                 (0 << 8)
+#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN                  BIT(8)
+
+#define ISP_TOP_FEO_CTRL1_0                             0x0410
+#define ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK                 BIT(3)
+#define ISP_TOP_FEO_CTRL1_0_DPC_DIS                     (0 << 3)
+#define ISP_TOP_FEO_CTRL1_0_DPC_EN                      BIT(3)
+#define ISP_TOP_FEO_CTRL1_0_OG_EN_MASK                  BIT(5)
+#define ISP_TOP_FEO_CTRL1_0_OG_DIS                      (0 << 5)
+#define ISP_TOP_FEO_CTRL1_0_OG_EN                       BIT(5)
+
+#define ISP_TOP_FED_CTRL                                0x0418
+#define ISP_TOP_FED_CTRL_PDPC_EN_MASK                   BIT(1)
+#define ISP_TOP_FED_CTRL_PDPC_DIS                       (0 << 1)
+#define ISP_TOP_FED_CTRL_PDPC_EN                        BIT(1)
+#define ISP_TOP_FED_CTRL_RAWCNR_EN_MASK                 GENMASK(6, 5)
+#define ISP_TOP_FED_CTRL_RAWCNR_DIS                     (0 << 5)
+#define ISP_TOP_FED_CTRL_RAWCNR_EN                      BIT(5)
+#define ISP_TOP_FED_CTRL_SNR1_EN_MASK                   BIT(9)
+#define ISP_TOP_FED_CTRL_SNR1_DIS                       (0 << 9)
+#define ISP_TOP_FED_CTRL_SNR1_EN                        BIT(9)
+#define ISP_TOP_FED_CTRL_TNR0_EN_MASK                   BIT(11)
+#define ISP_TOP_FED_CTRL_TNR0_DIS                       (0 << 11)
+#define ISP_TOP_FED_CTRL_TNR0_EN                        BIT(11)
+#define ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK               BIT(12)
+#define ISP_TOP_FED_CTRL_CUBIC_CS_DIS                   (0 << 12)
+#define ISP_TOP_FED_CTRL_CUBIC_CS_EN                    BIT(12)
+#define ISP_TOP_FED_CTRL_SQRT_EN_MASK                   BIT(14)
+#define ISP_TOP_FED_CTRL_SQRT_DIS                       (0 << 14)
+#define ISP_TOP_FED_CTRL_SQRT_EN                        BIT(14)
+#define ISP_TOP_FED_CTRL_DGAIN_EN_MASK                  BIT(16)
+#define ISP_TOP_FED_CTRL_DGAIN_DIS                      (0 << 16)
+#define ISP_TOP_FED_CTRL_DGAIN_EN                       BIT(16)
+
+#define ISP_TOP_BEO_CTRL                                0x041c
+#define ISP_TOP_BEO_CTRL_WB_EN_MASK                     BIT(6)
+#define ISP_TOP_BEO_CTRL_WB_DIS                         (0 << 6)
+#define ISP_TOP_BEO_CTRL_WB_EN                          BIT(6)
+#define ISP_TOP_BEO_CTRL_BLC_EN_MASK                    BIT(7)
+#define ISP_TOP_BEO_CTRL_BLC_DIS                        (0 << 7)
+#define ISP_TOP_BEO_CTRL_BLC_EN                         BIT(7)
+#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK              BIT(8)
+#define ISP_TOP_BEO_CTRL_INV_DGAIN_DIS                  (0 << 8)
+#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN                   BIT(8)
+#define ISP_TOP_BEO_CTRL_EOTF_EN_MASK                   BIT(9)
+#define ISP_TOP_BEO_CTRL_EOTF_DIS                       (0 << 9)
+#define ISP_TOP_BEO_CTRL_EOTF_EN                        BIT(9)
+
+#define ISP_TOP_BED_CTRL                                0x0420
+#define ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK               GENMASK(1, 0)
+#define ISP_TOP_BED_CTRL_YHS_STAT_DIS                   (0 << 0)
+#define ISP_TOP_BED_CTRL_YHS_STAT_EN                    BIT(0)
+#define ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK              BIT(2)
+#define ISP_TOP_BED_CTRL_GRPH_STAT_DIS                  (0 << 2)
+#define ISP_TOP_BED_CTRL_GRPH_STAT_EN                   BIT(2)
+#define ISP_TOP_BED_CTRL_FMETER_EN_MASK                 BIT(3)
+#define ISP_TOP_BED_CTRL_FMETER_DIS                     (0 << 3)
+#define ISP_TOP_BED_CTRL_FMETER_EN                      BIT(3)
+#define ISP_TOP_BED_CTRL_BSC_EN_MASK                    BIT(10)
+#define ISP_TOP_BED_CTRL_BSC_DIS                        (0 << 10)
+#define ISP_TOP_BED_CTRL_BSC_EN                         BIT(10)
+#define ISP_TOP_BED_CTRL_CNR2_EN_MASK                   BIT(11)
+#define ISP_TOP_BED_CTRL_CNR2_DIS                       (0 << 11)
+#define ISP_TOP_BED_CTRL_CNR2_EN                        BIT(11)
+#define ISP_TOP_BED_CTRL_CM1_EN_MASK                    BIT(13)
+#define ISP_TOP_BED_CTRL_CM1_DIS                        (0 << 13)
+#define ISP_TOP_BED_CTRL_CM1_EN                         BIT(13)
+#define ISP_TOP_BED_CTRL_CM0_EN_MASK                    BIT(14)
+#define ISP_TOP_BED_CTRL_CM0_DIS                        (0 << 14)
+#define ISP_TOP_BED_CTRL_CM0_EN                         BIT(14)
+#define ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK              BIT(16)
+#define ISP_TOP_BED_CTRL_PST_GAMMA_DIS                  (0 << 16)
+#define ISP_TOP_BED_CTRL_PST_GAMMA_EN                   BIT(16)
+#define ISP_TOP_BED_CTRL_LUT3D_EN_MASK                  BIT(17)
+#define ISP_TOP_BED_CTRL_LUT3D_DIS                      (0 << 17)
+#define ISP_TOP_BED_CTRL_LUT3D_EN                       BIT(17)
+#define ISP_TOP_BED_CTRL_CCM_EN_MASK                    BIT(18)
+#define ISP_TOP_BED_CTRL_CCM_DIS                        (0 << 18)
+#define ISP_TOP_BED_CTRL_CCM_EN                         BIT(18)
+#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK           BIT(21)
+#define ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS               (0 << 21)
+#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN                BIT(21)
+#define ISP_TOP_BED_CTRL_AMCM_EN_MASK                   BIT(25)
+#define ISP_TOP_BED_CTRL_AMCM_DIS                       (0 << 25)
+#define ISP_TOP_BED_CTRL_AMCM_EN                        BIT(25)
+
+#define ISP_TOP_3A_STAT_CRTL                            0x0424
+#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK            BIT(0)
+#define ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS                (0 << 0)
+#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN                 BIT(0)
+#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK           BIT(1)
+#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS               (0 << 1)
+#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN                BIT(1)
+#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK            BIT(2)
+#define ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS                (0 << 2)
+#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN                 BIT(2)
+#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT_MASK        GENMASK(6, 4)
+#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_POINT(x)          ((x) << 4)
+#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT_MASK         GENMASK(9, 8)
+#define ISP_TOP_3A_STAT_CRTL_AE_STAT_POINT(x)           ((x) << 8)
+#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT_MASK         GENMASK(13, 12)
+#define ISP_TOP_3A_STAT_CRTL_AF_STAT_POINT(x)           ((x) << 12)
+
+#define ISP_LSWB_BLC_OFST0                              0x4028
+#define ISP_LSWB_BLC_OFST0_R_OFST_MASK                  GENMASK(15, 0)
+#define ISP_LSWB_BLC_OFST0_R_OFST(x)                    ((x) << 0)
+#define ISP_LSWB_BLC_OFST0_GR_OFST_MASK                 GENMASK(31, 16)
+#define ISP_LSWB_BLC_OFST0_GR_OFST(x)                   ((x) << 16)
+
+#define ISP_LSWB_BLC_OFST1                              0x402c
+#define ISP_LSWB_BLC_OFST1_GB_OFST_MASK                 GENMASK(15, 0)
+#define ISP_LSWB_BLC_OFST1_GB_OFST(x)                   ((x) << 0)
+#define ISP_LSWB_BLC_OFST1_B_OFST_MASK                  GENMASK(31, 16)
+#define ISP_LSWB_BLC_OFST1_B_OFST(x)                    ((x) << 16)
+
+#define ISP_LSWB_BLC_PHSOFST                            0x4034
+#define ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
+#define ISP_LSWB_BLC_PHSOFST_VERT_OFST(x)               ((x) << 0)
+#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
+#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
+
+#define ISP_LSWB_WB_GAIN0                               0x4038
+#define ISP_LSWB_WB_GAIN0_R_GAIN_MASK                   GENMASK(11, 0)
+#define ISP_LSWB_WB_GAIN0_R_GAIN(x)                     ((x) << 0)
+#define ISP_LSWB_WB_GAIN0_GR_GAIN_MASK                  GENMASK(27, 16)
+#define ISP_LSWB_WB_GAIN0_GR_GAIN(x)                    ((x) << 16)
+
+#define ISP_LSWB_WB_GAIN1                               0x403c
+#define ISP_LSWB_WB_GAIN1_GB_GAIN_MASK                  GENMASK(11, 0)
+#define ISP_LSWB_WB_GAIN1_GB_GAIN(x)                    ((x) << 0)
+#define ISP_LSWB_WB_GAIN1_B_GAIN_MASK                   GENMASK(27, 16)
+#define ISP_LSWB_WB_GAIN1_B_GAIN(x)                     ((x) << 16)
+
+#define ISP_LSWB_WB_GAIN2                               0x4040
+#define ISP_LSWB_WB_GAIN2_IR_GAIN_MASK                  GENMASK(11, 0)
+#define ISP_LSWB_WB_GAIN2_IR_GAIN(x)                    ((x) << 0)
+
+#define ISP_LSWB_WB_LIMIT0                              0x4044
+#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MASK              GENMASK(15, 0)
+#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R(x)                ((x) << 0)
+#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX               (0x8fff << 0)
+#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MASK             GENMASK(31, 16)
+#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR(x)               ((x) << 16)
+#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX              (0x8fff << 16)
+
+#define ISP_LSWB_WB_LIMIT1                              0x4048
+#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MASK             GENMASK(15, 0)
+#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB(x)               ((x) << 0)
+#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX              (0x8fff << 0)
+#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MASK              GENMASK(31, 16)
+#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B(x)                ((x) << 16)
+#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX               (0x8fff << 16)
+
+#define ISP_LSWB_WB_PHSOFST                             0x4050
+#define ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK              GENMASK(1, 0)
+#define ISP_LSWB_WB_PHSOFST_VERT_OFST(x)                ((x) << 0)
+#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK             GENMASK(3, 2)
+#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST(x)               ((x) << 2)
+
+#define ISP_LSWB_LNS_PHSOFST                            0x4054
+#define ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK             GENMASK(1, 0)
+#define ISP_LSWB_LNS_PHSOFST_VERT_OFST(x)               ((x) << 0)
+#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK            GENMASK(3, 2)
+#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(x)              ((x) << 2)
+
+#define ISP_DMS_COMMON_PARAM0                           0x5000
+#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK         GENMASK(1, 0)
+#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(x)          ((x) << 0)
+#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK        GENMASK(3, 2)
+#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(x)         ((x) << 2)
+
+#define ISP_CM0_COEF00_01                               0x6048
+#define ISP_CM0_COEF00_01_MTX_00_MASK                   GENMASK(12, 0)
+#define ISP_CM0_COEF00_01_MTX_00(x)                     ((x) << 0)
+#define ISP_CM0_COEF00_01_MTX_01_MASK                   GENMASK(28, 16)
+#define ISP_CM0_COEF00_01_MTX_01(x)                     ((x) << 16)
+
+#define ISP_CM0_COEF02_10                               0x604c
+#define ISP_CM0_COEF02_10_MTX_02_MASK                   GENMASK(12, 0)
+#define ISP_CM0_COEF02_10_MTX_02(x)                     ((x) << 0)
+#define ISP_CM0_COEF02_10_MTX_10_MASK                   GENMASK(28, 16)
+#define ISP_CM0_COEF02_10_MTX_10(x)                     ((x) << 16)
+
+#define ISP_CM0_COEF11_12                               0x6050
+#define ISP_CM0_COEF11_12_MTX_11_MASK                   GENMASK(12, 0)
+#define ISP_CM0_COEF11_12_MTX_11(x)                     ((x) << 0)
+#define ISP_CM0_COEF11_12_MTX_12_MASK                   GENMASK(28, 16)
+#define ISP_CM0_COEF11_12_MTX_12(x)                     ((x) << 16)
+
+#define ISP_CM0_COEF20_21                               0x6054
+#define ISP_CM0_COEF20_21_MTX_20_MASK                   GENMASK(12, 0)
+#define ISP_CM0_COEF20_21_MTX_20(x)                     ((x) << 0)
+#define ISP_CM0_COEF20_21_MTX_21_MASK                   GENMASK(28, 16)
+#define ISP_CM0_COEF20_21_MTX_21(x)                     ((x) << 16)
+
+#define ISP_CM0_COEF22_OUP_OFST0                        0x6058
+#define ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK            GENMASK(12, 0)
+#define ISP_CM0_COEF22_OUP_OFST0_MTX_22(x)              ((x) << 0)
+
+#define ISP_CCM_MTX_00_01                               0x6098
+#define ISP_CCM_MTX_00_01_MTX_00_MASK                   GENMASK(12, 0)
+#define ISP_CCM_MTX_00_01_MTX_00(x)                     ((x) << 0)
+#define ISP_CCM_MTX_00_01_MTX_01_MASK                   GENMASK(28, 16)
+#define ISP_CCM_MTX_00_01_MTX_01(x)                     ((x) << 16)
+
+#define ISP_CCM_MTX_02_03                               0x609c
+#define ISP_CCM_MTX_02_03_MTX_02_MASK                   GENMASK(12, 0)
+#define ISP_CCM_MTX_02_03_MTX_02(x)                     ((x) << 0)
+
+#define ISP_CCM_MTX_10_11                               0x60A0
+#define ISP_CCM_MTX_10_11_MTX_10_MASK                   GENMASK(12, 0)
+#define ISP_CCM_MTX_10_11_MTX_10(x)                     ((x) << 0)
+#define ISP_CCM_MTX_10_11_MTX_11_MASK                   GENMASK(28, 16)
+#define ISP_CCM_MTX_10_11_MTX_11(x)                     ((x) << 16)
+
+#define ISP_CCM_MTX_12_13                               0x60A4
+#define ISP_CCM_MTX_12_13_MTX_12_MASK                   GENMASK(12, 0)
+#define ISP_CCM_MTX_12_13_MTX_12(x)                     ((x) << 0)
+
+#define ISP_CCM_MTX_20_21                               0x60A8
+#define ISP_CCM_MTX_20_21_MTX_20_MASK                   GENMASK(12, 0)
+#define ISP_CCM_MTX_20_21_MTX_20(x)                     ((x) << 0)
+#define ISP_CCM_MTX_20_21_MTX_21_MASK                   GENMASK(28, 16)
+#define ISP_CCM_MTX_20_21_MTX_21(x)                     ((x) << 16)
+
+#define ISP_CCM_MTX_22_23_RS                            0x60Ac
+#define ISP_CCM_MTX_22_23_RS_MTX_22_MASK                GENMASK(12, 0)
+#define ISP_CCM_MTX_22_23_RS_MTX_22(x)                  ((x) << 0)
+
+#define ISP_PST_GAMMA_LUT_ADDR                          0x60cc
+#define ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(x)              ((x) << 7)
+
+#define ISP_PST_GAMMA_LUT_DATA                          0x60d0
+#define ISP_PST_GAMMA_LUT_DATA_DATA0(x)                 (((x) & GENMASK(15, 0)) << 0)
+#define ISP_PST_GAMMA_LUT_DATA_DATA1(x)                 (((x) & GENMASK(15, 0)) << 16)
+
+#define DISP0_TOP_TOP_CTRL                              0x8000
+#define DISP0_TOP_TOP_CTRL_CROP2_EN_MASK                BIT(5)
+#define DISP0_TOP_TOP_CTRL_CROP2_EN                     BIT(5)
+#define DISP0_TOP_TOP_CTRL_CROP2_DIS                    (0 << 5)
+
+#define DISP0_TOP_CRP2_START                            0x8004
+#define DISP0_TOP_CRP2_START_V_START_MASK               GENMASK(15, 0)
+#define DISP0_TOP_CRP2_START_V_START(x)                 ((x) << 0)
+#define DISP0_TOP_CRP2_START_H_START_MASK               GENMASK(31, 16)
+#define DISP0_TOP_CRP2_START_H_START(x)                 ((x) << 16)
+
+#define DISP0_TOP_CRP2_SIZE                             0x8008
+#define DISP0_TOP_CRP2_SIZE_V_SIZE_MASK                 GENMASK(15, 0)
+#define DISP0_TOP_CRP2_SIZE_V_SIZE(x)                   ((x) << 0)
+#define DISP0_TOP_CRP2_SIZE_H_SIZE_MASK                 GENMASK(31, 16)
+#define DISP0_TOP_CRP2_SIZE_H_SIZE(x)                   ((x) << 16)
+
+#define DISP0_TOP_OUT_SIZE                              0x800c
+#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT_MASK       GENMASK(12, 0)
+#define DISP0_TOP_OUT_SIZE_SCALER_OUT_HEIGHT(x)         ((x) << 0)
+#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH_MASK        GENMASK(28, 16)
+#define DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(x)          ((x) << 16)
+
+#define ISP_DISP0_TOP_IN_SIZE                           0x804c
+#define ISP_DISP0_TOP_IN_SIZE_VSIZE_MASK                GENMASK(12, 0)
+#define ISP_DISP0_TOP_IN_SIZE_VSIZE(x)                  ((x) << 0)
+#define ISP_DISP0_TOP_IN_SIZE_HSIZE_MASK                GENMASK(28, 16)
+#define ISP_DISP0_TOP_IN_SIZE_HSIZE(x)                  ((x) << 16)
+
+#define DISP0_PPS_SCALE_EN                              0x8200
+#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK             GENMASK(3, 0)
+#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM(x)               ((x) << 0)
+#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM_MASK             GENMASK(7, 4)
+#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM(x)               ((x) << 4)
+#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK          GENMASK(11, 8)
+#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(x)            ((x) << 8)
+#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM_MASK          GENMASK(15, 12)
+#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM(x)            ((x) << 12)
+#define DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK             GENMASK(17, 16)
+#define DISP0_PPS_SCALE_EN_PREVSC_RATE(x)               ((x) << 16)
+#define DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK             GENMASK(19, 18)
+#define DISP0_PPS_SCALE_EN_PREHSC_RATE(x)               ((x) << 18)
+#define DISP0_PPS_SCALE_EN_HSC_EN_MASK                  BIT(20)
+#define DISP0_PPS_SCALE_EN_HSC_EN(x)                    ((x) << 20)
+#define DISP0_PPS_SCALE_EN_HSC_DIS                      (0 << 20)
+#define DISP0_PPS_SCALE_EN_VSC_EN_MASK                  BIT(21)
+#define DISP0_PPS_SCALE_EN_VSC_EN(x)                    ((x) << 21)
+#define DISP0_PPS_SCALE_EN_VSC_DIS                      (0 << 21)
+#define DISP0_PPS_SCALE_EN_PREVSC_EN_MASK               BIT(22)
+#define DISP0_PPS_SCALE_EN_PREVSC_EN(x)                 ((x) << 22)
+#define DISP0_PPS_SCALE_EN_PREVSC_DIS                   (0 << 22)
+#define DISP0_PPS_SCALE_EN_PREHSC_EN_MASK               BIT(23)
+#define DISP0_PPS_SCALE_EN_PREHSC_EN(x)                 ((x) << 23)
+#define DISP0_PPS_SCALE_EN_PREHSC_DIS                   (0 << 23)
+#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK         GENMASK(27, 24)
+#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(x)           ((x) << 24)
+#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK         GENMASK(31, 28)
+#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(x)           ((x) << 28)
+
+#define DISP0_PPS_VSC_START_PHASE_STEP                  0x8224
+#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK   GENMASK(23, 0)
+#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(x)     ((x) << 0)
+#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK   GENMASK(27, 24)
+#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(x)     ((x) << 24)
+
+#define DISP0_PPS_HSC_START_PHASE_STEP                  0x8230
+#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK  GENMASK(23, 0)
+#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(x)    ((x) << 0)
+#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK  GENMASK(27, 24)
+#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(x)    ((x) << 24)
+
+#define DISP0_PPS_444TO422                              0x823c
+#define DISP0_PPS_444TO422_EN_MASK                      BIT(0)
+#define DISP0_PPS_444TO422_EN(x)                        ((x) << 0)
+
+#define ISP_SCALE0_COEF_IDX_LUMA                        0x8240
+#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_MASK     BIT(9)
+#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN       BIT(9)
+#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_DIS      (0 << 9)
+#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE_MASK             GENMASK(12, 10)
+#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE(x)               ((x) << 10)
+
+#define ISP_SCALE0_COEF_LUMA                            0x8244
+#define ISP_SCALE0_COEF_LUMA_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
+#define ISP_SCALE0_COEF_LUMA_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
+
+#define ISP_SCALE0_COEF_IDX_CHRO                        0x8248
+#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_MASK     BIT(9)
+#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN       BIT(9)
+#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_DIS      (0 << 9)
+#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE_MASK             GENMASK(12, 10)
+#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE(x)               ((x) << 10)
+
+#define ISP_SCALE0_COEF_CHRO                            0x824c
+#define ISP_SCALE0_COEF_CHRO_DATA1(x)                   (((x) & GENMASK(10, 0)) << 0)
+#define ISP_SCALE0_COEF_CHRO_DATA0(x)                   (((x) & GENMASK(10, 0)) << 16)
+
+#define ISP_AF_CTRL                                     0xa044
+#define ISP_AF_CTRL_VERT_OFST_MASK                      GENMASK(15, 14)
+#define ISP_AF_CTRL_VERT_OFST(x)                        ((x) << 14)
+#define ISP_AF_CTRL_HORIZ_OFST_MASK                     GENMASK(17, 16)
+#define ISP_AF_CTRL_HORIZ_OFST(x)                       ((x) << 16)
+
+#define ISP_AF_HV_SIZE                                  0xa04c
+#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE_MASK               GENMASK(15, 0)
+#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE(x)                 ((x) << 0)
+#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE_MASK               GENMASK(31, 16)
+#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE(x)                 ((x) << 16)
+
+#define ISP_AF_HV_BLKNUM                                0xa050
+#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(5, 0)
+#define ISP_AF_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
+#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(21, 16)
+#define ISP_AF_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
+
+#define ISP_AF_EN_CTRL                                  0xa054
+#define ISP_AF_EN_CTRL_STAT_SEL_MASK                    BIT(21)
+#define ISP_AF_EN_CTRL_STAT_SEL_OLD                     (0 << 21)
+#define ISP_AF_EN_CTRL_STAT_SEL_NEW                     BIT(21)
+
+#define ISP_AF_IDX_ADDR                                 0xa1c0
+#define ISP_AF_IDX_DATA                                 0xa1c4
+#define ISP_AF_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
+#define ISP_AF_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
+
+#define ISP_AE_CTRL                                     0xa448
+#define ISP_AE_CTRL_INPUT_2LINE_MASK                    BIT(7)
+#define ISP_AE_CTRL_INPUT_2LINE_EN                      BIT(7)
+#define ISP_AE_CTRL_INPUT_2LINE_DIS                     (0 << 7)
+#define ISP_AE_CTRL_LUMA_MODE_MASK                      GENMASK(9, 8)
+#define ISP_AE_CTRL_LUMA_MODE_CUR                       (0 << 8)
+#define ISP_AE_CTRL_LUMA_MODE_MAX                       BIT(8)
+#define ISP_AE_CTRL_LUMA_MODE_FILTER                    (2 << 8)
+#define ISP_AE_CTRL_VERT_OFST_MASK                      GENMASK(25, 24)
+#define ISP_AE_CTRL_VERT_OFST(x)                        ((x) << 24)
+#define ISP_AE_CTRL_HORIZ_OFST_MASK                     GENMASK(27, 26)
+#define ISP_AE_CTRL_HORIZ_OFST(x)                       ((x) << 26)
+
+#define ISP_AE_HV_SIZE                                  0xa464
+#define ISP_AE_HV_SIZE_VERT_SIZE_MASK                   GENMASK(15, 0)
+#define ISP_AE_HV_SIZE_VERT_SIZE(x)                     ((x) << 0)
+#define ISP_AE_HV_SIZE_HORIZ_SIZE_MASK                  GENMASK(31, 16)
+#define ISP_AE_HV_SIZE_HORIZ_SIZE(x)                    ((x) << 16)
+
+#define ISP_AE_HV_BLKNUM                                0xa468
+#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM_MASK              GENMASK(6, 0)
+#define ISP_AE_HV_BLKNUM_BLK_VERT_NUM(x)                ((x) << 0)
+#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM_MASK             GENMASK(22, 16)
+#define ISP_AE_HV_BLKNUM_BLK_HORIZ_NUM(x)               ((x) << 16)
+
+#define ISP_AE_IDX_ADDR                                 0xa600
+#define ISP_AE_IDX_DATA                                 0xa604
+#define ISP_AE_IDX_DATA_VIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 0)
+#define ISP_AE_IDX_DATA_HIDX_DATA(x)                    (((x) & GENMASK(15, 0)) << 16)
+
+#define ISP_AE_BLK_WT_ADDR                              0xa608
+#define ISP_AE_BLK_WT_DATA                              0xa60c
+#define ISP_AE_BLK_WT_DATA_IDX_DATA(i, x)               (((x) & GENMASK(3, 0)) << ((i) * 4))
+
+#define ISP_AWB_CTRL                                    0xa834
+#define ISP_AWB_CTRL_VERT_OFST_MASK                     GENMASK(1, 0)
+#define ISP_AWB_CTRL_VERT_OFST(x)                       ((x) << 0)
+#define ISP_AWB_CTRL_HORIZ_OFST_MASK                    GENMASK(3, 2)
+#define ISP_AWB_CTRL_HORIZ_OFST(x)                      ((x) << 2)
+
+#define ISP_AWB_HV_SIZE                                 0xa83c
+#define ISP_AWB_HV_SIZE_VERT_SIZE_MASK                  GENMASK(15, 0)
+#define ISP_AWB_HV_SIZE_VERT_SIZE(x)                    ((x) << 0)
+#define ISP_AWB_HV_SIZE_HORIZ_SIZE_MASK                 GENMASK(31, 16)
+#define ISP_AWB_HV_SIZE_HORIZ_SIZE(x)                   ((x) << 16)
+
+#define ISP_AWB_HV_BLKNUM                               0xa840
+#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM_MASK            GENMASK(5, 0)
+#define ISP_AWB_HV_BLKNUM_VERT_BLKS_NUM(x)              ((x) << 0)
+#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM_MASK           GENMASK(21, 16)
+#define ISP_AWB_HV_BLKNUM_HORIZ_BLKS_NUM(x)             ((x) << 16)
+
+#define ISP_AWB_STAT_RG                                 0xa848
+#define ISP_AWB_STAT_RG_MIN_VALUE_MASK                  GENMASK(11, 0)
+#define ISP_AWB_STAT_RG_MIN_VALUE(x)                    ((x) << 0)
+#define ISP_AWB_STAT_RG_MAX_VALUE_MASK                  GENMASK(27, 16)
+#define ISP_AWB_STAT_RG_MAX_VALUE(x)                    ((x) << 16)
+
+#define ISP_AWB_STAT_BG                                 0xa84c
+#define ISP_AWB_STAT_BG_MIN_VALUE_MASK                  GENMASK(11, 0)
+#define ISP_AWB_STAT_BG_MIN_VALUE(x)                    ((x) << 0)
+#define ISP_AWB_STAT_BG_MAX_VALUE_MASK                  GENMASK(27, 16)
+#define ISP_AWB_STAT_BG_MAX_VALUE(x)                    ((x) << 16)
+
+#define ISP_AWB_STAT_RG_HL                              0xa850
+#define ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
+#define ISP_AWB_STAT_RG_HL_LOW_VALUE(x)                 ((x) << 0)
+#define ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
+#define ISP_AWB_STAT_RG_HL_HIGH_VALUE(x)                ((x) << 16)
+
+#define ISP_AWB_STAT_BG_HL                              0xa854
+#define ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK               GENMASK(11, 0)
+#define ISP_AWB_STAT_BG_HL_LOW_VALUE(x)                 ((x) << 0)
+#define ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK              GENMASK(27, 16)
+#define ISP_AWB_STAT_BG_HL_HIGH_VALUE(x)                ((x) << 16)
+
+#define ISP_AWB_STAT_CTRL2                              0xa858
+#define ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK              BIT(0)
+#define ISP_AWB_STAT_CTRL2_SATUR_CTRL(x)                ((x) << 0)
+
+#define ISP_AWB_IDX_ADDR                                0xaa00
+#define ISP_AWB_IDX_DATA                                0xaa04
+#define ISP_AWB_IDX_DATA_VIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 0)
+#define ISP_AWB_IDX_DATA_HIDX_DATA(x)                   (((x) & GENMASK(15, 0)) << 16)
+
+#define ISP_AWB_BLK_WT_ADDR                             0xaa08
+#define ISP_AWB_BLK_WT_DATA                             0xaa0c
+#define ISP_AWB_BLK_WT_DATA_IDX_DATA(i, x)              (((x) & GENMASK(3, 0)) << ((i) * 4))
+
+#define ISP_WRMIFX3_0_CH0_CTRL0                         0xc400
+#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK             GENMASK(28, 16)
+#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(x)               ((x) << 16)
+
+#define ISP_WRMIFX3_0_CH0_CTRL1                         0xc404
+#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
+#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS          BIT(27)
+#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS         (2 << 27)
+
+#define ISP_WRMIFX3_0_CH1_CTRL0                         0xc408
+#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK             GENMASK(28, 16)
+#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(x)               ((x) << 16)
+
+#define ISP_WRMIFX3_0_CH1_CTRL1                         0xc40c
+#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK      GENMASK(30, 27)
+#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS         (2 << 27)
+#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_32BITS         (3 << 27)
+
+#define ISP_WRMIFX3_0_CH0_BADDR                         0xc440
+#define ISP_WRMIFX3_0_CH0_BASE_ADDR(x)                  ((x) >> 4)
+
+#define ISP_WRMIFX3_0_CH1_BADDR                         0xc444
+#define ISP_WRMIFX3_0_CH1_BASE_ADDR(x)                  ((x) >> 4)
+
+#define ISP_WRMIFX3_0_FMT_SIZE                          0xc464
+#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE_MASK               GENMASK(15, 0)
+#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE(x)                 ((x) << 0)
+#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE_MASK               GENMASK(31, 16)
+#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE(x)                 ((x) << 16)
+
+#define ISP_WRMIFX3_0_FMT_CTRL                          0xc468
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK           GENMASK(1, 0)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT           (0 << 0)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_10BIT          BIT(0)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_12BIT          (2 << 0)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT          (3 << 0)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK         BIT(2)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU           (0 << 2)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV           BIT(2)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK           GENMASK(5, 4)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1             (0 << 4)
+#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2             BIT(4)
+#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK            GENMASK(18, 16)
+#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422          BIT(16)
+#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420          (2 << 16)
+#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY          (3 << 16)
+
+#define ISP_WRMIFX3_0_WIN_LUMA_H                        0xc420
+#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK         GENMASK(28, 16)
+#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(x)           (((x) - 1) << 16)
+
+#define ISP_WRMIFX3_0_WIN_LUMA_V                        0xc424
+#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK         GENMASK(28, 16)
+#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(x)           (((x) - 1) << 16)
+
+#define ISP_WRMIFX3_0_WIN_CHROM_H                       0xc428
+#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK       GENMASK(28, 16)
+#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(x)         (((x) - 1) << 16)
+
+#define ISP_WRMIFX3_0_WIN_CHROM_V                       0xc42c
+#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK       GENMASK(28, 16)
+#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(x)         (((x) - 1) << 16)
+
+#define VIU_DMAWR_BADDR0                                0xc840
+#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
+#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(x)          ((x) >> 4)
+
+#define VIU_DMAWR_BADDR1                                0xc844
+#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK       GENMASK(27, 0)
+#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(x)         ((x) >> 4)
+
+#define VIU_DMAWR_BADDR2                                0xc848
+#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK        GENMASK(27, 0)
+#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(x)          ((x) >> 4)
+
+#define VIU_DMAWR_SIZE0                                 0xc854
+#define VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK              GENMASK(15, 0)
+#define VIU_DMAWR_SIZE0_AF_STATS_SIZE(x)                ((x) << 0)
+#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK             GENMASK(31, 16)
+#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE(x)               ((x) << 16)
+
+#define VIU_DMAWR_SIZE1                                 0xc858
+#define VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK              GENMASK(15, 0)
+#define VIU_DMAWR_SIZE1_AE_STATS_SIZE(x)                ((x) << 0)
+
+#endif
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
new file mode 100644
index 000000000000..b179eff4cfd6
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c
@@ -0,0 +1,779 @@ 
+// 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"
+
+/* The normal parameters of pps module */
+static const int pps_lut_tap4_s11[C3_ISP_PPS_TAP4_S11_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 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 step_h_integer, step_v_integer;
+	int step_h_fraction, step_v_fraction;
+	int yuv444to422_en;
+
+	/* Calculate the integer part of horizonal scaler step */
+	step_h_integer = thsize / ohsize;
+
+	/* Calculate the vertical part of horizonal scaler step */
+	step_v_integer = 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.
+	 */
+	step_h_fraction = ((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.
+	 */
+	step_v_fraction = ((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_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
+			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK,
+			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(step_v_fraction));
+	c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
+			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK,
+			   DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(step_v_integer));
+
+	c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
+			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK,
+			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(step_h_fraction));
+	c3_isp_update_bits(rsz->isp, C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
+			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK,
+			   DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(step_h_integer));
+}
+
+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_TAP4_S11_H_NUM; i++) {
+		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
+			     ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
+			     ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
+		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
+			     ISP_SCALE0_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
+			     ISP_SCALE0_COEF_LUMA_DATA1(pps_lut_tap4_s11[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_TAP4_S11_H_NUM; i++) {
+		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
+			     ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
+			     ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
+		c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
+			     ISP_SCALE0_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
+			     ISP_SCALE0_COEF_CHRO_DATA1(pps_lut_tap4_s11[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_RESIZER_PAD_SINK);
+	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
+
+	ihsize = crop->width;
+	ivsize = crop->height;
+
+	hsc_en = (ihsize == cmps->width) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_EN;
+	vsc_en = (ivsize == cmps->height) ? C3_ISP_SCALE_DIS : C3_ISP_SCALE_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_SCALE_EN : C3_ISP_SCALE_DIS;
+	prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
+
+	if (rsz->id == C3_ISP_RSZ_2) {
+		max_hsize = C3_ISP_MAX_WIDTH;
+		/*
+		 * Set vertical tap number and
+		 * the max hsize of pre-vertical scale.
+		 */
+		prevsc_flt_num = 4;
+		pre_vscale_max_hsize = max_hsize / 2;
+	} else {
+		max_hsize = C3_ISP_DEFAULT_WIDTH;
+		preh_en  = (ihsize > max_hsize) ? C3_ISP_SCALE_EN : C3_ISP_SCALE_DIS;
+		/*
+		 * Set vertical tap number and
+		 * the max hsize of pre-vertical scale.
+		 */
+		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;
+	struct v4l2_rect *sink_crop;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
+	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
+
+	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);
+
+	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_SCALER_OUT_HEIGHT_MASK,
+			   DISP0_TOP_OUT_SIZE_SCALER_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_SCALER_OUT_WIDTH_MASK,
+			   DISP0_TOP_OUT_SIZE_SCALER_OUT_WIDTH(src_fmt->width));
+
+	if (rsz->id == C3_ISP_RSZ_0) {
+		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
+				   ISP_TOP_PATH_EN_DISP0_EN);
+	} else if (rsz->id == C3_ISP_RSZ_1) {
+		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
+				   ISP_TOP_PATH_EN_DISP1_EN);
+	} else if (rsz->id == C3_ISP_RSZ_2) {
+		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
+				   ISP_TOP_PATH_EN_DISP2_EN);
+	}
+}
+
+static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
+{
+	if (rsz->id == C3_ISP_RSZ_0) {
+		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP0_EN_MASK,
+				   ISP_TOP_PATH_EN_DISP0_DIS);
+	} else if (rsz->id == C3_ISP_RSZ_1) {
+		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP1_EN_MASK,
+				   ISP_TOP_PATH_EN_DISP1_DIS);
+	} else if (rsz->id == C3_ISP_RSZ_2) {
+		c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, ISP_TOP_PATH_EN_DISP2_EN_MASK,
+				   ISP_TOP_PATH_EN_DISP2_DIS);
+	}
+
+	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 bool c3_isp_rsz_streams_ready(struct c3_isp_resizer *rsz)
+{
+	unsigned int n_links = 0;
+	struct media_link *link;
+
+	for_each_media_entity_data_link(&rsz->src_sd->entity, link) {
+		if (link->source->index == rsz->src_sd_pad &&
+		    link->flags == MEDIA_LNK_FL_ENABLED)
+			n_links++;
+	}
+
+	return n_links == rsz->isp->pipe.start_count;
+}
+
+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);
+	u64 sink_streams;
+	int ret;
+
+	c3_isp_rsz_start(rsz, state);
+
+	if (!c3_isp_rsz_streams_ready(rsz))
+		return 0;
+
+	c3_isp_params_pre_cfg(rsz->isp);
+	c3_isp_stats_pre_cfg(rsz->isp);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       C3_ISP_RESIZER_PAD_SINK,
+						       &streams_mask);
+	ret = v4l2_subdev_enable_streams(rsz->src_sd,
+					 rsz->src_sd_pad, sink_streams);
+	if (ret)
+		return ret;
+
+	return 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);
+	u64 sink_streams;
+	int ret;
+
+	c3_isp_rsz_stop(rsz);
+
+	if (rsz->isp->pipe.start_count != 1)
+		return 0;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       C3_ISP_RESIZER_PAD_SINK,
+						       &streams_mask);
+	ret = v4l2_subdev_disable_streams(rsz->src_sd,
+					  rsz->src_sd_pad, sink_streams);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = C3_ISP_DEFAULT_WIDTH,
+		.height = C3_ISP_DEFAULT_HEIGHT,
+		.code = C3_ISP_RSZ_DEF_PAD_FMT,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes;
+	struct v4l2_subdev_krouting routing;
+
+	routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
+	routes.sink_stream = 0;
+	routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
+	routes.source_stream = 0;
+	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routing.num_routes = 1;
+	routing.routes = &routes;
+
+	return c3_isp_rsz_cfg_routing(sd, state, &routing);
+}
+
+static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_YUV8_1X24;
+
+	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;
+
+	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_RESIZER_PAD_SOURCE);
+
+	sink_fmt->code = MEDIA_BUS_FMT_YUV8_1X24;
+	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_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;
+
+	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
+	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
+
+	src_fmt->code = sink_fmt->code;
+	src_fmt->width = sink_cmps->width;
+	src_fmt->height = sink_cmps->height;
+
+	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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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_RESIZER_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 *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+	struct v4l2_rect *crop;
+	struct v4l2_rect *cmps;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
+	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
+	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
+	sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+	sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
+	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_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_RESIZER_PAD_SINK);
+	cmps->width = C3_ISP_DEFAULT_WIDTH;
+	cmps->height = C3_ISP_DEFAULT_HEIGHT;
+	cmps->left = 0;
+	cmps->top = 0;
+
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
+	*src_fmt = *sink_fmt;
+
+	return c3_isp_rsz_init_routing(sd, state);
+}
+
+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_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_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)
+{
+	u32 i;
+	int ret;
+
+	for (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_sd_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
+
+		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)
+{
+	u32 i;
+
+	for (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);
+	};
+}
diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
new file mode 100644
index 000000000000..1c376c800176
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c
@@ -0,0 +1,319 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#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-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+/* Hardware configuration */
+
+static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
+{
+	u32 awb_dma_size = sizeof(struct c3_isp_awb_stats);
+	u32 ae_dma_size = sizeof(struct c3_isp_ae_stats);
+	u32 awb_dma_addr = stats->buff->dma_addr;
+	u32 af_dma_addr;
+	u32 ae_dma_addr;
+
+	ae_dma_addr = awb_dma_addr + awb_dma_size;
+	af_dma_addr = ae_dma_addr + ae_dma_size;
+
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR0,
+			   VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK,
+			   VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(af_dma_addr));
+
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR1,
+			   VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK,
+			   VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(awb_dma_addr));
+
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR2,
+			   VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK,
+			   VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(ae_dma_addr));
+}
+
+static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
+{
+	stats->buff = list_first_entry_or_null(&stats->pending,
+					       struct c3_isp_buffer, list);
+	if (stats->buff) {
+		c3_isp_stats_cfg_dmawr_addr(stats);
+		list_del(&stats->buff->list);
+	}
+}
+
+void c3_isp_stats_pre_cfg(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+	u32 dma_size;
+
+	c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, ISP_AF_EN_CTRL_STAT_SEL_MASK,
+			   ISP_AF_EN_CTRL_STAT_SEL_NEW);
+	c3_isp_update_bits(stats->isp, ISP_AE_CTRL, ISP_AE_CTRL_LUMA_MODE_MASK,
+			   ISP_AE_CTRL_LUMA_MODE_FILTER);
+
+	/* The unit of dma_size is 16 bytes */
+	dma_size = sizeof(struct c3_isp_af_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK,
+			   VIU_DMAWR_SIZE0_AF_STATS_SIZE(dma_size));
+
+	dma_size = sizeof(struct c3_isp_awb_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK,
+			   VIU_DMAWR_SIZE0_AWB_STATS_SIZE(dma_size));
+
+	dma_size = sizeof(struct c3_isp_ae_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES;
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK,
+			   VIU_DMAWR_SIZE1_AE_STATS_SIZE(dma_size));
+
+	guard(spinlock_irqsave)(&stats->buff_lock);
+
+	c3_isp_stats_cfg_buff(stats);
+}
+
+static int c3_isp_stats_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_stats_enum_fmt(struct file *file, void *fh,
+				 struct v4l2_fmtdesc *f)
+{
+	struct c3_isp_stats *stats = video_drvdata(file);
+
+	if (f->index > 0 || f->type != stats->vb2_q.type)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
+
+	return 0;
+}
+
+static int c3_isp_stats_g_fmt(struct file *file, void *fh,
+			      struct v4l2_format *f)
+{
+	struct c3_isp_stats *stats = video_drvdata(file);
+
+	f->fmt.meta = stats->vfmt.fmt.meta;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
+	.vidioc_querycap                = c3_isp_stats_querycap,
+	.vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
+	.vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
+	.vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
+	.vidioc_try_fmt_meta_cap        = c3_isp_stats_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_stats_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_stats_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_stats_buffer))
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = sizeof(struct c3_isp_stats_buffer);
+	}
+
+	return 0;
+}
+
+static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_buffer, vb);
+	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
+
+	guard(spinlock_irqsave)(&stats->buff_lock);
+
+	list_add_tail(&buf->list, &stats->pending);
+}
+
+static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned int size = stats->vfmt.fmt.meta.buffersize;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(stats->isp->dev,
+			"User buffer too small (%ld < %u)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_buffer, vb);
+
+	buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+	return 0;
+}
+
+static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
+
+	guard(spinlock_irqsave)(&stats->buff_lock);
+
+	if (stats->buff) {
+		vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		stats->buff = NULL;
+	}
+
+	while (!list_empty(&stats->pending)) {
+		struct c3_isp_buffer *buff;
+
+		buff = list_first_entry(&stats->pending,
+					struct c3_isp_buffer, list);
+		list_del(&buff->list);
+		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+}
+
+static const struct vb2_ops isp_stats_vb2_ops = {
+	.queue_setup = c3_isp_stats_vb2_queue_setup,
+	.buf_queue = c3_isp_stats_vb2_buf_queue,
+	.buf_prepare = c3_isp_stats_vb2_buf_prepare,
+	.buf_init = c3_isp_stats_vb2_buf_init,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.stop_streaming = c3_isp_stats_vb2_stop_streaming,
+};
+
+int c3_isp_stats_register(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+	struct video_device *vdev = &stats->vdev;
+	struct vb2_queue *vb2_q = &stats->vb2_q;
+	int ret;
+
+	memset(stats, 0, sizeof(*stats));
+	stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
+	stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_buffer);
+	stats->isp = isp;
+	INIT_LIST_HEAD(&stats->pending);
+	spin_lock_init(&stats->buff_lock);
+
+	mutex_init(&stats->lock);
+
+	snprintf(vdev->name, sizeof(vdev->name), "c3-isp-stats");
+	vdev->fops = &isp_stats_v4l2_fops;
+	vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
+	vdev->v4l2_dev = &isp->v4l2_dev;
+	vdev->lock = &stats->lock;
+	vdev->minor = -1;
+	vdev->queue = vb2_q;
+	vdev->release = video_device_release_empty;
+	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_RX;
+	video_set_drvdata(vdev, stats);
+
+	vb2_q->drv_priv = stats;
+	vb2_q->mem_ops = &vb2_dma_contig_memops;
+	vb2_q->ops = &isp_stats_vb2_ops;
+	vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
+	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_buffer);
+	vb2_q->dev = isp->dev;
+	vb2_q->lock = &stats->lock;
+	vb2_q->min_queued_buffers = 2;
+
+	ret = vb2_queue_init(vb2_q);
+	if (ret)
+		goto err_destroy;
+
+	stats->pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
+	if (ret)
+		goto err_queue_release;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		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_destroy:
+	mutex_destroy(&stats->lock);
+	return ret;
+}
+
+void c3_isp_stats_unregister(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+
+	vb2_queue_release(&stats->vb2_q);
+	media_entity_cleanup(&stats->vdev.entity);
+	video_unregister_device(&stats->vdev);
+	mutex_destroy(&stats->lock);
+}
+
+void c3_isp_stats_isr(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+
+	guard(spinlock_irqsave)(&stats->buff_lock);
+
+	if (stats->buff) {
+		stats->buff->vb.sequence = stats->isp->frm_sequence;
+		stats->buff->vb.vb2_buf.timestamp = ktime_get();
+		stats->buff->vb.field = V4L2_FIELD_NONE;
+		vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+
+	c3_isp_stats_cfg_buff(stats);
+}