public inbox for linux-media@vger.kernel.org
 help / color / mirror / Atom feed
From: Bryan O'Donoghue <bod@kernel.org>
To: Loic Poulain <loic.poulain@oss.qualcomm.com>,
	vladimir.zapolskiy@linaro.org, laurent.pinchart@ideasonboard.com,
	kieran.bingham@ideasonboard.com, robh@kernel.org,
	krzk+dt@kernel.org, andersson@kernel.org, konradybcio@kernel.org
Cc: linux-media@vger.kernel.org, linux-arm-msm@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	johannes.goede@oss.qualcomm.com, mchehab@kernel.org
Subject: Re: [RFC PATCH 2/3] media: qcom: camss: Add CAMSS Offline Processing Engine driver
Date: Mon, 23 Mar 2026 13:43:33 +0000	[thread overview]
Message-ID: <1ba54ec0-be51-4694-a79b-f272e76303d2@kernel.org> (raw)
In-Reply-To: <20260323125824.211615-3-loic.poulain@oss.qualcomm.com>

On 23/03/2026 12:58, Loic Poulain wrote:
> Provide a initial implementation for the Qualcomm Offline Processing
> Engine (OPE). OPE is a memory-to-memory hardware block designed for
> image processing on a source frame. Typically, the input frame
> originates from the SoC CSI capture path, though not limited to.
> 
> The hardware architecture consists of Fetch Engines and Write Engines,
> connected through intermediate pipeline modules:
>     [FETCH ENGINES] => [Pipeline Modules] => [WRITE ENGINES]
> 
> Current Configuration:
>      Fetch Engine: One fetch engine is used for Bayer frame input.
>      Write Engines: Two display write engines for Y and UV planes output.
> 
> Enabled Pipeline Modules:
>     CLC_WB: White balance (channel gain configuration)
>     CLC_DEMO: Demosaic (Bayer to RGB conversion)
>     CLC_CHROMA_ENHAN: RGB to YUV conversion
>     CLC_DOWNSCALE*: Downscaling for UV and Y planes
> 
> Default configuration values are based on public standards such as BT.601.
> 
> Processing Model:
> OPE processes frames in stripes of up to 336 pixels. Therefore, frames must
> be split into stripes for processing. Each stripe is configured after the
> previous one has been acquired (double buffered registers). To minimize
> inter-stripe latency, stripe configurations are generated ahead of time.

A yavata command set showing usage would be appreciated.

> 
> Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> ---
>   drivers/media/platform/qcom/camss/Makefile    |    4 +
>   drivers/media/platform/qcom/camss/camss-ope.c | 2058 +++++++++++++++++
>   2 files changed, 2062 insertions(+)
>   create mode 100644 drivers/media/platform/qcom/camss/camss-ope.c
> 
> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
> index 5e349b491513..67f261ae0855 100644
> --- a/drivers/media/platform/qcom/camss/Makefile
> +++ b/drivers/media/platform/qcom/camss/Makefile
> @@ -29,3 +29,7 @@ qcom-camss-objs += \
>   		camss-format.o \
> 
>   obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
> +
> +qcom-camss-ope-objs += camss-ope.o
> +
> +obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss-ope.o

Needs a Kconfig entry.

> diff --git a/drivers/media/platform/qcom/camss/camss-ope.c b/drivers/media/platform/qcom/camss/camss-ope.c
> new file mode 100644
> index 000000000000..f45a16437b6d
> --- /dev/null
> +++ b/drivers/media/platform/qcom/camss/camss-ope.c
> @@ -0,0 +1,2058 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * camss-ope.c
> + *
> + * Qualcomm MSM Camera Subsystem - Offline Processing Engine
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +/*
> + * This driver provides a minimal implementation for the Qualcomm Offline
> + * Processing Engine (OPE). OPE is a memory-to-memory hardware block
> + * designed for image processing on a source frame. Typically, the input
> + * frame originates from the SoC CSI capture path, though not limited to.
> + *
> + * The hardware architecture consists of Fetch Engines and Write Engines,
> + * connected through intermediate pipeline modules:
> + *   [FETCH ENGINES] => [Pipeline Modules] => [WRITE ENGINES]
> + *
> + * Current Configuration:
> + *     Fetch Engine: One fetch engine is used for Bayer frame input.
> + *     Write Engines: Two display write engines for Y and UV planes output.
> + *
> + * Only a subset of the pipeline modules are enabled:
> + *   CLC_WB: White balance for channel gain configuration
> + *   CLC_DEMO: Demosaic for Bayer to RGB conversion
> + *   CLC_CHROMA_ENHAN: for RGB to YUV conversion
> + *   CLC_DOWNSCALE*: Downscaling for UV (YUV444 -> YUV422/YUV420) and YUV planes
> + *
> + * Default configuration values are based on public standards such as BT.601.
> + *
> + * Processing Model:
> + * OPE processes frames in stripes of up to 336 pixels. Therefore, frames must
> + * be split into stripes for processing. Each stripe is configured after the
> + * previous one has been acquired (double buffered registers). To minimize
> + * inter-stripe latency, the stripe configurations are generated ahead of time.
> + *
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interconnect.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_clock.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_opp.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <media/media-device.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mem2mem.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#define MEM2MEM_NAME		"qcom-camss-ope"
> +
> +/* TOP registers */
> +#define OPE_TOP_HW_VERSION						0x000
> +#define		OPE_TOP_HW_VERSION_STEP		GENMASK(15, 0)
> +#define		OPE_TOP_HW_VERSION_REV		GENMASK(27, 16)
> +#define		OPE_TOP_HW_VERSION_GEN		GENMASK(31, 28)
> +#define OPE_TOP_RESET_CMD						0x004
> +#define		OPE_TOP_RESET_CMD_HW		BIT(0)
> +#define		OPE_TOP_RESET_CMD_SW		BIT(1)
> +#define		OPE_TOP_CORE_CFG					0x010
> +#define OPE_TOP_IRQ_STATUS						0x014
> +#define OPE_TOP_IRQ_MASK						0x018
> +#define		OPE_TOP_IRQ_STATUS_RST_DONE	BIT(0)
> +#define		OPE_TOP_IRQ_STATUS_WE		BIT(1)
> +#define		OPE_TOP_IRQ_STATUS_FE		BIT(2)
> +#define		OPE_TOP_IRQ_STATUS_VIOL		BIT(3)
> +#define		OPE_TOP_IRQ_STATUS_IDLE		BIT(4)
> +#define OPE_TOP_IRQ_CLEAR						0x01c
> +#define OPE_TOP_IRQ_SET							0x020
> +#define OPE_TOP_IRQ_CMD							0x024
> +#define		OPE_TOP_IRQ_CMD_CLEAR		BIT(0)
> +#define		OPE_TOP_IRQ_CMD_SET		BIT(4)
> +#define OPE_TOP_VIOLATION_STATUS					0x028
> +#define OPE_TOP_DEBUG(i)						(0x0a0 + (i) * 4)
> +#define OPE_TOP_DEBUG_CFG						0x0dc
> +
> +/* Fetch engines */
> +#define OPE_BUS_RD_INPUT_IF_IRQ_MASK					0x00c
> +#define OPE_BUS_RD_INPUT_IF_IRQ_CLEAR					0x010
> +#define OPE_BUS_RD_INPUT_IF_IRQ_CMD					0x014
> +#define		OPE_BUS_RD_INPUT_IF_IRQ_CMD_CLEAR	BIT(0)
> +#define		OPE_BUS_RD_INPUT_IF_IRQ_CMD_SET		BIT(4)
> +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS					0x018
> +#define		OPE_BUS_RD_INPUT_IF_IRQ_STATUS_RST_DONE	BIT(0)
> +#define		OPE_BUS_RD_INPUT_IF_IRQ_STATUS_RUP_DONE	BIT(1)
> +#define		OPE_BUS_RD_INPUT_IF_IRQ_STATUS_BUF_DONE	BIT(2)
> +#define OPE_BUS_RD_INPUT_IF_CMD						0x01c
> +#define		OPE_BUS_RD_INPUT_IF_CMD_GO_CMD		BIT(0)
> +#define OPE_BUS_RD_CLIENT_0_CORE_CFG					0x050
> +#define		OPE_BUS_RD_CLIENT_0_CORE_CFG_EN		BIT(0)
> +#define OPE_BUS_RD_CLIENT_0_CCIF_META_DATA				0x054
> +#define		OPE_BUS_RD_CLIENT_0_CCIF_MD_PIX_PATTERN	GENMASK(7, 2)
> +#define OPE_BUS_RD_CLIENT_0_ADDR_IMAGE						0x058
> +#define OPE_BUS_RD_CLIENT_0_RD_BUFFER_SIZE				0x05c
> +#define OPE_BUS_RD_CLIENT_0_RD_STRIDE					0x060
> +#define OPE_BUS_RD_CLIENT_0_UNPACK_CFG_0				0x064
> +
> +/* Write engines */
> +#define OPE_BUS_WR_INPUT_IF_IRQ_MASK_0					0x018
> +#define OPE_BUS_WR_INPUT_IF_IRQ_MASK_1					0x01c
> +#define OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0					0x020
> +#define OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_1					0x024
> +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0				0x028
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE	BIT(0)
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_BUF_DONE	BIT(8)
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL	BIT(28)
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL		BIT(30)
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL	BIT(31)
> +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_1				0x02c
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_STATUS_1_CLIENT_DONE(c)	BIT(0 + (c))
> +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD					0x030
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR		BIT(0)
> +#define		OPE_BUS_WR_INPUT_IF_IRQ_CMD_SET			BIT(1)
> +#define OPE_BUS_WR_VIOLATION_STATUS					0x064
> +#define OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS				0x070
> +#define OPE_BUS_WR_CLIENT_CFG(c)					(0x200 + (c) * 0x100)
> +#define		OPE_BUS_WR_CLIENT_CFG_EN			BIT(0)
> +#define		OPE_BUS_WR_CLIENT_CFG_AUTORECOVER		BIT(4)
> +#define OPE_BUS_WR_CLIENT_ADDR_IMAGE(c)					(0x204 + (c) * 0x100)
> +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_0(c)				(0x20c + (c) * 0x100)
> +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_1(c)				(0x210 + (c) * 0x100)
> +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_2(c)				(0x214 + (c) * 0x100)
> +#define OPE_BUS_WR_CLIENT_PACKER_CFG(c)					(0x218 + (c) * 0x100)
> +#define OPE_BUS_WR_CLIENT_ADDR_FRAME_HEADER(c)				(0x220 + (c) * 0x100)
> +#define OPE_BUS_WR_CLIENT_MAX	8
> +
> +/* Pipeline modules */
> +#define OPE_PP_CLC_WB_GAIN_MODULE_CFG					(0x200 + 0x60)
> +#define		OPE_PP_CLC_WB_GAIN_MODULE_CFG_EN	BIT(0)
> +#define OPE_PP_CLC_WB_GAIN_WB_CFG(ch)					(0x200 + 0x68 + 4 * (ch))
> +
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_C_PRE_BASE				0x1c00
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_Y_DISP_BASE				0x3000
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_C_DISP_BASE				0x3200
> +#define OPE_PP_CLC_DOWNSCALE_MN_CFG(ds)					((ds) + 0x60)
> +#define		OPE_PP_CLC_DOWNSCALE_MN_CFG_EN			BIT(0)
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG(ds)				((ds) + 0x64)
> +#define		OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_H_SCALE_EN	BIT(9)
> +#define		OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_V_SCALE_EN	BIT(10)
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(ds)			((ds) + 0x68)
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_MN_H_CFG(ds)				((ds) + 0x6c)
> +#define OPE_PP_CLC_DOWNSCALE_MN_DS_MN_V_CFG(ds)				((ds) + 0x74)
> +
> +#define OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG				(0x1200 + 0x60)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG_EN		BIT(0)
> +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0				(0x1200 + 0x68)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V0		GENMASK(11, 0)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V1		GENMASK(27, 16)
> +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1				(0x1200 + 0x6c)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1_K		GENMASK(31, 23)
> +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2				(0x1200 + 0x70)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2_V2		GENMASK(11, 0)
> +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG				(0x1200 + 0x74)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AP		GENMASK(11, 0)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AM		GENMASK(27, 16)
> +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG				(0x1200 + 0x78)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BP		GENMASK(11, 0)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BM		GENMASK(27, 16)
> +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG				(0x1200 + 0x7C)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CP		GENMASK(11, 0)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CM		GENMASK(27, 16)
> +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG				(0x1200 + 0x80)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DP		GENMASK(11, 0)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DM		GENMASK(27, 16)
> +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0				(0x1200 + 0x84)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0_KCB	GENMASK(31, 21)
> +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1				(0x1200 + 0x88)
> +#define		OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1_KCR	GENMASK(31, 21)
> +
> +#define OPE_STRIPE_MAX_W	336
> +#define OPE_STRIPE_MAX_H	8192
> +#define OPE_STRIPE_MIN_W	16
> +#define OPE_STRIPE_MIN_H	OPE_STRIPE_MIN_W
> +#define OPE_MAX_STRIPE		16
> +#define OPE_ALIGN_H		1
> +#define OPE_ALIGN_W		1
> +#define OPE_MIN_W		24
> +#define OPE_MIN_H		16
> +#define OPE_MAX_W		(OPE_STRIPE_MAX_W * OPE_MAX_STRIPE)
> +#define OPE_MAX_H		OPE_STRIPE_MAX_H
> +
> +#define MEM2MEM_CAPTURE		BIT(0)
> +#define MEM2MEM_OUTPUT		BIT(1)
> +
> +#define OPE_RESET_TIMEOUT_MS	100
> +
> +/* Expected framerate for power scaling */
> +#define DEFAULT_FRAMERATE 60
> +
> +/* Downscaler helpers */
> +#define Q21(v) (((uint64_t)(v)) << 21)
> +#define DS_Q21(n, d) ((uint32_t)(((uint64_t)(n) << 21) / (d)))

u64 and u32 here.

> +#define DS_RESOLUTION(input, output) \
> +	(((output) * 128 <= (input)) ? 0x0 : \
> +	((output) * 16 <= (input))  ? 0x1 : \
> +	((output) * 8  <= (input))  ? 0x2 : 0x3)
> +#define DS_OUTPUT_PIX(input, phase_init, phase_step) \
> +		((Q21(input) - (phase_init)) / (phase_step))
> +
> +enum ope_downscaler {
> +	OPE_DS_C_PRE,
> +	OPE_DS_C_DISP,
> +	OPE_DS_Y_DISP,
> +	OPE_DS_MAX,
> +};
> +
> +static const u32 ope_ds_base[OPE_DS_MAX] = { OPE_PP_CLC_DOWNSCALE_MN_DS_C_PRE_BASE,
> +					     OPE_PP_CLC_DOWNSCALE_MN_DS_C_DISP_BASE,
> +					     OPE_PP_CLC_DOWNSCALE_MN_DS_Y_DISP_BASE };
> +
> +enum ope_wr_client {
> +	OPE_WR_CLIENT_VID_Y,
> +	OPE_WR_CLIENT_VID_C,
> +	OPE_WR_CLIENT_DISP_Y,
> +	OPE_WR_CLIENT_DISP_C,
> +	OPE_WR_CLIENT_ARGB,
> +	OPE_WR_CLIENT_MAX
> +};
> +
> +enum ope_pixel_pattern {
> +	OPE_PIXEL_PATTERN_RGRGRG,
> +	OPE_PIXEL_PATTERN_GRGRGR,
> +	OPE_PIXEL_PATTERN_BGBGBG,
> +	OPE_PIXEL_PATTERN_GBGBGB,
> +	OPE_PIXEL_PATTERN_YCBYCR,
> +	OPE_PIXEL_PATTERN_YCRYCB,
> +	OPE_PIXEL_PATTERN_CBYCRY,
> +	OPE_PIXEL_PATTERN_CRYCBY
> +};
> +
> +enum ope_stripe_location {
> +	OPE_STRIPE_LOCATION_FULL,
> +	OPE_STRIPE_LOCATION_LEFT,
> +	OPE_STRIPE_LOCATION_RIGHT,
> +	OPE_STRIPE_LOCATION_MIDDLE
> +};
> +
> +enum ope_unpacker_format {
> +	OPE_UNPACKER_FMT_PLAIN_128,
> +	OPE_UNPACKER_FMT_PLAIN_8,
> +	OPE_UNPACKER_FMT_PLAIN_16_10BPP,
> +	OPE_UNPACKER_FMT_PLAIN_16_12BPP,
> +	OPE_UNPACKER_FMT_PLAIN_16_14BPP,
> +	OPE_UNPACKER_FMT_PLAIN_32_20BPP,
> +	OPE_UNPACKER_FMT_ARGB_16_10BPP,
> +	OPE_UNPACKER_FMT_ARGB_16_12BPP,
> +	OPE_UNPACKER_FMT_ARGB_16_14BPP,
> +	OPE_UNPACKER_FMT_PLAIN_32,
> +	OPE_UNPACKER_FMT_PLAIN_64,
> +	OPE_UNPACKER_FMT_TP_10,
> +	OPE_UNPACKER_FMT_MIPI_8,
> +	OPE_UNPACKER_FMT_MIPI_10,
> +	OPE_UNPACKER_FMT_MIPI_12,
> +	OPE_UNPACKER_FMT_MIPI_14,
> +	OPE_UNPACKER_FMT_PLAIN_16_16BPP,
> +	OPE_UNPACKER_FMT_PLAIN_128_ODD_EVEN,
> +	OPE_UNPACKER_FMT_PLAIN_8_ODD_EVEN
> +};
> +
> +enum ope_packer_format {
> +	OPE_PACKER_FMT_PLAIN_128,
> +	OPE_PACKER_FMT_PLAIN_8,
> +	OPE_PACKER_FMT_PLAIN_8_ODD_EVEN,
> +	OPE_PACKER_FMT_PLAIN_8_10BPP,
> +	OPE_PACKER_FMT_PLAIN_8_10BPP_ODD_EVEN,
> +	OPE_PACKER_FMT_PLAIN_16_10BPP,
> +	OPE_PACKER_FMT_PLAIN_16_12BPP,
> +	OPE_PACKER_FMT_PLAIN_16_14BPP,
> +	OPE_PACKER_FMT_PLAIN_16_16BPP,
> +	OPE_PACKER_FMT_PLAIN_32,
> +	OPE_PACKER_FMT_PLAIN_64,
> +	OPE_PACKER_FMT_TP_10,
> +	OPE_PACKER_FMT_MIPI_10,
> +	OPE_PACKER_FMT_MIPI_12
> +};
> +
> +struct ope_fmt {
> +	u32 fourcc;
> +	unsigned int types;
> +	enum ope_pixel_pattern pattern;
> +	enum ope_unpacker_format unpacker_format;
> +	enum ope_packer_format packer_format;
> +	unsigned int depth;
> +	unsigned int align; /* pix alignment = 2^align */
> +};
> +
> +static const struct ope_fmt formats[] = { /* TODO: add multi-planes formats */
> +	/* Output - Bayer MIPI 10 */
> +	{ V4L2_PIX_FMT_SBGGR10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_BGBGBG,
> +	  OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 },
> +	{ V4L2_PIX_FMT_SGBRG10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GBGBGB,
> +	  OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 },
> +	{ V4L2_PIX_FMT_SGRBG10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GRGRGR,
> +	 OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 },
> +	{ V4L2_PIX_FMT_SRGGB10P, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_RGRGRG,
> +	  OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10, 10, 2 },
> +	/* Output - Bayer MIPI/Plain 8 */
> +	{ V4L2_PIX_FMT_SRGGB8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_RGRGRG,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 },
> +	{ V4L2_PIX_FMT_SBGGR8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_BGBGBG,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 },
> +	{ V4L2_PIX_FMT_SGBRG8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GBGBGB,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 },
> +	{ V4L2_PIX_FMT_SGRBG8, MEM2MEM_OUTPUT, OPE_PIXEL_PATTERN_GRGRGR,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 },
> +	/* Capture - YUV 8-bit per component */
> +	{ V4L2_PIX_FMT_NV24, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_YCBYCR,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 24, 0 },
> +	{ V4L2_PIX_FMT_NV42, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_YCRYCB,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, 24, 0 },
> +	{ V4L2_PIX_FMT_NV16, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 16, 1 },
> +	{ V4L2_PIX_FMT_NV61, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, 16, 1 },
> +	{ V4L2_PIX_FMT_NV12, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 12, 1 },
> +	{ V4L2_PIX_FMT_NV21, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_CBYCRY,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN, 12, 1 },
> +	/* Capture - Greyscale 8-bit */
> +	{ V4L2_PIX_FMT_GREY, MEM2MEM_CAPTURE, OPE_PIXEL_PATTERN_RGRGRG,
> +	  OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8, 8, 0 },
> +};
> +
> +#define OPE_NUM_FORMATS ARRAY_SIZE(formats)
> +
> +#define OPE_WB(n, d) (((n) << 10) / (d))
> +
> +/* Per-queue, driver-specific private data */
> +struct ope_q_data {
> +	unsigned int width;
> +	unsigned int height;
> +	unsigned int bytesperline;
> +	unsigned int sizeimage;
> +	const struct ope_fmt *fmt;
> +	enum v4l2_ycbcr_encoding ycbcr_enc;
> +	enum v4l2_quantization quant;
> +	unsigned int sequence;
> +};
> +
> +struct ope_dev {
> +	struct device *dev;
> +	struct v4l2_device v4l2_dev;
> +	struct video_device vfd;
> +	struct media_device mdev;
> +	struct v4l2_m2m_dev *m2m_dev;
> +
> +	void __iomem *base;
> +	void __iomem *base_rd;
> +	void __iomem *base_wr;
> +	void __iomem *base_pp;
> +
> +	struct completion reset_complete;
> +
> +	struct icc_path *icc_data;
> +	struct icc_path *icc_config;
> +
> +	struct mutex mutex;
> +	struct list_head ctx_list;
> +	void *context;
> +};
> +
> +struct ope_dsc_config {
> +	u32 input_width;
> +	u32 input_height;
> +	u32 output_width;
> +	u32 output_height;
> +	u32 phase_step_h;
> +	u32 phase_step_v;
> +};
> +
> +struct ope_stripe {
> +	struct {
> +		dma_addr_t addr;
> +		u32 width;
> +		u32 height;
> +		u32 stride;
> +		enum ope_stripe_location location;
> +		enum ope_pixel_pattern pattern;
> +		enum ope_unpacker_format format;
> +	} src;
> +	struct {
> +		dma_addr_t addr;
> +		u32 width;
> +		u32 height;
> +		u32 stride;
> +		u32 x_init;
> +		enum ope_packer_format format;
> +		bool enabled;
> +	} dst[OPE_WR_CLIENT_MAX];
> +	struct ope_dsc_config dsc[OPE_DS_MAX];
> +};
> +
> +struct ope_ctx {
> +	struct v4l2_fh fh;
> +	struct ope_dev *ope;
> +
> +	/* Processing mode */
> +	int			mode;
> +	u8			alpha_component;
> +	u8			rotation;
> +	unsigned int framerate;
> +
> +	enum v4l2_colorspace	colorspace;
> +	enum v4l2_xfer_func	xfer_func;
> +
> +	/* Source and destination queue data */
> +	struct ope_q_data   q_data_src;
> +	struct ope_q_data   q_data_dst;
> +
> +	u8 current_stripe;
> +	struct ope_stripe stripe[OPE_MAX_STRIPE];
> +
> +	bool started;
> +
> +	struct list_head list;
> +};
> +
> +struct ope_freq_tbl {
> +	unsigned int load;
> +	unsigned long freq;
> +};
> +
> +static inline char *print_fourcc(u32 fmt)
> +{
> +	static char code[5];
> +
> +	code[0] = (unsigned char)(fmt & 0xff);
> +	code[1] = (unsigned char)((fmt >> 8) & 0xff);
> +	code[2] = (unsigned char)((fmt >> 16) & 0xff);
> +	code[3] = (unsigned char)((fmt >> 24) & 0xff);
> +	code[4] = '\0';
> +
> +	return code;
> +}

This is a bug

> +
> +static inline enum ope_stripe_location ope_stripe_location(unsigned int index,
> +							   unsigned int count)
> +{
> +	if (count == 1)
> +		return OPE_STRIPE_LOCATION_FULL;
> +	if (index == 0)
> +		return OPE_STRIPE_LOCATION_LEFT;
> +	if (index == (count - 1))
> +		return OPE_STRIPE_LOCATION_RIGHT;
> +
> +	return OPE_STRIPE_LOCATION_MIDDLE;
> +}
> +
> +static inline bool ope_stripe_is_last(struct ope_stripe *stripe)
> +{
> +	if (!stripe)
> +		return false;
> +
> +	if (stripe->src.location == OPE_STRIPE_LOCATION_RIGHT ||
> +	    stripe->src.location == OPE_STRIPE_LOCATION_FULL)
> +		return true;
> +
> +	return false;
> +}
> +
> +static inline struct ope_stripe *ope_get_stripe(struct ope_ctx *ctx, unsigned int index)
> +{
> +	return &ctx->stripe[index];
> +}
> +
> +static inline struct ope_stripe *ope_current_stripe(struct ope_ctx *ctx)
> +{
> +	if (!ctx)
> +		return NULL;
> +
> +	if (ctx->current_stripe >= OPE_MAX_STRIPE)
> +		return NULL;
> +
> +	return ope_get_stripe(ctx, ctx->current_stripe);
> +}
> +
> +static inline unsigned int ope_stripe_index(struct ope_ctx *ctx, struct ope_stripe *stripe)
> +{
> +	return stripe - &ctx->stripe[0];
> +}
> +
> +static inline struct ope_stripe *ope_prev_stripe(struct ope_ctx *ctx, struct ope_stripe *stripe)
> +{
> +	unsigned int index = ope_stripe_index(ctx, stripe);
> +
> +	return index ? ope_get_stripe(ctx, index - 1) : NULL;
> +}
> +
> +static inline struct ope_q_data *get_q_data(struct ope_ctx *ctx, enum v4l2_buf_type type)
> +{
> +	if (V4L2_TYPE_IS_OUTPUT(type))
> +		return &ctx->q_data_src;
> +	else
> +		return &ctx->q_data_dst;
> +}
> +
> +static inline unsigned long __q_data_pixclk(struct ope_q_data *q, unsigned int fps)
> +{
> +	return (unsigned long)q->width * q->height * fps;
> +}
> +
> +static inline unsigned int __q_data_load_avg(struct ope_q_data *q, unsigned int fps)
> +{
> +	/* Data load in kBps, calculated from pixel clock and bits per pixel */
> +	return mult_frac(__q_data_pixclk(q, fps), q->fmt->depth, 1000) / 8;
> +}
> +
> +static inline unsigned int __q_data_load_peak(struct ope_q_data *q, unsigned int fps)
> +{
> +	return __q_data_load_avg(q, fps) * 2;
> +}
> +
> +static inline unsigned int __q_data_load_config(struct ope_q_data *q, unsigned int fps)
> +{
> +	unsigned int stripe_count = q->width / OPE_STRIPE_MAX_W + 1;
> +	unsigned int stripe_load = 50 * 4 * fps; /* about 50 x 32-bit registers to configure */
> +
> +	/* Return config load in kBps */
> +	return mult_frac(stripe_count, stripe_load, 1000);
> +}
> +
> +static inline struct ope_ctx *file2ctx(struct file *file)
> +{
> +	return container_of(file->private_data, struct ope_ctx, fh);
> +}
> +
> +static inline u32 ope_read(struct ope_dev *ope, u32 reg)
> +{
> +	return readl(ope->base + reg);
> +}
> +
> +static inline void ope_write(struct ope_dev *ope, u32 reg, u32 value)
> +{
> +	writel(value, ope->base + reg);
> +}
> +
> +static inline u32 ope_read_wr(struct ope_dev *ope, u32 reg)
> +{
> +	return readl_relaxed(ope->base_wr + reg);
> +}
> +
> +static inline void ope_write_wr(struct ope_dev *ope, u32 reg, u32 value)
> +{
> +	writel_relaxed(value, ope->base_wr + reg);
> +}
> +
> +static inline u32 ope_read_rd(struct ope_dev *ope, u32 reg)
> +{
> +	return readl_relaxed(ope->base_rd + reg);
> +}
> +
> +static inline void ope_write_rd(struct ope_dev *ope, u32 reg, u32 value)
> +{
> +	writel_relaxed(value, ope->base_rd + reg);
> +}
> +
> +static inline u32 ope_read_pp(struct ope_dev *ope, u32 reg)
> +{
> +	return readl_relaxed(ope->base_pp + reg);
> +}
> +
> +static inline void ope_write_pp(struct ope_dev *ope, u32 reg, u32 value)
> +{
> +	writel_relaxed(value, ope->base_pp + reg);
> +}
> +
> +static inline void ope_start(struct ope_dev *ope)
> +{
> +	wmb(); /* Ensure the next write occurs only after all prior normal memory accesses */
> +	ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_CMD, OPE_BUS_RD_INPUT_IF_CMD_GO_CMD);
> +}
> +
> +static bool ope_pix_fmt_is_yuv(u32 fourcc)
> +{
> +	switch (fourcc) {
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV24:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV21:
> +	case V4L2_PIX_FMT_NV42:
> +	case V4L2_PIX_FMT_GREY:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static const struct ope_fmt *find_format(unsigned int pixelformat)
> +{
> +	const struct ope_fmt *fmt;
> +	unsigned int i;
> +
> +	for (i = 0; i < OPE_NUM_FORMATS; i++) {
> +		fmt = &formats[i];
> +		if (fmt->fourcc == pixelformat)
> +			break;
> +	}
> +
> +	if (i == OPE_NUM_FORMATS)
> +		return NULL;
> +
> +	return &formats[i];
> +}
> +
> +static inline void ope_dbg_print_stripe(struct ope_ctx *ctx, struct ope_stripe *stripe)
> +{
> +	struct ope_dev *ope = ctx->ope;
> +	int i;
> +
> +	dev_dbg(ope->dev, "S%u/FE0: addr=%pad;W=%ub;H=%u;stride=%u;loc=%u;pattern=%u;fmt=%u\n",
> +		ope_stripe_index(ctx, stripe), &stripe->src.addr, stripe->src.width,
> +		stripe->src.height, stripe->src.stride, stripe->src.location, stripe->src.pattern,
> +		stripe->src.format);
> +
> +	for (i = 0; i < OPE_DS_MAX; i++) {
> +		struct ope_dsc_config *c = &stripe->dsc[i];
> +
> +		dev_dbg(ope->dev, "S%u/DSC%d: %ux%u => %ux%u\n",
> +			ope_stripe_index(ctx, stripe), i, c->input_width, c->input_height,
> +			c->output_width, c->output_height);
> +	}
> +
> +	for (i = 0; i < OPE_WR_CLIENT_MAX; i++) {
> +		if (!stripe->dst[i].enabled)
> +			continue;
> +
> +		dev_dbg(ope->dev,
> +			"S%u/WE%d: addr=%pad;X=%u;W=%upx;H=%u;stride=%u;fmt=%u\n",
> +			ope_stripe_index(ctx, stripe), i, &stripe->dst[i].addr,
> +			stripe->dst[i].x_init, stripe->dst[i].width, stripe->dst[i].height,
> +			stripe->dst[i].stride, stripe->dst[i].format);
> +	}
> +}
> +
> +static void ope_gen_stripe_argb_dst(struct ope_ctx *ctx, struct ope_stripe *stripe, dma_addr_t dst)
> +{
> +	unsigned int index = ope_stripe_index(ctx, stripe);
> +	unsigned int img_width = ctx->q_data_dst.width;
> +	unsigned int width, height;
> +	dma_addr_t addr;
> +
> +	/* This is GBRA64 format (le16)G (le16)B (le16)R (le16)A */
> +
> +	stripe->dst[OPE_WR_CLIENT_ARGB].enabled = true;
> +
> +	width = stripe->src.width;
> +	height = stripe->src.height;
> +
> +	if (!index) {
> +		addr = dst;
> +	} else {
> +		struct ope_stripe *prev = ope_get_stripe(ctx, index - 1);
> +
> +		addr = prev->dst[OPE_WR_CLIENT_ARGB].addr + prev->dst[OPE_WR_CLIENT_ARGB].width * 8;
> +	}
> +
> +	stripe->dst[OPE_WR_CLIENT_ARGB].addr = addr;
> +	stripe->dst[OPE_WR_CLIENT_ARGB].x_init = 0;
> +	stripe->dst[OPE_WR_CLIENT_ARGB].width = width;
> +	stripe->dst[OPE_WR_CLIENT_ARGB].height = height;
> +	stripe->dst[OPE_WR_CLIENT_ARGB].stride = img_width * 8;
> +	stripe->dst[OPE_WR_CLIENT_ARGB].format = OPE_PACKER_FMT_PLAIN_64;
> +}
> +
> +static void ope_gen_stripe_yuv_dst(struct ope_ctx *ctx, struct ope_stripe *stripe, dma_addr_t dst)
> +{
> +	struct ope_stripe *prev = ope_prev_stripe(ctx, stripe);
> +	unsigned int img_width = ctx->q_data_dst.width;
> +	unsigned int img_height = ctx->q_data_dst.height;
> +	unsigned int width, height;
> +	u32 x_init = 0;
> +
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].enabled = true;
> +	stripe->dst[OPE_WR_CLIENT_DISP_C].enabled = true;
> +
> +	/* Y */
> +	width = stripe->dsc[OPE_DS_Y_DISP].output_width;
> +	height = stripe->dsc[OPE_DS_Y_DISP].output_height;
> +
> +	if (prev)
> +		x_init = prev->dst[OPE_WR_CLIENT_DISP_Y].x_init +
> +			 prev->dst[OPE_WR_CLIENT_DISP_Y].width;
> +
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].addr = dst;
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].x_init = x_init;
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].width = width;
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].height = height;
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].stride = img_width;
> +	stripe->dst[OPE_WR_CLIENT_DISP_Y].format = OPE_PACKER_FMT_PLAIN_8;
> +
> +	/* UV */
> +	width = stripe->dsc[OPE_DS_C_DISP].output_width;
> +	height = stripe->dsc[OPE_DS_C_DISP].output_height;
> +
> +	if (prev)
> +		x_init = prev->dst[OPE_WR_CLIENT_DISP_C].x_init +
> +			 prev->dst[OPE_WR_CLIENT_DISP_C].width;
> +
> +	stripe->dst[OPE_WR_CLIENT_DISP_C].addr = dst + img_width * img_height;
> +	stripe->dst[OPE_WR_CLIENT_DISP_C].x_init = x_init;
> +	stripe->dst[OPE_WR_CLIENT_DISP_C].format = ctx->q_data_dst.fmt->packer_format;
> +	stripe->dst[OPE_WR_CLIENT_DISP_C].width = width * 2;
> +	stripe->dst[OPE_WR_CLIENT_DISP_C].height = height;
> +
> +	switch (ctx->q_data_dst.fmt->fourcc) {
> +	case V4L2_PIX_FMT_NV42:
> +	case V4L2_PIX_FMT_NV24: /* YUV 4:4:4 */
> +		stripe->dst[OPE_WR_CLIENT_DISP_C].stride = img_width * 2;
> +		break;
> +	case V4L2_PIX_FMT_GREY: /* No UV */
> +		stripe->dst[OPE_WR_CLIENT_DISP_C].enabled = false;
> +		break;
> +	default:
> +		stripe->dst[OPE_WR_CLIENT_DISP_C].stride = img_width;
> +	}
> +}
> +
> +static void ope_gen_stripe_dsc(struct ope_ctx *ctx, struct ope_stripe *stripe,
> +			       unsigned int h_scale, unsigned int v_scale)
> +{
> +	struct ope_dsc_config *dsc_c, *dsc_y;
> +
> +	dsc_c = &stripe->dsc[OPE_DS_C_DISP];
> +	dsc_y = &stripe->dsc[OPE_DS_Y_DISP];
> +
> +	dsc_c->phase_step_h = dsc_y->phase_step_h = h_scale;
> +	dsc_c->phase_step_v = dsc_y->phase_step_v = v_scale;
> +
> +	dsc_c->input_width = stripe->dsc[OPE_DS_C_PRE].output_width;
> +	dsc_c->input_height = stripe->dsc[OPE_DS_C_PRE].output_height;
> +
> +	dsc_y->input_width = stripe->src.width;
> +	dsc_y->input_height = stripe->src.height;
> +
> +	dsc_c->output_width = DS_OUTPUT_PIX(dsc_c->input_width, 0, h_scale);
> +	dsc_c->output_height = DS_OUTPUT_PIX(dsc_c->input_height, 0, v_scale);
> +
> +	dsc_y->output_width = DS_OUTPUT_PIX(dsc_y->input_width, 0, h_scale);
> +	dsc_y->output_height = DS_OUTPUT_PIX(dsc_y->input_height, 0, v_scale);
> +
> +	/* Adjust initial phase ? */
> +}
> +
> +static void ope_gen_stripe_chroma_dsc(struct ope_ctx *ctx, struct ope_stripe *stripe)
> +{
> +	struct ope_dsc_config *dsc;
> +
> +	dsc = &stripe->dsc[OPE_DS_C_PRE];
> +
> +	dsc->input_width = stripe->src.width;
> +	dsc->input_height = stripe->src.height;
> +
> +	switch (ctx->q_data_dst.fmt->fourcc) {
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV16:
> +		dsc->output_width = dsc->input_width / 2;
> +		dsc->output_height = dsc->input_height;
> +		break;
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		dsc->output_width = dsc->input_width / 2;
> +		dsc->output_height = dsc->input_height / 2;
> +		break;
> +	default:
> +		dsc->output_width = dsc->input_width;
> +		dsc->output_height = dsc->input_height;
> +	}
> +
> +	dsc->phase_step_h = DS_Q21(dsc->input_width, dsc->output_width);
> +	dsc->phase_step_v = DS_Q21(dsc->input_height, dsc->output_height);
> +}
> +
> +static void ope_gen_stripes(struct ope_ctx *ctx, dma_addr_t src, dma_addr_t dst)
> +{
> +	const struct ope_fmt *src_fmt = ctx->q_data_src.fmt;
> +	const struct ope_fmt *dst_fmt = ctx->q_data_dst.fmt;
> +	unsigned int num_stripes, width, i;
> +	unsigned int h_scale, v_scale;
> +
> +	width = ctx->q_data_src.width;
> +	num_stripes = DIV_ROUND_UP(ctx->q_data_src.width, OPE_STRIPE_MAX_W);
> +	h_scale = DS_Q21(ctx->q_data_src.width, ctx->q_data_dst.width);
> +	v_scale = DS_Q21(ctx->q_data_src.height, ctx->q_data_dst.height);
> +
> +	for (i = 0; i < num_stripes; i++) {
> +		struct ope_stripe *stripe = &ctx->stripe[i];
> +
> +		/* Clear config */
> +		memset(stripe, 0, sizeof(*stripe));
> +
> +		/* Fetch Engine */
> +		stripe->src.addr = src;
> +		stripe->src.width = width;
> +		stripe->src.height = ctx->q_data_src.height;
> +		stripe->src.stride = ctx->q_data_src.bytesperline;
> +		stripe->src.location = ope_stripe_location(i, num_stripes);
> +		stripe->src.pattern = src_fmt->pattern;
> +		stripe->src.format = src_fmt->unpacker_format;
> +
> +		/* Ensure the last stripe will be large enough */
> +		if (width > OPE_STRIPE_MAX_W && width < (OPE_STRIPE_MAX_W + OPE_STRIPE_MIN_W))
> +			stripe->src.width -= OPE_STRIPE_MIN_W * 2;
> +
> +		v4l_bound_align_image(&stripe->src.width, src_fmt->align,
> +				      OPE_STRIPE_MAX_W, src_fmt->align,
> +				      &stripe->src.height, OPE_STRIPE_MIN_H, OPE_STRIPE_MAX_H,
> +				      OPE_ALIGN_H, 0);
> +
> +		width -= stripe->src.width;
> +		src += stripe->src.width * src_fmt->depth / 8;
> +
> +		if (ope_pix_fmt_is_yuv(dst_fmt->fourcc)) {
> +			/* YUV Chroma downscaling */
> +			ope_gen_stripe_chroma_dsc(ctx, stripe);
> +
> +			/* YUV downscaling */
> +			ope_gen_stripe_dsc(ctx, stripe, h_scale, v_scale);
> +
> +			/* Write Engines */
> +			ope_gen_stripe_yuv_dst(ctx, stripe, dst);
> +		} else {
> +			ope_gen_stripe_argb_dst(ctx, stripe, dst);
> +		}
> +
> +		/* Source width is in byte unit, not pixel */
> +		stripe->src.width = stripe->src.width * src_fmt->depth / 8;
> +
> +		ope_dbg_print_stripe(ctx, stripe);
> +	}
> +}
> +
> +static void ope_prog_rgb2yuv(struct ope_dev *ope)
> +{
> +	/* Default RGB to YUV - no special effect - CF BT.601 */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V0, 0x04d) | /* R to Y  0.299  12sQ8 */
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V1, 0x096)); /* G to Y  0.587 12sQ8 */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2_V2, 0x01d)); /* B to Y  0.114 12sQ8 */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1_K, 0)); /* Y offset 0 9sQ0 */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AP, 0x0e6) |
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AM, 0x0e6)); /* 0.886 12sQ8 (Cb) */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BP, 0xfb3) |
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BM, 0xfb3)); /* -0.338 12sQ8 (Cb) */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CP, 0xb3) |
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CM, 0xb3)); /* 0.701 12sQ8 (Cr) */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DP, 0xfe3) |
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DM, 0xfe3)); /* -0.114 12sQ8 (Cr) */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1_KCR, 128)); /* KCR 128 11s */
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0,
> +		     FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0_KCB, 128)); /* KCB 128 11s */
> +
> +	ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG,
> +		     OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG_EN);
> +}
> +
> +static void ope_prog_bayer2rgb(struct ope_dev *ope)
> +{
> +	/* Fixed Settings */
> +	ope_write_pp(ope, 0x860, 0x4001);
> +	ope_write_pp(ope, 0x868, 128);
> +	ope_write_pp(ope, 0x86c, 128 << 20);
> +	ope_write_pp(ope, 0x870, 102);

What are the magic numbers about ? Please define bit-fields and offsets.

Parameters passed in from user-space/libcamera and then translated to 
registers etc.

> +}
> +
> +static void ope_prog_wb(struct ope_dev *ope)
> +{
> +	/* Default white balance config */
> +	u32 g_gain = OPE_WB(1, 1);
> +	u32 b_gain = OPE_WB(3, 2);
> +	u32 r_gain = OPE_WB(3, 2);
> +
> +	ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(0), g_gain);
> +	ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(1), b_gain);
> +	ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(2), r_gain);
> +
> +	ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_MODULE_CFG, OPE_PP_CLC_WB_GAIN_MODULE_CFG_EN);
> +}

Fixed gains will have to come from real data.

> +
> +static void ope_prog_stripe(struct ope_ctx *ctx, struct ope_stripe *stripe)
> +{
> +	struct ope_dev *ope = ctx->ope;
> +	int i;
> +
> +	dev_dbg(ope->dev, "Context %p - Programming S%u\n", ctx, ope_stripe_index(ctx, stripe));
> +
> +	/* Fetch Engine */
> +	ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_UNPACK_CFG_0, stripe->src.format);
> +	ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_RD_BUFFER_SIZE,
> +		     (stripe->src.width << 16) + stripe->src.height);
> +	ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_ADDR_IMAGE, stripe->src.addr);
> +	ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_RD_STRIDE, stripe->src.stride);
> +	ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_CCIF_META_DATA,
> +		     FIELD_PREP(OPE_BUS_RD_CLIENT_0_CCIF_MD_PIX_PATTERN, stripe->src.pattern));
> +	ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_CORE_CFG, OPE_BUS_RD_CLIENT_0_CORE_CFG_EN);
> +
> +	/* Write Engines */
> +	for (i = 0; i < OPE_WR_CLIENT_MAX; i++) {
> +		if (!stripe->dst[i].enabled) {
> +			ope_write_wr(ope, OPE_BUS_WR_CLIENT_CFG(i), 0);
> +			continue;
> +		}
> +
> +		ope_write_wr(ope, OPE_BUS_WR_CLIENT_ADDR_IMAGE(i), stripe->dst[i].addr);
> +		ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_0(i),
> +			     (stripe->dst[i].height << 16) + stripe->dst[i].width);
> +		ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_1(i), stripe->dst[i].x_init);
> +		ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_2(i), stripe->dst[i].stride);
> +		ope_write_wr(ope, OPE_BUS_WR_CLIENT_PACKER_CFG(i), stripe->dst[i].format);
> +		ope_write_wr(ope, OPE_BUS_WR_CLIENT_CFG(i),
> +			     OPE_BUS_WR_CLIENT_CFG_EN + OPE_BUS_WR_CLIENT_CFG_AUTORECOVER);
> +	}
> +
> +	/* Downscalers */
> +	for (i = 0; i < OPE_DS_MAX; i++) {
> +		struct ope_dsc_config *dsc = &stripe->dsc[i];
> +		u32 base = ope_ds_base[i];
> +		u32 cfg = 0;
> +
> +		if (dsc->input_width != dsc->output_width) {
> +			dsc->phase_step_h |= DS_RESOLUTION(dsc->input_width,
> +							   dsc->output_width) << 30;
> +			cfg |= OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_H_SCALE_EN;
> +		}
> +
> +		if (dsc->input_height != dsc->output_height) {
> +			dsc->phase_step_v |= DS_RESOLUTION(dsc->input_height,
> +							   dsc->output_height) << 30;
> +			cfg |= OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_V_SCALE_EN;
> +		}
> +
> +		ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_CFG(base), cfg);
> +		ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(base),
> +			     ((dsc->input_width - 1) << 16) + dsc->input_height - 1);
> +		ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_MN_H_CFG(base), dsc->phase_step_h);
> +		ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_MN_V_CFG(base), dsc->phase_step_v);
> +		ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_CFG(base),
> +			     cfg ? OPE_PP_CLC_DOWNSCALE_MN_CFG_EN : 0);
> +	}
> +}

So - this is where the CDM should be used - so that you don't have to do 
all of these MMIO writes inside of your ISR.

Is that and additional step after the RFC ?

> +
> +/*
> + * mem2mem callbacks
> + */
> +static void ope_device_run(void *priv)
> +{
> +	struct vb2_v4l2_buffer *src_buf, *dst_buf;
> +	struct ope_ctx *ctx = priv;
> +	struct ope_dev *ope = ctx->ope;
> +	dma_addr_t src, dst;
> +
> +	dev_dbg(ope->dev, "Start context %p", ctx);
> +
> +	src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
> +	if (!src_buf)
> +		return;
> +
> +	dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
> +	if (!dst_buf)
> +		return;
> +
> +	src = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
> +	dst = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
> +
> +	/* Generate stripes from full frame */
> +	ope_gen_stripes(ctx, src, dst);
> +
> +	if (priv != ope->context) {
> +		/* If context changed, reprogram the submodules */
> +		ope_prog_wb(ope);
> +		ope_prog_bayer2rgb(ope);
> +		ope_prog_rgb2yuv(ope);
> +		ope->context = priv;
> +	}
> +
> +	/* Program the first stripe */
> +	ope_prog_stripe(ctx, &ctx->stripe[0]);
> +
> +	/* Go! */
> +	ope_start(ope);
> +}
> +
> +static void ope_job_done(struct ope_ctx *ctx, enum vb2_buffer_state vbstate)
> +{
> +	struct vb2_v4l2_buffer *src, *dst;
> +
> +	if (!ctx)
> +		return;
> +
> +	src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
> +	dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
> +
> +	if (dst && src)
> +		dst->vb2_buf.timestamp = src->vb2_buf.timestamp;
> +
> +	if (src)
> +		v4l2_m2m_buf_done(src, vbstate);
> +	if (dst)
> +		v4l2_m2m_buf_done(dst, vbstate);
> +
> +	v4l2_m2m_job_finish(ctx->ope->m2m_dev, ctx->fh.m2m_ctx);
> +}
> +
> +static void ope_buf_done(struct ope_ctx *ctx)
> +{
> +	struct ope_stripe *stripe = ope_current_stripe(ctx);
> +
> +	if (!ctx)
> +		return;
> +
> +	dev_dbg(ctx->ope->dev, "Context %p Stripe %u done\n",
> +		ctx,  ope_stripe_index(ctx, stripe));
> +
> +	if (ope_stripe_is_last(stripe)) {
> +		ctx->current_stripe = 0;
> +		ope_job_done(ctx, VB2_BUF_STATE_DONE);
> +	} else {
> +		ctx->current_stripe++;
> +		ope_start(ctx->ope);
> +	}
> +}
> +
> +static void ope_job_abort(void *priv)
> +{
> +	struct ope_ctx *ctx = priv;
> +
> +	/* reset to abort */
> +	ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW);
> +}

Shoudln't this wait for ope_job_done() ?

> +
> +static void ope_rup_done(struct ope_ctx *ctx)
> +{
> +	struct ope_stripe *stripe = ope_current_stripe(ctx);
> +
> +	/* We can program next stripe (double buffered registers) */
> +	if (!ope_stripe_is_last(stripe))
> +		ope_prog_stripe(ctx, ++stripe);
> +}
> +
> +/*
> + * interrupt handler
> + */
> +static void ope_fe_irq(struct ope_dev *ope)
> +{
> +	u32 status = ope_read_rd(ope, OPE_BUS_RD_INPUT_IF_IRQ_STATUS);
> +
> +	ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_IRQ_CLEAR, status);
> +	ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_IRQ_CMD, OPE_BUS_RD_INPUT_IF_IRQ_CMD_CLEAR);
> +
> +	/* Nothing to do */
> +}
> +
> +static void ope_we_irq(struct ope_ctx *ctx)
> +{
> +	struct ope_dev *ope;
> +	u32 status0;
> +
> +	if (!ctx) {
> +		pr_err("Instance released before the end of transaction\n");
> +		return;
> +	}
> +
> +	ope = ctx->ope;
> +
> +	status0 = ope_read_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0);
> +	ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0, status0);
> +	ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CMD, OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR);
> +
> +	if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL) {
> +		dev_err_ratelimited(ope->dev, "Write Engine configuration violates constrains\n");
> +		ope_job_abort(ctx);
> +	}
> +
> +	if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL) {
> +		u32 status = ope_read_wr(ope, OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS);
> +		int i;
> +
> +		for (i = 0; i < OPE_WR_CLIENT_MAX; i++) {
> +			if (BIT(i) & status)
> +				dev_err_ratelimited(ope->dev,
> +					 "Write Engine (WE%d) image size violation\n", i);
> +		}
> +
> +		ope_job_abort(ctx);
> +	}
> +
> +	if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL) {
> +		dev_err_ratelimited(ope->dev, "Write Engine fatal violation\n");
> +		ope_job_abort(ctx);
> +	}
> +
> +	if (status0 & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE)
> +		ope_rup_done(ctx);
> +}
> +
> +static irqreturn_t ope_irq(int irq, void *dev_id)
> +{
> +	struct ope_dev *ope = dev_id;
> +	struct ope_ctx *ctx = ope->m2m_dev ? v4l2_m2m_get_curr_priv(ope->m2m_dev) : NULL;

You have a mutex for this pointer but it doesn't seem to be in-use here

Should this be a threadded IRQ with reference to that mutex then ?

> +	u32 status;
> +
> +	status = ope_read(ope, OPE_TOP_IRQ_STATUS);
> +	ope_write(ope, OPE_TOP_IRQ_CLEAR, status);
> +	ope_write(ope, OPE_TOP_IRQ_CMD, OPE_TOP_IRQ_CMD_CLEAR);
> +
> +	if (status & OPE_TOP_IRQ_STATUS_RST_DONE) {
> +		ope_job_done(ctx, VB2_BUF_STATE_ERROR);
> +		complete(&ope->reset_complete);
> +	}
> +
> +	if (status & OPE_TOP_IRQ_STATUS_VIOL) {
> +		u32 violation = ope_read(ope, OPE_TOP_VIOLATION_STATUS);
> +
> +		dev_warn(ope->dev, "OPE Violation: %u", violation);
> +	}
> +
> +	if (status & OPE_TOP_IRQ_STATUS_FE)
> +		ope_fe_irq(ope);
> +
> +	if (status & OPE_TOP_IRQ_STATUS_WE)
> +		ope_we_irq(ctx);
> +
> +	if (status & OPE_TOP_IRQ_STATUS_IDLE)
> +		ope_buf_done(ctx);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void ope_irq_init(struct ope_dev *ope)
> +{
> +	ope_write(ope, OPE_TOP_IRQ_MASK,
> +		  OPE_TOP_IRQ_STATUS_RST_DONE | OPE_TOP_IRQ_STATUS_WE |
> +		  OPE_TOP_IRQ_STATUS_VIOL | OPE_TOP_IRQ_STATUS_IDLE);
> +
> +	ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_MASK_0,
> +		     OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE |
> +		     OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL |
> +		     OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL |
> +		     OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL);
> +}
> +
> +/*
> + * video ioctls
> + */
> +static int ope_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "Qualcomm Offline Processing Engine", sizeof(cap->card));
> +	return 0;
> +}
> +
> +static int ope_enum_fmt(struct v4l2_fmtdesc *f, u32 type)
> +{
> +	const struct ope_fmt *fmt;
> +	int i, num = 0;
> +
> +	for (i = 0; i < OPE_NUM_FORMATS; ++i) {
> +		if (formats[i].types & type) {
> +			if (num == f->index)
> +				break;
> +			++num;
> +		}
> +	}
> +
> +	if (i < OPE_NUM_FORMATS) {
> +		fmt = &formats[i];
> +		f->pixelformat = fmt->fourcc;
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int ope_enum_fmt_vid_cap(struct file *file, void *priv,
> +				struct v4l2_fmtdesc *f)
> +{
> +	f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +
> +	return ope_enum_fmt(f, MEM2MEM_CAPTURE);
> +}
> +
> +static int ope_enum_fmt_vid_out(struct file *file, void *priv,
> +				struct v4l2_fmtdesc *f)
> +{
> +	f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +
> +	return ope_enum_fmt(f, MEM2MEM_OUTPUT);
> +}
> +
> +static int ope_g_fmt(struct ope_ctx *ctx, struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct ope_q_data *q_data;
> +	struct vb2_queue *vq;
> +
> +	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
> +	if (!vq)
> +		return -EINVAL;
> +
> +	q_data = get_q_data(ctx, f->type);
> +
> +	pix_mp->width = q_data->width;
> +	pix_mp->height = q_data->height;
> +	pix_mp->pixelformat = q_data->fmt->fourcc;
> +	pix_mp->num_planes = 1;
> +	pix_mp->field = V4L2_FIELD_NONE;
> +	pix_mp->colorspace = ctx->colorspace;
> +	pix_mp->xfer_func = ctx->xfer_func;
> +	pix_mp->ycbcr_enc = q_data->ycbcr_enc;
> +	pix_mp->quantization = q_data->quant;
> +	pix_mp->plane_fmt[0].bytesperline = q_data->bytesperline;
> +	pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage;
> +
> +	return 0;
> +}
> +
> +static int ope_g_fmt_vid_out(struct file *file, void *priv,
> +				struct v4l2_format *f)
> +{
> +	return ope_g_fmt(file2ctx(file), f);
> +}
> +
> +static int ope_g_fmt_vid_cap(struct file *file, void *priv,
> +				struct v4l2_format *f)
> +{
> +	return ope_g_fmt(file2ctx(file), f);
> +}
> +
> +static int ope_try_fmt(struct v4l2_format *f, const struct ope_fmt *fmt)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	unsigned int stride = pix_mp->plane_fmt[0].bytesperline;
> +	unsigned int size;
> +
> +	pix_mp->num_planes = 1;
> +	pix_mp->field = V4L2_FIELD_NONE;
> +
> +	v4l_bound_align_image(&pix_mp->width, OPE_MIN_W, OPE_MAX_W, fmt->align,
> +			      &pix_mp->height, OPE_MIN_H, OPE_MAX_H, OPE_ALIGN_H, 0);
> +
> +	if (ope_pix_fmt_is_yuv(pix_mp->pixelformat)) {
> +		stride = MAX(pix_mp->width, stride);
> +		size = fmt->depth * pix_mp->width / 8 * pix_mp->height;
> +	} else {
> +		stride = MAX(pix_mp->width * fmt->depth / 8, stride);
> +		size = stride * pix_mp->height;
> +	}
> +
> +	pix_mp->plane_fmt[0].bytesperline = stride;
> +	pix_mp->plane_fmt[0].sizeimage = size;
> +
> +	return 0;
> +}
> +
> +static int ope_try_fmt_vid_cap(struct file *file, void *priv,
> +			       struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct ope_ctx *ctx = file2ctx(file);
> +	const struct ope_fmt *fmt;
> +	int ret;
> +
> +	dev_dbg(ctx->ope->dev, "Try capture format: %ux%u-%s (planes:%u bpl:%u size:%u)\n",
> +		pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat),
> +		pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline,
> +		pix_mp->plane_fmt[0].sizeimage);
> +
> +	fmt = find_format(pix_mp->pixelformat);
> +	if (!fmt) {
> +		pix_mp->pixelformat = ctx->q_data_dst.fmt->fourcc;
> +		fmt = ctx->q_data_dst.fmt;
> +	}
> +
> +	if (!(fmt->types & MEM2MEM_CAPTURE) && (fmt != ctx->q_data_src.fmt))
> +		return -EINVAL;
> +
> +	if (pix_mp->width > ctx->q_data_src.width ||
> +	    pix_mp->height > ctx->q_data_src.height) {
> +		pix_mp->width = ctx->q_data_src.width;
> +		pix_mp->height = ctx->q_data_src.height;
> +	}
> +
> +	pix_mp->colorspace = ope_pix_fmt_is_yuv(pix_mp->pixelformat) ?
> +		ctx->colorspace : V4L2_COLORSPACE_RAW;
> +	pix_mp->xfer_func = ctx->xfer_func;
> +	pix_mp->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace);
> +	pix_mp->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
> +					pix_mp->colorspace, pix_mp->ycbcr_enc);
> +
> +	ret = ope_try_fmt(f, fmt);
> +
> +	dev_dbg(ctx->ope->dev, "Fixed capture format: %ux%u-%s (planes:%u bpl:%u size:%u)\n",
> +		pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat),
> +		pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline,
> +		pix_mp->plane_fmt[0].sizeimage);
> +
> +	return ret;
> +}
> +
> +static int ope_try_fmt_vid_out(struct file *file, void *priv,
> +			       struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	const struct ope_fmt *fmt;
> +	struct ope_ctx *ctx = file2ctx(file);
> +	int ret;
> +
> +	dev_dbg(ctx->ope->dev, "Try output format: %ux%u-%s (planes:%u bpl:%u size:%u)\n",
> +		pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat),
> +		pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline,
> +		pix_mp->plane_fmt[0].sizeimage);
> +
> +	fmt = find_format(pix_mp->pixelformat);
> +	if (!fmt) {
> +		pix_mp->pixelformat = ctx->q_data_src.fmt->fourcc;
> +		fmt = ctx->q_data_src.fmt;
> +	}
> +	if (!(fmt->types & MEM2MEM_OUTPUT))
> +		return -EINVAL;
> +
> +	if (!pix_mp->colorspace)
> +		pix_mp->colorspace = V4L2_COLORSPACE_SRGB;
> +
> +	pix_mp->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace);
> +	pix_mp->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
> +					pix_mp->colorspace, pix_mp->ycbcr_enc);
> +
> +	ret = ope_try_fmt(f, fmt);
> +
> +	dev_dbg(ctx->ope->dev, "Fixed output format: %ux%u-%s (planes:%u bpl:%u size:%u)\n",
> +		pix_mp->width, pix_mp->height, print_fourcc(pix_mp->pixelformat),
> +		pix_mp->num_planes, pix_mp->plane_fmt[0].bytesperline,
> +		pix_mp->plane_fmt[0].sizeimage);
> +
> +	return ret;
> +}
> +
> +static int ope_s_fmt(struct ope_ctx *ctx, struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct ope_q_data *q_data;
> +	struct vb2_queue *vq;
> +
> +	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
> +	if (!vq)
> +		return -EINVAL;
> +
> +	q_data = get_q_data(ctx, f->type);
> +	if (!q_data)
> +		return -EINVAL;
> +
> +	if (vb2_is_busy(vq)) {
> +		v4l2_err(&ctx->ope->v4l2_dev, "%s queue busy\n", __func__);
> +		return -EBUSY;
> +	}
> +
> +	q_data->fmt = find_format(pix_mp->pixelformat);
> +	if (!q_data->fmt)
> +		return -EINVAL;
> +	q_data->width = pix_mp->width;
> +	q_data->height = pix_mp->height;
> +	q_data->bytesperline = pix_mp->plane_fmt[0].bytesperline;
> +	q_data->sizeimage = pix_mp->plane_fmt[0].sizeimage;
> +
> +	dev_dbg(ctx->ope->dev, "Set %s format: %ux%u %s (%u bytes)\n",
> +		V4L2_TYPE_IS_OUTPUT(f->type) ? "output" : "capture",
> +		q_data->width, q_data->height, print_fourcc(q_data->fmt->fourcc),
> +		q_data->sizeimage);
> +
> +	return 0;
> +}
> +
> +static int ope_s_fmt_vid_cap(struct file *file, void *priv,
> +			     struct v4l2_format *f)
> +{
> +	struct ope_ctx *ctx = file2ctx(file);
> +	int ret;
> +
> +	ret = ope_try_fmt_vid_cap(file, priv, f);
> +	if (ret)
> +		return ret;
> +
> +	ret = ope_s_fmt(file2ctx(file), f);
> +	if (ret)
> +		return ret;
> +
> +	ctx->q_data_dst.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
> +	ctx->q_data_dst.quant = f->fmt.pix_mp.quantization;
> +
> +	return 0;
> +}
> +
> +static int ope_s_fmt_vid_out(struct file *file, void *priv,
> +			     struct v4l2_format *f)
> +{
> +	struct ope_ctx *ctx = file2ctx(file);
> +	int ret;
> +
> +	ret = ope_try_fmt_vid_out(file, priv, f);
> +	if (ret)
> +		return ret;
> +
> +	ret = ope_s_fmt(file2ctx(file), f);
> +	if (ret)
> +		return ret;
> +
> +	ctx->colorspace = f->fmt.pix_mp.colorspace;
> +	ctx->xfer_func = f->fmt.pix_mp.xfer_func;
> +	ctx->q_data_src.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
> +	ctx->q_data_src.quant = f->fmt.pix_mp.quantization;
> +
> +	return 0;
> +}
> +
> +static int ope_enum_framesizes(struct file *file, void *fh,
> +			       struct v4l2_frmsizeenum *fsize)
> +{
> +	if (fsize->index > 0)
> +		return -EINVAL;
> +
> +	if (!find_format(fsize->pixel_format))
> +		return -EINVAL;
> +
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise.min_width = OPE_MIN_W;
> +	fsize->stepwise.max_width = OPE_MAX_W;
> +	fsize->stepwise.step_width = 1 << OPE_ALIGN_W;
> +	fsize->stepwise.min_height = OPE_MIN_H;
> +	fsize->stepwise.max_height = OPE_MAX_H;
> +	fsize->stepwise.step_height = 1 << OPE_ALIGN_H;
> +
> +	return 0;
> +}
> +
> +static int ope_enum_frameintervals(struct file *file, void *fh,
> +				   struct v4l2_frmivalenum *fival)
> +{
> +	fival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
> +	fival->stepwise.min.numerator = 1;
> +	fival->stepwise.min.denominator = 120;
> +	fival->stepwise.max.numerator = 1;
> +	fival->stepwise.max.denominator = 1;
> +	fival->stepwise.step.numerator = 1;
> +	fival->stepwise.step.denominator = 1;

fival->index should return -EINVAL for index > 0

should also valid width and height and pixel format

> +	return 0;
> +}
> +
> +static int ope_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	return -EINVAL;
> +}
> +
> +static const struct v4l2_ctrl_ops ope_ctrl_ops = {
> +	.s_ctrl = ope_s_ctrl,
> +};

Eh - I think you can drop this ..

> +
> +static const struct v4l2_ioctl_ops ope_ioctl_ops = {
> +	.vidioc_querycap	= ope_querycap,
> +
> +	.vidioc_enum_fmt_vid_cap = ope_enum_fmt_vid_cap,
> +	.vidioc_g_fmt_vid_cap_mplane	= ope_g_fmt_vid_cap,
> +	.vidioc_try_fmt_vid_cap_mplane	= ope_try_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap_mplane	= ope_s_fmt_vid_cap,
> +
> +	.vidioc_enum_fmt_vid_out = ope_enum_fmt_vid_out,
> +	.vidioc_g_fmt_vid_out_mplane	= ope_g_fmt_vid_out,
> +	.vidioc_try_fmt_vid_out_mplane	= ope_try_fmt_vid_out,
> +	.vidioc_s_fmt_vid_out_mplane	= ope_s_fmt_vid_out,
> +
> +	.vidioc_enum_framesizes	= ope_enum_framesizes,
> +	.vidioc_enum_frameintervals = ope_enum_frameintervals,
> +
> +	.vidioc_reqbufs		= v4l2_m2m_ioctl_reqbufs,
> +	.vidioc_querybuf	= v4l2_m2m_ioctl_querybuf,
> +	.vidioc_qbuf		= v4l2_m2m_ioctl_qbuf,
> +	.vidioc_dqbuf		= v4l2_m2m_ioctl_dqbuf,
> +	.vidioc_prepare_buf	= v4l2_m2m_ioctl_prepare_buf,
> +	.vidioc_create_bufs	= v4l2_m2m_ioctl_create_bufs,
> +	.vidioc_expbuf		= v4l2_m2m_ioctl_expbuf,
> +
> +	.vidioc_streamon	= v4l2_m2m_ioctl_streamon,
> +	.vidioc_streamoff	= v4l2_m2m_ioctl_streamoff,
> +
> +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +/*
> + * Queue operations
> + */
> +static int ope_queue_setup(struct vb2_queue *vq,
> +			   unsigned int *nbuffers, unsigned int *nplanes,
> +			   unsigned int sizes[], struct device *alloc_devs[])
> +{
> +	struct ope_ctx *ctx = vb2_get_drv_priv(vq);
> +	struct ope_q_data *q_data = get_q_data(ctx, vq->type);
> +	unsigned int size = q_data->sizeimage;
> +
> +	if (*nplanes) {
> +		if (*nplanes != 1)
> +			return -EINVAL;
> +	} else {
> +		*nplanes = 1;
> +	}
> +
> +	if (sizes[0]) {
> +		if (sizes[0] < size)
> +			return -EINVAL;
> +	} else {
> +		sizes[0] = size;
> +	}
> +
> +	dev_dbg(ctx->ope->dev, "get %d buffer(s) of size %d each.\n", *nbuffers, size);
> +
> +	return 0;
> +}
> +
> +static int ope_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct ope_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
> +	struct ope_dev *ope = ctx->ope;
> +	struct ope_q_data *q_data;
> +
> +	q_data = get_q_data(ctx, vb->vb2_queue->type);
> +	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
> +		if (vbuf->field == V4L2_FIELD_ANY)
> +			vbuf->field = V4L2_FIELD_NONE;
> +		if (vbuf->field != V4L2_FIELD_NONE) {
> +			v4l2_err(&ope->v4l2_dev, "Field isn't supported\n");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (vb2_plane_size(vb, 0) < q_data->sizeimage) {
> +		v4l2_err(&ope->v4l2_dev, "Data will not fit into plane (%lu < %lu)\n",
> +			 vb2_plane_size(vb, 0), (long)q_data->sizeimage);
> +		return -EINVAL;
> +	}
> +
> +	if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type))
> +		vb2_set_plane_payload(vb, 0, q_data->sizeimage);
> +
> +	vbuf->sequence = q_data->sequence++;
> +
> +	return 0;
> +}
> +
> +static void ope_adjust_power(struct ope_dev *ope)
> +{
> +	int ret;
> +	unsigned long pixclk = 0;
> +	unsigned int loadavg = 0;
> +	unsigned int loadpeak = 0;
> +	unsigned int loadconfig = 0;
> +	struct ope_ctx *ctx;
> +
> +	lockdep_assert_held(&ope->mutex);
> +
> +	list_for_each_entry(ctx, &ope->ctx_list, list) {
> +		if (!ctx->started)
> +			continue;
> +
> +		if (!ctx->framerate)
> +			ctx->framerate = DEFAULT_FRAMERATE;
> +
> +		pixclk += __q_data_pixclk(&ctx->q_data_src, ctx->framerate);
> +		loadavg += __q_data_load_avg(&ctx->q_data_src, ctx->framerate);
> +		loadavg += __q_data_load_avg(&ctx->q_data_dst, ctx->framerate);
> +		loadpeak += __q_data_load_peak(&ctx->q_data_src, ctx->framerate);
> +		loadpeak += __q_data_load_peak(&ctx->q_data_dst, ctx->framerate);
> +		loadconfig += __q_data_load_config(&ctx->q_data_src, ctx->framerate);
> +	}
> +
> +	/* 30% margin for overhead */
> +	pixclk = mult_frac(pixclk, 13, 10);
> +
> +	dev_dbg(ope->dev, "Adjusting clock:%luHz avg:%uKBps peak:%uKBps config:%uKBps\n",
> +		pixclk, loadavg, loadpeak, loadconfig);
> +
> +	ret = dev_pm_opp_set_rate(ope->dev, pixclk);
> +	if (ret)
> +		dev_warn(ope->dev, "Failed to adjust OPP rate: %d\n", ret);
> +
> +	ret = icc_set_bw(ope->icc_data, loadavg, loadpeak);
> +	if (ret)
> +		dev_warn(ope->dev, "Failed to set data path bandwidth: %d\n", ret);
> +
> +	ret = icc_set_bw(ope->icc_config, loadconfig, loadconfig * 5);
> +	if (ret)
> +		dev_warn(ope->dev, "Failed to set config path bandwidth: %d\n", ret);
> +}
> +
> +static void ope_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct ope_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
> +}
> +
> +static int ope_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> +	struct ope_ctx *ctx = vb2_get_drv_priv(q);
> +	struct ope_dev *ope = ctx->ope;
> +	struct ope_q_data *q_data;
> +	int ret;
> +
> +	dev_dbg(ope->dev, "Start streaming (ctx %p/%u)\n", ctx, q->type);
> +
> +	lockdep_assert_held(&ope->mutex);
> +
> +	q_data = get_q_data(ctx, q->type);
> +	q_data->sequence = 0;
> +
> +	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
> +		ctx->started = true;
> +		ope_adjust_power(ctx->ope);
> +	}
> +
> +	ret = pm_runtime_resume_and_get(ctx->ope->dev);
> +	if (ret) {
> +		dev_err(ope->dev, "Could not resume\n");
> +		return ret;
> +	}
> +
> +	ope_irq_init(ope);
> +
> +	return 0;
> +}
> +
> +static void ope_stop_streaming(struct vb2_queue *q)
> +{
> +	struct ope_ctx *ctx = vb2_get_drv_priv(q);
> +	struct ope_dev *ope = ctx->ope;
> +	struct vb2_v4l2_buffer *vbuf;
> +
> +	dev_dbg(ctx->ope->dev, "Stop streaming (ctx %p/%u)\n", ctx, q->type);
> +
> +	lockdep_assert_held(&ope->mutex);
> +
> +	if (ope->context == ctx)
> +		ope->context = NULL;
> +
> +	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
> +		ctx->started = false;
> +		ope_adjust_power(ctx->ope);
> +	}
> +
> +	pm_runtime_put(ctx->ope->dev);
> +
> +	for (;;) {
> +		if (V4L2_TYPE_IS_OUTPUT(q->type))
> +			vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
> +		else
> +			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
> +		if (vbuf == NULL)
> +			return;
> +
> +		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
> +	}
> +}
> +
> +static const struct vb2_ops ope_qops = {
> +	.queue_setup	 = ope_queue_setup,
> +	.buf_prepare	 = ope_buf_prepare,
> +	.buf_queue	 = ope_buf_queue,
> +	.start_streaming = ope_start_streaming,
> +	.stop_streaming  = ope_stop_streaming,
> +};
> +
> +static int queue_init(void *priv, struct vb2_queue *src_vq,
> +		      struct vb2_queue *dst_vq)
> +{
> +	struct ope_ctx *ctx = priv;
> +	int ret;
> +
> +	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +	src_vq->drv_priv = ctx;
> +	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
> +	src_vq->ops = &ope_qops;
> +	src_vq->mem_ops = &vb2_dma_contig_memops;
> +	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +	src_vq->lock = &ctx->ope->mutex;
> +	src_vq->dev = ctx->ope->v4l2_dev.dev;
> +
> +	ret = vb2_queue_init(src_vq);
> +	if (ret)
> +		return ret;
> +
> +	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +	dst_vq->drv_priv = ctx;
> +	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
> +	dst_vq->ops = &ope_qops;
> +	dst_vq->mem_ops = &vb2_dma_contig_memops;
> +	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +	dst_vq->lock = &ctx->ope->mutex;
> +	dst_vq->dev = ctx->ope->v4l2_dev.dev;
> +
> +	return vb2_queue_init(dst_vq);
> +}
> +
> +/*
> + * File operations
> + */
> +static int ope_open(struct file *file)
> +{
> +	struct ope_dev *ope = video_drvdata(file);
> +	struct ope_ctx *ctx = NULL;
> +	int rc = 0;
> +
> +	if (mutex_lock_interruptible(&ope->mutex))
> +		return -ERESTARTSYS;
> +
> +	ctx = kvzalloc(sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx) {
> +		rc = -ENOMEM;
> +		goto open_unlock;
> +	}
> +
> +	v4l2_fh_init(&ctx->fh, video_devdata(file));
> +	file->private_data = &ctx->fh;
> +	ctx->ope = ope;
> +	ctx->colorspace = V4L2_COLORSPACE_RAW;
> +
> +	ctx->q_data_src.fmt = find_format(V4L2_PIX_FMT_SRGGB8);
> +	ctx->q_data_src.width = 640;
> +	ctx->q_data_src.height = 480;
> +	ctx->q_data_src.bytesperline = 640;
> +	ctx->q_data_src.sizeimage = 640 * 480;
> +
> +	ctx->q_data_dst.fmt = find_format(V4L2_PIX_FMT_NV12);
> +	ctx->q_data_dst.width = 640;
> +	ctx->q_data_dst.height = 480;
> +	ctx->q_data_dst.bytesperline = 640;
> +	ctx->q_data_dst.sizeimage = 640 * 480 * 3 / 2;
> +
> +	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(ope->m2m_dev, ctx, &queue_init);
> +	if (IS_ERR(ctx->fh.m2m_ctx)) {
> +		rc = PTR_ERR(ctx->fh.m2m_ctx);
> +		v4l2_fh_exit(&ctx->fh);
> +		kvfree(ctx);
> +		goto open_unlock;
> +	}
> +
> +	v4l2_fh_add(&ctx->fh, file);
> +
> +	list_add(&ctx->list, &ope->ctx_list);
> +
> +	dev_dbg(ope->dev, "Created ctx %p\n", ctx);
> +
> +open_unlock:
> +	mutex_unlock(&ope->mutex);
> +	return rc;
> +}
> +
> +static int ope_release(struct file *file)
> +{
> +	struct ope_dev *ope = video_drvdata(file);
> +	struct ope_ctx *ctx = file2ctx(file);
> +
> +	dev_dbg(ope->dev, "Releasing ctx %p\n", ctx);
> +
> +	guard(mutex)(&ope->mutex);
> +
> +	if (ope->context == ctx)
> +		ope->context = NULL;
> +
> +	list_del(&ctx->list);
> +	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
> +	v4l2_fh_del(&ctx->fh, file);
> +	v4l2_fh_exit(&ctx->fh);
> +	kvfree(ctx);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_file_operations ope_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= ope_open,
> +	.release	= ope_release,
> +	.poll		= v4l2_m2m_fop_poll,
> +	.unlocked_ioctl	= video_ioctl2,
> +	.mmap		= v4l2_m2m_fop_mmap,
> +};
> +
> +static const struct video_device ope_videodev = {
> +	.name		= MEM2MEM_NAME,
> +	.vfl_dir	= VFL_DIR_M2M,
> +	.fops		= &ope_fops,
> +	.device_caps	= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE,
> +	.ioctl_ops	= &ope_ioctl_ops,
> +	.minor		= -1,
> +	.release	= video_device_release_empty,
> +};
> +
> +static const struct v4l2_m2m_ops m2m_ops = {
> +	.device_run	= ope_device_run,
> +	.job_abort	= ope_job_abort,
> +};
> +
> +static int ope_soft_reset(struct ope_dev *ope)
> +{
> +	u32 version;
> +	int ret = 0;
> +
> +	ret = pm_runtime_resume_and_get(ope->dev);
> +	if (ret) {
> +		dev_err(ope->dev, "Could not resume\n");
> +		return ret;
> +	}
> +
> +	version = ope_read(ope, OPE_TOP_HW_VERSION);
> +
> +	dev_dbg(ope->dev, "HW Version = %u.%u.%u\n",
> +		(u32)FIELD_GET(OPE_TOP_HW_VERSION_GEN, version),
> +		(u32)FIELD_GET(OPE_TOP_HW_VERSION_REV, version),
> +		(u32)FIELD_GET(OPE_TOP_HW_VERSION_STEP, version));
> +
> +	reinit_completion(&ope->reset_complete);
> +
> +	ope_write(ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW);
> +
> +	if (!wait_for_completion_timeout(&ope->reset_complete,
> +					 msecs_to_jiffies(OPE_RESET_TIMEOUT_MS))) {
> +		dev_err(ope->dev, "Reset timeout\n");
> +		ret = -ETIMEDOUT;
> +	}
> +
> +	pm_runtime_put(ope->dev);
> +
> +	return ret;
> +}
> +
> +static int ope_init_power(struct ope_dev *ope)
> +{
> +	struct dev_pm_domain_list *pmdomains;
> +	struct device *dev = ope->dev;
> +	int ret;
> +
> +	ope->icc_data = devm_of_icc_get(dev, "data");
> +	if (IS_ERR(ope->icc_data))
> +		return dev_err_probe(dev, PTR_ERR(ope->icc_data),
> +				     "failed to get interconnect data path\n");
> +
> +	ope->icc_config = devm_of_icc_get(dev, "config");
> +	if (IS_ERR(ope->icc_config))
> +		return dev_err_probe(dev, PTR_ERR(ope->icc_config),
> +				     "failed to get interconnect config path\n");
> +
> +	/*  Devices with multiple PM domains must be attached separately */
> +	devm_pm_domain_attach_list(dev, NULL, &pmdomains);
> +
> +	/* core clock is scaled as part of operating points */
> +	ret = devm_pm_opp_set_clkname(dev, "core");
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_pm_opp_of_add_table(dev);
> +	if (ret && ret != -ENODEV)
> +		return dev_err_probe(dev, ret, "invalid OPP table\n");
> +
> +	ret = devm_pm_runtime_enable(dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_pm_clk_create(dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = of_pm_clk_add_clks(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ope_init_mmio(struct ope_dev *ope)
> +{
> +	struct platform_device *pdev = to_platform_device(ope->dev);
> +
> +	ope->base = devm_platform_ioremap_resource_byname(pdev, "top");
> +	if (IS_ERR(ope->base))
> +		return PTR_ERR(ope->base);
> +
> +	ope->base_rd = devm_platform_ioremap_resource_byname(pdev, "bus_read");
> +	if (IS_ERR(ope->base_rd))
> +		return PTR_ERR(ope->base_rd);
> +
> +	ope->base_wr = devm_platform_ioremap_resource_byname(pdev, "bus_write");
> +	if (IS_ERR(ope->base_wr))
> +		return PTR_ERR(ope->base_wr);
> +
> +	ope->base_pp = devm_platform_ioremap_resource_byname(pdev, "pipeline");
> +	if (IS_ERR(ope->base_pp))
> +		return PTR_ERR(ope->base_pp);
> +
> +	return 0;
> +}
> +
> +static int ope_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct video_device *vfd;
> +	struct ope_dev *ope;
> +	int ret, irq;
> +
> +	ope = devm_kzalloc(&pdev->dev, sizeof(*ope), GFP_KERNEL);
> +	if (!ope)
> +		return -ENOMEM;
> +
> +	ope->dev = dev;
> +	init_completion(&ope->reset_complete);
> +
> +	ret = ope_init_power(ope);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Power init failed\n");
> +
> +	ret = ope_init_mmio(ope);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "MMIO init failed\n");
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return dev_err_probe(dev, irq, "Unable to get IRQ\n");
> +
> +	ret = devm_request_irq(dev, irq, ope_irq, IRQF_TRIGGER_RISING, "ope", ope);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Requesting IRQ failed\n");
> +
> +	ret = ope_soft_reset(ope);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = v4l2_device_register(&pdev->dev, &ope->v4l2_dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Registering V4L2 device failed\n");
> +
> +	mutex_init(&ope->mutex);
> +	INIT_LIST_HEAD(&ope->ctx_list);
> +
> +	ope->vfd = ope_videodev;
> +	vfd = &ope->vfd;
> +	vfd->lock = &ope->mutex;
> +	vfd->v4l2_dev = &ope->v4l2_dev;
> +	video_set_drvdata(vfd, ope);
> +	snprintf(vfd->name, sizeof(vfd->name), "%s", ope_videodev.name);
> +
> +	platform_set_drvdata(pdev, ope);
> +
> +	ope->m2m_dev = v4l2_m2m_init(&m2m_ops);
> +	if (IS_ERR(ope->m2m_dev)) {
> +		ret = dev_err_probe(dev, PTR_ERR(ope->m2m_dev), "Failed to init mem2mem device\n");
> +		goto err_unregister_v4l2;
> +	}
> +
> +	ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0);
> +	if (ret) {
> +		dev_err(dev, "Failed to refgister video device\n");
> +		goto err_release_m2m;
> +	}
> +
> +	/* TODO: Add stat device and link it to media */
> +	ope->mdev.dev = dev;
> +	strscpy(ope->mdev.model, MEM2MEM_NAME, sizeof(ope->mdev.model));
> +	media_device_init(&ope->mdev);
> +	ope->v4l2_dev.mdev = &ope->mdev;
> +
> +	ret = v4l2_m2m_register_media_controller(ope->m2m_dev, vfd,
> +						 MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register m2m media controller\n");
> +		goto err_unregister_video;
> +	}
> +
> +	ret = media_device_register(&ope->mdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register media device\n");
> +		goto err_unregister_m2m_mc;
> +	}
> +
> +	return 0;
> +
> +err_unregister_m2m_mc:
> +	v4l2_m2m_unregister_media_controller(ope->m2m_dev);
> +err_unregister_video:
> +	video_unregister_device(&ope->vfd);
> +err_release_m2m:
> +	v4l2_m2m_release(ope->m2m_dev);
> +err_unregister_v4l2:
> +	v4l2_device_unregister(&ope->v4l2_dev);
> +
> +	return ret;
> +}
> +
> +static void ope_remove(struct platform_device *pdev)
> +{
> +	struct ope_dev *ope = platform_get_drvdata(pdev);
> +
> +	media_device_unregister(&ope->mdev);
> +	v4l2_m2m_unregister_media_controller(ope->m2m_dev);
> +	video_unregister_device(&ope->vfd);
> +	v4l2_m2m_release(ope->m2m_dev);
> +	v4l2_device_unregister(&ope->v4l2_dev);
> +}
> +
> +static const struct of_device_id ope_dt_ids[] = {
> +	{ .compatible = "qcom,qcm2290-camss-ope"},
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, ope_dt_ids);
> +
> +static const struct dev_pm_ops ope_pm_ops = {
> +	SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
> +};
> +
> +static struct platform_driver ope_driver = {
> +	.probe		= ope_probe,
> +	.remove		= ope_remove,
> +	.driver		= {
> +		.name	= MEM2MEM_NAME,
> +		.of_match_table = ope_dt_ids,
> +		.pm = &ope_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(ope_driver);
> +
> +MODULE_DESCRIPTION("CAMSS Offline Processing Engine");
> +MODULE_AUTHOR("Loic Poulain <loic.poulain@oss.qualcomm.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.34.1
> 


  reply	other threads:[~2026-03-23 13:43 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <xy6TKmdveRx4cMshSHEUGZ7s3lbsurWcsc2vq05A7_N4bCialR7EelZitouugtZDkpFCAghjqY4NDdSQEIPprw==@protonmail.internalid>
2026-03-23 12:58 ` [RFC PATCH 0/3] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain
2026-03-23 12:58   ` [RFC PATCH 1/3] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) Loic Poulain
2026-03-23 13:03     ` Krzysztof Kozlowski
2026-03-23 16:03       ` Loic Poulain
2026-03-23 16:10         ` Krzysztof Kozlowski
2026-03-23 13:03     ` Bryan O'Donoghue
2026-03-23 12:58   ` [RFC PATCH 2/3] media: qcom: camss: Add CAMSS Offline Processing Engine driver Loic Poulain
2026-03-23 13:43     ` Bryan O'Donoghue [this message]
2026-03-23 15:31       ` Loic Poulain
2026-03-24 11:00         ` Bryan O'Donoghue
2026-03-24 15:57           ` Loic Poulain
2026-03-24 21:27           ` Dmitry Baryshkov
2026-03-26 12:06             ` johannes.goede
2026-03-25  9:30           ` Konrad Dybcio
2026-03-23 12:58   ` [RFC PATCH 3/3] arm64: dts: qcom: qcm2290: Add CAMSS OPE node Loic Poulain
2026-03-23 13:03     ` Bryan O'Donoghue
2026-03-23 13:24     ` Konrad Dybcio
2026-03-23 13:33       ` Bryan O'Donoghue
2026-03-23 16:15         ` Krzysztof Kozlowski
2026-03-24 10:30           ` Bryan O'Donoghue
2026-03-23 16:31       ` Loic Poulain
2026-03-24 10:43         ` Konrad Dybcio
2026-03-24 12:54   ` [RFC PATCH 0/3] media: qcom: camss: CAMSS Offline Processing Engine support Bryan O'Donoghue
2026-03-24 16:16     ` Loic Poulain

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1ba54ec0-be51-4694-a79b-f272e76303d2@kernel.org \
    --to=bod@kernel.org \
    --cc=andersson@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=johannes.goede@oss.qualcomm.com \
    --cc=kieran.bingham@ideasonboard.com \
    --cc=konradybcio@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=loic.poulain@oss.qualcomm.com \
    --cc=mchehab@kernel.org \
    --cc=robh@kernel.org \
    --cc=vladimir.zapolskiy@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox